From 3de959029cf37b99c7362e404b05cd8aca11a1e4 Mon Sep 17 00:00:00 2001 From: Dominik Pfennig <dominik@suma-ev.de> Date: Fri, 20 Jan 2017 15:07:16 +0100 Subject: [PATCH] Und die Dateien hinterher --- public/build/js/routing-5b35f7668b.js | 93950 ++++++++++++++++++++++++ public/build/js/routing-fd33c92d44.js | 93950 ++++++++++++++++++++++++ 2 files changed, 187900 insertions(+) create mode 100644 public/build/js/routing-5b35f7668b.js create mode 100644 public/build/js/routing-fd33c92d44.js diff --git a/public/build/js/routing-5b35f7668b.js b/public/build/js/routing-5b35f7668b.js new file mode 100644 index 0000000..7984878 --- /dev/null +++ b/public/build/js/routing-5b35f7668b.js @@ -0,0 +1,93950 @@ +/*! jQuery v1.11.2 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.2",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=hb(),z=hb(),A=hb(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},eb=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fb){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function gb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+rb(o[l]);w=ab.test(a)&&pb(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function hb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ib(a){return a[u]=!0,a}function jb(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function kb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function lb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function nb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function ob(a){return ib(function(b){return b=+b,ib(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pb(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=gb.support={},f=gb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=gb.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",eb,!1):e.attachEvent&&e.attachEvent("onunload",eb)),p=!f(g),c.attributes=jb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=jb(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=jb(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(jb(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\f]' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),jb(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&jb(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return lb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?lb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},gb.matches=function(a,b){return gb(a,null,null,b)},gb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return gb(b,n,null,[a]).length>0},gb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},gb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},gb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},gb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=gb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=gb.selectors={cacheLength:50,createPseudo:ib,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||gb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&gb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=gb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||gb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ib(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ib(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ib(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ib(function(a){return function(b){return gb(a,b).length>0}}),contains:ib(function(a){return a=a.replace(cb,db),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ib(function(a){return W.test(a||"")||gb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:ob(function(){return[0]}),last:ob(function(a,b){return[b-1]}),eq:ob(function(a,b,c){return[0>c?c+b:c]}),even:ob(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:ob(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:ob(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:ob(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=mb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=nb(b);function qb(){}qb.prototype=d.filters=d.pseudos,d.setFilters=new qb,g=gb.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?gb.error(a):z(a,i).slice(0)};function rb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function sb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function tb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ub(a,b,c){for(var d=0,e=b.length;e>d;d++)gb(a,b[d],c);return c}function vb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wb(a,b,c,d,e,f){return d&&!d[u]&&(d=wb(d)),e&&!e[u]&&(e=wb(e,f)),ib(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ub(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:vb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=vb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=vb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sb(function(a){return a===b},h,!0),l=sb(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sb(tb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wb(i>1&&tb(m),i>1&&rb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xb(a.slice(i,e)),f>e&&xb(a=a.slice(e)),f>e&&rb(a))}m.push(c)}return tb(m)}function yb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=vb(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&gb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ib(f):f}return h=gb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,yb(e,d)),f.selector=a}return f},i=gb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&pb(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&rb(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&pb(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=jb(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),jb(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||kb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&jb(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||kb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),jb(function(a){return null==a.getAttribute("disabled")})||kb(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),gb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1; +return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},fix:function(a){if(a[m.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=Z.test(e)?this.mouseHooks:Y.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new m.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=f.srcElement||y),3===a.target.nodeType&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,g.filter?g.filter(a,f):a},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button,g=b.fromElement;return null==a.pageX&&null!=b.clientX&&(d=a.target.ownerDocument||y,e=d.documentElement,c=d.body,a.pageX=b.clientX+(e&&e.scrollLeft||c&&c.scrollLeft||0)-(e&&e.clientLeft||c&&c.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||c&&c.scrollTop||0)-(e&&e.clientTop||c&&c.clientTop||0)),!a.relatedTarget&&g&&(a.relatedTarget=g===a.target?b.toElement:g),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==cb()&&this.focus)try{return this.focus(),!1}catch(a){}},delegateType:"focusin"},blur:{trigger:function(){return this===cb()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return m.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):void 0},_default:function(a){return m.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=m.extend(new m.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?m.event.trigger(e,null,b):m.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},m.removeEvent=y.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]===K&&(a[d]=null),a.detachEvent(d,c))},m.Event=function(a,b){return this instanceof m.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ab:bb):this.type=a,b&&m.extend(this,b),this.timeStamp=a&&a.timeStamp||m.now(),void(this[m.expando]=!0)):new m.Event(a,b)},m.Event.prototype={isDefaultPrevented:bb,isPropagationStopped:bb,isImmediatePropagationStopped:bb,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ab,a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ab,a&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ab,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},m.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){m.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!m.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.submitBubbles||(m.event.special.submit={setup:function(){return m.nodeName(this,"form")?!1:void m.event.add(this,"click._submit keypress._submit",function(a){var b=a.target,c=m.nodeName(b,"input")||m.nodeName(b,"button")?b.form:void 0;c&&!m._data(c,"submitBubbles")&&(m.event.add(c,"submit._submit",function(a){a._submit_bubble=!0}),m._data(c,"submitBubbles",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&m.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){return m.nodeName(this,"form")?!1:void m.event.remove(this,"._submit")}}),k.changeBubbles||(m.event.special.change={setup:function(){return X.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(m.event.add(this,"propertychange._change",function(a){"checked"===a.originalEvent.propertyName&&(this._just_changed=!0)}),m.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),m.event.simulate("change",this,a,!0)})),!1):void m.event.add(this,"beforeactivate._change",function(a){var b=a.target;X.test(b.nodeName)&&!m._data(b,"changeBubbles")&&(m.event.add(b,"change._change",function(a){!this.parentNode||a.isSimulated||a.isTrigger||m.event.simulate("change",this.parentNode,a,!0)}),m._data(b,"changeBubbles",!0))})},handle:function(a){var b=a.target;return this!==b||a.isSimulated||a.isTrigger||"radio"!==b.type&&"checkbox"!==b.type?a.handleObj.handler.apply(this,arguments):void 0},teardown:function(){return m.event.remove(this,"._change"),!X.test(this.nodeName)}}),k.focusinBubbles||m.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){m.event.simulate(b,a.target,m.event.fix(a),!0)};m.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=m._data(d,b);e||d.addEventListener(a,c,!0),m._data(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=m._data(d,b)-1;e?m._data(d,b,e):(d.removeEventListener(a,c,!0),m._removeData(d,b))}}}),m.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(f in a)this.on(f,b,c,a[f],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=bb;else if(!d)return this;return 1===e&&(g=d,d=function(a){return m().off(a),g.apply(this,arguments)},d.guid=g.guid||(g.guid=m.guid++)),this.each(function(){m.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,m(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=bb),this.each(function(){m.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){m.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?m.event.trigger(a,b,c,!0):void 0}});function db(a){var b=eb.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}var eb="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",fb=/ jQuery\d+="(?:null|\d+)"/g,gb=new RegExp("<(?:"+eb+")[\\s/>]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/<tbody/i,lb=/<|&#?\w+;/,mb=/<(?:script|style|link)/i,nb=/checked\s*(?:[^=]|=\s*.checked.)/i,ob=/^$|\/(?:java|ecma)script/i,pb=/^true\/(.*)/,qb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,rb={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:k.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1></$2>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?"<table>"!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=(Cb[0].contentWindow||Cb[0].contentDocument).document,b.write(),b.close(),c=Eb(a,b),Cb.detach()),Db[a]=c),c}!function(){var a;k.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,d;return c=y.getElementsByTagName("body")[0],c&&c.style?(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(y.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(d),a):void 0}}();var Gb=/^margin/,Hb=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ib,Jb,Kb=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ib=function(b){return b.ownerDocument.defaultView.opener?b.ownerDocument.defaultView.getComputedStyle(b,null):a.getComputedStyle(b,null)},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||m.contains(a.ownerDocument,a)||(g=m.style(a,b)),Hb.test(g)&&Gb.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):y.documentElement.currentStyle&&(Ib=function(a){return a.currentStyle},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Hb.test(g)&&!Kb.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function Lb(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h;if(b=y.createElement("div"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=d&&d.style){c.cssText="float:left;opacity:.5",k.opacity="0.5"===c.opacity,k.cssFloat=!!c.cssFloat,b.style.backgroundClip="content-box",b.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===b.style.backgroundClip,k.boxSizing=""===c.boxSizing||""===c.MozBoxSizing||""===c.WebkitBoxSizing,m.extend(k,{reliableHiddenOffsets:function(){return null==g&&i(),g},boxSizingReliable:function(){return null==f&&i(),f},pixelPosition:function(){return null==e&&i(),e},reliableMarginRight:function(){return null==h&&i(),h}});function i(){var b,c,d,i;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),b.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",e=f=!1,h=!0,a.getComputedStyle&&(e="1%"!==(a.getComputedStyle(b,null)||{}).top,f="4px"===(a.getComputedStyle(b,null)||{width:"4px"}).width,i=b.appendChild(y.createElement("div")),i.style.cssText=b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",b.style.width="1px",h=!parseFloat((a.getComputedStyle(i,null)||{}).marginRight),b.removeChild(i)),b.innerHTML="<table><tr><td></td><td>t</td></tr></table>",i=b.getElementsByTagName("td"),i[0].style.cssText="margin:0;border:0;padding:0;display:none",g=0===i[0].offsetHeight,g&&(i[0].style.display="",i[1].style.display="none",g=0===i[0].offsetHeight),c.removeChild(d))}}}(),m.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Mb=/alpha\([^)]*\)/i,Nb=/opacity\s*=\s*([^)]*)/,Ob=/^(none|table(?!-c[ea]).+)/,Pb=new RegExp("^("+S+")(.*)$","i"),Qb=new RegExp("^([+-])=("+S+")","i"),Rb={position:"absolute",visibility:"hidden",display:"block"},Sb={letterSpacing:"0",fontWeight:"400"},Tb=["Webkit","O","Moz","ms"];function Ub(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Tb.length;while(e--)if(b=Tb[e]+c,b in a)return b;return d}function Vb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=m._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&U(d)&&(f[g]=m._data(d,"olddisplay",Fb(d.nodeName)))):(e=U(d),(c&&"none"!==c||!e)&&m._data(d,"olddisplay",e?c:m.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Wb(a,b,c){var d=Pb.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Xb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=m.css(a,c+T[f],!0,e)),d?("content"===c&&(g-=m.css(a,"padding"+T[f],!0,e)),"margin"!==c&&(g-=m.css(a,"border"+T[f]+"Width",!0,e))):(g+=m.css(a,"padding"+T[f],!0,e),"padding"!==c&&(g+=m.css(a,"border"+T[f]+"Width",!0,e)));return g}function Yb(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ib(a),g=k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Jb(a,b,f),(0>e||null==e)&&(e=a.style[b]),Hb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Xb(a,b,c||(g?"border":"content"),d,f)+"px"}m.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Jb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":k.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=m.camelCase(b),i=a.style;if(b=m.cssProps[h]||(m.cssProps[h]=Ub(i,h)),g=m.cssHooks[b]||m.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Qb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(m.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||m.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=m.camelCase(b);return b=m.cssProps[h]||(m.cssProps[h]=Ub(a.style,h)),g=m.cssHooks[b]||m.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Jb(a,b,d)),"normal"===f&&b in Sb&&(f=Sb[b]),""===c||c?(e=parseFloat(f),c===!0||m.isNumeric(e)?e||0:f):f}}),m.each(["height","width"],function(a,b){m.cssHooks[b]={get:function(a,c,d){return c?Ob.test(m.css(a,"display"))&&0===a.offsetWidth?m.swap(a,Rb,function(){return Yb(a,b,d)}):Yb(a,b,d):void 0},set:function(a,c,d){var e=d&&Ib(a);return Wb(a,c,d?Xb(a,b,d,k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,e),e):0)}}}),k.opacity||(m.cssHooks.opacity={get:function(a,b){return Nb.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=m.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===m.trim(f.replace(Mb,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Mb.test(f)?f.replace(Mb,e):f+" "+e)}}),m.cssHooks.marginRight=Lb(k.reliableMarginRight,function(a,b){return b?m.swap(a,{display:"inline-block"},Jb,[a,"marginRight"]):void 0}),m.each({margin:"",padding:"",border:"Width"},function(a,b){m.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+T[d]+b]=f[d]||f[d-2]||f[0];return e}},Gb.test(a)||(m.cssHooks[a+b].set=Wb)}),m.fn.extend({css:function(a,b){return V(this,function(a,b,c){var d,e,f={},g=0;if(m.isArray(b)){for(d=Ib(a),e=b.length;e>g;g++)f[b[g]]=m.css(a,b[g],!1,d);return f}return void 0!==c?m.style(a,b,c):m.css(a,b)},a,b,arguments.length>1)},show:function(){return Vb(this,!0)},hide:function(){return Vb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){U(this)?m(this).show():m(this).hide()})}});function Zb(a,b,c,d,e){return new Zb.prototype.init(a,b,c,d,e) +}m.Tween=Zb,Zb.prototype={constructor:Zb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")},cur:function(){var a=Zb.propHooks[this.prop];return a&&a.get?a.get(this):Zb.propHooks._default.get(this)},run:function(a){var b,c=Zb.propHooks[this.prop];return this.pos=b=this.options.duration?m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Zb.propHooks._default.set(this),this}},Zb.prototype.init.prototype=Zb.prototype,Zb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Zb.propHooks.scrollTop=Zb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Zb.prototype.init,m.fx.step={};var $b,_b,ac=/^(?:toggle|show|hide)$/,bc=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cc=/queueHooks$/,dc=[ic],ec={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bc.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bc.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fc(){return setTimeout(function(){$b=void 0}),$b=m.now()}function gc(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hc(a,b,c){for(var d,e=(ec[b]||[]).concat(ec["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ic(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fb(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fb(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ac.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fb(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hc(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jc(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kc(a,b,c){var d,e,f=0,g=dc.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$b||fc(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$b||fc(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jc(k,j.opts.specialEasing);g>f;f++)if(d=dc[f].call(j,a,k,j.opts))return d;return m.map(k,hc,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kc,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],ec[c]=ec[c]||[],ec[c].unshift(b)},prefilter:function(a,b){b?dc.unshift(a):dc.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in m.fx.speeds?m.fx.speeds[d.duration]:m.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){m.isFunction(d.old)&&d.old.call(this),d.queue&&m.dequeue(this,d.queue)},d},m.fn.extend({fadeTo:function(a,b,c,d){return this.filter(U).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=m.isEmptyObject(a),f=m.speed(b,c,d),g=function(){var b=kc(this,m.extend({},a),f);(e||m._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=m.timers,g=m._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&cc.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&m.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=m._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=m.timers,g=d?d.length:0;for(c.finish=!0,m.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),m.each(["toggle","show","hide"],function(a,b){var c=m.fn[b];m.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gc(b,!0),a,d,e)}}),m.each({slideDown:gc("show"),slideUp:gc("hide"),slideToggle:gc("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){m.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),m.timers=[],m.fx.tick=function(){var a,b=m.timers,c=0;for($b=m.now();c<b.length;c++)a=b[c],a()||b[c]!==a||b.splice(c--,1);b.length||m.fx.stop(),$b=void 0},m.fx.timer=function(a){m.timers.push(a),a()?m.fx.start():m.timers.pop()},m.fx.interval=13,m.fx.start=function(){_b||(_b=setInterval(m.fx.tick,m.fx.interval))},m.fx.stop=function(){clearInterval(_b),_b=null},m.fx.speeds={slow:600,fast:200,_default:400},m.fn.delay=function(a,b){return a=m.fx?m.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a,b,c,d,e;b=y.createElement("div"),b.setAttribute("className","t"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=y.createElement("select"),e=c.appendChild(y.createElement("option")),a=b.getElementsByTagName("input")[0],d.style.cssText="top:1px",k.getSetAttribute="t"!==b.className,k.style=/top/.test(d.getAttribute("style")),k.hrefNormalized="/a"===d.getAttribute("href"),k.checkOn=!!a.value,k.optSelected=e.selected,k.enctype=!!y.createElement("form").enctype,c.disabled=!0,k.optDisabled=!e.disabled,a=y.createElement("input"),a.setAttribute("value",""),k.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),k.radioValue="t"===a.value}();var lc=/\r/g;m.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=m.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,m(this).val()):a,null==e?e="":"number"==typeof e?e+="":m.isArray(e)&&(e=m.map(e,function(a){return null==a?"":a+""})),b=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=m.valHooks[e.type]||m.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(lc,""):null==c?"":c)}}}),m.extend({valHooks:{option:{get:function(a){var b=m.find.attr(a,"value");return null!=b?b:m.trim(m.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&m.nodeName(c.parentNode,"optgroup"))){if(b=m(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=m.makeArray(b),g=e.length;while(g--)if(d=e[g],m.inArray(m.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(a,b){return m.isArray(b)?a.checked=m.inArray(m(a).val(),b)>=0:void 0}},k.checkOn||(m.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var mc,nc,oc=m.expr.attrHandle,pc=/^(?:checked|selected)$/i,qc=k.getSetAttribute,rc=k.input;m.fn.extend({attr:function(a,b){return V(this,m.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){m.removeAttr(this,a)})}}),m.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===K?m.prop(a,b,c):(1===f&&m.isXMLDoc(a)||(b=b.toLowerCase(),d=m.attrHooks[b]||(m.expr.match.bool.test(b)?nc:mc)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=m.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void m.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=m.propFix[c]||c,m.expr.match.bool.test(c)?rc&&qc||!pc.test(c)?a[d]=!1:a[m.camelCase("default-"+c)]=a[d]=!1:m.attr(a,c,""),a.removeAttribute(qc?c:d)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&m.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),nc={set:function(a,b,c){return b===!1?m.removeAttr(a,c):rc&&qc||!pc.test(c)?a.setAttribute(!qc&&m.propFix[c]||c,c):a[m.camelCase("default-"+c)]=a[c]=!0,c}},m.each(m.expr.match.bool.source.match(/\w+/g),function(a,b){var c=oc[b]||m.find.attr;oc[b]=rc&&qc||!pc.test(b)?function(a,b,d){var e,f;return d||(f=oc[b],oc[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,oc[b]=f),e}:function(a,b,c){return c?void 0:a[m.camelCase("default-"+b)]?b.toLowerCase():null}}),rc&&qc||(m.attrHooks.value={set:function(a,b,c){return m.nodeName(a,"input")?void(a.defaultValue=b):mc&&mc.set(a,b,c)}}),qc||(mc={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},oc.id=oc.name=oc.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},m.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:mc.set},m.attrHooks.contenteditable={set:function(a,b,c){mc.set(a,""===b?!1:b,c)}},m.each(["width","height"],function(a,b){m.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),k.style||(m.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var sc=/^(?:input|select|textarea|button|object)$/i,tc=/^(?:a|area)$/i;m.fn.extend({prop:function(a,b){return V(this,m.prop,a,b,arguments.length>1)},removeProp:function(a){return a=m.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),m.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!m.isXMLDoc(a),f&&(b=m.propFix[b]||b,e=m.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=m.find.attr(a,"tabindex");return b?parseInt(b,10):sc.test(a.nodeName)||tc.test(a.nodeName)&&a.href?0:-1}}}}),k.hrefNormalized||m.each(["href","src"],function(a,b){m.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),k.optSelected||(m.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),k.enctype||(m.propFix.enctype="encoding");var uc=/[\t\r\n\f]/g;m.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?m.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(m.isFunction(a)?function(c){m(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=m(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===K||"boolean"===c)&&(this.className&&m._data(this,"__className__",this.className),this.className=this.className||a===!1?"":m._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(uc," ").indexOf(b)>=0)return!0;return!1}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){m.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),m.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var vc=m.now(),wc=/\?/,xc=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;m.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=m.trim(b+"");return e&&!m.trim(e.replace(xc,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():m.error("Invalid JSON: "+b)},m.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+b),c};var yc,zc,Ac=/#.*$/,Bc=/([?&])_=[^&]*/,Cc=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Dc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ec=/^(?:GET|HEAD)$/,Fc=/^\/\//,Gc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hc={},Ic={},Jc="*/".concat("*");try{zc=location.href}catch(Kc){zc=y.createElement("a"),zc.href="",zc=zc.href}yc=Gc.exec(zc.toLowerCase())||[];function Lc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(m.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Mc(a,b,c,d){var e={},f=a===Ic;function g(h){var i;return e[h]=!0,m.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Nc(a,b){var c,d,e=m.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&m.extend(!0,a,c),a}function Oc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Pc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:zc,type:"GET",isLocal:Dc.test(yc[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Jc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Nc(Nc(a,m.ajaxSettings),b):Nc(m.ajaxSettings,a)},ajaxPrefilter:Lc(Hc),ajaxTransport:Lc(Ic),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=m.ajaxSetup({},b),l=k.context||k,n=k.context&&(l.nodeType||l.jquery)?m(l):m.event,o=m.Deferred(),p=m.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Cc.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||zc)+"").replace(Ac,"").replace(Fc,yc[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=m.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(c=Gc.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===yc[1]&&c[2]===yc[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(yc[3]||("http:"===yc[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=m.param(k.data,k.traditional)),Mc(Hc,k,b,v),2===t)return v;h=m.event&&k.global,h&&0===m.active++&&m.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Ec.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(wc.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Bc.test(e)?e.replace(Bc,"$1_="+vc++):e+(wc.test(e)?"&":"?")+"_="+vc++)),k.ifModified&&(m.lastModified[e]&&v.setRequestHeader("If-Modified-Since",m.lastModified[e]),m.etag[e]&&v.setRequestHeader("If-None-Match",m.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Jc+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Mc(Ic,k,b,v)){v.readyState=1,h&&n.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Oc(k,v,c)),u=Pc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(m.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(m.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&n.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(n.trigger("ajaxComplete",[v,k]),--m.active||m.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return m.get(a,b,c,"json")},getScript:function(a,b){return m.get(a,void 0,b,"script")}}),m.each(["get","post"],function(a,b){m[b]=function(a,c,d,e){return m.isFunction(c)&&(e=e||d,d=c,c=void 0),m.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),m._evalUrl=function(a){return m.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},m.fn.extend({wrapAll:function(a){if(m.isFunction(a))return this.each(function(b){m(this).wrapAll(a.call(this,b))});if(this[0]){var b=m(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(m.isFunction(a)?function(b){m(this).wrapInner(a.call(this,b))}:function(){var b=m(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=m.isFunction(a);return this.each(function(c){m(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!k.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||m.css(a,"display"))},m.expr.filters.visible=function(a){return!m.expr.filters.hidden(a)};var Qc=/%20/g,Rc=/\[\]$/,Sc=/\r?\n/g,Tc=/^(?:submit|button|image|reset|file)$/i,Uc=/^(?:input|select|textarea|keygen)/i;function Vc(a,b,c,d){var e;if(m.isArray(b))m.each(b,function(b,e){c||Rc.test(a)?d(a,e):Vc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==m.type(b))d(a,b);else for(e in b)Vc(a+"["+e+"]",b[e],c,d)}m.param=function(a,b){var c,d=[],e=function(a,b){b=m.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(a)||a.jquery&&!m.isPlainObject(a))m.each(a,function(){e(this.name,this.value)});else for(c in a)Vc(c,a[c],b,e);return d.join("&").replace(Qc,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=m.prop(this,"elements");return a?m.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!m(this).is(":disabled")&&Uc.test(this.nodeName)&&!Tc.test(a)&&(this.checked||!W.test(a))}).map(function(a,b){var c=m(this).val();return null==c?null:m.isArray(c)?m.map(c,function(a){return{name:b.name,value:a.replace(Sc,"\r\n")}}):{name:b.name,value:c.replace(Sc,"\r\n")}}).get()}}),m.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zc()||$c()}:Zc;var Wc=0,Xc={},Yc=m.ajaxSettings.xhr();a.attachEvent&&a.attachEvent("onunload",function(){for(var a in Xc)Xc[a](void 0,!0)}),k.cors=!!Yc&&"withCredentials"in Yc,Yc=k.ajax=!!Yc,Yc&&m.ajaxTransport(function(a){if(!a.crossDomain||k.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Wc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Xc[g],b=void 0,f.onreadystatechange=m.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Xc[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function Zc(){try{return new a.XMLHttpRequest}catch(b){}}function $c(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return m.globalEval(a),a}}}),m.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),m.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=y.head||m("head")[0]||y.documentElement;return{send:function(d,e){b=y.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var _c=[],ad=/(=)\?(?=&|$)|\?\?/;m.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=_c.pop()||m.expando+"_"+vc++;return this[a]=!0,a}}),m.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(ad.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&ad.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=m.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(ad,"$1"+e):b.jsonp!==!1&&(b.url+=(wc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||m.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,_c.push(e)),g&&m.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),m.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||y;var d=u.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=m.buildFragment([a],b,e),e&&e.length&&m(e).remove(),m.merge([],d.childNodes))};var bd=m.fn.load;m.fn.load=function(a,b,c){if("string"!=typeof a&&bd)return bd.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=m.trim(a.slice(h,a.length)),a=a.slice(0,h)),m.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&m.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?m("<div>").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cd=a.document.documentElement;function dd(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dd(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cd;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cd})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dd(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=Lb(k.pixelPosition,function(a,c){return c?(c=Jb(a,b),Hb.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ed=a.jQuery,fd=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fd),b&&a.jQuery===m&&(a.jQuery=ed),m},typeof b===K&&(a.jQuery=a.$=m),m}); + +/*! + * Bootstrap v3.3.4 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.4",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.4",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.4",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.4",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27|32)/.test(b.which)&&!/input|textarea/i.test(b.target.tagName)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g&&27!=b.which||g&&27==b.which)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(b.target);38==b.which&&j>0&&j--,40==b.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="menu"]',g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="listbox"]',g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in").attr("aria-hidden",!1),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a('<div class="modal-backdrop '+e+'" />').appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.init("tooltip",a,b)};c.VERSION="3.3.4",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport),this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-m<p.top?"bottom":"right"==h&&k.right+l>p.width?"left":"left"==h&&k.left-l<p.left?"right":h,f.removeClass(n).addClass(h)}var q=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(q,h);var r=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",r).emulateTransitionEnd(c.TRANSITION_DURATION):r()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top=b.top+g,b.left=b.left+h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);return this.$element.trigger(g),g.isDefaultPrevented()?void 0:(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this)},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.4",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.4",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.4",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){ +var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.4",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a(document.body).height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); +// OpenLayers 3. See https://openlayers.org/ +// License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md +// Version: v3.20.1 +;(function (root, factory) { + if (typeof exports === "object") { + module.exports = factory(); + } else if (typeof define === "function" && define.amd) { + define([], factory); + } else { + root.ol = factory(); + } +}(this, function () { + var OPENLAYERS = {}; + var goog = this.goog = {}; +this.CLOSURE_NO_DEPS = true; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Bootstrap for the Google JS Library (Closure). + * + * In uncompiled mode base.js will write out Closure's deps file, unless the + * global <code>CLOSURE_NO_DEPS</code> is set to true. This allows projects to + * include their own deps file(s) from different locations. + * + * @author arv@google.com (Erik Arvidsson) + * + * @provideGoog + */ + + +/** + * @define {boolean} Overridden to true by the compiler when + * --process_closure_primitives is specified. + */ +var COMPILED = false; + + +/** + * Base namespace for the Closure library. Checks to see goog is already + * defined in the current scope before assigning to prevent clobbering if + * base.js is loaded more than once. + * + * @const + */ +var goog = goog || {}; + + +/** + * Reference to the global context. In most cases this will be 'window'. + */ +goog.global = this; + + +/** + * A hook for overriding the define values in uncompiled mode. + * + * In uncompiled mode, {@code CLOSURE_UNCOMPILED_DEFINES} may be defined before + * loading base.js. If a key is defined in {@code CLOSURE_UNCOMPILED_DEFINES}, + * {@code goog.define} will use the value instead of the default value. This + * allows flags to be overwritten without compilation (this is normally + * accomplished with the compiler's "define" flag). + * + * Example: + * <pre> + * var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false}; + * </pre> + * + * @type {Object<string, (string|number|boolean)>|undefined} + */ +goog.global.CLOSURE_UNCOMPILED_DEFINES; + + +/** + * A hook for overriding the define values in uncompiled or compiled mode, + * like CLOSURE_UNCOMPILED_DEFINES but effective in compiled code. In + * uncompiled code CLOSURE_UNCOMPILED_DEFINES takes precedence. + * + * Also unlike CLOSURE_UNCOMPILED_DEFINES the values must be number, boolean or + * string literals or the compiler will emit an error. + * + * While any @define value may be set, only those set with goog.define will be + * effective for uncompiled code. + * + * Example: + * <pre> + * var CLOSURE_DEFINES = {'goog.DEBUG': false} ; + * </pre> + * + * @type {Object<string, (string|number|boolean)>|undefined} + */ +goog.global.CLOSURE_DEFINES; + + +/** + * Returns true if the specified value is not undefined. + * WARNING: Do not use this to test if an object has a property. Use the in + * operator instead. + * + * @param {?} val Variable to test. + * @return {boolean} Whether variable is defined. + */ +goog.isDef = function(val) { + // void 0 always evaluates to undefined and hence we do not need to depend on + // the definition of the global variable named 'undefined'. + return val !== void 0; +}; + + +/** + * Builds an object structure for the provided namespace path, ensuring that + * names that already exist are not overwritten. For example: + * "a.b.c" -> a = {};a.b={};a.b.c={}; + * Used by goog.provide and goog.exportSymbol. + * @param {string} name name of the object that this file defines. + * @param {*=} opt_object the object to expose at the end of the path. + * @param {Object=} opt_objectToExportTo The object to add the path to; default + * is |goog.global|. + * @private + */ +goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) { + var parts = name.split('.'); + var cur = opt_objectToExportTo || goog.global; + + // Internet Explorer exhibits strange behavior when throwing errors from + // methods externed in this manner. See the testExportSymbolExceptions in + // base_test.html for an example. + if (!(parts[0] in cur) && cur.execScript) { + cur.execScript('var ' + parts[0]); + } + + // Certain browsers cannot parse code in the form for((a in b); c;); + // This pattern is produced by the JSCompiler when it collapses the + // statement above into the conditional loop below. To prevent this from + // happening, use a for-loop and reserve the init logic as below. + + // Parentheses added to eliminate strict JS warning in Firefox. + for (var part; parts.length && (part = parts.shift());) { + if (!parts.length && goog.isDef(opt_object)) { + // last part and we have an object; use it + cur[part] = opt_object; + } else if (cur[part]) { + cur = cur[part]; + } else { + cur = cur[part] = {}; + } + } +}; + + +/** + * Defines a named value. In uncompiled mode, the value is retrieved from + * CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and + * has the property specified, and otherwise used the defined defaultValue. + * When compiled the default can be overridden using the compiler + * options or the value set in the CLOSURE_DEFINES object. + * + * @param {string} name The distinguished name to provide. + * @param {string|number|boolean} defaultValue + */ +goog.define = function(name, defaultValue) { + var value = defaultValue; + if (!COMPILED) { + if (goog.global.CLOSURE_UNCOMPILED_DEFINES && + Object.prototype.hasOwnProperty.call( + goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) { + value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name]; + } else if ( + goog.global.CLOSURE_DEFINES && + Object.prototype.hasOwnProperty.call( + goog.global.CLOSURE_DEFINES, name)) { + value = goog.global.CLOSURE_DEFINES[name]; + } + } + goog.exportPath_(name, value); +}; + + +/** + * @define {boolean} DEBUG is provided as a convenience so that debugging code + * that should not be included in a production js_binary can be easily stripped + * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most + * toString() methods should be declared inside an "if (goog.DEBUG)" conditional + * because they are generally used for debugging purposes and it is difficult + * for the JSCompiler to statically determine whether they are used. + */ +goog.define('goog.DEBUG', true); + + +/** + * @define {string} LOCALE defines the locale being used for compilation. It is + * used to select locale specific data to be compiled in js binary. BUILD rule + * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler + * option. + * + * Take into account that the locale code format is important. You should use + * the canonical Unicode format with hyphen as a delimiter. Language must be + * lowercase, Language Script - Capitalized, Region - UPPERCASE. + * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN. + * + * See more info about locale codes here: + * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers + * + * For language codes you should use values defined by ISO 693-1. See it here + * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from + * this rule: the Hebrew language. For legacy reasons the old code (iw) should + * be used instead of the new code (he), see http://wiki/Main/IIISynonyms. + */ +goog.define('goog.LOCALE', 'en'); // default to en + + +/** + * @define {boolean} Whether this code is running on trusted sites. + * + * On untrusted sites, several native functions can be defined or overridden by + * external libraries like Prototype, Datejs, and JQuery and setting this flag + * to false forces closure to use its own implementations when possible. + * + * If your JavaScript can be loaded by a third party site and you are wary about + * relying on non-standard implementations, specify + * "--define goog.TRUSTED_SITE=false" to the JSCompiler. + */ +goog.define('goog.TRUSTED_SITE', true); + + +/** + * @define {boolean} Whether a project is expected to be running in strict mode. + * + * This define can be used to trigger alternate implementations compatible with + * running in EcmaScript Strict mode or warn about unavailable functionality. + * @see https://goo.gl/PudQ4y + * + */ +goog.define('goog.STRICT_MODE_COMPATIBLE', false); + + +/** + * @define {boolean} Whether code that calls {@link goog.setTestOnly} should + * be disallowed in the compilation unit. + */ +goog.define('goog.DISALLOW_TEST_ONLY_CODE', COMPILED && !goog.DEBUG); + + +/** + * @define {boolean} Whether to use a Chrome app CSP-compliant method for + * loading scripts via goog.require. @see appendScriptSrcNode_. + */ +goog.define('goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING', false); + + +/** + * Defines a namespace in Closure. + * + * A namespace may only be defined once in a codebase. It may be defined using + * goog.provide() or goog.module(). + * + * The presence of one or more goog.provide() calls in a file indicates + * that the file defines the given objects/namespaces. + * Provided symbols must not be null or undefined. + * + * In addition, goog.provide() creates the object stubs for a namespace + * (for example, goog.provide("goog.foo.bar") will create the object + * goog.foo.bar if it does not already exist). + * + * Build tools also scan for provide/require/module statements + * to discern dependencies, build dependency files (see deps.js), etc. + * + * @see goog.require + * @see goog.module + * @param {string} name Namespace provided by this file in the form + * "goog.package.part". + */ +goog.provide = function(name) { + if (goog.isInModuleLoader_()) { + throw Error('goog.provide can not be used within a goog.module.'); + } + if (!COMPILED) { + // Ensure that the same namespace isn't provided twice. + // A goog.module/goog.provide maps a goog.require to a specific file + if (goog.isProvided_(name)) { + throw Error('Namespace "' + name + '" already declared.'); + } + } + + goog.constructNamespace_(name); +}; + + +/** + * @param {string} name Namespace provided by this file in the form + * "goog.package.part". + * @param {Object=} opt_obj The object to embed in the namespace. + * @private + */ +goog.constructNamespace_ = function(name, opt_obj) { + if (!COMPILED) { + delete goog.implicitNamespaces_[name]; + + var namespace = name; + while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) { + if (goog.getObjectByName(namespace)) { + break; + } + goog.implicitNamespaces_[namespace] = true; + } + } + + goog.exportPath_(name, opt_obj); +}; + + +/** + * Module identifier validation regexp. + * Note: This is a conservative check, it is very possible to be more lenient, + * the primary exclusion here is "/" and "\" and a leading ".", these + * restrictions are intended to leave the door open for using goog.require + * with relative file paths rather than module identifiers. + * @private + */ +goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/; + + +/** + * Defines a module in Closure. + * + * Marks that this file must be loaded as a module and claims the namespace. + * + * A namespace may only be defined once in a codebase. It may be defined using + * goog.provide() or goog.module(). + * + * goog.module() has three requirements: + * - goog.module may not be used in the same file as goog.provide. + * - goog.module must be the first statement in the file. + * - only one goog.module is allowed per file. + * + * When a goog.module annotated file is loaded, it is enclosed in + * a strict function closure. This means that: + * - any variables declared in a goog.module file are private to the file + * (not global), though the compiler is expected to inline the module. + * - The code must obey all the rules of "strict" JavaScript. + * - the file will be marked as "use strict" + * + * NOTE: unlike goog.provide, goog.module does not declare any symbols by + * itself. If declared symbols are desired, use + * goog.module.declareLegacyNamespace(). + * + * + * See the public goog.module proposal: http://goo.gl/Va1hin + * + * @param {string} name Namespace provided by this file in the form + * "goog.package.part", is expected but not required. + */ +goog.module = function(name) { + if (!goog.isString(name) || !name || + name.search(goog.VALID_MODULE_RE_) == -1) { + throw Error('Invalid module identifier'); + } + if (!goog.isInModuleLoader_()) { + throw Error('Module ' + name + ' has been loaded incorrectly.'); + } + if (goog.moduleLoaderState_.moduleName) { + throw Error('goog.module may only be called once per module.'); + } + + // Store the module name for the loader. + goog.moduleLoaderState_.moduleName = name; + if (!COMPILED) { + // Ensure that the same namespace isn't provided twice. + // A goog.module/goog.provide maps a goog.require to a specific file + if (goog.isProvided_(name)) { + throw Error('Namespace "' + name + '" already declared.'); + } + delete goog.implicitNamespaces_[name]; + } +}; + + +/** + * @param {string} name The module identifier. + * @return {?} The module exports for an already loaded module or null. + * + * Note: This is not an alternative to goog.require, it does not + * indicate a hard dependency, instead it is used to indicate + * an optional dependency or to access the exports of a module + * that has already been loaded. + * @suppress {missingProvide} + */ +goog.module.get = function(name) { + return goog.module.getInternal_(name); +}; + + +/** + * @param {string} name The module identifier. + * @return {?} The module exports for an already loaded module or null. + * @private + */ +goog.module.getInternal_ = function(name) { + if (!COMPILED) { + if (goog.isProvided_(name)) { + // goog.require only return a value with-in goog.module files. + return name in goog.loadedModules_ ? goog.loadedModules_[name] : + goog.getObjectByName(name); + } else { + return null; + } + } +}; + + +/** + * @private {?{moduleName: (string|undefined), declareLegacyNamespace:boolean}} + */ +goog.moduleLoaderState_ = null; + + +/** + * @private + * @return {boolean} Whether a goog.module is currently being initialized. + */ +goog.isInModuleLoader_ = function() { + return goog.moduleLoaderState_ != null; +}; + + +/** + * Provide the module's exports as a globally accessible object under the + * module's declared name. This is intended to ease migration to goog.module + * for files that have existing usages. + * @suppress {missingProvide} + */ +goog.module.declareLegacyNamespace = function() { + if (!COMPILED && !goog.isInModuleLoader_()) { + throw new Error( + 'goog.module.declareLegacyNamespace must be called from ' + + 'within a goog.module'); + } + if (!COMPILED && !goog.moduleLoaderState_.moduleName) { + throw Error( + 'goog.module must be called prior to ' + + 'goog.module.declareLegacyNamespace.'); + } + goog.moduleLoaderState_.declareLegacyNamespace = true; +}; + + +/** + * Marks that the current file should only be used for testing, and never for + * live code in production. + * + * In the case of unit tests, the message may optionally be an exact namespace + * for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra + * provide (if not explicitly defined in the code). + * + * @param {string=} opt_message Optional message to add to the error that's + * raised when used in production code. + */ +goog.setTestOnly = function(opt_message) { + if (goog.DISALLOW_TEST_ONLY_CODE) { + opt_message = opt_message || ''; + throw Error( + 'Importing test-only code into non-debug environment' + + (opt_message ? ': ' + opt_message : '.')); + } +}; + + +/** + * Forward declares a symbol. This is an indication to the compiler that the + * symbol may be used in the source yet is not required and may not be provided + * in compilation. + * + * The most common usage of forward declaration is code that takes a type as a + * function parameter but does not need to require it. By forward declaring + * instead of requiring, no hard dependency is made, and (if not required + * elsewhere) the namespace may never be required and thus, not be pulled + * into the JavaScript binary. If it is required elsewhere, it will be type + * checked as normal. + * + * + * @param {string} name The namespace to forward declare in the form of + * "goog.package.part". + */ +goog.forwardDeclare = function(name) {}; + + +/** + * Forward declare type information. Used to assign types to goog.global + * referenced object that would otherwise result in unknown type references + * and thus block property disambiguation. + */ +goog.forwardDeclare('Document'); +goog.forwardDeclare('HTMLScriptElement'); +goog.forwardDeclare('XMLHttpRequest'); + + +if (!COMPILED) { + /** + * Check if the given name has been goog.provided. This will return false for + * names that are available only as implicit namespaces. + * @param {string} name name of the object to look for. + * @return {boolean} Whether the name has been provided. + * @private + */ + goog.isProvided_ = function(name) { + return (name in goog.loadedModules_) || + (!goog.implicitNamespaces_[name] && + goog.isDefAndNotNull(goog.getObjectByName(name))); + }; + + /** + * Namespaces implicitly defined by goog.provide. For example, + * goog.provide('goog.events.Event') implicitly declares that 'goog' and + * 'goog.events' must be namespaces. + * + * @type {!Object<string, (boolean|undefined)>} + * @private + */ + goog.implicitNamespaces_ = {'goog.module': true}; + + // NOTE: We add goog.module as an implicit namespace as goog.module is defined + // here and because the existing module package has not been moved yet out of + // the goog.module namespace. This satisifies both the debug loader and + // ahead-of-time dependency management. +} + + +/** + * Returns an object based on its fully qualified external name. The object + * is not found if null or undefined. If you are using a compilation pass that + * renames property names beware that using this function will not find renamed + * properties. + * + * @param {string} name The fully qualified name. + * @param {Object=} opt_obj The object within which to look; default is + * |goog.global|. + * @return {?} The value (object or primitive) or, if not found, null. + */ +goog.getObjectByName = function(name, opt_obj) { + var parts = name.split('.'); + var cur = opt_obj || goog.global; + for (var part; part = parts.shift();) { + if (goog.isDefAndNotNull(cur[part])) { + cur = cur[part]; + } else { + return null; + } + } + return cur; +}; + + +/** + * Globalizes a whole namespace, such as goog or goog.lang. + * + * @param {!Object} obj The namespace to globalize. + * @param {Object=} opt_global The object to add the properties to. + * @deprecated Properties may be explicitly exported to the global scope, but + * this should no longer be done in bulk. + */ +goog.globalize = function(obj, opt_global) { + var global = opt_global || goog.global; + for (var x in obj) { + global[x] = obj[x]; + } +}; + + +/** + * Adds a dependency from a file to the files it requires. + * @param {string} relPath The path to the js file. + * @param {!Array<string>} provides An array of strings with + * the names of the objects this file provides. + * @param {!Array<string>} requires An array of strings with + * the names of the objects this file requires. + * @param {boolean|!Object<string>=} opt_loadFlags Parameters indicating + * how the file must be loaded. The boolean 'true' is equivalent + * to {'module': 'goog'} for backwards-compatibility. Valid properties + * and values include {'module': 'goog'} and {'lang': 'es6'}. + */ +goog.addDependency = function(relPath, provides, requires, opt_loadFlags) { + if (goog.DEPENDENCIES_ENABLED) { + var provide, require; + var path = relPath.replace(/\\/g, '/'); + var deps = goog.dependencies_; + if (!opt_loadFlags || typeof opt_loadFlags === 'boolean') { + opt_loadFlags = opt_loadFlags ? {'module': 'goog'} : {}; + } + for (var i = 0; provide = provides[i]; i++) { + deps.nameToPath[provide] = path; + deps.loadFlags[path] = opt_loadFlags; + } + for (var j = 0; require = requires[j]; j++) { + if (!(path in deps.requires)) { + deps.requires[path] = {}; + } + deps.requires[path][require] = true; + } + } +}; + + + + +// NOTE(nnaze): The debug DOM loader was included in base.js as an original way +// to do "debug-mode" development. The dependency system can sometimes be +// confusing, as can the debug DOM loader's asynchronous nature. +// +// With the DOM loader, a call to goog.require() is not blocking -- the script +// will not load until some point after the current script. If a namespace is +// needed at runtime, it needs to be defined in a previous script, or loaded via +// require() with its registered dependencies. +// +// User-defined namespaces may need their own deps file. For a reference on +// creating a deps file, see: +// Externally: https://developers.google.com/closure/library/docs/depswriter +// +// Because of legacy clients, the DOM loader can't be easily removed from +// base.js. Work is being done to make it disableable or replaceable for +// different environments (DOM-less JavaScript interpreters like Rhino or V8, +// for example). See bootstrap/ for more information. + + +/** + * @define {boolean} Whether to enable the debug loader. + * + * If enabled, a call to goog.require() will attempt to load the namespace by + * appending a script tag to the DOM (if the namespace has been registered). + * + * If disabled, goog.require() will simply assert that the namespace has been + * provided (and depend on the fact that some outside tool correctly ordered + * the script). + */ +goog.define('goog.ENABLE_DEBUG_LOADER', true); + + +/** + * @param {string} msg + * @private + */ +goog.logToConsole_ = function(msg) { + if (goog.global.console) { + goog.global.console['error'](msg); + } +}; + + +/** + * Implements a system for the dynamic resolution of dependencies that works in + * parallel with the BUILD system. Note that all calls to goog.require will be + * stripped by the JSCompiler when the --process_closure_primitives option is + * used. + * @see goog.provide + * @param {string} name Namespace to include (as was given in goog.provide()) in + * the form "goog.package.part". + * @return {?} If called within a goog.module file, the associated namespace or + * module otherwise null. + */ +goog.require = function(name) { + // If the object already exists we do not need do do anything. + if (!COMPILED) { + if (goog.ENABLE_DEBUG_LOADER && goog.IS_OLD_IE_) { + goog.maybeProcessDeferredDep_(name); + } + + if (goog.isProvided_(name)) { + if (goog.isInModuleLoader_()) { + return goog.module.getInternal_(name); + } else { + return null; + } + } + + if (goog.ENABLE_DEBUG_LOADER) { + var path = goog.getPathFromDeps_(name); + if (path) { + goog.writeScripts_(path); + return null; + } + } + + var errorMessage = 'goog.require could not find: ' + name; + goog.logToConsole_(errorMessage); + + throw Error(errorMessage); + } +}; + + +/** + * Path for included scripts. + * @type {string} + */ +goog.basePath = ''; + + +/** + * A hook for overriding the base path. + * @type {string|undefined} + */ +goog.global.CLOSURE_BASE_PATH; + + +/** + * Whether to write out Closure's deps file. By default, the deps are written. + * @type {boolean|undefined} + */ +goog.global.CLOSURE_NO_DEPS; + + +/** + * A function to import a single script. This is meant to be overridden when + * Closure is being run in non-HTML contexts, such as web workers. It's defined + * in the global scope so that it can be set before base.js is loaded, which + * allows deps.js to be imported properly. + * + * The function is passed the script source, which is a relative URI. It should + * return true if the script was imported, false otherwise. + * @type {(function(string): boolean)|undefined} + */ +goog.global.CLOSURE_IMPORT_SCRIPT; + + +/** + * Null function used for default values of callbacks, etc. + * @return {void} Nothing. + */ +goog.nullFunction = function() {}; + + +/** + * When defining a class Foo with an abstract method bar(), you can do: + * Foo.prototype.bar = goog.abstractMethod + * + * Now if a subclass of Foo fails to override bar(), an error will be thrown + * when bar() is invoked. + * + * Note: This does not take the name of the function to override as an argument + * because that would make it more difficult to obfuscate our JavaScript code. + * + * @type {!Function} + * @throws {Error} when invoked to indicate the method should be overridden. + */ +goog.abstractMethod = function() { + throw Error('unimplemented abstract method'); +}; + + +/** + * Adds a {@code getInstance} static method that always returns the same + * instance object. + * @param {!Function} ctor The constructor for the class to add the static + * method to. + */ +goog.addSingletonGetter = function(ctor) { + ctor.getInstance = function() { + if (ctor.instance_) { + return ctor.instance_; + } + if (goog.DEBUG) { + // NOTE: JSCompiler can't optimize away Array#push. + goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor; + } + return ctor.instance_ = new ctor; + }; +}; + + +/** + * All singleton classes that have been instantiated, for testing. Don't read + * it directly, use the {@code goog.testing.singleton} module. The compiler + * removes this variable if unused. + * @type {!Array<!Function>} + * @private + */ +goog.instantiatedSingletons_ = []; + + +/** + * @define {boolean} Whether to load goog.modules using {@code eval} when using + * the debug loader. This provides a better debugging experience as the + * source is unmodified and can be edited using Chrome Workspaces or similar. + * However in some environments the use of {@code eval} is banned + * so we provide an alternative. + */ +goog.define('goog.LOAD_MODULE_USING_EVAL', true); + + +/** + * @define {boolean} Whether the exports of goog.modules should be sealed when + * possible. + */ +goog.define('goog.SEAL_MODULE_EXPORTS', goog.DEBUG); + + +/** + * The registry of initialized modules: + * the module identifier to module exports map. + * @private @const {!Object<string, ?>} + */ +goog.loadedModules_ = {}; + + +/** + * True if goog.dependencies_ is available. + * @const {boolean} + */ +goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER; + + +/** + * @define {string} How to decide whether to transpile. Valid values + * are 'always', 'never', and 'detect'. The default ('detect') is to + * use feature detection to determine which language levels need + * transpilation. + */ +// NOTE(user): we could expand this to accept a language level to bypass +// detection: e.g. goog.TRANSPILE == 'es5' would transpile ES6 files but +// would leave ES3 and ES5 files alone. +goog.define('goog.TRANSPILE', 'detect'); + + +/** + * @define {string} Path to the transpiler. Executing the script at this + * path (relative to base.js) should define a function $jscomp.transpile. + */ +goog.define('goog.TRANSPILER', 'transpile.js'); + + +if (goog.DEPENDENCIES_ENABLED) { + /** + * This object is used to keep track of dependencies and other data that is + * used for loading scripts. + * @private + * @type {{ + * loadFlags: !Object<string, !Object<string, string>>, + * nameToPath: !Object<string, string>, + * requires: !Object<string, !Object<string, boolean>>, + * visited: !Object<string, boolean>, + * written: !Object<string, boolean>, + * deferred: !Object<string, string> + * }} + */ + goog.dependencies_ = { + loadFlags: {}, // 1 to 1 + + nameToPath: {}, // 1 to 1 + + requires: {}, // 1 to many + + // Used when resolving dependencies to prevent us from visiting file twice. + visited: {}, + + written: {}, // Used to keep track of script files we have written. + + deferred: {} // Used to track deferred module evaluations in old IEs + }; + + + /** + * Tries to detect whether is in the context of an HTML document. + * @return {boolean} True if it looks like HTML document. + * @private + */ + goog.inHtmlDocument_ = function() { + /** @type {Document} */ + var doc = goog.global.document; + return doc != null && 'write' in doc; // XULDocument misses write. + }; + + + /** + * Tries to detect the base path of base.js script that bootstraps Closure. + * @private + */ + goog.findBasePath_ = function() { + if (goog.isDef(goog.global.CLOSURE_BASE_PATH)) { + goog.basePath = goog.global.CLOSURE_BASE_PATH; + return; + } else if (!goog.inHtmlDocument_()) { + return; + } + /** @type {Document} */ + var doc = goog.global.document; + var scripts = doc.getElementsByTagName('SCRIPT'); + // Search backwards since the current script is in almost all cases the one + // that has base.js. + for (var i = scripts.length - 1; i >= 0; --i) { + var script = /** @type {!HTMLScriptElement} */ (scripts[i]); + var src = script.src; + var qmark = src.lastIndexOf('?'); + var l = qmark == -1 ? src.length : qmark; + if (src.substr(l - 7, 7) == 'base.js') { + goog.basePath = src.substr(0, l - 7); + return; + } + } + }; + + + /** + * Imports a script if, and only if, that script hasn't already been imported. + * (Must be called at execution time) + * @param {string} src Script source. + * @param {string=} opt_sourceText The optionally source text to evaluate + * @private + */ + goog.importScript_ = function(src, opt_sourceText) { + var importScript = + goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_; + if (importScript(src, opt_sourceText)) { + goog.dependencies_.written[src] = true; + } + }; + + + /** + * Whether the browser is IE9 or earlier, which needs special handling + * for deferred modules. + * @const @private {boolean} + */ + goog.IS_OLD_IE_ = + !!(!goog.global.atob && goog.global.document && goog.global.document.all); + + + /** + * Given a URL initiate retrieval and execution of a script that needs + * pre-processing. + * @param {string} src Script source URL. + * @param {boolean} isModule Whether this is a goog.module. + * @param {boolean} needsTranspile Whether this source needs transpilation. + * @private + */ + goog.importProcessedScript_ = function(src, isModule, needsTranspile) { + // In an attempt to keep browsers from timing out loading scripts using + // synchronous XHRs, put each load in its own script block. + var bootstrap = 'goog.retrieveAndExec_("' + src + '", ' + isModule + ', ' + + needsTranspile + ');'; + + goog.importScript_('', bootstrap); + }; + + + /** @private {!Array<string>} */ + goog.queuedModules_ = []; + + + /** + * Return an appropriate module text. Suitable to insert into + * a script tag (that is unescaped). + * @param {string} srcUrl + * @param {string} scriptText + * @return {string} + * @private + */ + goog.wrapModule_ = function(srcUrl, scriptText) { + if (!goog.LOAD_MODULE_USING_EVAL || !goog.isDef(goog.global.JSON)) { + return '' + + 'goog.loadModule(function(exports) {' + + '"use strict";' + scriptText + + '\n' + // terminate any trailing single line comment. + ';return exports' + + '});' + + '\n//# sourceURL=' + srcUrl + '\n'; + } else { + return '' + + 'goog.loadModule(' + + goog.global.JSON.stringify( + scriptText + '\n//# sourceURL=' + srcUrl + '\n') + + ');'; + } + }; + + // On IE9 and earlier, it is necessary to handle + // deferred module loads. In later browsers, the + // code to be evaluated is simply inserted as a script + // block in the correct order. To eval deferred + // code at the right time, we piggy back on goog.require to call + // goog.maybeProcessDeferredDep_. + // + // The goog.requires are used both to bootstrap + // the loading process (when no deps are available) and + // declare that they should be available. + // + // Here we eval the sources, if all the deps are available + // either already eval'd or goog.require'd. This will + // be the case when all the dependencies have already + // been loaded, and the dependent module is loaded. + // + // But this alone isn't sufficient because it is also + // necessary to handle the case where there is no root + // that is not deferred. For that there we register for an event + // and trigger goog.loadQueuedModules_ handle any remaining deferred + // evaluations. + + /** + * Handle any remaining deferred goog.module evals. + * @private + */ + goog.loadQueuedModules_ = function() { + var count = goog.queuedModules_.length; + if (count > 0) { + var queue = goog.queuedModules_; + goog.queuedModules_ = []; + for (var i = 0; i < count; i++) { + var path = queue[i]; + goog.maybeProcessDeferredPath_(path); + } + } + }; + + + /** + * Eval the named module if its dependencies are + * available. + * @param {string} name The module to load. + * @private + */ + goog.maybeProcessDeferredDep_ = function(name) { + if (goog.isDeferredModule_(name) && goog.allDepsAreAvailable_(name)) { + var path = goog.getPathFromDeps_(name); + goog.maybeProcessDeferredPath_(goog.basePath + path); + } + }; + + /** + * @param {string} name The module to check. + * @return {boolean} Whether the name represents a + * module whose evaluation has been deferred. + * @private + */ + goog.isDeferredModule_ = function(name) { + var path = goog.getPathFromDeps_(name); + var loadFlags = path && goog.dependencies_.loadFlags[path] || {}; + if (path && (loadFlags['module'] == 'goog' || + goog.needsTranspile_(loadFlags['lang']))) { + var abspath = goog.basePath + path; + return (abspath) in goog.dependencies_.deferred; + } + return false; + }; + + /** + * @param {string} name The module to check. + * @return {boolean} Whether the name represents a + * module whose declared dependencies have all been loaded + * (eval'd or a deferred module load) + * @private + */ + goog.allDepsAreAvailable_ = function(name) { + var path = goog.getPathFromDeps_(name); + if (path && (path in goog.dependencies_.requires)) { + for (var requireName in goog.dependencies_.requires[path]) { + if (!goog.isProvided_(requireName) && + !goog.isDeferredModule_(requireName)) { + return false; + } + } + } + return true; + }; + + + /** + * @param {string} abspath + * @private + */ + goog.maybeProcessDeferredPath_ = function(abspath) { + if (abspath in goog.dependencies_.deferred) { + var src = goog.dependencies_.deferred[abspath]; + delete goog.dependencies_.deferred[abspath]; + goog.globalEval(src); + } + }; + + + /** + * Load a goog.module from the provided URL. This is not a general purpose + * code loader and does not support late loading code, that is it should only + * be used during page load. This method exists to support unit tests and + * "debug" loaders that would otherwise have inserted script tags. Under the + * hood this needs to use a synchronous XHR and is not recommeneded for + * production code. + * + * The module's goog.requires must have already been satisified; an exception + * will be thrown if this is not the case. This assumption is that no + * "deps.js" file exists, so there is no way to discover and locate the + * module-to-be-loaded's dependencies and no attempt is made to do so. + * + * There should only be one attempt to load a module. If + * "goog.loadModuleFromUrl" is called for an already loaded module, an + * exception will be throw. + * + * @param {string} url The URL from which to attempt to load the goog.module. + */ + goog.loadModuleFromUrl = function(url) { + // Because this executes synchronously, we don't need to do any additional + // bookkeeping. When "goog.loadModule" the namespace will be marked as + // having been provided which is sufficient. + goog.retrieveAndExec_(url, true, false); + }; + + + /** + * Writes a new script pointing to {@code src} directly into the DOM. + * + * NOTE: This method is not CSP-compliant. @see goog.appendScriptSrcNode_ for + * the fallback mechanism. + * + * @param {string} src The script URL. + * @private + */ + goog.writeScriptSrcNode_ = function(src) { + goog.global.document.write( + '<script type="text/javascript" src="' + src + '"></' + + 'script>'); + }; + + + /** + * Appends a new script node to the DOM using a CSP-compliant mechanism. This + * method exists as a fallback for document.write (which is not allowed in a + * strict CSP context, e.g., Chrome apps). + * + * NOTE: This method is not analogous to using document.write to insert a + * <script> tag; specifically, the user agent will execute a script added by + * document.write immediately after the current script block finishes + * executing, whereas the DOM-appended script node will not be executed until + * the entire document is parsed and executed. That is to say, this script is + * added to the end of the script execution queue. + * + * The page must not attempt to call goog.required entities until after the + * document has loaded, e.g., in or after the window.onload callback. + * + * @param {string} src The script URL. + * @private + */ + goog.appendScriptSrcNode_ = function(src) { + /** @type {Document} */ + var doc = goog.global.document; + var scriptEl = + /** @type {HTMLScriptElement} */ (doc.createElement('script')); + scriptEl.type = 'text/javascript'; + scriptEl.src = src; + scriptEl.defer = false; + scriptEl.async = false; + doc.head.appendChild(scriptEl); + }; + + + /** + * The default implementation of the import function. Writes a script tag to + * import the script. + * + * @param {string} src The script url. + * @param {string=} opt_sourceText The optionally source text to evaluate + * @return {boolean} True if the script was imported, false otherwise. + * @private + */ + goog.writeScriptTag_ = function(src, opt_sourceText) { + if (goog.inHtmlDocument_()) { + /** @type {!HTMLDocument} */ + var doc = goog.global.document; + + // If the user tries to require a new symbol after document load, + // something has gone terribly wrong. Doing a document.write would + // wipe out the page. This does not apply to the CSP-compliant method + // of writing script tags. + if (!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING && + doc.readyState == 'complete') { + // Certain test frameworks load base.js multiple times, which tries + // to write deps.js each time. If that happens, just fail silently. + // These frameworks wipe the page between each load of base.js, so this + // is OK. + var isDeps = /\bdeps.js$/.test(src); + if (isDeps) { + return false; + } else { + throw Error('Cannot write "' + src + '" after document load'); + } + } + + if (opt_sourceText === undefined) { + if (!goog.IS_OLD_IE_) { + if (goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING) { + goog.appendScriptSrcNode_(src); + } else { + goog.writeScriptSrcNode_(src); + } + } else { + var state = " onreadystatechange='goog.onScriptLoad_(this, " + + ++goog.lastNonModuleScriptIndex_ + ")' "; + doc.write( + '<script type="text/javascript" src="' + src + '"' + state + + '></' + + 'script>'); + } + } else { + doc.write( + '<script type="text/javascript">' + opt_sourceText + '</' + + 'script>'); + } + return true; + } else { + return false; + } + }; + + + /** + * Determines whether the given language needs to be transpiled. + * @param {string} lang + * @return {boolean} + * @private + */ + goog.needsTranspile_ = function(lang) { + if (goog.TRANSPILE == 'always') { + return true; + } else if (goog.TRANSPILE == 'never') { + return false; + } else if (!goog.transpiledLanguages_) { + goog.transpiledLanguages_ = {'es5': true, 'es6': true, 'es6-impl': true}; + /** @preserveTry */ + try { + // Perform some quick conformance checks, to distinguish + // between browsers that support es5, es6-impl, or es6. + + // Identify ES3-only browsers by their incorrect treatment of commas. + goog.transpiledLanguages_['es5'] = eval('[1,].length!=1'); + + // As browsers mature, features will be moved from the full test + // into the impl test. This must happen before the corresponding + // features are changed in the Closure Compiler's FeatureSet object. + + // Test 1: es6-impl [FF49, Edge 13, Chrome 49] + // (a) let/const keyword, (b) class expressions, (c) Map object, + // (d) iterable arguments, (e) spread operator + var es6implTest = + 'let a={};const X=class{constructor(){}x(z){return new Map([' + + '...arguments]).get(z[0])==3}};return new X().x([a,3])'; + + // Test 2: es6 [FF50 (?), Edge 14 (?), Chrome 50] + // (a) default params (specifically shadowing locals), + // (b) destructuring, (c) block-scoped functions, + // (d) for-of (const), (e) new.target/Reflect.construct + var es6fullTest = + 'class X{constructor(){if(new.target!=String)throw 1;this.x=42}}' + + 'let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof ' + + 'String))throw 1;for(const a of[2,3]){if(a==2)continue;function ' + + 'f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()' + + '==3}'; + + if (eval('(()=>{"use strict";' + es6implTest + '})()')) { + goog.transpiledLanguages_['es6-impl'] = false; + } + if (eval('(()=>{"use strict";' + es6fullTest + '})()')) { + goog.transpiledLanguages_['es6'] = false; + } + } catch (err) { + } + } + return !!goog.transpiledLanguages_[lang]; + }; + + + /** @private {?Object<string, boolean>} */ + goog.transpiledLanguages_ = null; + + + /** @private {number} */ + goog.lastNonModuleScriptIndex_ = 0; + + + /** + * A readystatechange handler for legacy IE + * @param {!HTMLScriptElement} script + * @param {number} scriptIndex + * @return {boolean} + * @private + */ + goog.onScriptLoad_ = function(script, scriptIndex) { + // for now load the modules when we reach the last script, + // later allow more inter-mingling. + if (script.readyState == 'complete' && + goog.lastNonModuleScriptIndex_ == scriptIndex) { + goog.loadQueuedModules_(); + } + return true; + }; + + /** + * Resolves dependencies based on the dependencies added using addDependency + * and calls importScript_ in the correct order. + * @param {string} pathToLoad The path from which to start discovering + * dependencies. + * @private + */ + goog.writeScripts_ = function(pathToLoad) { + /** @type {!Array<string>} The scripts we need to write this time. */ + var scripts = []; + var seenScript = {}; + var deps = goog.dependencies_; + + /** @param {string} path */ + function visitNode(path) { + if (path in deps.written) { + return; + } + + // We have already visited this one. We can get here if we have cyclic + // dependencies. + if (path in deps.visited) { + return; + } + + deps.visited[path] = true; + + if (path in deps.requires) { + for (var requireName in deps.requires[path]) { + // If the required name is defined, we assume that it was already + // bootstrapped by other means. + if (!goog.isProvided_(requireName)) { + if (requireName in deps.nameToPath) { + visitNode(deps.nameToPath[requireName]); + } else { + throw Error('Undefined nameToPath for ' + requireName); + } + } + } + } + + if (!(path in seenScript)) { + seenScript[path] = true; + scripts.push(path); + } + } + + visitNode(pathToLoad); + + // record that we are going to load all these scripts. + for (var i = 0; i < scripts.length; i++) { + var path = scripts[i]; + goog.dependencies_.written[path] = true; + } + + // If a module is loaded synchronously then we need to + // clear the current inModuleLoader value, and restore it when we are + // done loading the current "requires". + var moduleState = goog.moduleLoaderState_; + goog.moduleLoaderState_ = null; + + for (var i = 0; i < scripts.length; i++) { + var path = scripts[i]; + if (path) { + var loadFlags = deps.loadFlags[path] || {}; + var needsTranspile = goog.needsTranspile_(loadFlags['lang']); + if (loadFlags['module'] == 'goog' || needsTranspile) { + goog.importProcessedScript_( + goog.basePath + path, loadFlags['module'] == 'goog', + needsTranspile); + } else { + goog.importScript_(goog.basePath + path); + } + } else { + goog.moduleLoaderState_ = moduleState; + throw Error('Undefined script input'); + } + } + + // restore the current "module loading state" + goog.moduleLoaderState_ = moduleState; + }; + + + /** + * Looks at the dependency rules and tries to determine the script file that + * fulfills a particular rule. + * @param {string} rule In the form goog.namespace.Class or project.script. + * @return {?string} Url corresponding to the rule, or null. + * @private + */ + goog.getPathFromDeps_ = function(rule) { + if (rule in goog.dependencies_.nameToPath) { + return goog.dependencies_.nameToPath[rule]; + } else { + return null; + } + }; + + goog.findBasePath_(); + + // Allow projects to manage the deps files themselves. + if (!goog.global.CLOSURE_NO_DEPS) { + goog.importScript_(goog.basePath + 'deps.js'); + } +} + + +/** + * @param {function(?):?|string} moduleDef The module definition. + */ +goog.loadModule = function(moduleDef) { + // NOTE: we allow function definitions to be either in the from + // of a string to eval (which keeps the original source intact) or + // in a eval forbidden environment (CSP) we allow a function definition + // which in its body must call {@code goog.module}, and return the exports + // of the module. + var previousState = goog.moduleLoaderState_; + try { + goog.moduleLoaderState_ = { + moduleName: undefined, + declareLegacyNamespace: false + }; + var exports; + if (goog.isFunction(moduleDef)) { + exports = moduleDef.call(undefined, {}); + } else if (goog.isString(moduleDef)) { + exports = goog.loadModuleFromSource_.call(undefined, moduleDef); + } else { + throw Error('Invalid module definition'); + } + + var moduleName = goog.moduleLoaderState_.moduleName; + if (!goog.isString(moduleName) || !moduleName) { + throw Error('Invalid module name \"' + moduleName + '\"'); + } + + // Don't seal legacy namespaces as they may be uses as a parent of + // another namespace + if (goog.moduleLoaderState_.declareLegacyNamespace) { + goog.constructNamespace_(moduleName, exports); + } else if (goog.SEAL_MODULE_EXPORTS && Object.seal) { + Object.seal(exports); + } + + goog.loadedModules_[moduleName] = exports; + } finally { + goog.moduleLoaderState_ = previousState; + } +}; + + +/** + * @private @const {function(string):?} + * + * The new type inference warns because this function has no formal + * parameters, but its jsdoc says that it takes one argument. + * (The argument is used via arguments[0], but NTI does not detect this.) + * @suppress {newCheckTypes} + */ +goog.loadModuleFromSource_ = function() { + // NOTE: we avoid declaring parameters or local variables here to avoid + // masking globals or leaking values into the module definition. + 'use strict'; + var exports = {}; + eval(arguments[0]); + return exports; +}; + + +/** + * Normalize a file path by removing redundant ".." and extraneous "." file + * path components. + * @param {string} path + * @return {string} + * @private + */ +goog.normalizePath_ = function(path) { + var components = path.split('/'); + var i = 0; + while (i < components.length) { + if (components[i] == '.') { + components.splice(i, 1); + } else if ( + i && components[i] == '..' && components[i - 1] && + components[i - 1] != '..') { + components.splice(--i, 2); + } else { + i++; + } + } + return components.join('/'); +}; + + +/** + * Loads file by synchronous XHR. Should not be used in production environments. + * @param {string} src Source URL. + * @return {?string} File contents, or null if load failed. + * @private + */ +goog.loadFileSync_ = function(src) { + if (goog.global.CLOSURE_LOAD_FILE_SYNC) { + return goog.global.CLOSURE_LOAD_FILE_SYNC(src); + } else { + try { + /** @type {XMLHttpRequest} */ + var xhr = new goog.global['XMLHttpRequest'](); + xhr.open('get', src, false); + xhr.send(); + // NOTE: Successful http: requests have a status of 200, but successful + // file: requests may have a status of zero. Any other status, or a + // thrown exception (particularly in case of file: requests) indicates + // some sort of error, which we treat as a missing or unavailable file. + return xhr.status == 0 || xhr.status == 200 ? xhr.responseText : null; + } catch (err) { + // No need to rethrow or log, since errors should show up on their own. + return null; + } + } +}; + + +/** + * Retrieve and execute a script that needs some sort of wrapping. + * @param {string} src Script source URL. + * @param {boolean} isModule Whether to load as a module. + * @param {boolean} needsTranspile Whether to transpile down to ES3. + * @private + */ +goog.retrieveAndExec_ = function(src, isModule, needsTranspile) { + if (!COMPILED) { + // The full but non-canonicalized URL for later use. + var originalPath = src; + // Canonicalize the path, removing any /./ or /../ since Chrome's debugging + // console doesn't auto-canonicalize XHR loads as it does <script> srcs. + src = goog.normalizePath_(src); + + var importScript = + goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_; + + var scriptText = goog.loadFileSync_(src); + if (scriptText == null) { + throw new Error('Load of "' + src + '" failed'); + } + + if (needsTranspile) { + scriptText = goog.transpile_.call(goog.global, scriptText, src); + } + + if (isModule) { + scriptText = goog.wrapModule_(src, scriptText); + } else { + scriptText += '\n//# sourceURL=' + src; + } + var isOldIE = goog.IS_OLD_IE_; + if (isOldIE) { + goog.dependencies_.deferred[originalPath] = scriptText; + goog.queuedModules_.push(originalPath); + } else { + importScript(src, scriptText); + } + } +}; + + +/** + * Lazily retrieves the transpiler and applies it to the source. + * @param {string} code JS code. + * @param {string} path Path to the code. + * @return {string} The transpiled code. + * @private + */ +goog.transpile_ = function(code, path) { + var jscomp = goog.global['$jscomp']; + if (!jscomp) { + goog.global['$jscomp'] = jscomp = {}; + } + var transpile = jscomp.transpile; + if (!transpile) { + var transpilerPath = goog.basePath + goog.TRANSPILER; + var transpilerCode = goog.loadFileSync_(transpilerPath); + if (transpilerCode) { + // This must be executed synchronously, since by the time we know we + // need it, we're about to load and write the ES6 code synchronously, + // so a normal script-tag load will be too slow. + eval(transpilerCode + '\n//# sourceURL=' + transpilerPath); + // Note: transpile.js reassigns goog.global['$jscomp'] so pull it again. + jscomp = goog.global['$jscomp']; + transpile = jscomp.transpile; + } + } + if (!transpile) { + // The transpiler is an optional component. If it's not available then + // replace it with a pass-through function that simply logs. + var suffix = ' requires transpilation but no transpiler was found.'; + transpile = jscomp.transpile = function(code, path) { + // TODO(user): figure out some way to get this error to show up + // in test results, noting that the failure may occur in many + // different ways, including in loadModule() before the test + // runner even comes up. + goog.logToConsole_(path + suffix); + return code; + }; + } + // Note: any transpilation errors/warnings will be logged to the console. + return transpile(code, path); +}; + + +//============================================================================== +// Language Enhancements +//============================================================================== + + +/** + * This is a "fixed" version of the typeof operator. It differs from the typeof + * operator in such a way that null returns 'null' and arrays return 'array'. + * @param {?} value The value to get the type of. + * @return {string} The name of the type. + */ +goog.typeOf = function(value) { + var s = typeof value; + if (s == 'object') { + if (value) { + // Check these first, so we can avoid calling Object.prototype.toString if + // possible. + // + // IE improperly marshals typeof across execution contexts, but a + // cross-context object will still return false for "instanceof Object". + if (value instanceof Array) { + return 'array'; + } else if (value instanceof Object) { + return s; + } + + // HACK: In order to use an Object prototype method on the arbitrary + // value, the compiler requires the value be cast to type Object, + // even though the ECMA spec explicitly allows it. + var className = Object.prototype.toString.call( + /** @type {!Object} */ (value)); + // In Firefox 3.6, attempting to access iframe window objects' length + // property throws an NS_ERROR_FAILURE, so we need to special-case it + // here. + if (className == '[object Window]') { + return 'object'; + } + + // We cannot always use constructor == Array or instanceof Array because + // different frames have different Array objects. In IE6, if the iframe + // where the array was created is destroyed, the array loses its + // prototype. Then dereferencing val.splice here throws an exception, so + // we can't use goog.isFunction. Calling typeof directly returns 'unknown' + // so that will work. In this case, this function will return false and + // most array functions will still work because the array is still + // array-like (supports length and []) even though it has lost its + // prototype. + // Mark Miller noticed that Object.prototype.toString + // allows access to the unforgeable [[Class]] property. + // 15.2.4.2 Object.prototype.toString ( ) + // When the toString method is called, the following steps are taken: + // 1. Get the [[Class]] property of this object. + // 2. Compute a string value by concatenating the three strings + // "[object ", Result(1), and "]". + // 3. Return Result(2). + // and this behavior survives the destruction of the execution context. + if ((className == '[object Array]' || + // In IE all non value types are wrapped as objects across window + // boundaries (not iframe though) so we have to do object detection + // for this edge case. + typeof value.length == 'number' && + typeof value.splice != 'undefined' && + typeof value.propertyIsEnumerable != 'undefined' && + !value.propertyIsEnumerable('splice') + + )) { + return 'array'; + } + // HACK: There is still an array case that fails. + // function ArrayImpostor() {} + // ArrayImpostor.prototype = []; + // var impostor = new ArrayImpostor; + // this can be fixed by getting rid of the fast path + // (value instanceof Array) and solely relying on + // (value && Object.prototype.toString.vall(value) === '[object Array]') + // but that would require many more function calls and is not warranted + // unless closure code is receiving objects from untrusted sources. + + // IE in cross-window calls does not correctly marshal the function type + // (it appears just as an object) so we cannot use just typeof val == + // 'function'. However, if the object has a call property, it is a + // function. + if ((className == '[object Function]' || + typeof value.call != 'undefined' && + typeof value.propertyIsEnumerable != 'undefined' && + !value.propertyIsEnumerable('call'))) { + return 'function'; + } + + } else { + return 'null'; + } + + } else if (s == 'function' && typeof value.call == 'undefined') { + // In Safari typeof nodeList returns 'function', and on Firefox typeof + // behaves similarly for HTML{Applet,Embed,Object}, Elements and RegExps. We + // would like to return object for those and we can detect an invalid + // function by making sure that the function object has a call method. + return 'object'; + } + return s; +}; + + +/** + * Returns true if the specified value is null. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is null. + */ +goog.isNull = function(val) { + return val === null; +}; + + +/** + * Returns true if the specified value is defined and not null. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is defined and not null. + */ +goog.isDefAndNotNull = function(val) { + // Note that undefined == null. + return val != null; +}; + + +/** + * Returns true if the specified value is an array. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is an array. + */ +goog.isArray = function(val) { + return goog.typeOf(val) == 'array'; +}; + + +/** + * Returns true if the object looks like an array. To qualify as array like + * the value needs to be either a NodeList or an object with a Number length + * property. As a special case, a function value is not array like, because its + * length property is fixed to correspond to the number of expected arguments. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is an array. + */ +goog.isArrayLike = function(val) { + var type = goog.typeOf(val); + // We do not use goog.isObject here in order to exclude function values. + return type == 'array' || type == 'object' && typeof val.length == 'number'; +}; + + +/** + * Returns true if the object looks like a Date. To qualify as Date-like the + * value needs to be an object and have a getFullYear() function. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a like a Date. + */ +goog.isDateLike = function(val) { + return goog.isObject(val) && typeof val.getFullYear == 'function'; +}; + + +/** + * Returns true if the specified value is a string. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a string. + */ +goog.isString = function(val) { + return typeof val == 'string'; +}; + + +/** + * Returns true if the specified value is a boolean. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is boolean. + */ +goog.isBoolean = function(val) { + return typeof val == 'boolean'; +}; + + +/** + * Returns true if the specified value is a number. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a number. + */ +goog.isNumber = function(val) { + return typeof val == 'number'; +}; + + +/** + * Returns true if the specified value is a function. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a function. + */ +goog.isFunction = function(val) { + return goog.typeOf(val) == 'function'; +}; + + +/** + * Returns true if the specified value is an object. This includes arrays and + * functions. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is an object. + */ +goog.isObject = function(val) { + var type = typeof val; + return type == 'object' && val != null || type == 'function'; + // return Object(val) === val also works, but is slower, especially if val is + // not an object. +}; + + +/** + * Gets a unique ID for an object. This mutates the object so that further calls + * with the same object as a parameter returns the same value. The unique ID is + * guaranteed to be unique across the current session amongst objects that are + * passed into {@code getUid}. There is no guarantee that the ID is unique or + * consistent across sessions. It is unsafe to generate unique ID for function + * prototypes. + * + * @param {Object} obj The object to get the unique ID for. + * @return {number} The unique ID for the object. + */ +goog.getUid = function(obj) { + // TODO(arv): Make the type stricter, do not accept null. + + // In Opera window.hasOwnProperty exists but always returns false so we avoid + // using it. As a consequence the unique ID generated for BaseClass.prototype + // and SubClass.prototype will be the same. + return obj[goog.UID_PROPERTY_] || + (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_); +}; + + +/** + * Whether the given object is already assigned a unique ID. + * + * This does not modify the object. + * + * @param {!Object} obj The object to check. + * @return {boolean} Whether there is an assigned unique id for the object. + */ +goog.hasUid = function(obj) { + return !!obj[goog.UID_PROPERTY_]; +}; + + +/** + * Removes the unique ID from an object. This is useful if the object was + * previously mutated using {@code goog.getUid} in which case the mutation is + * undone. + * @param {Object} obj The object to remove the unique ID field from. + */ +goog.removeUid = function(obj) { + // TODO(arv): Make the type stricter, do not accept null. + + // In IE, DOM nodes are not instances of Object and throw an exception if we + // try to delete. Instead we try to use removeAttribute. + if (obj !== null && 'removeAttribute' in obj) { + obj.removeAttribute(goog.UID_PROPERTY_); + } + /** @preserveTry */ + try { + delete obj[goog.UID_PROPERTY_]; + } catch (ex) { + } +}; + + +/** + * Name for unique ID property. Initialized in a way to help avoid collisions + * with other closure JavaScript on the same page. + * @type {string} + * @private + */ +goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0); + + +/** + * Counter for UID. + * @type {number} + * @private + */ +goog.uidCounter_ = 0; + + +/** + * Adds a hash code field to an object. The hash code is unique for the + * given object. + * @param {Object} obj The object to get the hash code for. + * @return {number} The hash code for the object. + * @deprecated Use goog.getUid instead. + */ +goog.getHashCode = goog.getUid; + + +/** + * Removes the hash code field from an object. + * @param {Object} obj The object to remove the field from. + * @deprecated Use goog.removeUid instead. + */ +goog.removeHashCode = goog.removeUid; + + +/** + * Clones a value. The input may be an Object, Array, or basic type. Objects and + * arrays will be cloned recursively. + * + * WARNINGS: + * <code>goog.cloneObject</code> does not detect reference loops. Objects that + * refer to themselves will cause infinite recursion. + * + * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies + * UIDs created by <code>getUid</code> into cloned results. + * + * @param {*} obj The value to clone. + * @return {*} A clone of the input value. + * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods. + */ +goog.cloneObject = function(obj) { + var type = goog.typeOf(obj); + if (type == 'object' || type == 'array') { + if (obj.clone) { + return obj.clone(); + } + var clone = type == 'array' ? [] : {}; + for (var key in obj) { + clone[key] = goog.cloneObject(obj[key]); + } + return clone; + } + + return obj; +}; + + +/** + * A native implementation of goog.bind. + * @param {Function} fn A function to partially apply. + * @param {Object|undefined} selfObj Specifies the object which this should + * point to when the function is run. + * @param {...*} var_args Additional arguments that are partially applied to the + * function. + * @return {!Function} A partially-applied form of the function bind() was + * invoked as a method of. + * @private + * @suppress {deprecated} The compiler thinks that Function.prototype.bind is + * deprecated because some people have declared a pure-JS version. + * Only the pure-JS version is truly deprecated. + */ +goog.bindNative_ = function(fn, selfObj, var_args) { + return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments)); +}; + + +/** + * A pure-JS implementation of goog.bind. + * @param {Function} fn A function to partially apply. + * @param {Object|undefined} selfObj Specifies the object which this should + * point to when the function is run. + * @param {...*} var_args Additional arguments that are partially applied to the + * function. + * @return {!Function} A partially-applied form of the function bind() was + * invoked as a method of. + * @private + */ +goog.bindJs_ = function(fn, selfObj, var_args) { + if (!fn) { + throw new Error(); + } + + if (arguments.length > 2) { + var boundArgs = Array.prototype.slice.call(arguments, 2); + return function() { + // Prepend the bound arguments to the current arguments. + var newArgs = Array.prototype.slice.call(arguments); + Array.prototype.unshift.apply(newArgs, boundArgs); + return fn.apply(selfObj, newArgs); + }; + + } else { + return function() { return fn.apply(selfObj, arguments); }; + } +}; + + +/** + * Partially applies this function to a particular 'this object' and zero or + * more arguments. The result is a new function with some arguments of the first + * function pre-filled and the value of this 'pre-specified'. + * + * Remaining arguments specified at call-time are appended to the pre-specified + * ones. + * + * Also see: {@link #partial}. + * + * Usage: + * <pre>var barMethBound = goog.bind(myFunction, myObj, 'arg1', 'arg2'); + * barMethBound('arg3', 'arg4');</pre> + * + * @param {?function(this:T, ...)} fn A function to partially apply. + * @param {T} selfObj Specifies the object which this should point to when the + * function is run. + * @param {...*} var_args Additional arguments that are partially applied to the + * function. + * @return {!Function} A partially-applied form of the function goog.bind() was + * invoked as a method of. + * @template T + * @suppress {deprecated} See above. + */ +goog.bind = function(fn, selfObj, var_args) { + // TODO(nicksantos): narrow the type signature. + if (Function.prototype.bind && + // NOTE(nicksantos): Somebody pulled base.js into the default Chrome + // extension environment. This means that for Chrome extensions, they get + // the implementation of Function.prototype.bind that calls goog.bind + // instead of the native one. Even worse, we don't want to introduce a + // circular dependency between goog.bind and Function.prototype.bind, so + // we have to hack this to make sure it works correctly. + Function.prototype.bind.toString().indexOf('native code') != -1) { + goog.bind = goog.bindNative_; + } else { + goog.bind = goog.bindJs_; + } + return goog.bind.apply(null, arguments); +}; + + +/** + * Like goog.bind(), except that a 'this object' is not required. Useful when + * the target function is already bound. + * + * Usage: + * var g = goog.partial(f, arg1, arg2); + * g(arg3, arg4); + * + * @param {Function} fn A function to partially apply. + * @param {...*} var_args Additional arguments that are partially applied to fn. + * @return {!Function} A partially-applied form of the function goog.partial() + * was invoked as a method of. + */ +goog.partial = function(fn, var_args) { + var args = Array.prototype.slice.call(arguments, 1); + return function() { + // Clone the array (with slice()) and append additional arguments + // to the existing arguments. + var newArgs = args.slice(); + newArgs.push.apply(newArgs, arguments); + return fn.apply(this, newArgs); + }; +}; + + +/** + * Copies all the members of a source object to a target object. This method + * does not work on all browsers for all objects that contain keys such as + * toString or hasOwnProperty. Use goog.object.extend for this purpose. + * @param {Object} target Target. + * @param {Object} source Source. + */ +goog.mixin = function(target, source) { + for (var x in source) { + target[x] = source[x]; + } + + // For IE7 or lower, the for-in-loop does not contain any properties that are + // not enumerable on the prototype object (for example, isPrototypeOf from + // Object.prototype) but also it will not include 'replace' on objects that + // extend String and change 'replace' (not that it is common for anyone to + // extend anything except Object). +}; + + +/** + * @return {number} An integer value representing the number of milliseconds + * between midnight, January 1, 1970 and the current time. + */ +goog.now = (goog.TRUSTED_SITE && Date.now) || (function() { + // Unary plus operator converts its operand to a number which in + // the case of + // a date is done by calling getTime(). + return +new Date(); + }); + + +/** + * Evals JavaScript in the global scope. In IE this uses execScript, other + * browsers use goog.global.eval. If goog.global.eval does not evaluate in the + * global scope (for example, in Safari), appends a script tag instead. + * Throws an exception if neither execScript or eval is defined. + * @param {string} script JavaScript string. + */ +goog.globalEval = function(script) { + if (goog.global.execScript) { + goog.global.execScript(script, 'JavaScript'); + } else if (goog.global.eval) { + // Test to see if eval works + if (goog.evalWorksForGlobals_ == null) { + goog.global.eval('var _evalTest_ = 1;'); + if (typeof goog.global['_evalTest_'] != 'undefined') { + try { + delete goog.global['_evalTest_']; + } catch (ignore) { + // Microsoft edge fails the deletion above in strict mode. + } + goog.evalWorksForGlobals_ = true; + } else { + goog.evalWorksForGlobals_ = false; + } + } + + if (goog.evalWorksForGlobals_) { + goog.global.eval(script); + } else { + /** @type {Document} */ + var doc = goog.global.document; + var scriptElt = + /** @type {!HTMLScriptElement} */ (doc.createElement('SCRIPT')); + scriptElt.type = 'text/javascript'; + scriptElt.defer = false; + // Note(user): can't use .innerHTML since "t('<test>')" will fail and + // .text doesn't work in Safari 2. Therefore we append a text node. + scriptElt.appendChild(doc.createTextNode(script)); + doc.body.appendChild(scriptElt); + doc.body.removeChild(scriptElt); + } + } else { + throw Error('goog.globalEval not available'); + } +}; + + +/** + * Indicates whether or not we can call 'eval' directly to eval code in the + * global scope. Set to a Boolean by the first call to goog.globalEval (which + * empirically tests whether eval works for globals). @see goog.globalEval + * @type {?boolean} + * @private + */ +goog.evalWorksForGlobals_ = null; + + +/** + * Optional map of CSS class names to obfuscated names used with + * goog.getCssName(). + * @private {!Object<string, string>|undefined} + * @see goog.setCssNameMapping + */ +goog.cssNameMapping_; + + +/** + * Optional obfuscation style for CSS class names. Should be set to either + * 'BY_WHOLE' or 'BY_PART' if defined. + * @type {string|undefined} + * @private + * @see goog.setCssNameMapping + */ +goog.cssNameMappingStyle_; + + +/** + * Handles strings that are intended to be used as CSS class names. + * + * This function works in tandem with @see goog.setCssNameMapping. + * + * Without any mapping set, the arguments are simple joined with a hyphen and + * passed through unaltered. + * + * When there is a mapping, there are two possible styles in which these + * mappings are used. In the BY_PART style, each part (i.e. in between hyphens) + * of the passed in css name is rewritten according to the map. In the BY_WHOLE + * style, the full css name is looked up in the map directly. If a rewrite is + * not specified by the map, the compiler will output a warning. + * + * When the mapping is passed to the compiler, it will replace calls to + * goog.getCssName with the strings from the mapping, e.g. + * var x = goog.getCssName('foo'); + * var y = goog.getCssName(this.baseClass, 'active'); + * becomes: + * var x = 'foo'; + * var y = this.baseClass + '-active'; + * + * If one argument is passed it will be processed, if two are passed only the + * modifier will be processed, as it is assumed the first argument was generated + * as a result of calling goog.getCssName. + * + * @param {string} className The class name. + * @param {string=} opt_modifier A modifier to be appended to the class name. + * @return {string} The class name or the concatenation of the class name and + * the modifier. + */ +goog.getCssName = function(className, opt_modifier) { + var getMapping = function(cssName) { + return goog.cssNameMapping_[cssName] || cssName; + }; + + var renameByParts = function(cssName) { + // Remap all the parts individually. + var parts = cssName.split('-'); + var mapped = []; + for (var i = 0; i < parts.length; i++) { + mapped.push(getMapping(parts[i])); + } + return mapped.join('-'); + }; + + var rename; + if (goog.cssNameMapping_) { + rename = + goog.cssNameMappingStyle_ == 'BY_WHOLE' ? getMapping : renameByParts; + } else { + rename = function(a) { return a; }; + } + + if (opt_modifier) { + return className + '-' + rename(opt_modifier); + } else { + return rename(className); + } +}; + + +/** + * Sets the map to check when returning a value from goog.getCssName(). Example: + * <pre> + * goog.setCssNameMapping({ + * "goog": "a", + * "disabled": "b", + * }); + * + * var x = goog.getCssName('goog'); + * // The following evaluates to: "a a-b". + * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled') + * </pre> + * When declared as a map of string literals to string literals, the JSCompiler + * will replace all calls to goog.getCssName() using the supplied map if the + * --process_closure_primitives flag is set. + * + * @param {!Object} mapping A map of strings to strings where keys are possible + * arguments to goog.getCssName() and values are the corresponding values + * that should be returned. + * @param {string=} opt_style The style of css name mapping. There are two valid + * options: 'BY_PART', and 'BY_WHOLE'. + * @see goog.getCssName for a description. + */ +goog.setCssNameMapping = function(mapping, opt_style) { + goog.cssNameMapping_ = mapping; + goog.cssNameMappingStyle_ = opt_style; +}; + + +/** + * To use CSS renaming in compiled mode, one of the input files should have a + * call to goog.setCssNameMapping() with an object literal that the JSCompiler + * can extract and use to replace all calls to goog.getCssName(). In uncompiled + * mode, JavaScript code should be loaded before this base.js file that declares + * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is + * to ensure that the mapping is loaded before any calls to goog.getCssName() + * are made in uncompiled mode. + * + * A hook for overriding the CSS name mapping. + * @type {!Object<string, string>|undefined} + */ +goog.global.CLOSURE_CSS_NAME_MAPPING; + + +if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) { + // This does not call goog.setCssNameMapping() because the JSCompiler + // requires that goog.setCssNameMapping() be called with an object literal. + goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING; +} + + +/** + * Gets a localized message. + * + * This function is a compiler primitive. If you give the compiler a localized + * message bundle, it will replace the string at compile-time with a localized + * version, and expand goog.getMsg call to a concatenated string. + * + * Messages must be initialized in the form: + * <code> + * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'}); + * </code> + * + * This function produces a string which should be treated as plain text. Use + * {@link goog.html.SafeHtmlFormatter} in conjunction with goog.getMsg to + * produce SafeHtml. + * + * @param {string} str Translatable string, places holders in the form {$foo}. + * @param {Object<string, string>=} opt_values Maps place holder name to value. + * @return {string} message with placeholders filled. + */ +goog.getMsg = function(str, opt_values) { + if (opt_values) { + str = str.replace(/\{\$([^}]+)}/g, function(match, key) { + return (opt_values != null && key in opt_values) ? opt_values[key] : + match; + }); + } + return str; +}; + + +/** + * Gets a localized message. If the message does not have a translation, gives a + * fallback message. + * + * This is useful when introducing a new message that has not yet been + * translated into all languages. + * + * This function is a compiler primitive. Must be used in the form: + * <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code> + * where MSG_A and MSG_B were initialized with goog.getMsg. + * + * @param {string} a The preferred message. + * @param {string} b The fallback message. + * @return {string} The best translated message. + */ +goog.getMsgWithFallback = function(a, b) { + return a; +}; + + +/** + * Exposes an unobfuscated global namespace path for the given object. + * Note that fields of the exported object *will* be obfuscated, unless they are + * exported in turn via this function or goog.exportProperty. + * + * Also handy for making public items that are defined in anonymous closures. + * + * ex. goog.exportSymbol('public.path.Foo', Foo); + * + * ex. goog.exportSymbol('public.path.Foo.staticFunction', Foo.staticFunction); + * public.path.Foo.staticFunction(); + * + * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod', + * Foo.prototype.myMethod); + * new public.path.Foo().myMethod(); + * + * @param {string} publicPath Unobfuscated name to export. + * @param {*} object Object the name should point to. + * @param {Object=} opt_objectToExportTo The object to add the path to; default + * is goog.global. + */ +goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) { + goog.exportPath_(publicPath, object, opt_objectToExportTo); +}; + + +/** + * Exports a property unobfuscated into the object's namespace. + * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction); + * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod); + * @param {Object} object Object whose static property is being exported. + * @param {string} publicName Unobfuscated name to export. + * @param {*} symbol Object the name should point to. + */ +goog.exportProperty = function(object, publicName, symbol) { + object[publicName] = symbol; +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * Usage: + * <pre> + * function ParentClass(a, b) { } + * ParentClass.prototype.foo = function(a) { }; + * + * function ChildClass(a, b, c) { + * ChildClass.base(this, 'constructor', a, b); + * } + * goog.inherits(ChildClass, ParentClass); + * + * var child = new ChildClass('a', 'b', 'see'); + * child.foo(); // This works. + * </pre> + * + * @param {!Function} childCtor Child class. + * @param {!Function} parentCtor Parent class. + */ +goog.inherits = function(childCtor, parentCtor) { + /** @constructor */ + function tempCtor() {} + tempCtor.prototype = parentCtor.prototype; + childCtor.superClass_ = parentCtor.prototype; + childCtor.prototype = new tempCtor(); + /** @override */ + childCtor.prototype.constructor = childCtor; + + /** + * Calls superclass constructor/method. + * + * This function is only available if you use goog.inherits to + * express inheritance relationships between classes. + * + * NOTE: This is a replacement for goog.base and for superClass_ + * property defined in childCtor. + * + * @param {!Object} me Should always be "this". + * @param {string} methodName The method name to call. Calling + * superclass constructor can be done with the special string + * 'constructor'. + * @param {...*} var_args The arguments to pass to superclass + * method/constructor. + * @return {*} The return value of the superclass method/constructor. + */ + childCtor.base = function(me, methodName, var_args) { + // Copying using loop to avoid deop due to passing arguments object to + // function. This is faster in many JS engines as of late 2014. + var args = new Array(arguments.length - 2); + for (var i = 2; i < arguments.length; i++) { + args[i - 2] = arguments[i]; + } + return parentCtor.prototype[methodName].apply(me, args); + }; +}; + + +/** + * Call up to the superclass. + * + * If this is called from a constructor, then this calls the superclass + * constructor with arguments 1-N. + * + * If this is called from a prototype method, then you must pass the name of the + * method as the second argument to this function. If you do not, you will get a + * runtime error. This calls the superclass' method with arguments 2-N. + * + * This function only works if you use goog.inherits to express inheritance + * relationships between your classes. + * + * This function is a compiler primitive. At compile-time, the compiler will do + * macro expansion to remove a lot of the extra overhead that this function + * introduces. The compiler will also enforce a lot of the assumptions that this + * function makes, and treat it as a compiler error if you break them. + * + * @param {!Object} me Should always be "this". + * @param {*=} opt_methodName The method name if calling a super method. + * @param {...*} var_args The rest of the arguments. + * @return {*} The return value of the superclass method. + * @suppress {es5Strict} This method can not be used in strict mode, but + * all Closure Library consumers must depend on this file. + */ +goog.base = function(me, opt_methodName, var_args) { + var caller = arguments.callee.caller; + + if (goog.STRICT_MODE_COMPATIBLE || (goog.DEBUG && !caller)) { + throw Error( + 'arguments.caller not defined. goog.base() cannot be used ' + + 'with strict mode code. See ' + + 'http://www.ecma-international.org/ecma-262/5.1/#sec-C'); + } + + if (caller.superClass_) { + // Copying using loop to avoid deop due to passing arguments object to + // function. This is faster in many JS engines as of late 2014. + var ctorArgs = new Array(arguments.length - 1); + for (var i = 1; i < arguments.length; i++) { + ctorArgs[i - 1] = arguments[i]; + } + // This is a constructor. Call the superclass constructor. + return caller.superClass_.constructor.apply(me, ctorArgs); + } + + // Copying using loop to avoid deop due to passing arguments object to + // function. This is faster in many JS engines as of late 2014. + var args = new Array(arguments.length - 2); + for (var i = 2; i < arguments.length; i++) { + args[i - 2] = arguments[i]; + } + var foundCaller = false; + for (var ctor = me.constructor; ctor; + ctor = ctor.superClass_ && ctor.superClass_.constructor) { + if (ctor.prototype[opt_methodName] === caller) { + foundCaller = true; + } else if (foundCaller) { + return ctor.prototype[opt_methodName].apply(me, args); + } + } + + // If we did not find the caller in the prototype chain, then one of two + // things happened: + // 1) The caller is an instance method. + // 2) This method was not called by the right caller. + if (me[opt_methodName] === caller) { + return me.constructor.prototype[opt_methodName].apply(me, args); + } else { + throw Error( + 'goog.base called from a method of one name ' + + 'to a method of a different name'); + } +}; + + +/** + * Allow for aliasing within scope functions. This function exists for + * uncompiled code - in compiled code the calls will be inlined and the aliases + * applied. In uncompiled code the function is simply run since the aliases as + * written are valid JavaScript. + * + * + * @param {function()} fn Function to call. This function can contain aliases + * to namespaces (e.g. "var dom = goog.dom") or classes + * (e.g. "var Timer = goog.Timer"). + */ +goog.scope = function(fn) { + if (goog.isInModuleLoader_()) { + throw Error('goog.scope is not supported within a goog.module.'); + } + fn.call(goog.global); +}; + + +/* + * To support uncompiled, strict mode bundles that use eval to divide source + * like so: + * eval('someSource;//# sourceUrl sourcefile.js'); + * We need to export the globally defined symbols "goog" and "COMPILED". + * Exporting "goog" breaks the compiler optimizations, so we required that + * be defined externally. + * NOTE: We don't use goog.exportSymbol here because we don't want to trigger + * extern generation when that compiler option is enabled. + */ +if (!COMPILED) { + goog.global['COMPILED'] = COMPILED; +} + + +//============================================================================== +// goog.defineClass implementation +//============================================================================== + + +/** + * Creates a restricted form of a Closure "class": + * - from the compiler's perspective, the instance returned from the + * constructor is sealed (no new properties may be added). This enables + * better checks. + * - the compiler will rewrite this definition to a form that is optimal + * for type checking and optimization (initially this will be a more + * traditional form). + * + * @param {Function} superClass The superclass, Object or null. + * @param {goog.defineClass.ClassDescriptor} def + * An object literal describing + * the class. It may have the following properties: + * "constructor": the constructor function + * "statics": an object literal containing methods to add to the constructor + * as "static" methods or a function that will receive the constructor + * function as its only parameter to which static properties can + * be added. + * all other properties are added to the prototype. + * @return {!Function} The class constructor. + */ +goog.defineClass = function(superClass, def) { + // TODO(johnlenz): consider making the superClass an optional parameter. + var constructor = def.constructor; + var statics = def.statics; + // Wrap the constructor prior to setting up the prototype and static methods. + if (!constructor || constructor == Object.prototype.constructor) { + constructor = function() { + throw Error('cannot instantiate an interface (no constructor defined).'); + }; + } + + var cls = goog.defineClass.createSealingConstructor_(constructor, superClass); + if (superClass) { + goog.inherits(cls, superClass); + } + + // Remove all the properties that should not be copied to the prototype. + delete def.constructor; + delete def.statics; + + goog.defineClass.applyProperties_(cls.prototype, def); + if (statics != null) { + if (statics instanceof Function) { + statics(cls); + } else { + goog.defineClass.applyProperties_(cls, statics); + } + } + + return cls; +}; + + +/** + * @typedef {{ + * constructor: (!Function|undefined), + * statics: (Object|undefined|function(Function):void) + * }} + * @suppress {missingProvide} + */ +goog.defineClass.ClassDescriptor; + + +/** + * @define {boolean} Whether the instances returned by goog.defineClass should + * be sealed when possible. + * + * When sealing is disabled the constructor function will not be wrapped by + * goog.defineClass, making it incompatible with ES6 class methods. + */ +goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG); + + +/** + * If goog.defineClass.SEAL_CLASS_INSTANCES is enabled and Object.seal is + * defined, this function will wrap the constructor in a function that seals the + * results of the provided constructor function. + * + * @param {!Function} ctr The constructor whose results maybe be sealed. + * @param {Function} superClass The superclass constructor. + * @return {!Function} The replacement constructor. + * @private + */ +goog.defineClass.createSealingConstructor_ = function(ctr, superClass) { + if (!goog.defineClass.SEAL_CLASS_INSTANCES) { + // Do now wrap the constructor when sealing is disabled. Angular code + // depends on this for injection to work properly. + return ctr; + } + + // Compute whether the constructor is sealable at definition time, rather + // than when the instance is being constructed. + var superclassSealable = !goog.defineClass.isUnsealable_(superClass); + + /** + * @this {Object} + * @return {?} + */ + var wrappedCtr = function() { + // Don't seal an instance of a subclass when it calls the constructor of + // its super class as there is most likely still setup to do. + var instance = ctr.apply(this, arguments) || this; + instance[goog.UID_PROPERTY_] = instance[goog.UID_PROPERTY_]; + + if (this.constructor === wrappedCtr && superclassSealable && + Object.seal instanceof Function) { + Object.seal(instance); + } + return instance; + }; + + return wrappedCtr; +}; + + +/** + * @param {Function} ctr The constructor to test. + * @returns {boolean} Whether the constructor has been tagged as unsealable + * using goog.tagUnsealableClass. + * @private + */ +goog.defineClass.isUnsealable_ = function(ctr) { + return ctr && ctr.prototype && + ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]; +}; + + +// TODO(johnlenz): share these values with the goog.object +/** + * The names of the fields that are defined on Object.prototype. + * @type {!Array<string>} + * @private + * @const + */ +goog.defineClass.OBJECT_PROTOTYPE_FIELDS_ = [ + 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', + 'toLocaleString', 'toString', 'valueOf' +]; + + +// TODO(johnlenz): share this function with the goog.object +/** + * @param {!Object} target The object to add properties to. + * @param {!Object} source The object to copy properties from. + * @private + */ +goog.defineClass.applyProperties_ = function(target, source) { + // TODO(johnlenz): update this to support ES5 getters/setters + + var key; + for (key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + + // For IE the for-in-loop does not contain any properties that are not + // enumerable on the prototype object (for example isPrototypeOf from + // Object.prototype) and it will also not include 'replace' on objects that + // extend String and change 'replace' (not that it is common for anyone to + // extend anything except Object). + for (var i = 0; i < goog.defineClass.OBJECT_PROTOTYPE_FIELDS_.length; i++) { + key = goog.defineClass.OBJECT_PROTOTYPE_FIELDS_[i]; + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } +}; + + +/** + * Sealing classes breaks the older idiom of assigning properties on the + * prototype rather than in the constructor. As such, goog.defineClass + * must not seal subclasses of these old-style classes until they are fixed. + * Until then, this marks a class as "broken", instructing defineClass + * not to seal subclasses. + * @param {!Function} ctr The legacy constructor to tag as unsealable. + */ +goog.tagUnsealableClass = function(ctr) { + if (!COMPILED && goog.defineClass.SEAL_CLASS_INSTANCES) { + ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_] = true; + } +}; + + +/** + * Name for unsealable tag property. + * @const @private {string} + */ +goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_ = 'goog_defineClass_legacy_unsealable'; + +goog.provide('ol'); + + +/** + * Constants defined with the define tag cannot be changed in application + * code, but can be set at compile time. + * Some reduce the size of the build in advanced compile mode. + */ + + +/** + * @define {boolean} Enable debug mode. Default is `true`. + */ +ol.DEBUG = true; + + +/** + * @define {boolean} Assume touch. Default is `false`. + */ +ol.ASSUME_TOUCH = false; + + +/** + * TODO: rename this to something having to do with tile grids + * see https://github.com/openlayers/ol3/issues/2076 + * @define {number} Default maximum zoom for default tile grids. + */ +ol.DEFAULT_MAX_ZOOM = 42; + + +/** + * @define {number} Default min zoom level for the map view. Default is `0`. + */ +ol.DEFAULT_MIN_ZOOM = 0; + + +/** + * @define {number} Default maximum allowed threshold (in pixels) for + * reprojection triangulation. Default is `0.5`. + */ +ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD = 0.5; + + +/** + * @define {number} Default tile size. + */ +ol.DEFAULT_TILE_SIZE = 256; + + +/** + * @define {string} Default WMS version. + */ +ol.DEFAULT_WMS_VERSION = '1.3.0'; + + +/** + * @define {number} Hysteresis pixels. + */ +ol.DRAG_BOX_HYSTERESIS_PIXELS = 8; + + +/** + * @define {boolean} Enable the Canvas renderer. Default is `true`. Setting + * this to false at compile time in advanced mode removes all code + * supporting the Canvas renderer from the build. + */ +ol.ENABLE_CANVAS = true; + + +/** + * @define {boolean} Enable rendering of ol.layer.Image based layers. Default + * is `true`. Setting this to false at compile time in advanced mode removes + * all code supporting Image layers from the build. + */ +ol.ENABLE_IMAGE = true; + + +/** + * @define {boolean} Enable integration with the Proj4js library. Default is + * `true`. + */ +ol.ENABLE_PROJ4JS = true; + + +/** + * @define {boolean} Enable automatic reprojection of raster sources. Default is + * `true`. + */ +ol.ENABLE_RASTER_REPROJECTION = true; + + +/** + * @define {boolean} Enable rendering of ol.layer.Tile based layers. Default is + * `true`. Setting this to false at compile time in advanced mode removes + * all code supporting Tile layers from the build. + */ +ol.ENABLE_TILE = true; + + +/** + * @define {boolean} Enable rendering of ol.layer.Vector based layers. Default + * is `true`. Setting this to false at compile time in advanced mode removes + * all code supporting Vector layers from the build. + */ +ol.ENABLE_VECTOR = true; + + +/** + * @define {boolean} Enable rendering of ol.layer.VectorTile based layers. + * Default is `true`. Setting this to false at compile time in advanced mode + * removes all code supporting VectorTile layers from the build. + */ +ol.ENABLE_VECTOR_TILE = true; + + +/** + * @define {boolean} Enable the WebGL renderer. Default is `true`. Setting + * this to false at compile time in advanced mode removes all code + * supporting the WebGL renderer from the build. + */ +ol.ENABLE_WEBGL = true; + + +/** + * @define {number} The size in pixels of the first atlas image. Default is + * `256`. + */ +ol.INITIAL_ATLAS_SIZE = 256; + + +/** + * @define {number} The maximum size in pixels of atlas images. Default is + * `-1`, meaning it is not used (and `ol.WEBGL_MAX_TEXTURE_SIZE` is + * used instead). + */ +ol.MAX_ATLAS_SIZE = -1; + + +/** + * @define {number} Maximum mouse wheel delta. + */ +ol.MOUSEWHEELZOOM_MAXDELTA = 1; + + +/** + * @define {number} Maximum width and/or height extent ratio that determines + * when the overview map should be zoomed out. + */ +ol.OVERVIEWMAP_MAX_RATIO = 0.75; + + +/** + * @define {number} Minimum width and/or height extent ratio that determines + * when the overview map should be zoomed in. + */ +ol.OVERVIEWMAP_MIN_RATIO = 0.1; + + +/** + * @define {number} Maximum number of source tiles for raster reprojection of + * a single tile. + * If too many source tiles are determined to be loaded to create a single + * reprojected tile the browser can become unresponsive or even crash. + * This can happen if the developer defines projections improperly and/or + * with unlimited extents. + * If too many tiles are required, no tiles are loaded and + * `ol.Tile.State.ERROR` state is set. Default is `100`. + */ +ol.RASTER_REPROJECTION_MAX_SOURCE_TILES = 100; + + +/** + * @define {number} Maximum number of subdivision steps during raster + * reprojection triangulation. Prevents high memory usage and large + * number of proj4 calls (for certain transformations and areas). + * At most `2*(2^this)` triangles are created for each triangulated + * extent (tile/image). Default is `10`. + */ +ol.RASTER_REPROJECTION_MAX_SUBDIVISION = 10; + + +/** + * @define {number} Maximum allowed size of triangle relative to world width. + * When transforming corners of world extent between certain projections, + * the resulting triangulation seems to have zero error and no subdivision + * is performed. + * If the triangle width is more than this (relative to world width; 0-1), + * subdivison is forced (up to `ol.RASTER_REPROJECTION_MAX_SUBDIVISION`). + * Default is `0.25`. + */ +ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH = 0.25; + + +/** + * @define {number} Tolerance for geometry simplification in device pixels. + */ +ol.SIMPLIFY_TOLERANCE = 0.5; + + +/** + * @define {number} Texture cache high water mark. + */ +ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024; + + +/** + * @define {string} OpenLayers version. + */ +ol.VERSION = ''; + + +/** + * The maximum supported WebGL texture size in pixels. If WebGL is not + * supported, the value is set to `undefined`. + * @const + * @type {number|undefined} + */ +ol.WEBGL_MAX_TEXTURE_SIZE; // value is set in `ol.has` + + +/** + * List of supported WebGL extensions. + * @const + * @type {Array.<string>} + */ +ol.WEBGL_EXTENSIONS; // value is set in `ol.has` + + +/** + * Inherit the prototype methods from one constructor into another. + * + * Usage: + * + * function ParentClass(a, b) { } + * ParentClass.prototype.foo = function(a) { } + * + * function ChildClass(a, b, c) { + * // Call parent constructor + * ParentClass.call(this, a, b); + * } + * ol.inherits(ChildClass, ParentClass); + * + * var child = new ChildClass('a', 'b', 'see'); + * child.foo(); // This works. + * + * @param {!Function} childCtor Child constructor. + * @param {!Function} parentCtor Parent constructor. + * @function + * @api + */ +ol.inherits = function(childCtor, parentCtor) { + childCtor.prototype = Object.create(parentCtor.prototype); + childCtor.prototype.constructor = childCtor; +}; + + +/** + * A reusable function, used e.g. as a default for callbacks. + * + * @return {undefined} Nothing. + */ +ol.nullFunction = function() {}; + + +/** + * Gets a unique ID for an object. This mutates the object so that further calls + * with the same object as a parameter returns the same value. Unique IDs are generated + * as a strictly increasing sequence. Adapted from goog.getUid. + * + * @param {Object} obj The object to get the unique ID for. + * @return {number} The unique ID for the object. + */ +ol.getUid = function(obj) { + return obj.ol_uid || + (obj.ol_uid = ++ol.uidCounter_); +}; + + +/** + * Counter for getUid. + * @type {number} + * @private + */ +ol.uidCounter_ = 0; + +goog.provide('ol.AssertionError'); + +goog.require('ol'); + +/** + * Error object thrown when an assertion failed. This is an ECMA-262 Error, + * extended with a `code` property. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error} + * @constructor + * @extends {Error} + * @implements {oli.AssertionError} + * @param {number} code Error code. + */ +ol.AssertionError = function(code) { + + /** + * @type {string} + */ + this.message = 'Assertion failed. See ' + + (ol.VERSION ? 'https://openlayers.org/en/' + ol.VERSION.split('-')[0] : '') + + '/doc/errors/#' + code + ' for details.'; + + /** + * Error code. The meaning of the code can be found on + * {@link https://openlayers.org/en/latest/doc/errors/} (replace `latest` with + * the version found in the OpenLayers script's header comment if a version + * other than the latest is used). + * @type {number} + * @api + */ + this.code = code; + + this.name = 'AssertionError'; + +}; +ol.inherits(ol.AssertionError, Error); + +goog.provide('ol.asserts'); + +goog.require('ol.AssertionError'); + + +/** + * @param {*} assertion Assertion we expected to be truthy. + * @param {number} errorCode Error code. + */ +ol.asserts.assert = function(assertion, errorCode) { + if (!assertion) { + throw new ol.AssertionError(errorCode); + } +}; + +goog.provide('ol.math'); + +goog.require('ol'); +goog.require('ol.asserts'); + + +/** + * Takes a number and clamps it to within the provided bounds. + * @param {number} value The input number. + * @param {number} min The minimum value to return. + * @param {number} max The maximum value to return. + * @return {number} The input number if it is within bounds, or the nearest + * number within the bounds. + */ +ol.math.clamp = function(value, min, max) { + return Math.min(Math.max(value, min), max); +}; + + +/** + * Return the hyperbolic cosine of a given number. The method will use the + * native `Math.cosh` function if it is available, otherwise the hyperbolic + * cosine will be calculated via the reference implementation of the Mozilla + * developer network. + * + * @param {number} x X. + * @return {number} Hyperbolic cosine of x. + */ +ol.math.cosh = (function() { + // Wrapped in a iife, to save the overhead of checking for the native + // implementation on every invocation. + var cosh; + if ('cosh' in Math) { + // The environment supports the native Math.cosh function, use it… + cosh = Math.cosh; + } else { + // … else, use the reference implementation of MDN: + cosh = function(x) { + var y = Math.exp(x); + return (y + 1 / y) / 2; + }; + } + return cosh; +}()); + + +/** + * @param {number} x X. + * @return {number} The smallest power of two greater than or equal to x. + */ +ol.math.roundUpToPowerOfTwo = function(x) { + ol.asserts.assert(0 < x, 29); // `x` must be greater than `0` + return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2)); +}; + + +/** + * Returns the square of the closest distance between the point (x, y) and the + * line segment (x1, y1) to (x2, y2). + * @param {number} x X. + * @param {number} y Y. + * @param {number} x1 X1. + * @param {number} y1 Y1. + * @param {number} x2 X2. + * @param {number} y2 Y2. + * @return {number} Squared distance. + */ +ol.math.squaredSegmentDistance = function(x, y, x1, y1, x2, y2) { + var dx = x2 - x1; + var dy = y2 - y1; + if (dx !== 0 || dy !== 0) { + var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy); + if (t > 1) { + x1 = x2; + y1 = y2; + } else if (t > 0) { + x1 += dx * t; + y1 += dy * t; + } + } + return ol.math.squaredDistance(x, y, x1, y1); +}; + + +/** + * Returns the square of the distance between the points (x1, y1) and (x2, y2). + * @param {number} x1 X1. + * @param {number} y1 Y1. + * @param {number} x2 X2. + * @param {number} y2 Y2. + * @return {number} Squared distance. + */ +ol.math.squaredDistance = function(x1, y1, x2, y2) { + var dx = x2 - x1; + var dy = y2 - y1; + return dx * dx + dy * dy; +}; + + +/** + * Solves system of linear equations using Gaussian elimination method. + * + * @param {Array.<Array.<number>>} mat Augmented matrix (n x n + 1 column) + * in row-major order. + * @return {Array.<number>} The resulting vector. + */ +ol.math.solveLinearSystem = function(mat) { + var n = mat.length; + + if (ol.DEBUG) { + for (var row = 0; row < n; row++) { + console.assert(mat[row].length == n + 1, + 'every row should have correct number of columns'); + } + } + + for (var i = 0; i < n; i++) { + // Find max in the i-th column (ignoring i - 1 first rows) + var maxRow = i; + var maxEl = Math.abs(mat[i][i]); + for (var r = i + 1; r < n; r++) { + var absValue = Math.abs(mat[r][i]); + if (absValue > maxEl) { + maxEl = absValue; + maxRow = r; + } + } + + if (maxEl === 0) { + return null; // matrix is singular + } + + // Swap max row with i-th (current) row + var tmp = mat[maxRow]; + mat[maxRow] = mat[i]; + mat[i] = tmp; + + // Subtract the i-th row to make all the remaining rows 0 in the i-th column + for (var j = i + 1; j < n; j++) { + var coef = -mat[j][i] / mat[i][i]; + for (var k = i; k < n + 1; k++) { + if (i == k) { + mat[j][k] = 0; + } else { + mat[j][k] += coef * mat[i][k]; + } + } + } + } + + // Solve Ax=b for upper triangular matrix A (mat) + var x = new Array(n); + for (var l = n - 1; l >= 0; l--) { + x[l] = mat[l][n] / mat[l][l]; + for (var m = l - 1; m >= 0; m--) { + mat[m][n] -= mat[m][l] * x[l]; + } + } + return x; +}; + + +/** + * Converts radians to to degrees. + * + * @param {number} angleInRadians Angle in radians. + * @return {number} Angle in degrees. + */ +ol.math.toDegrees = function(angleInRadians) { + return angleInRadians * 180 / Math.PI; +}; + + +/** + * Converts degrees to radians. + * + * @param {number} angleInDegrees Angle in degrees. + * @return {number} Angle in radians. + */ +ol.math.toRadians = function(angleInDegrees) { + return angleInDegrees * Math.PI / 180; +}; + +/** + * Returns the modulo of a / b, depending on the sign of b. + * + * @param {number} a Dividend. + * @param {number} b Divisor. + * @return {number} Modulo. + */ +ol.math.modulo = function(a, b) { + var r = a % b; + return r * b < 0 ? r + b : r; +}; + +/** + * Calculates the linearly interpolated value of x between a and b. + * + * @param {number} a Number + * @param {number} b Number + * @param {number} x Value to be interpolated. + * @return {number} Interpolated value. + */ +ol.math.lerp = function(a, b, x) { + return a + x * (b - a); +}; + +goog.provide('ol.CenterConstraint'); + +goog.require('ol.math'); + + +/** + * @param {ol.Extent} extent Extent. + * @return {ol.CenterConstraintType} The constraint. + */ +ol.CenterConstraint.createExtent = function(extent) { + return ( + /** + * @param {ol.Coordinate|undefined} center Center. + * @return {ol.Coordinate|undefined} Center. + */ + function(center) { + if (center) { + return [ + ol.math.clamp(center[0], extent[0], extent[2]), + ol.math.clamp(center[1], extent[1], extent[3]) + ]; + } else { + return undefined; + } + }); +}; + + +/** + * @param {ol.Coordinate|undefined} center Center. + * @return {ol.Coordinate|undefined} Center. + */ +ol.CenterConstraint.none = function(center) { + return center; +}; + +goog.provide('ol.Constraints'); + + +/** + * @constructor + * @param {ol.CenterConstraintType} centerConstraint Center constraint. + * @param {ol.ResolutionConstraintType} resolutionConstraint + * Resolution constraint. + * @param {ol.RotationConstraintType} rotationConstraint + * Rotation constraint. + */ +ol.Constraints = function(centerConstraint, resolutionConstraint, rotationConstraint) { + + /** + * @type {ol.CenterConstraintType} + */ + this.center = centerConstraint; + + /** + * @type {ol.ResolutionConstraintType} + */ + this.resolution = resolutionConstraint; + + /** + * @type {ol.RotationConstraintType} + */ + this.rotation = rotationConstraint; + +}; + +goog.provide('ol.obj'); + + +/** + * Polyfill for Object.assign(). Assigns enumerable and own properties from + * one or more source objects to a target object. + * + * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign + * @param {!Object} target The target object. + * @param {...Object} var_sources The source object(s). + * @return {!Object} The modified target object. + */ +ol.obj.assign = (typeof Object.assign === 'function') ? Object.assign : function(target, var_sources) { + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + var output = Object(target); + for (var i = 1, ii = arguments.length; i < ii; ++i) { + var source = arguments[i]; + if (source !== undefined && source !== null) { + for (var key in source) { + if (source.hasOwnProperty(key)) { + output[key] = source[key]; + } + } + } + } + return output; +}; + + +/** + * Removes all properties from an object. + * @param {Object} object The object to clear. + */ +ol.obj.clear = function(object) { + for (var property in object) { + delete object[property]; + } +}; + + +/** + * Get an array of property values from an object. + * @param {Object<K,V>} object The object from which to get the values. + * @return {!Array<V>} The property values. + * @template K,V + */ +ol.obj.getValues = function(object) { + var values = []; + for (var property in object) { + values.push(object[property]); + } + return values; +}; + + +/** + * Determine if an object has any properties. + * @param {Object} object The object to check. + * @return {boolean} The object is empty. + */ +ol.obj.isEmpty = function(object) { + var property; + for (property in object) { + return false; + } + return !property; +}; + +goog.provide('ol.events'); + +goog.require('ol.obj'); + + +/** + * @param {ol.EventsKey} listenerObj Listener object. + * @return {ol.EventsListenerFunctionType} Bound listener. + */ +ol.events.bindListener_ = function(listenerObj) { + var boundListener = function(evt) { + var listener = listenerObj.listener; + var bindTo = listenerObj.bindTo || listenerObj.target; + if (listenerObj.callOnce) { + ol.events.unlistenByKey(listenerObj); + } + return listener.call(bindTo, evt); + }; + listenerObj.boundListener = boundListener; + return boundListener; +}; + + +/** + * Finds the matching {@link ol.EventsKey} in the given listener + * array. + * + * @param {!Array<!ol.EventsKey>} listeners Array of listeners. + * @param {!Function} listener The listener function. + * @param {Object=} opt_this The `this` value inside the listener. + * @param {boolean=} opt_setDeleteIndex Set the deleteIndex on the matching + * listener, for {@link ol.events.unlistenByKey}. + * @return {ol.EventsKey|undefined} The matching listener object. + * @private + */ +ol.events.findListener_ = function(listeners, listener, opt_this, + opt_setDeleteIndex) { + var listenerObj; + for (var i = 0, ii = listeners.length; i < ii; ++i) { + listenerObj = listeners[i]; + if (listenerObj.listener === listener && + listenerObj.bindTo === opt_this) { + if (opt_setDeleteIndex) { + listenerObj.deleteIndex = i; + } + return listenerObj; + } + } + return undefined; +}; + + +/** + * @param {ol.EventTargetLike} target Target. + * @param {string} type Type. + * @return {Array.<ol.EventsKey>|undefined} Listeners. + */ +ol.events.getListeners = function(target, type) { + var listenerMap = target.ol_lm; + return listenerMap ? listenerMap[type] : undefined; +}; + + +/** + * Get the lookup of listeners. If one does not exist on the target, it is + * created. + * @param {ol.EventTargetLike} target Target. + * @return {!Object.<string, Array.<ol.EventsKey>>} Map of + * listeners by event type. + * @private + */ +ol.events.getListenerMap_ = function(target) { + var listenerMap = target.ol_lm; + if (!listenerMap) { + listenerMap = target.ol_lm = {}; + } + return listenerMap; +}; + + +/** + * Clean up all listener objects of the given type. All properties on the + * listener objects will be removed, and if no listeners remain in the listener + * map, it will be removed from the target. + * @param {ol.EventTargetLike} target Target. + * @param {string} type Type. + * @private + */ +ol.events.removeListeners_ = function(target, type) { + var listeners = ol.events.getListeners(target, type); + if (listeners) { + for (var i = 0, ii = listeners.length; i < ii; ++i) { + target.removeEventListener(type, listeners[i].boundListener); + ol.obj.clear(listeners[i]); + } + listeners.length = 0; + var listenerMap = target.ol_lm; + if (listenerMap) { + delete listenerMap[type]; + if (Object.keys(listenerMap).length === 0) { + delete target.ol_lm; + } + } + } +}; + + +/** + * Registers an event listener on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * This function efficiently binds a `listener` to a `this` object, and returns + * a key for use with {@link ol.events.unlistenByKey}. + * + * @param {ol.EventTargetLike} target Event target. + * @param {string} type Event type. + * @param {ol.EventsListenerFunctionType} listener Listener. + * @param {Object=} opt_this Object referenced by the `this` keyword in the + * listener. Default is the `target`. + * @param {boolean=} opt_once If true, add the listener as one-off listener. + * @return {ol.EventsKey} Unique key for the listener. + */ +ol.events.listen = function(target, type, listener, opt_this, opt_once) { + var listenerMap = ol.events.getListenerMap_(target); + var listeners = listenerMap[type]; + if (!listeners) { + listeners = listenerMap[type] = []; + } + var listenerObj = ol.events.findListener_(listeners, listener, opt_this, + false); + if (listenerObj) { + if (!opt_once) { + // Turn one-off listener into a permanent one. + listenerObj.callOnce = false; + } + } else { + listenerObj = /** @type {ol.EventsKey} */ ({ + bindTo: opt_this, + callOnce: !!opt_once, + listener: listener, + target: target, + type: type + }); + target.addEventListener(type, ol.events.bindListener_(listenerObj)); + listeners.push(listenerObj); + } + + return listenerObj; +}; + + +/** + * Registers a one-off event listener on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * This function efficiently binds a `listener` as self-unregistering listener + * to a `this` object, and returns a key for use with + * {@link ol.events.unlistenByKey} in case the listener needs to be unregistered + * before it is called. + * + * When {@link ol.events.listen} is called with the same arguments after this + * function, the self-unregistering listener will be turned into a permanent + * listener. + * + * @param {ol.EventTargetLike} target Event target. + * @param {string} type Event type. + * @param {ol.EventsListenerFunctionType} listener Listener. + * @param {Object=} opt_this Object referenced by the `this` keyword in the + * listener. Default is the `target`. + * @return {ol.EventsKey} Key for unlistenByKey. + */ +ol.events.listenOnce = function(target, type, listener, opt_this) { + return ol.events.listen(target, type, listener, opt_this, true); +}; + + +/** + * Unregisters an event listener on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * To return a listener, this function needs to be called with the exact same + * arguments that were used for a previous {@link ol.events.listen} call. + * + * @param {ol.EventTargetLike} target Event target. + * @param {string} type Event type. + * @param {ol.EventsListenerFunctionType} listener Listener. + * @param {Object=} opt_this Object referenced by the `this` keyword in the + * listener. Default is the `target`. + */ +ol.events.unlisten = function(target, type, listener, opt_this) { + var listeners = ol.events.getListeners(target, type); + if (listeners) { + var listenerObj = ol.events.findListener_(listeners, listener, opt_this, + true); + if (listenerObj) { + ol.events.unlistenByKey(listenerObj); + } + } +}; + + +/** + * Unregisters event listeners on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * The argument passed to this function is the key returned from + * {@link ol.events.listen} or {@link ol.events.listenOnce}. + * + * @param {ol.EventsKey} key The key. + */ +ol.events.unlistenByKey = function(key) { + if (key && key.target) { + key.target.removeEventListener(key.type, key.boundListener); + var listeners = ol.events.getListeners(key.target, key.type); + if (listeners) { + var i = 'deleteIndex' in key ? key.deleteIndex : listeners.indexOf(key); + if (i !== -1) { + listeners.splice(i, 1); + } + if (listeners.length === 0) { + ol.events.removeListeners_(key.target, key.type); + } + } + ol.obj.clear(key); + } +}; + + +/** + * Unregisters all event listeners on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * @param {ol.EventTargetLike} target Target. + */ +ol.events.unlistenAll = function(target) { + var listenerMap = ol.events.getListenerMap_(target); + for (var type in listenerMap) { + ol.events.removeListeners_(target, type); + } +}; + +goog.provide('ol.Disposable'); + +goog.require('ol'); + +/** + * Objects that need to clean up after themselves. + * @constructor + */ +ol.Disposable = function() {}; + +/** + * The object has already been disposed. + * @type {boolean} + * @private + */ +ol.Disposable.prototype.disposed_ = false; + +/** + * Clean up. + */ +ol.Disposable.prototype.dispose = function() { + if (!this.disposed_) { + this.disposed_ = true; + this.disposeInternal(); + } +}; + +/** + * Extension point for disposable objects. + * @protected + */ +ol.Disposable.prototype.disposeInternal = ol.nullFunction; + +goog.provide('ol.events.Event'); + + +/** + * @classdesc + * Stripped down implementation of the W3C DOM Level 2 Event interface. + * @see {@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface} + * + * This implementation only provides `type` and `target` properties, and + * `stopPropagation` and `preventDefault` methods. It is meant as base class + * for higher level events defined in the library, and works with + * {@link ol.events.EventTarget}. + * + * @constructor + * @implements {oli.events.Event} + * @param {string} type Type. + */ +ol.events.Event = function(type) { + + /** + * @type {boolean} + */ + this.propagationStopped; + + /** + * The event type. + * @type {string} + * @api stable + */ + this.type = type; + + /** + * The event target. + * @type {Object} + * @api stable + */ + this.target = null; + +}; + + +/** + * Stop event propagation. + * @function + * @api stable + */ +ol.events.Event.prototype.preventDefault = + +/** + * Stop event propagation. + * @function + * @api stable + */ +ol.events.Event.prototype.stopPropagation = function() { + this.propagationStopped = true; +}; + + +/** + * @param {Event|ol.events.Event} evt Event + */ +ol.events.Event.stopPropagation = function(evt) { + evt.stopPropagation(); +}; + + +/** + * @param {Event|ol.events.Event} evt Event + */ +ol.events.Event.preventDefault = function(evt) { + evt.preventDefault(); +}; + +goog.provide('ol.events.EventTarget'); + +goog.require('ol'); +goog.require('ol.Disposable'); +goog.require('ol.events'); +goog.require('ol.events.Event'); + + +/** + * @classdesc + * A simplified implementation of the W3C DOM Level 2 EventTarget interface. + * @see {@link https://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html#Events-EventTarget} + * + * There are two important simplifications compared to the specification: + * + * 1. The handling of `useCapture` in `addEventListener` and + * `removeEventListener`. There is no real capture model. + * 2. The handling of `stopPropagation` and `preventDefault` on `dispatchEvent`. + * There is no event target hierarchy. When a listener calls + * `stopPropagation` or `preventDefault` on an event object, it means that no + * more listeners after this one will be called. Same as when the listener + * returns false. + * + * @constructor + * @extends {ol.Disposable} + */ +ol.events.EventTarget = function() { + + ol.Disposable.call(this); + + /** + * @private + * @type {!Object.<string, number>} + */ + this.pendingRemovals_ = {}; + + /** + * @private + * @type {!Object.<string, number>} + */ + this.dispatching_ = {}; + + /** + * @private + * @type {!Object.<string, Array.<ol.EventsListenerFunctionType>>} + */ + this.listeners_ = {}; + +}; +ol.inherits(ol.events.EventTarget, ol.Disposable); + + +/** + * @param {string} type Type. + * @param {ol.EventsListenerFunctionType} listener Listener. + */ +ol.events.EventTarget.prototype.addEventListener = function(type, listener) { + var listeners = this.listeners_[type]; + if (!listeners) { + listeners = this.listeners_[type] = []; + } + if (listeners.indexOf(listener) === -1) { + listeners.push(listener); + } +}; + + +/** + * @param {{type: string, + * target: (EventTarget|ol.events.EventTarget|undefined)}|ol.events.Event| + * string} event Event or event type. + * @return {boolean|undefined} `false` if anyone called preventDefault on the + * event object or if any of the listeners returned false. + */ +ol.events.EventTarget.prototype.dispatchEvent = function(event) { + var evt = typeof event === 'string' ? new ol.events.Event(event) : event; + var type = evt.type; + evt.target = this; + var listeners = this.listeners_[type]; + var propagate; + if (listeners) { + if (!(type in this.dispatching_)) { + this.dispatching_[type] = 0; + this.pendingRemovals_[type] = 0; + } + ++this.dispatching_[type]; + for (var i = 0, ii = listeners.length; i < ii; ++i) { + if (listeners[i].call(this, evt) === false || evt.propagationStopped) { + propagate = false; + break; + } + } + --this.dispatching_[type]; + if (this.dispatching_[type] === 0) { + var pendingRemovals = this.pendingRemovals_[type]; + delete this.pendingRemovals_[type]; + while (pendingRemovals--) { + this.removeEventListener(type, ol.nullFunction); + } + delete this.dispatching_[type]; + } + return propagate; + } +}; + + +/** + * @inheritDoc + */ +ol.events.EventTarget.prototype.disposeInternal = function() { + ol.events.unlistenAll(this); +}; + + +/** + * Get the listeners for a specified event type. Listeners are returned in the + * order that they will be called in. + * + * @param {string} type Type. + * @return {Array.<ol.EventsListenerFunctionType>} Listeners. + */ +ol.events.EventTarget.prototype.getListeners = function(type) { + return this.listeners_[type]; +}; + + +/** + * @param {string=} opt_type Type. If not provided, + * `true` will be returned if this EventTarget has any listeners. + * @return {boolean} Has listeners. + */ +ol.events.EventTarget.prototype.hasListener = function(opt_type) { + return opt_type ? + opt_type in this.listeners_ : + Object.keys(this.listeners_).length > 0; +}; + + +/** + * @param {string} type Type. + * @param {ol.EventsListenerFunctionType} listener Listener. + */ +ol.events.EventTarget.prototype.removeEventListener = function(type, listener) { + var listeners = this.listeners_[type]; + if (listeners) { + var index = listeners.indexOf(listener); + ol.DEBUG && console.assert(index != -1, 'listener not found'); + if (type in this.pendingRemovals_) { + // make listener a no-op, and remove later in #dispatchEvent() + listeners[index] = ol.nullFunction; + ++this.pendingRemovals_[type]; + } else { + listeners.splice(index, 1); + if (listeners.length === 0) { + delete this.listeners_[type]; + } + } + } +}; + +goog.provide('ol.events.EventType'); + +/** + * @enum {string} + * @const + */ +ol.events.EventType = { + /** + * Generic change event. Triggered when the revision counter is increased. + * @event ol.events.Event#change + * @api + */ + CHANGE: 'change', + + CLICK: 'click', + DBLCLICK: 'dblclick', + DRAGENTER: 'dragenter', + DRAGOVER: 'dragover', + DROP: 'drop', + ERROR: 'error', + KEYDOWN: 'keydown', + KEYPRESS: 'keypress', + LOAD: 'load', + MOUSEDOWN: 'mousedown', + MOUSEMOVE: 'mousemove', + MOUSEOUT: 'mouseout', + MOUSEUP: 'mouseup', + MOUSEWHEEL: 'mousewheel', + MSPOINTERDOWN: 'mspointerdown', + RESIZE: 'resize', + TOUCHSTART: 'touchstart', + TOUCHMOVE: 'touchmove', + TOUCHEND: 'touchend', + WHEEL: 'wheel' +}; + +goog.provide('ol.Observable'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); +goog.require('ol.events.EventType'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * An event target providing convenient methods for listener registration + * and unregistration. A generic `change` event is always available through + * {@link ol.Observable#changed}. + * + * @constructor + * @extends {ol.events.EventTarget} + * @fires ol.events.Event + * @struct + * @api stable + */ +ol.Observable = function() { + + ol.events.EventTarget.call(this); + + /** + * @private + * @type {number} + */ + this.revision_ = 0; + +}; +ol.inherits(ol.Observable, ol.events.EventTarget); + + +/** + * Removes an event listener using the key returned by `on()` or `once()`. + * @param {ol.EventsKey|Array.<ol.EventsKey>} key The key returned by `on()` + * or `once()` (or an array of keys). + * @api stable + */ +ol.Observable.unByKey = function(key) { + if (Array.isArray(key)) { + for (var i = 0, ii = key.length; i < ii; ++i) { + ol.events.unlistenByKey(key[i]); + } + } else { + ol.events.unlistenByKey(/** @type {ol.EventsKey} */ (key)); + } +}; + + +/** + * Increases the revision counter and dispatches a 'change' event. + * @api + */ +ol.Observable.prototype.changed = function() { + ++this.revision_; + this.dispatchEvent(ol.events.EventType.CHANGE); +}; + + +/** + * Dispatches an event and calls all listeners listening for events + * of this type. The event parameter can either be a string or an + * Object with a `type` property. + * + * @param {{type: string, + * target: (EventTarget|ol.events.EventTarget|undefined)}|ol.events.Event| + * string} event Event object. + * @function + * @api + */ +ol.Observable.prototype.dispatchEvent; + + +/** + * Get the version number for this object. Each time the object is modified, + * its version number will be incremented. + * @return {number} Revision. + * @api + */ +ol.Observable.prototype.getRevision = function() { + return this.revision_; +}; + + +/** + * Listen for a certain type of event. + * @param {string|Array.<string>} type The event type or array of event types. + * @param {function(?): ?} listener The listener function. + * @param {Object=} opt_this The object to use as `this` in `listener`. + * @return {ol.EventsKey|Array.<ol.EventsKey>} Unique key for the listener. If + * called with an array of event types as the first argument, the return + * will be an array of keys. + * @api stable + */ +ol.Observable.prototype.on = function(type, listener, opt_this) { + if (Array.isArray(type)) { + var len = type.length; + var keys = new Array(len); + for (var i = 0; i < len; ++i) { + keys[i] = ol.events.listen(this, type[i], listener, opt_this); + } + return keys; + } else { + return ol.events.listen( + this, /** @type {string} */ (type), listener, opt_this); + } +}; + + +/** + * Listen once for a certain type of event. + * @param {string|Array.<string>} type The event type or array of event types. + * @param {function(?): ?} listener The listener function. + * @param {Object=} opt_this The object to use as `this` in `listener`. + * @return {ol.EventsKey|Array.<ol.EventsKey>} Unique key for the listener. If + * called with an array of event types as the first argument, the return + * will be an array of keys. + * @api stable + */ +ol.Observable.prototype.once = function(type, listener, opt_this) { + if (Array.isArray(type)) { + var len = type.length; + var keys = new Array(len); + for (var i = 0; i < len; ++i) { + keys[i] = ol.events.listenOnce(this, type[i], listener, opt_this); + } + return keys; + } else { + return ol.events.listenOnce( + this, /** @type {string} */ (type), listener, opt_this); + } +}; + + +/** + * Unlisten for a certain type of event. + * @param {string|Array.<string>} type The event type or array of event types. + * @param {function(?): ?} listener The listener function. + * @param {Object=} opt_this The object which was used as `this` by the + * `listener`. + * @api stable + */ +ol.Observable.prototype.un = function(type, listener, opt_this) { + if (Array.isArray(type)) { + for (var i = 0, ii = type.length; i < ii; ++i) { + ol.events.unlisten(this, type[i], listener, opt_this); + } + return; + } else { + ol.events.unlisten(this, /** @type {string} */ (type), listener, opt_this); + } +}; + + +/** + * Removes an event listener using the key returned by `on()` or `once()`. + * Note that using the {@link ol.Observable.unByKey} static function is to + * be preferred. + * @param {ol.EventsKey|Array.<ol.EventsKey>} key The key returned by `on()` + * or `once()` (or an array of keys). + * @function + * @api stable + */ +ol.Observable.prototype.unByKey = ol.Observable.unByKey; + +goog.provide('ol.Object'); + +goog.require('ol'); +goog.require('ol.Observable'); +goog.require('ol.events.Event'); +goog.require('ol.obj'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Most non-trivial classes inherit from this. + * + * This extends {@link ol.Observable} with observable properties, where each + * property is observable as well as the object as a whole. + * + * Classes that inherit from this have pre-defined properties, to which you can + * add your owns. The pre-defined properties are listed in this documentation as + * 'Observable Properties', and have their own accessors; for example, + * {@link ol.Map} has a `target` property, accessed with `getTarget()` and + * changed with `setTarget()`. Not all properties are however settable. There + * are also general-purpose accessors `get()` and `set()`. For example, + * `get('target')` is equivalent to `getTarget()`. + * + * The `set` accessors trigger a change event, and you can monitor this by + * registering a listener. For example, {@link ol.View} has a `center` + * property, so `view.on('change:center', function(evt) {...});` would call the + * function whenever the value of the center property changes. Within the + * function, `evt.target` would be the view, so `evt.target.getCenter()` would + * return the new center. + * + * You can add your own observable properties with + * `object.set('prop', 'value')`, and retrieve that with `object.get('prop')`. + * You can listen for changes on that property value with + * `object.on('change:prop', listener)`. You can get a list of all + * properties with {@link ol.Object#getProperties object.getProperties()}. + * + * Note that the observable properties are separate from standard JS properties. + * You can, for example, give your map object a title with + * `map.title='New title'` and with `map.set('title', 'Another title')`. The + * first will be a `hasOwnProperty`; the second will appear in + * `getProperties()`. Only the second is observable. + * + * Properties can be deleted by using the unset method. E.g. + * object.unset('foo'). + * + * @constructor + * @extends {ol.Observable} + * @param {Object.<string, *>=} opt_values An object with key-value pairs. + * @fires ol.Object.Event + * @api + */ +ol.Object = function(opt_values) { + ol.Observable.call(this); + + // Call ol.getUid to ensure that the order of objects' ids is the same as + // the order in which they were created. This also helps to ensure that + // object properties are always added in the same order, which helps many + // JavaScript engines generate faster code. + ol.getUid(this); + + /** + * @private + * @type {!Object.<string, *>} + */ + this.values_ = {}; + + if (opt_values !== undefined) { + this.setProperties(opt_values); + } +}; +ol.inherits(ol.Object, ol.Observable); + + +/** + * @private + * @type {Object.<string, string>} + */ +ol.Object.changeEventTypeCache_ = {}; + + +/** + * @param {string} key Key name. + * @return {string} Change name. + */ +ol.Object.getChangeEventType = function(key) { + return ol.Object.changeEventTypeCache_.hasOwnProperty(key) ? + ol.Object.changeEventTypeCache_[key] : + (ol.Object.changeEventTypeCache_[key] = 'change:' + key); +}; + + +/** + * Gets a value. + * @param {string} key Key name. + * @return {*} Value. + * @api stable + */ +ol.Object.prototype.get = function(key) { + var value; + if (this.values_.hasOwnProperty(key)) { + value = this.values_[key]; + } + return value; +}; + + +/** + * Get a list of object property names. + * @return {Array.<string>} List of property names. + * @api stable + */ +ol.Object.prototype.getKeys = function() { + return Object.keys(this.values_); +}; + + +/** + * Get an object of all property names and values. + * @return {Object.<string, *>} Object. + * @api stable + */ +ol.Object.prototype.getProperties = function() { + return ol.obj.assign({}, this.values_); +}; + + +/** + * @param {string} key Key name. + * @param {*} oldValue Old value. + */ +ol.Object.prototype.notify = function(key, oldValue) { + var eventType; + eventType = ol.Object.getChangeEventType(key); + this.dispatchEvent(new ol.Object.Event(eventType, key, oldValue)); + eventType = ol.Object.EventType.PROPERTYCHANGE; + this.dispatchEvent(new ol.Object.Event(eventType, key, oldValue)); +}; + + +/** + * Sets a value. + * @param {string} key Key name. + * @param {*} value Value. + * @param {boolean=} opt_silent Update without triggering an event. + * @api stable + */ +ol.Object.prototype.set = function(key, value, opt_silent) { + if (opt_silent) { + this.values_[key] = value; + } else { + var oldValue = this.values_[key]; + this.values_[key] = value; + if (oldValue !== value) { + this.notify(key, oldValue); + } + } +}; + + +/** + * Sets a collection of key-value pairs. Note that this changes any existing + * properties and adds new ones (it does not remove any existing properties). + * @param {Object.<string, *>} values Values. + * @param {boolean=} opt_silent Update without triggering an event. + * @api stable + */ +ol.Object.prototype.setProperties = function(values, opt_silent) { + var key; + for (key in values) { + this.set(key, values[key], opt_silent); + } +}; + + +/** + * Unsets a property. + * @param {string} key Key name. + * @param {boolean=} opt_silent Unset without triggering an event. + * @api stable + */ +ol.Object.prototype.unset = function(key, opt_silent) { + if (key in this.values_) { + var oldValue = this.values_[key]; + delete this.values_[key]; + if (!opt_silent) { + this.notify(key, oldValue); + } + } +}; + + +/** + * @enum {string} + */ +ol.Object.EventType = { + /** + * Triggered when a property is changed. + * @event ol.Object.Event#propertychange + * @api stable + */ + PROPERTYCHANGE: 'propertychange' +}; + + +/** + * @classdesc + * Events emitted by {@link ol.Object} instances are instances of this type. + * + * @param {string} type The event type. + * @param {string} key The property name. + * @param {*} oldValue The old value for `key`. + * @extends {ol.events.Event} + * @implements {oli.Object.Event} + * @constructor + */ +ol.Object.Event = function(type, key, oldValue) { + ol.events.Event.call(this, type); + + /** + * The name of the property whose value is changing. + * @type {string} + * @api stable + */ + this.key = key; + + /** + * The old value. To get the new value use `e.target.get(e.key)` where + * `e` is the event object. + * @type {*} + * @api stable + */ + this.oldValue = oldValue; + +}; +ol.inherits(ol.Object.Event, ol.events.Event); + +goog.provide('ol.array'); + +goog.require('ol'); + + +/** + * Performs a binary search on the provided sorted list and returns the index of the item if found. If it can't be found it'll return -1. + * https://github.com/darkskyapp/binary-search + * + * @param {Array.<*>} haystack Items to search through. + * @param {*} needle The item to look for. + * @param {Function=} opt_comparator Comparator function. + * @return {number} The index of the item if found, -1 if not. + */ +ol.array.binarySearch = function(haystack, needle, opt_comparator) { + var mid, cmp; + var comparator = opt_comparator || ol.array.numberSafeCompareFunction; + var low = 0; + var high = haystack.length; + var found = false; + + while (low < high) { + /* Note that "(low + high) >>> 1" may overflow, and results in a typecast + * to double (which gives the wrong results). */ + mid = low + (high - low >> 1); + cmp = +comparator(haystack[mid], needle); + + if (cmp < 0.0) { /* Too low. */ + low = mid + 1; + + } else { /* Key found or too high */ + high = mid; + found = !cmp; + } + } + + /* Key not found. */ + return found ? low : ~low; +}; + + +/** + * Compare function for array sort that is safe for numbers. + * @param {*} a The first object to be compared. + * @param {*} b The second object to be compared. + * @return {number} A negative number, zero, or a positive number as the first + * argument is less than, equal to, or greater than the second. + */ +ol.array.numberSafeCompareFunction = function(a, b) { + return a > b ? 1 : a < b ? -1 : 0; +}; + + +/** + * Whether the array contains the given object. + * @param {Array.<*>} arr The array to test for the presence of the element. + * @param {*} obj The object for which to test. + * @return {boolean} The object is in the array. + */ +ol.array.includes = function(arr, obj) { + return arr.indexOf(obj) >= 0; +}; + + +/** + * @param {Array.<number>} arr Array. + * @param {number} target Target. + * @param {number} direction 0 means return the nearest, > 0 + * means return the largest nearest, < 0 means return the + * smallest nearest. + * @return {number} Index. + */ +ol.array.linearFindNearest = function(arr, target, direction) { + var n = arr.length; + if (arr[0] <= target) { + return 0; + } else if (target <= arr[n - 1]) { + return n - 1; + } else { + var i; + if (direction > 0) { + for (i = 1; i < n; ++i) { + if (arr[i] < target) { + return i - 1; + } + } + } else if (direction < 0) { + for (i = 1; i < n; ++i) { + if (arr[i] <= target) { + return i; + } + } + } else { + for (i = 1; i < n; ++i) { + if (arr[i] == target) { + return i; + } else if (arr[i] < target) { + if (arr[i - 1] - target < target - arr[i]) { + return i - 1; + } else { + return i; + } + } + } + } + return n - 1; + } +}; + + +/** + * @param {Array.<*>} arr Array. + * @param {number} begin Begin index. + * @param {number} end End index. + */ +ol.array.reverseSubArray = function(arr, begin, end) { + ol.DEBUG && console.assert(begin >= 0, + 'Array begin index should be equal to or greater than 0'); + ol.DEBUG && console.assert(end < arr.length, + 'Array end index should be less than the array length'); + while (begin < end) { + var tmp = arr[begin]; + arr[begin] = arr[end]; + arr[end] = tmp; + ++begin; + --end; + } +}; + + +/** + * @param {Array.<*>} arr Array. + * @return {!Array.<?>} Flattened Array. + */ +ol.array.flatten = function(arr) { + var data = arr.reduce(function(flattened, value) { + if (Array.isArray(value)) { + return flattened.concat(ol.array.flatten(value)); + } else { + return flattened.concat(value); + } + }, []); + return data; +}; + + +/** + * @param {Array.<VALUE>} arr The array to modify. + * @param {Array.<VALUE>|VALUE} data The elements or arrays of elements + * to add to arr. + * @template VALUE + */ +ol.array.extend = function(arr, data) { + var i; + var extension = Array.isArray(data) ? data : [data]; + var length = extension.length; + for (i = 0; i < length; i++) { + arr[arr.length] = extension[i]; + } +}; + + +/** + * @param {Array.<VALUE>} arr The array to modify. + * @param {VALUE} obj The element to remove. + * @template VALUE + * @return {boolean} If the element was removed. + */ +ol.array.remove = function(arr, obj) { + var i = arr.indexOf(obj); + var found = i > -1; + if (found) { + arr.splice(i, 1); + } + return found; +}; + + +/** + * @param {Array.<VALUE>} arr The array to search in. + * @param {function(VALUE, number, ?) : boolean} func The function to compare. + * @template VALUE + * @return {VALUE} The element found. + */ +ol.array.find = function(arr, func) { + var length = arr.length >>> 0; + var value; + + for (var i = 0; i < length; i++) { + value = arr[i]; + if (func(value, i, arr)) { + return value; + } + } + return null; +}; + + +/** + * @param {Array|Uint8ClampedArray} arr1 The first array to compare. + * @param {Array|Uint8ClampedArray} arr2 The second array to compare. + * @return {boolean} Whether the two arrays are equal. + */ +ol.array.equals = function(arr1, arr2) { + var len1 = arr1.length; + if (len1 !== arr2.length) { + return false; + } + for (var i = 0; i < len1; i++) { + if (arr1[i] !== arr2[i]) { + return false; + } + } + return true; +}; + + +/** + * @param {Array.<*>} arr The array to sort (modifies original). + * @param {Function} compareFnc Comparison function. + */ +ol.array.stableSort = function(arr, compareFnc) { + var length = arr.length; + var tmp = Array(arr.length); + var i; + for (i = 0; i < length; i++) { + tmp[i] = {index: i, value: arr[i]}; + } + tmp.sort(function(a, b) { + return compareFnc(a.value, b.value) || a.index - b.index; + }); + for (i = 0; i < arr.length; i++) { + arr[i] = tmp[i].value; + } +}; + + +/** + * @param {Array.<*>} arr The array to search in. + * @param {Function} func Comparison function. + * @return {number} Return index. + */ +ol.array.findIndex = function(arr, func) { + var index; + var found = !arr.every(function(el, idx) { + index = idx; + return !func(el, idx, arr); + }); + return found ? index : -1; +}; + + +/** + * @param {Array.<*>} arr The array to test. + * @param {Function=} opt_func Comparison function. + * @param {boolean=} opt_strict Strictly sorted (default false). + * @return {boolean} Return index. + */ +ol.array.isSorted = function(arr, opt_func, opt_strict) { + var compare = opt_func || ol.array.numberSafeCompareFunction; + return arr.every(function(currentVal, index) { + if (index === 0) { + return true; + } + var res = compare(arr[index - 1], currentVal); + return !(res > 0 || opt_strict && res === 0); + }); +}; + +goog.provide('ol.ResolutionConstraint'); + +goog.require('ol.array'); +goog.require('ol.math'); + + +/** + * @param {Array.<number>} resolutions Resolutions. + * @return {ol.ResolutionConstraintType} Zoom function. + */ +ol.ResolutionConstraint.createSnapToResolutions = function(resolutions) { + return ( + /** + * @param {number|undefined} resolution Resolution. + * @param {number} delta Delta. + * @param {number} direction Direction. + * @return {number|undefined} Resolution. + */ + function(resolution, delta, direction) { + if (resolution !== undefined) { + var z = + ol.array.linearFindNearest(resolutions, resolution, direction); + z = ol.math.clamp(z + delta, 0, resolutions.length - 1); + var index = Math.floor(z); + if (z != index && index < resolutions.length - 1) { + var power = resolutions[index] / resolutions[index + 1]; + return resolutions[index] / Math.pow(power, z - index); + } else { + return resolutions[index]; + } + } else { + return undefined; + } + }); +}; + + +/** + * @param {number} power Power. + * @param {number} maxResolution Maximum resolution. + * @param {number=} opt_maxLevel Maximum level. + * @return {ol.ResolutionConstraintType} Zoom function. + */ +ol.ResolutionConstraint.createSnapToPower = function(power, maxResolution, opt_maxLevel) { + return ( + /** + * @param {number|undefined} resolution Resolution. + * @param {number} delta Delta. + * @param {number} direction Direction. + * @return {number|undefined} Resolution. + */ + function(resolution, delta, direction) { + if (resolution !== undefined) { + var offset = -direction / 2 + 0.5; + var oldLevel = Math.floor( + Math.log(maxResolution / resolution) / Math.log(power) + offset); + var newLevel = Math.max(oldLevel + delta, 0); + if (opt_maxLevel !== undefined) { + newLevel = Math.min(newLevel, opt_maxLevel); + } + return maxResolution / Math.pow(power, newLevel); + } else { + return undefined; + } + }); +}; + +goog.provide('ol.RotationConstraint'); + +goog.require('ol.math'); + + +/** + * @param {number|undefined} rotation Rotation. + * @param {number} delta Delta. + * @return {number|undefined} Rotation. + */ +ol.RotationConstraint.disable = function(rotation, delta) { + if (rotation !== undefined) { + return 0; + } else { + return undefined; + } +}; + + +/** + * @param {number|undefined} rotation Rotation. + * @param {number} delta Delta. + * @return {number|undefined} Rotation. + */ +ol.RotationConstraint.none = function(rotation, delta) { + if (rotation !== undefined) { + return rotation + delta; + } else { + return undefined; + } +}; + + +/** + * @param {number} n N. + * @return {ol.RotationConstraintType} Rotation constraint. + */ +ol.RotationConstraint.createSnapToN = function(n) { + var theta = 2 * Math.PI / n; + return ( + /** + * @param {number|undefined} rotation Rotation. + * @param {number} delta Delta. + * @return {number|undefined} Rotation. + */ + function(rotation, delta) { + if (rotation !== undefined) { + rotation = Math.floor((rotation + delta) / theta + 0.5) * theta; + return rotation; + } else { + return undefined; + } + }); +}; + + +/** + * @param {number=} opt_tolerance Tolerance. + * @return {ol.RotationConstraintType} Rotation constraint. + */ +ol.RotationConstraint.createSnapToZero = function(opt_tolerance) { + var tolerance = opt_tolerance || ol.math.toRadians(5); + return ( + /** + * @param {number|undefined} rotation Rotation. + * @param {number} delta Delta. + * @return {number|undefined} Rotation. + */ + function(rotation, delta) { + if (rotation !== undefined) { + if (Math.abs(rotation + delta) <= tolerance) { + return 0; + } else { + return rotation + delta; + } + } else { + return undefined; + } + }); +}; + +goog.provide('ol.string'); + +/** + * @param {number} number Number to be formatted + * @param {number} width The desired width + * @param {number=} opt_precision Precision of the output string (i.e. number of decimal places) + * @returns {string} Formatted string +*/ +ol.string.padNumber = function(number, width, opt_precision) { + var numberString = opt_precision !== undefined ? number.toFixed(opt_precision) : '' + number; + var decimal = numberString.indexOf('.'); + decimal = decimal === -1 ? numberString.length : decimal; + return decimal > width ? numberString : new Array(1 + width - decimal).join('0') + numberString; +}; + +/** + * Adapted from https://github.com/omichelsen/compare-versions/blob/master/index.js + * @param {string|number} v1 First version + * @param {string|number} v2 Second version + * @returns {number} Value + */ +ol.string.compareVersions = function(v1, v2) { + var s1 = ('' + v1).split('.'); + var s2 = ('' + v2).split('.'); + + for (var i = 0; i < Math.max(s1.length, s2.length); i++) { + var n1 = parseInt(s1[i] || '0', 10); + var n2 = parseInt(s2[i] || '0', 10); + + if (n1 > n2) { + return 1; + } + if (n2 > n1) { + return -1; + } + } + + return 0; +}; + +goog.provide('ol.coordinate'); + +goog.require('ol.math'); +goog.require('ol.string'); + + +/** + * Add `delta` to `coordinate`. `coordinate` is modified in place and returned + * by the function. + * + * Example: + * + * var coord = [7.85, 47.983333]; + * ol.coordinate.add(coord, [-2, 4]); + * // coord is now [5.85, 51.983333] + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.Coordinate} delta Delta. + * @return {ol.Coordinate} The input coordinate adjusted by the given delta. + * @api stable + */ +ol.coordinate.add = function(coordinate, delta) { + coordinate[0] += delta[0]; + coordinate[1] += delta[1]; + return coordinate; +}; + + +/** + * Calculates the point closest to the passed coordinate on the passed segment. + * This is the foot of the perpendicular of the coordinate to the segment when + * the foot is on the segment, or the closest segment coordinate when the foot + * is outside the segment. + * + * @param {ol.Coordinate} coordinate The coordinate. + * @param {Array.<ol.Coordinate>} segment The two coordinates of the segment. + * @return {ol.Coordinate} The foot of the perpendicular of the coordinate to + * the segment. + */ +ol.coordinate.closestOnSegment = function(coordinate, segment) { + var x0 = coordinate[0]; + var y0 = coordinate[1]; + var start = segment[0]; + var end = segment[1]; + var x1 = start[0]; + var y1 = start[1]; + var x2 = end[0]; + var y2 = end[1]; + var dx = x2 - x1; + var dy = y2 - y1; + var along = (dx === 0 && dy === 0) ? 0 : + ((dx * (x0 - x1)) + (dy * (y0 - y1))) / ((dx * dx + dy * dy) || 0); + var x, y; + if (along <= 0) { + x = x1; + y = y1; + } else if (along >= 1) { + x = x2; + y = y2; + } else { + x = x1 + along * dx; + y = y1 + along * dy; + } + return [x, y]; +}; + + +/** + * Returns a {@link ol.CoordinateFormatType} function that can be used to format + * a {ol.Coordinate} to a string. + * + * Example without specifying the fractional digits: + * + * var coord = [7.85, 47.983333]; + * var stringifyFunc = ol.coordinate.createStringXY(); + * var out = stringifyFunc(coord); + * // out is now '8, 48' + * + * Example with explicitly specifying 2 fractional digits: + * + * var coord = [7.85, 47.983333]; + * var stringifyFunc = ol.coordinate.createStringXY(2); + * var out = stringifyFunc(coord); + * // out is now '7.85, 47.98' + * + * @param {number=} opt_fractionDigits The number of digits to include + * after the decimal point. Default is `0`. + * @return {ol.CoordinateFormatType} Coordinate format. + * @api stable + */ +ol.coordinate.createStringXY = function(opt_fractionDigits) { + return ( + /** + * @param {ol.Coordinate|undefined} coordinate Coordinate. + * @return {string} String XY. + */ + function(coordinate) { + return ol.coordinate.toStringXY(coordinate, opt_fractionDigits); + }); +}; + + +/** + * @private + * @param {number} degrees Degrees. + * @param {string} hemispheres Hemispheres. + * @param {number=} opt_fractionDigits The number of digits to include + * after the decimal point. Default is `0`. + * @return {string} String. + */ +ol.coordinate.degreesToStringHDMS_ = function(degrees, hemispheres, opt_fractionDigits) { + var normalizedDegrees = ol.math.modulo(degrees + 180, 360) - 180; + var x = Math.abs(3600 * normalizedDegrees); + var dflPrecision = opt_fractionDigits || 0; + return Math.floor(x / 3600) + '\u00b0 ' + + ol.string.padNumber(Math.floor((x / 60) % 60), 2) + '\u2032 ' + + ol.string.padNumber((x % 60), 2, dflPrecision) + '\u2033 ' + + hemispheres.charAt(normalizedDegrees < 0 ? 1 : 0); +}; + + +/** + * Transforms the given {@link ol.Coordinate} to a string using the given string + * template. The strings `{x}` and `{y}` in the template will be replaced with + * the first and second coordinate values respectively. + * + * Example without specifying the fractional digits: + * + * var coord = [7.85, 47.983333]; + * var template = 'Coordinate is ({x}|{y}).'; + * var out = ol.coordinate.format(coord, template); + * // out is now 'Coordinate is (8|48).' + * + * Example explicitly specifying the fractional digits: + * + * var coord = [7.85, 47.983333]; + * var template = 'Coordinate is ({x}|{y}).'; + * var out = ol.coordinate.format(coord, template, 2); + * // out is now 'Coordinate is (7.85|47.98).' + * + * @param {ol.Coordinate|undefined} coordinate Coordinate. + * @param {string} template A template string with `{x}` and `{y}` placeholders + * that will be replaced by first and second coordinate values. + * @param {number=} opt_fractionDigits The number of digits to include + * after the decimal point. Default is `0`. + * @return {string} Formatted coordinate. + * @api stable + */ +ol.coordinate.format = function(coordinate, template, opt_fractionDigits) { + if (coordinate) { + return template + .replace('{x}', coordinate[0].toFixed(opt_fractionDigits)) + .replace('{y}', coordinate[1].toFixed(opt_fractionDigits)); + } else { + return ''; + } +}; + + +/** + * @param {ol.Coordinate} coordinate1 First coordinate. + * @param {ol.Coordinate} coordinate2 Second coordinate. + * @return {boolean} Whether the passed coordinates are equal. + */ +ol.coordinate.equals = function(coordinate1, coordinate2) { + var equals = true; + for (var i = coordinate1.length - 1; i >= 0; --i) { + if (coordinate1[i] != coordinate2[i]) { + equals = false; + break; + } + } + return equals; +}; + + +/** + * Rotate `coordinate` by `angle`. `coordinate` is modified in place and + * returned by the function. + * + * Example: + * + * var coord = [7.85, 47.983333]; + * var rotateRadians = Math.PI / 2; // 90 degrees + * ol.coordinate.rotate(coord, rotateRadians); + * // coord is now [-47.983333, 7.85] + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} angle Angle in radian. + * @return {ol.Coordinate} Coordinate. + * @api stable + */ +ol.coordinate.rotate = function(coordinate, angle) { + var cosAngle = Math.cos(angle); + var sinAngle = Math.sin(angle); + var x = coordinate[0] * cosAngle - coordinate[1] * sinAngle; + var y = coordinate[1] * cosAngle + coordinate[0] * sinAngle; + coordinate[0] = x; + coordinate[1] = y; + return coordinate; +}; + + +/** + * Scale `coordinate` by `scale`. `coordinate` is modified in place and returned + * by the function. + * + * Example: + * + * var coord = [7.85, 47.983333]; + * var scale = 1.2; + * ol.coordinate.scale(coord, scale); + * // coord is now [9.42, 57.5799996] + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} scale Scale factor. + * @return {ol.Coordinate} Coordinate. + */ +ol.coordinate.scale = function(coordinate, scale) { + coordinate[0] *= scale; + coordinate[1] *= scale; + return coordinate; +}; + + +/** + * Subtract `delta` to `coordinate`. `coordinate` is modified in place and + * returned by the function. + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.Coordinate} delta Delta. + * @return {ol.Coordinate} Coordinate. + */ +ol.coordinate.sub = function(coordinate, delta) { + coordinate[0] -= delta[0]; + coordinate[1] -= delta[1]; + return coordinate; +}; + + +/** + * @param {ol.Coordinate} coord1 First coordinate. + * @param {ol.Coordinate} coord2 Second coordinate. + * @return {number} Squared distance between coord1 and coord2. + */ +ol.coordinate.squaredDistance = function(coord1, coord2) { + var dx = coord1[0] - coord2[0]; + var dy = coord1[1] - coord2[1]; + return dx * dx + dy * dy; +}; + + +/** + * Calculate the squared distance from a coordinate to a line segment. + * + * @param {ol.Coordinate} coordinate Coordinate of the point. + * @param {Array.<ol.Coordinate>} segment Line segment (2 coordinates). + * @return {number} Squared distance from the point to the line segment. + */ +ol.coordinate.squaredDistanceToSegment = function(coordinate, segment) { + return ol.coordinate.squaredDistance(coordinate, + ol.coordinate.closestOnSegment(coordinate, segment)); +}; + + +/** + * Format a geographic coordinate with the hemisphere, degrees, minutes, and + * seconds. + * + * Example without specifying fractional digits: + * + * var coord = [7.85, 47.983333]; + * var out = ol.coordinate.toStringHDMS(coord); + * // out is now '47° 58′ 60″ N 7° 50′ 60″ E' + * + * Example explicitly specifying 1 fractional digit: + * + * var coord = [7.85, 47.983333]; + * var out = ol.coordinate.toStringHDMS(coord, 1); + * // out is now '47° 58′ 60.0″ N 7° 50′ 60.0″ E' + * + * @param {ol.Coordinate|undefined} coordinate Coordinate. + * @param {number=} opt_fractionDigits The number of digits to include + * after the decimal point. Default is `0`. + * @return {string} Hemisphere, degrees, minutes and seconds. + * @api stable + */ +ol.coordinate.toStringHDMS = function(coordinate, opt_fractionDigits) { + if (coordinate) { + return ol.coordinate.degreesToStringHDMS_(coordinate[1], 'NS', opt_fractionDigits) + ' ' + + ol.coordinate.degreesToStringHDMS_(coordinate[0], 'EW', opt_fractionDigits); + } else { + return ''; + } +}; + + +/** + * Format a coordinate as a comma delimited string. + * + * Example without specifying fractional digits: + * + * var coord = [7.85, 47.983333]; + * var out = ol.coordinate.toStringXY(coord); + * // out is now '8, 48' + * + * Example explicitly specifying 1 fractional digit: + * + * var coord = [7.85, 47.983333]; + * var out = ol.coordinate.toStringXY(coord, 1); + * // out is now '7.8, 48.0' + * + * @param {ol.Coordinate|undefined} coordinate Coordinate. + * @param {number=} opt_fractionDigits The number of digits to include + * after the decimal point. Default is `0`. + * @return {string} XY. + * @api stable + */ +ol.coordinate.toStringXY = function(coordinate, opt_fractionDigits) { + return ol.coordinate.format(coordinate, '{x}, {y}', opt_fractionDigits); +}; + +goog.provide('ol.easing'); + + +/** + * Start slow and speed up. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api + */ +ol.easing.easeIn = function(t) { + return Math.pow(t, 3); +}; + + +/** + * Start fast and slow down. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api + */ +ol.easing.easeOut = function(t) { + return 1 - ol.easing.easeIn(1 - t); +}; + + +/** + * Start slow, speed up, and then slow down again. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api + */ +ol.easing.inAndOut = function(t) { + return 3 * t * t - 2 * t * t * t; +}; + + +/** + * Maintain a constant speed over time. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api + */ +ol.easing.linear = function(t) { + return t; +}; + + +/** + * Start slow, speed up, and at the very end slow down again. This has the + * same general behavior as {@link ol.easing.inAndOut}, but the final slowdown + * is delayed. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api + */ +ol.easing.upAndDown = function(t) { + if (t < 0.5) { + return ol.easing.inAndOut(2 * t); + } else { + return 1 - ol.easing.inAndOut(2 * (t - 0.5)); + } +}; + +goog.provide('ol.extent.Corner'); + +/** + * Extent corner. + * @enum {string} + */ +ol.extent.Corner = { + BOTTOM_LEFT: 'bottom-left', + BOTTOM_RIGHT: 'bottom-right', + TOP_LEFT: 'top-left', + TOP_RIGHT: 'top-right' +}; + +goog.provide('ol.extent.Relationship'); + + +/** + * Relationship to an extent. + * @enum {number} + */ +ol.extent.Relationship = { + UNKNOWN: 0, + INTERSECTING: 1, + ABOVE: 2, + RIGHT: 4, + BELOW: 8, + LEFT: 16 +}; + +goog.provide('ol.extent'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.extent.Corner'); +goog.require('ol.extent.Relationship'); + + +/** + * Build an extent that includes all given coordinates. + * + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @return {ol.Extent} Bounding extent. + * @api stable + */ +ol.extent.boundingExtent = function(coordinates) { + var extent = ol.extent.createEmpty(); + for (var i = 0, ii = coordinates.length; i < ii; ++i) { + ol.extent.extendCoordinate(extent, coordinates[i]); + } + return extent; +}; + + +/** + * @param {Array.<number>} xs Xs. + * @param {Array.<number>} ys Ys. + * @param {ol.Extent=} opt_extent Destination extent. + * @private + * @return {ol.Extent} Extent. + */ +ol.extent.boundingExtentXYs_ = function(xs, ys, opt_extent) { + ol.DEBUG && console.assert(xs.length > 0, 'xs length should be larger than 0'); + ol.DEBUG && console.assert(ys.length > 0, 'ys length should be larger than 0'); + var minX = Math.min.apply(null, xs); + var minY = Math.min.apply(null, ys); + var maxX = Math.max.apply(null, xs); + var maxY = Math.max.apply(null, ys); + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); +}; + + +/** + * Return extent increased by the provided value. + * @param {ol.Extent} extent Extent. + * @param {number} value The amount by which the extent should be buffered. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + * @api stable + */ +ol.extent.buffer = function(extent, value, opt_extent) { + if (opt_extent) { + opt_extent[0] = extent[0] - value; + opt_extent[1] = extent[1] - value; + opt_extent[2] = extent[2] + value; + opt_extent[3] = extent[3] + value; + return opt_extent; + } else { + return [ + extent[0] - value, + extent[1] - value, + extent[2] + value, + extent[3] + value + ]; + } +}; + + +/** + * Creates a clone of an extent. + * + * @param {ol.Extent} extent Extent to clone. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} The clone. + */ +ol.extent.clone = function(extent, opt_extent) { + if (opt_extent) { + opt_extent[0] = extent[0]; + opt_extent[1] = extent[1]; + opt_extent[2] = extent[2]; + opt_extent[3] = extent[3]; + return opt_extent; + } else { + return extent.slice(); + } +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} x X. + * @param {number} y Y. + * @return {number} Closest squared distance. + */ +ol.extent.closestSquaredDistanceXY = function(extent, x, y) { + var dx, dy; + if (x < extent[0]) { + dx = extent[0] - x; + } else if (extent[2] < x) { + dx = x - extent[2]; + } else { + dx = 0; + } + if (y < extent[1]) { + dy = extent[1] - y; + } else if (extent[3] < y) { + dy = y - extent[3]; + } else { + dy = 0; + } + return dx * dx + dy * dy; +}; + + +/** + * Check if the passed coordinate is contained or on the edge of the extent. + * + * @param {ol.Extent} extent Extent. + * @param {ol.Coordinate} coordinate Coordinate. + * @return {boolean} The coordinate is contained in the extent. + * @api stable + */ +ol.extent.containsCoordinate = function(extent, coordinate) { + return ol.extent.containsXY(extent, coordinate[0], coordinate[1]); +}; + + +/** + * Check if one extent contains another. + * + * An extent is deemed contained if it lies completely within the other extent, + * including if they share one or more edges. + * + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @return {boolean} The second extent is contained by or on the edge of the + * first. + * @api stable + */ +ol.extent.containsExtent = function(extent1, extent2) { + return extent1[0] <= extent2[0] && extent2[2] <= extent1[2] && + extent1[1] <= extent2[1] && extent2[3] <= extent1[3]; +}; + + +/** + * Check if the passed coordinate is contained or on the edge of the extent. + * + * @param {ol.Extent} extent Extent. + * @param {number} x X coordinate. + * @param {number} y Y coordinate. + * @return {boolean} The x, y values are contained in the extent. + * @api stable + */ +ol.extent.containsXY = function(extent, x, y) { + return extent[0] <= x && x <= extent[2] && extent[1] <= y && y <= extent[3]; +}; + + +/** + * Get the relationship between a coordinate and extent. + * @param {ol.Extent} extent The extent. + * @param {ol.Coordinate} coordinate The coordinate. + * @return {number} The relationship (bitwise compare with + * ol.extent.Relationship). + */ +ol.extent.coordinateRelationship = function(extent, coordinate) { + var minX = extent[0]; + var minY = extent[1]; + var maxX = extent[2]; + var maxY = extent[3]; + var x = coordinate[0]; + var y = coordinate[1]; + var relationship = ol.extent.Relationship.UNKNOWN; + if (x < minX) { + relationship = relationship | ol.extent.Relationship.LEFT; + } else if (x > maxX) { + relationship = relationship | ol.extent.Relationship.RIGHT; + } + if (y < minY) { + relationship = relationship | ol.extent.Relationship.BELOW; + } else if (y > maxY) { + relationship = relationship | ol.extent.Relationship.ABOVE; + } + if (relationship === ol.extent.Relationship.UNKNOWN) { + relationship = ol.extent.Relationship.INTERSECTING; + } + return relationship; +}; + + +/** + * Create an empty extent. + * @return {ol.Extent} Empty extent. + * @api stable + */ +ol.extent.createEmpty = function() { + return [Infinity, Infinity, -Infinity, -Infinity]; +}; + + +/** + * Create a new extent or update the provided extent. + * @param {number} minX Minimum X. + * @param {number} minY Minimum Y. + * @param {number} maxX Maximum X. + * @param {number} maxY Maximum Y. + * @param {ol.Extent=} opt_extent Destination extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdate = function(minX, minY, maxX, maxY, opt_extent) { + if (opt_extent) { + opt_extent[0] = minX; + opt_extent[1] = minY; + opt_extent[2] = maxX; + opt_extent[3] = maxY; + return opt_extent; + } else { + return [minX, minY, maxX, maxY]; + } +}; + + +/** + * Create a new empty extent or make the provided one empty. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateEmpty = function(opt_extent) { + return ol.extent.createOrUpdate( + Infinity, Infinity, -Infinity, -Infinity, opt_extent); +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromCoordinate = function(coordinate, opt_extent) { + var x = coordinate[0]; + var y = coordinate[1]; + return ol.extent.createOrUpdate(x, y, x, y, opt_extent); +}; + + +/** + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromCoordinates = function(coordinates, opt_extent) { + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + return ol.extent.extendCoordinates(extent, coordinates); +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromFlatCoordinates = function(flatCoordinates, offset, end, stride, opt_extent) { + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + return ol.extent.extendFlatCoordinates( + extent, flatCoordinates, offset, end, stride); +}; + + +/** + * @param {Array.<Array.<ol.Coordinate>>} rings Rings. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromRings = function(rings, opt_extent) { + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + return ol.extent.extendRings(extent, rings); +}; + + +/** + * Determine if two extents are equivalent. + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @return {boolean} The two extents are equivalent. + * @api stable + */ +ol.extent.equals = function(extent1, extent2) { + return extent1[0] == extent2[0] && extent1[2] == extent2[2] && + extent1[1] == extent2[1] && extent1[3] == extent2[3]; +}; + + +/** + * Modify an extent to include another extent. + * @param {ol.Extent} extent1 The extent to be modified. + * @param {ol.Extent} extent2 The extent that will be included in the first. + * @return {ol.Extent} A reference to the first (extended) extent. + * @api stable + */ +ol.extent.extend = function(extent1, extent2) { + if (extent2[0] < extent1[0]) { + extent1[0] = extent2[0]; + } + if (extent2[2] > extent1[2]) { + extent1[2] = extent2[2]; + } + if (extent2[1] < extent1[1]) { + extent1[1] = extent2[1]; + } + if (extent2[3] > extent1[3]) { + extent1[3] = extent2[3]; + } + return extent1; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Coordinate} coordinate Coordinate. + */ +ol.extent.extendCoordinate = function(extent, coordinate) { + if (coordinate[0] < extent[0]) { + extent[0] = coordinate[0]; + } + if (coordinate[0] > extent[2]) { + extent[2] = coordinate[0]; + } + if (coordinate[1] < extent[1]) { + extent[1] = coordinate[1]; + } + if (coordinate[1] > extent[3]) { + extent[3] = coordinate[1]; + } +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @return {ol.Extent} Extent. + */ +ol.extent.extendCoordinates = function(extent, coordinates) { + var i, ii; + for (i = 0, ii = coordinates.length; i < ii; ++i) { + ol.extent.extendCoordinate(extent, coordinates[i]); + } + return extent; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {ol.Extent} Extent. + */ +ol.extent.extendFlatCoordinates = function(extent, flatCoordinates, offset, end, stride) { + for (; offset < end; offset += stride) { + ol.extent.extendXY( + extent, flatCoordinates[offset], flatCoordinates[offset + 1]); + } + return extent; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {Array.<Array.<ol.Coordinate>>} rings Rings. + * @return {ol.Extent} Extent. + */ +ol.extent.extendRings = function(extent, rings) { + var i, ii; + for (i = 0, ii = rings.length; i < ii; ++i) { + ol.extent.extendCoordinates(extent, rings[i]); + } + return extent; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} x X. + * @param {number} y Y. + */ +ol.extent.extendXY = function(extent, x, y) { + extent[0] = Math.min(extent[0], x); + extent[1] = Math.min(extent[1], y); + extent[2] = Math.max(extent[2], x); + extent[3] = Math.max(extent[3], y); +}; + + +/** + * This function calls `callback` for each corner of the extent. If the + * callback returns a truthy value the function returns that value + * immediately. Otherwise the function returns `false`. + * @param {ol.Extent} extent Extent. + * @param {function(this:T, ol.Coordinate): S} callback Callback. + * @param {T=} opt_this Value to use as `this` when executing `callback`. + * @return {S|boolean} Value. + * @template S, T + */ +ol.extent.forEachCorner = function(extent, callback, opt_this) { + var val; + val = callback.call(opt_this, ol.extent.getBottomLeft(extent)); + if (val) { + return val; + } + val = callback.call(opt_this, ol.extent.getBottomRight(extent)); + if (val) { + return val; + } + val = callback.call(opt_this, ol.extent.getTopRight(extent)); + if (val) { + return val; + } + val = callback.call(opt_this, ol.extent.getTopLeft(extent)); + if (val) { + return val; + } + return false; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @return {number} Area. + */ +ol.extent.getArea = function(extent) { + var area = 0; + if (!ol.extent.isEmpty(extent)) { + area = ol.extent.getWidth(extent) * ol.extent.getHeight(extent); + } + return area; +}; + + +/** + * Get the bottom left coordinate of an extent. + * @param {ol.Extent} extent Extent. + * @return {ol.Coordinate} Bottom left coordinate. + * @api stable + */ +ol.extent.getBottomLeft = function(extent) { + return [extent[0], extent[1]]; +}; + + +/** + * Get the bottom right coordinate of an extent. + * @param {ol.Extent} extent Extent. + * @return {ol.Coordinate} Bottom right coordinate. + * @api stable + */ +ol.extent.getBottomRight = function(extent) { + return [extent[2], extent[1]]; +}; + + +/** + * Get the center coordinate of an extent. + * @param {ol.Extent} extent Extent. + * @return {ol.Coordinate} Center. + * @api stable + */ +ol.extent.getCenter = function(extent) { + return [(extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2]; +}; + + +/** + * Get a corner coordinate of an extent. + * @param {ol.Extent} extent Extent. + * @param {ol.extent.Corner} corner Corner. + * @return {ol.Coordinate} Corner coordinate. + */ +ol.extent.getCorner = function(extent, corner) { + var coordinate; + if (corner === ol.extent.Corner.BOTTOM_LEFT) { + coordinate = ol.extent.getBottomLeft(extent); + } else if (corner === ol.extent.Corner.BOTTOM_RIGHT) { + coordinate = ol.extent.getBottomRight(extent); + } else if (corner === ol.extent.Corner.TOP_LEFT) { + coordinate = ol.extent.getTopLeft(extent); + } else if (corner === ol.extent.Corner.TOP_RIGHT) { + coordinate = ol.extent.getTopRight(extent); + } else { + ol.asserts.assert(false, 13); // Invalid corner + } + return /** @type {!ol.Coordinate} */ (coordinate); +}; + + +/** + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @return {number} Enlarged area. + */ +ol.extent.getEnlargedArea = function(extent1, extent2) { + var minX = Math.min(extent1[0], extent2[0]); + var minY = Math.min(extent1[1], extent2[1]); + var maxX = Math.max(extent1[2], extent2[2]); + var maxY = Math.max(extent1[3], extent2[3]); + return (maxX - minX) * (maxY - minY); +}; + + +/** + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {ol.Extent=} opt_extent Destination extent. + * @return {ol.Extent} Extent. + */ +ol.extent.getForViewAndSize = function(center, resolution, rotation, size, opt_extent) { + var dx = resolution * size[0] / 2; + var dy = resolution * size[1] / 2; + var cosRotation = Math.cos(rotation); + var sinRotation = Math.sin(rotation); + var xCos = dx * cosRotation; + var xSin = dx * sinRotation; + var yCos = dy * cosRotation; + var ySin = dy * sinRotation; + var x = center[0]; + var y = center[1]; + var x0 = x - xCos + ySin; + var x1 = x - xCos - ySin; + var x2 = x + xCos - ySin; + var x3 = x + xCos + ySin; + var y0 = y - xSin - yCos; + var y1 = y - xSin + yCos; + var y2 = y + xSin + yCos; + var y3 = y + xSin - yCos; + return ol.extent.createOrUpdate( + Math.min(x0, x1, x2, x3), Math.min(y0, y1, y2, y3), + Math.max(x0, x1, x2, x3), Math.max(y0, y1, y2, y3), + opt_extent); +}; + + +/** + * Get the height of an extent. + * @param {ol.Extent} extent Extent. + * @return {number} Height. + * @api stable + */ +ol.extent.getHeight = function(extent) { + return extent[3] - extent[1]; +}; + + +/** + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @return {number} Intersection area. + */ +ol.extent.getIntersectionArea = function(extent1, extent2) { + var intersection = ol.extent.getIntersection(extent1, extent2); + return ol.extent.getArea(intersection); +}; + + +/** + * Get the intersection of two extents. + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @param {ol.Extent=} opt_extent Optional extent to populate with intersection. + * @return {ol.Extent} Intersecting extent. + * @api stable + */ +ol.extent.getIntersection = function(extent1, extent2, opt_extent) { + var intersection = opt_extent ? opt_extent : ol.extent.createEmpty(); + if (ol.extent.intersects(extent1, extent2)) { + if (extent1[0] > extent2[0]) { + intersection[0] = extent1[0]; + } else { + intersection[0] = extent2[0]; + } + if (extent1[1] > extent2[1]) { + intersection[1] = extent1[1]; + } else { + intersection[1] = extent2[1]; + } + if (extent1[2] < extent2[2]) { + intersection[2] = extent1[2]; + } else { + intersection[2] = extent2[2]; + } + if (extent1[3] < extent2[3]) { + intersection[3] = extent1[3]; + } else { + intersection[3] = extent2[3]; + } + } + return intersection; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @return {number} Margin. + */ +ol.extent.getMargin = function(extent) { + return ol.extent.getWidth(extent) + ol.extent.getHeight(extent); +}; + + +/** + * Get the size (width, height) of an extent. + * @param {ol.Extent} extent The extent. + * @return {ol.Size} The extent size. + * @api stable + */ +ol.extent.getSize = function(extent) { + return [extent[2] - extent[0], extent[3] - extent[1]]; +}; + + +/** + * Get the top left coordinate of an extent. + * @param {ol.Extent} extent Extent. + * @return {ol.Coordinate} Top left coordinate. + * @api stable + */ +ol.extent.getTopLeft = function(extent) { + return [extent[0], extent[3]]; +}; + + +/** + * Get the top right coordinate of an extent. + * @param {ol.Extent} extent Extent. + * @return {ol.Coordinate} Top right coordinate. + * @api stable + */ +ol.extent.getTopRight = function(extent) { + return [extent[2], extent[3]]; +}; + + +/** + * Get the width of an extent. + * @param {ol.Extent} extent Extent. + * @return {number} Width. + * @api stable + */ +ol.extent.getWidth = function(extent) { + return extent[2] - extent[0]; +}; + + +/** + * Determine if one extent intersects another. + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent. + * @return {boolean} The two extents intersect. + * @api stable + */ +ol.extent.intersects = function(extent1, extent2) { + return extent1[0] <= extent2[2] && + extent1[2] >= extent2[0] && + extent1[1] <= extent2[3] && + extent1[3] >= extent2[1]; +}; + + +/** + * Determine if an extent is empty. + * @param {ol.Extent} extent Extent. + * @return {boolean} Is empty. + * @api stable + */ +ol.extent.isEmpty = function(extent) { + return extent[2] < extent[0] || extent[3] < extent[1]; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.returnOrUpdate = function(extent, opt_extent) { + if (opt_extent) { + opt_extent[0] = extent[0]; + opt_extent[1] = extent[1]; + opt_extent[2] = extent[2]; + opt_extent[3] = extent[3]; + return opt_extent; + } else { + return extent; + } +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} value Value. + */ +ol.extent.scaleFromCenter = function(extent, value) { + var deltaX = ((extent[2] - extent[0]) / 2) * (value - 1); + var deltaY = ((extent[3] - extent[1]) / 2) * (value - 1); + extent[0] -= deltaX; + extent[2] += deltaX; + extent[1] -= deltaY; + extent[3] += deltaY; +}; + + +/** + * Determine if the segment between two coordinates intersects (crosses, + * touches, or is contained by) the provided extent. + * @param {ol.Extent} extent The extent. + * @param {ol.Coordinate} start Segment start coordinate. + * @param {ol.Coordinate} end Segment end coordinate. + * @return {boolean} The segment intersects the extent. + */ +ol.extent.intersectsSegment = function(extent, start, end) { + var intersects = false; + var startRel = ol.extent.coordinateRelationship(extent, start); + var endRel = ol.extent.coordinateRelationship(extent, end); + if (startRel === ol.extent.Relationship.INTERSECTING || + endRel === ol.extent.Relationship.INTERSECTING) { + intersects = true; + } else { + var minX = extent[0]; + var minY = extent[1]; + var maxX = extent[2]; + var maxY = extent[3]; + var startX = start[0]; + var startY = start[1]; + var endX = end[0]; + var endY = end[1]; + var slope = (endY - startY) / (endX - startX); + var x, y; + if (!!(endRel & ol.extent.Relationship.ABOVE) && + !(startRel & ol.extent.Relationship.ABOVE)) { + // potentially intersects top + x = endX - ((endY - maxY) / slope); + intersects = x >= minX && x <= maxX; + } + if (!intersects && !!(endRel & ol.extent.Relationship.RIGHT) && + !(startRel & ol.extent.Relationship.RIGHT)) { + // potentially intersects right + y = endY - ((endX - maxX) * slope); + intersects = y >= minY && y <= maxY; + } + if (!intersects && !!(endRel & ol.extent.Relationship.BELOW) && + !(startRel & ol.extent.Relationship.BELOW)) { + // potentially intersects bottom + x = endX - ((endY - minY) / slope); + intersects = x >= minX && x <= maxX; + } + if (!intersects && !!(endRel & ol.extent.Relationship.LEFT) && + !(startRel & ol.extent.Relationship.LEFT)) { + // potentially intersects left + y = endY - ((endX - minX) * slope); + intersects = y >= minY && y <= maxY; + } + + } + return intersects; +}; + + +/** + * Apply a transform function to the extent. + * @param {ol.Extent} extent Extent. + * @param {ol.TransformFunction} transformFn Transform function. Called with + * [minX, minY, maxX, maxY] extent coordinates. + * @param {ol.Extent=} opt_extent Destination extent. + * @return {ol.Extent} Extent. + * @api stable + */ +ol.extent.applyTransform = function(extent, transformFn, opt_extent) { + var coordinates = [ + extent[0], extent[1], + extent[0], extent[3], + extent[2], extent[1], + extent[2], extent[3] + ]; + transformFn(coordinates, coordinates, 2); + var xs = [coordinates[0], coordinates[2], coordinates[4], coordinates[6]]; + var ys = [coordinates[1], coordinates[3], coordinates[5], coordinates[7]]; + return ol.extent.boundingExtentXYs_(xs, ys, opt_extent); +}; + +goog.provide('ol.geom.GeometryLayout'); + + +/** + * The coordinate layout for geometries, indicating whether a 3rd or 4th z ('Z') + * or measure ('M') coordinate is available. Supported values are `'XY'`, + * `'XYZ'`, `'XYM'`, `'XYZM'`. + * @enum {string} + */ +ol.geom.GeometryLayout = { + XY: 'XY', + XYZ: 'XYZ', + XYM: 'XYM', + XYZM: 'XYZM' +}; + +goog.provide('ol.geom.GeometryType'); + + +/** + * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`, + * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`, + * `'GeometryCollection'`, `'Circle'`. + * @enum {string} + */ +ol.geom.GeometryType = { + POINT: 'Point', + LINE_STRING: 'LineString', + LINEAR_RING: 'LinearRing', + POLYGON: 'Polygon', + MULTI_POINT: 'MultiPoint', + MULTI_LINE_STRING: 'MultiLineString', + MULTI_POLYGON: 'MultiPolygon', + GEOMETRY_COLLECTION: 'GeometryCollection', + CIRCLE: 'Circle' +}; + +goog.provide('ol.functions'); + +/** + * Always returns true. + * @returns {boolean} true. + */ +ol.functions.TRUE = function() { + return true; +}; + +/** + * Always returns false. + * @returns {boolean} false. + */ +ol.functions.FALSE = function() { + return false; +}; + +/** + * @license + * Latitude/longitude spherical geodesy formulae taken from + * http://www.movable-type.co.uk/scripts/latlong.html + * Licensed under CC-BY-3.0. + */ + +goog.provide('ol.Sphere'); + +goog.require('ol.math'); + + +/** + * @classdesc + * Class to create objects that can be used with {@link + * ol.geom.Polygon.circular}. + * + * For example to create a sphere whose radius is equal to the semi-major + * axis of the WGS84 ellipsoid: + * + * ```js + * var wgs84Sphere= new ol.Sphere(6378137); + * ``` + * + * @constructor + * @param {number} radius Radius. + * @api + */ +ol.Sphere = function(radius) { + + /** + * @type {number} + */ + this.radius = radius; + +}; + + +/** + * Returns the geodesic area for a list of coordinates. + * + * [Reference](http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409) + * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for + * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion + * Laboratory, Pasadena, CA, June 2007 + * + * @param {Array.<ol.Coordinate>} coordinates List of coordinates of a linear + * ring. If the ring is oriented clockwise, the area will be positive, + * otherwise it will be negative. + * @return {number} Area. + * @api + */ +ol.Sphere.prototype.geodesicArea = function(coordinates) { + var area = 0, len = coordinates.length; + var x1 = coordinates[len - 1][0]; + var y1 = coordinates[len - 1][1]; + for (var i = 0; i < len; i++) { + var x2 = coordinates[i][0], y2 = coordinates[i][1]; + area += ol.math.toRadians(x2 - x1) * + (2 + Math.sin(ol.math.toRadians(y1)) + + Math.sin(ol.math.toRadians(y2))); + x1 = x2; + y1 = y2; + } + return area * this.radius * this.radius / 2.0; +}; + + +/** + * Returns the distance from c1 to c2 using the haversine formula. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {number} Haversine distance. + * @api + */ +ol.Sphere.prototype.haversineDistance = function(c1, c2) { + var lat1 = ol.math.toRadians(c1[1]); + var lat2 = ol.math.toRadians(c2[1]); + var deltaLatBy2 = (lat2 - lat1) / 2; + var deltaLonBy2 = ol.math.toRadians(c2[0] - c1[0]) / 2; + var a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) + + Math.sin(deltaLonBy2) * Math.sin(deltaLonBy2) * + Math.cos(lat1) * Math.cos(lat2); + return 2 * this.radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); +}; + + +/** + * Returns the coordinate at the given distance and bearing from `c1`. + * + * @param {ol.Coordinate} c1 The origin point (`[lon, lat]` in degrees). + * @param {number} distance The great-circle distance between the origin + * point and the target point. + * @param {number} bearing The bearing (in radians). + * @return {ol.Coordinate} The target point. + */ +ol.Sphere.prototype.offset = function(c1, distance, bearing) { + var lat1 = ol.math.toRadians(c1[1]); + var lon1 = ol.math.toRadians(c1[0]); + var dByR = distance / this.radius; + var lat = Math.asin( + Math.sin(lat1) * Math.cos(dByR) + + Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing)); + var lon = lon1 + Math.atan2( + Math.sin(bearing) * Math.sin(dByR) * Math.cos(lat1), + Math.cos(dByR) - Math.sin(lat1) * Math.sin(lat)); + return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)]; +}; + +goog.provide('ol.sphere.NORMAL'); + +goog.require('ol.Sphere'); + + +/** + * The normal sphere. + * @const + * @type {ol.Sphere} + */ +ol.sphere.NORMAL = new ol.Sphere(6370997); + +goog.provide('ol.proj.Units'); + +goog.require('ol.sphere.NORMAL'); + + +/** + * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, `'tile-pixels'` or + * `'us-ft'`. + * @enum {string} + */ +ol.proj.Units = { + DEGREES: 'degrees', + FEET: 'ft', + METERS: 'm', + PIXELS: 'pixels', + TILE_PIXELS: 'tile-pixels', + USFEET: 'us-ft' +}; + + +/** + * Meters per unit lookup table. + * @const + * @type {Object.<ol.proj.Units, number>} + * @api stable + */ +ol.proj.Units.METERS_PER_UNIT = {}; +ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.DEGREES] = + 2 * Math.PI * ol.sphere.NORMAL.radius / 360; +ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.FEET] = 0.3048; +ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.METERS] = 1; +ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.USFEET] = 1200 / 3937; + +goog.provide('ol.proj.proj4'); + + +/** + * @private + * @type {proj4} + */ +ol.proj.proj4.cache_ = null; + + +/** + * Store the proj4 function. + * @param {proj4} proj4 The proj4 function. + */ +ol.proj.proj4.set = function(proj4) { + ol.proj.proj4.cache_ = proj4; +}; + + +/** + * Get proj4. + * @return {proj4} The proj4 function set above or available globally. + */ +ol.proj.proj4.get = function() { + return ol.proj.proj4.cache_ || window['proj4']; +}; + +goog.provide('ol.proj.Projection'); + +goog.require('ol'); +goog.require('ol.proj.Units'); +goog.require('ol.proj.proj4'); + + +/** + * @classdesc + * Projection definition class. One of these is created for each projection + * supported in the application and stored in the {@link ol.proj} namespace. + * You can use these in applications, but this is not required, as API params + * and options use {@link ol.ProjectionLike} which means the simple string + * code will suffice. + * + * You can use {@link ol.proj.get} to retrieve the object for a particular + * projection. + * + * The library includes definitions for `EPSG:4326` and `EPSG:3857`, together + * with the following aliases: + * * `EPSG:4326`: CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, + * urn:ogc:def:crs:OGC:1.3:CRS84, urn:ogc:def:crs:OGC:2:84, + * http://www.opengis.net/gml/srs/epsg.xml#4326, + * urn:x-ogc:def:crs:EPSG:4326 + * * `EPSG:3857`: EPSG:102100, EPSG:102113, EPSG:900913, + * urn:ogc:def:crs:EPSG:6.18:3:3857, + * http://www.opengis.net/gml/srs/epsg.xml#3857 + * + * If you use proj4js, aliases can be added using `proj4.defs()`; see + * [documentation](https://github.com/proj4js/proj4js). To set an alternative + * namespace for proj4, use {@link ol.proj.setProj4}. + * + * @constructor + * @param {olx.ProjectionOptions} options Projection options. + * @struct + * @api stable + */ +ol.proj.Projection = function(options) { + + /** + * @private + * @type {string} + */ + this.code_ = options.code; + + /** + * @private + * @type {ol.proj.Units} + */ + this.units_ = /** @type {ol.proj.Units} */ (options.units); + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = options.extent !== undefined ? options.extent : null; + + /** + * @private + * @type {ol.Extent} + */ + this.worldExtent_ = options.worldExtent !== undefined ? + options.worldExtent : null; + + /** + * @private + * @type {string} + */ + this.axisOrientation_ = options.axisOrientation !== undefined ? + options.axisOrientation : 'enu'; + + /** + * @private + * @type {boolean} + */ + this.global_ = options.global !== undefined ? options.global : false; + + /** + * @private + * @type {boolean} + */ + this.canWrapX_ = !!(this.global_ && this.extent_); + + /** + * @private + * @type {function(number, ol.Coordinate):number|undefined} + */ + this.getPointResolutionFunc_ = options.getPointResolution; + + /** + * @private + * @type {ol.tilegrid.TileGrid} + */ + this.defaultTileGrid_ = null; + + /** + * @private + * @type {number|undefined} + */ + this.metersPerUnit_ = options.metersPerUnit; + + var code = options.code; + ol.DEBUG && console.assert(code !== undefined, + 'Option "code" is required for constructing instance'); + if (ol.ENABLE_PROJ4JS) { + var proj4js = ol.proj.proj4.get(); + if (typeof proj4js == 'function') { + var def = proj4js.defs(code); + if (def !== undefined) { + if (def.axis !== undefined && options.axisOrientation === undefined) { + this.axisOrientation_ = def.axis; + } + if (options.metersPerUnit === undefined) { + this.metersPerUnit_ = def.to_meter; + } + if (options.units === undefined) { + this.units_ = def.units; + } + } + } + } + +}; + + +/** + * @return {boolean} The projection is suitable for wrapping the x-axis + */ +ol.proj.Projection.prototype.canWrapX = function() { + return this.canWrapX_; +}; + + +/** + * Get the code for this projection, e.g. 'EPSG:4326'. + * @return {string} Code. + * @api stable + */ +ol.proj.Projection.prototype.getCode = function() { + return this.code_; +}; + + +/** + * Get the validity extent for this projection. + * @return {ol.Extent} Extent. + * @api stable + */ +ol.proj.Projection.prototype.getExtent = function() { + return this.extent_; +}; + + +/** + * Get the units of this projection. + * @return {ol.proj.Units} Units. + * @api stable + */ +ol.proj.Projection.prototype.getUnits = function() { + return this.units_; +}; + + +/** + * Get the amount of meters per unit of this projection. If the projection is + * not configured with `metersPerUnit` or a units identifier, the return is + * `undefined`. + * @return {number|undefined} Meters. + * @api stable + */ +ol.proj.Projection.prototype.getMetersPerUnit = function() { + return this.metersPerUnit_ || ol.proj.Units.METERS_PER_UNIT[this.units_]; +}; + + +/** + * Get the world extent for this projection. + * @return {ol.Extent} Extent. + * @api + */ +ol.proj.Projection.prototype.getWorldExtent = function() { + return this.worldExtent_; +}; + + +/** + * Get the axis orientation of this projection. + * Example values are: + * enu - the default easting, northing, elevation. + * neu - northing, easting, up - useful for "lat/long" geographic coordinates, + * or south orientated transverse mercator. + * wnu - westing, northing, up - some planetary coordinate systems have + * "west positive" coordinate systems + * @return {string} Axis orientation. + */ +ol.proj.Projection.prototype.getAxisOrientation = function() { + return this.axisOrientation_; +}; + + +/** + * Is this projection a global projection which spans the whole world? + * @return {boolean} Whether the projection is global. + * @api stable + */ +ol.proj.Projection.prototype.isGlobal = function() { + return this.global_; +}; + + +/** +* Set if the projection is a global projection which spans the whole world +* @param {boolean} global Whether the projection is global. +* @api stable +*/ +ol.proj.Projection.prototype.setGlobal = function(global) { + this.global_ = global; + this.canWrapX_ = !!(global && this.extent_); +}; + + +/** + * @return {ol.tilegrid.TileGrid} The default tile grid. + */ +ol.proj.Projection.prototype.getDefaultTileGrid = function() { + return this.defaultTileGrid_; +}; + + +/** + * @param {ol.tilegrid.TileGrid} tileGrid The default tile grid. + */ +ol.proj.Projection.prototype.setDefaultTileGrid = function(tileGrid) { + this.defaultTileGrid_ = tileGrid; +}; + + +/** + * Set the validity extent for this projection. + * @param {ol.Extent} extent Extent. + * @api stable + */ +ol.proj.Projection.prototype.setExtent = function(extent) { + this.extent_ = extent; + this.canWrapX_ = !!(this.global_ && extent); +}; + + +/** + * Set the world extent for this projection. + * @param {ol.Extent} worldExtent World extent + * [minlon, minlat, maxlon, maxlat]. + * @api + */ +ol.proj.Projection.prototype.setWorldExtent = function(worldExtent) { + this.worldExtent_ = worldExtent; +}; + + +/** + * Set the getPointResolution function for this projection. + * @param {function(number, ol.Coordinate):number} func Function + * @api + */ +ol.proj.Projection.prototype.setGetPointResolution = function(func) { + this.getPointResolutionFunc_ = func; +}; + + +/** + * Get the custom point resolution function for this projection (if set). + * @return {function(number, ol.Coordinate):number|undefined} The custom point + * resolution function (if set). + */ +ol.proj.Projection.prototype.getPointResolutionFunc = function() { + return this.getPointResolutionFunc_; +}; + +goog.provide('ol.proj.projections'); + + +/** + * @private + * @type {Object.<string, ol.proj.Projection>} + */ +ol.proj.projections.cache_ = {}; + + +/** + * Clear the projections cache. + */ +ol.proj.projections.clear = function() { + ol.proj.projections.cache_ = {}; +}; + + +/** + * Get a cached projection by code. + * @param {string} code The code for the projection. + * @return {ol.proj.Projection} The projection (if cached). + */ +ol.proj.projections.get = function(code) { + var projections = ol.proj.projections.cache_; + return projections[code] || null; +}; + + +/** + * Add a projection to the cache. + * @param {string} code The projection code. + * @param {ol.proj.Projection} projection The projection to cache. + */ +ol.proj.projections.add = function(code, projection) { + var projections = ol.proj.projections.cache_; + projections[code] = projection; +}; + +goog.provide('ol.proj.transforms'); + +goog.require('ol'); +goog.require('ol.obj'); + + +/** + * @private + * @type {Object.<string, Object.<string, ol.TransformFunction>>} + */ +ol.proj.transforms.cache_ = {}; + + +/** + * Clear the transform cache. + */ +ol.proj.transforms.clear = function() { + ol.proj.transforms.cache_ = {}; +}; + + +/** + * Registers a conversion function to convert coordinates from the source + * projection to the destination projection. + * + * @param {ol.proj.Projection} source Source. + * @param {ol.proj.Projection} destination Destination. + * @param {ol.TransformFunction} transformFn Transform. + */ +ol.proj.transforms.add = function(source, destination, transformFn) { + var sourceCode = source.getCode(); + var destinationCode = destination.getCode(); + var transforms = ol.proj.transforms.cache_; + if (!(sourceCode in transforms)) { + transforms[sourceCode] = {}; + } + transforms[sourceCode][destinationCode] = transformFn; +}; + + +/** + * Unregisters the conversion function to convert coordinates from the source + * projection to the destination projection. This method is used to clean up + * cached transforms during testing. + * + * @param {ol.proj.Projection} source Source projection. + * @param {ol.proj.Projection} destination Destination projection. + * @return {ol.TransformFunction} transformFn The unregistered transform. + */ +ol.proj.transforms.remove = function(source, destination) { + var sourceCode = source.getCode(); + var destinationCode = destination.getCode(); + var transforms = ol.proj.transforms.cache_; + ol.DEBUG && console.assert(sourceCode in transforms, + 'sourceCode should be in transforms'); + ol.DEBUG && console.assert(destinationCode in transforms[sourceCode], + 'destinationCode should be in transforms of sourceCode'); + var transform = transforms[sourceCode][destinationCode]; + delete transforms[sourceCode][destinationCode]; + if (ol.obj.isEmpty(transforms[sourceCode])) { + delete transforms[sourceCode]; + } + return transform; +}; + + +/** + * Get a transform given a source code and a destination code. + * @param {string} sourceCode The code for the source projection. + * @param {string} destinationCode The code for the destination projection. + * @return {ol.TransformFunction|undefined} The transform function (if found). + */ +ol.proj.transforms.get = function(sourceCode, destinationCode) { + var transform; + var transforms = ol.proj.transforms.cache_; + if (sourceCode in transforms && destinationCode in transforms[sourceCode]) { + transform = transforms[sourceCode][destinationCode]; + } + return transform; +}; + +goog.provide('ol.proj'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); +goog.require('ol.proj.proj4'); +goog.require('ol.proj.projections'); +goog.require('ol.proj.transforms'); +goog.require('ol.sphere.NORMAL'); + + +/** + * Meters per unit lookup table. + * @const + * @type {Object.<ol.proj.Units, number>} + * @api stable + */ +ol.proj.METERS_PER_UNIT = ol.proj.Units.METERS_PER_UNIT; + + +if (ol.ENABLE_PROJ4JS) { + /** + * Register proj4. If not explicitly registered, it will be assumed that + * proj4js will be loaded in the global namespace. For example in a + * browserify ES6 environment you could use: + * + * import ol from 'openlayers'; + * import proj4 from 'proj4'; + * ol.proj.setProj4(proj4); + * + * @param {proj4} proj4 Proj4. + * @api + */ + ol.proj.setProj4 = function(proj4) { + ol.DEBUG && console.assert(typeof proj4 == 'function', + 'proj4 argument should be a function'); + ol.proj.proj4.set(proj4); + }; +} + + +/** + * Get the resolution of the point in degrees or distance units. + * For projections with degrees as the unit this will simply return the + * provided resolution. For other projections the point resolution is + * estimated by transforming the 'point' pixel to EPSG:4326, + * measuring its width and height on the normal sphere, + * and taking the average of the width and height. + * @param {ol.proj.Projection} projection The projection. + * @param {number} resolution Nominal resolution in projection units. + * @param {ol.Coordinate} point Point to find adjusted resolution at. + * @return {number} Point resolution at point in projection units. + * @api + */ +ol.proj.getPointResolution = function(projection, resolution, point) { + var pointResolution; + var getter = projection.getPointResolutionFunc(); + if (getter) { + pointResolution = getter(resolution, point); + } else { + var units = projection.getUnits(); + if (units == ol.proj.Units.DEGREES) { + pointResolution = resolution; + } else { + // Estimate point resolution by transforming the center pixel to EPSG:4326, + // measuring its width and height on the normal sphere, and taking the + // average of the width and height. + var toEPSG4326 = ol.proj.getTransformFromProjections(projection, ol.proj.get('EPSG:4326')); + var vertices = [ + point[0] - resolution / 2, point[1], + point[0] + resolution / 2, point[1], + point[0], point[1] - resolution / 2, + point[0], point[1] + resolution / 2 + ]; + vertices = toEPSG4326(vertices, vertices, 2); + var width = ol.sphere.NORMAL.haversineDistance( + vertices.slice(0, 2), vertices.slice(2, 4)); + var height = ol.sphere.NORMAL.haversineDistance( + vertices.slice(4, 6), vertices.slice(6, 8)); + pointResolution = (width + height) / 2; + var metersPerUnit = projection.getMetersPerUnit(); + if (metersPerUnit !== undefined) { + pointResolution /= metersPerUnit; + } + } + } + return pointResolution; +}; + + +/** + * Registers transformation functions that don't alter coordinates. Those allow + * to transform between projections with equal meaning. + * + * @param {Array.<ol.proj.Projection>} projections Projections. + * @api + */ +ol.proj.addEquivalentProjections = function(projections) { + ol.proj.addProjections(projections); + projections.forEach(function(source) { + projections.forEach(function(destination) { + if (source !== destination) { + ol.proj.transforms.add(source, destination, ol.proj.cloneTransform); + } + }); + }); +}; + + +/** + * Registers transformation functions to convert coordinates in any projection + * in projection1 to any projection in projection2. + * + * @param {Array.<ol.proj.Projection>} projections1 Projections with equal + * meaning. + * @param {Array.<ol.proj.Projection>} projections2 Projections with equal + * meaning. + * @param {ol.TransformFunction} forwardTransform Transformation from any + * projection in projection1 to any projection in projection2. + * @param {ol.TransformFunction} inverseTransform Transform from any projection + * in projection2 to any projection in projection1.. + */ +ol.proj.addEquivalentTransforms = function(projections1, projections2, forwardTransform, inverseTransform) { + projections1.forEach(function(projection1) { + projections2.forEach(function(projection2) { + ol.proj.transforms.add(projection1, projection2, forwardTransform); + ol.proj.transforms.add(projection2, projection1, inverseTransform); + }); + }); +}; + + +/** + * Add a Projection object to the list of supported projections that can be + * looked up by their code. + * + * @param {ol.proj.Projection} projection Projection instance. + * @api stable + */ +ol.proj.addProjection = function(projection) { + ol.proj.projections.add(projection.getCode(), projection); + ol.proj.transforms.add(projection, projection, ol.proj.cloneTransform); +}; + + +/** + * @param {Array.<ol.proj.Projection>} projections Projections. + */ +ol.proj.addProjections = function(projections) { + var addedProjections = []; + projections.forEach(function(projection) { + addedProjections.push(ol.proj.addProjection(projection)); + }); +}; + + +/** + * Clear all cached projections and transforms. + */ +ol.proj.clearAllProjections = function() { + ol.proj.projections.clear(); + ol.proj.transforms.clear(); +}; + + +/** + * @param {ol.proj.Projection|string|undefined} projection Projection. + * @param {string} defaultCode Default code. + * @return {ol.proj.Projection} Projection. + */ +ol.proj.createProjection = function(projection, defaultCode) { + if (!projection) { + return ol.proj.get(defaultCode); + } else if (typeof projection === 'string') { + return ol.proj.get(projection); + } else { + return /** @type {ol.proj.Projection} */ (projection); + } +}; + + +/** + * Registers coordinate transform functions to convert coordinates between the + * source projection and the destination projection. + * The forward and inverse functions convert coordinate pairs; this function + * converts these into the functions used internally which also handle + * extents and coordinate arrays. + * + * @param {ol.ProjectionLike} source Source projection. + * @param {ol.ProjectionLike} destination Destination projection. + * @param {function(ol.Coordinate): ol.Coordinate} forward The forward transform + * function (that is, from the source projection to the destination + * projection) that takes a {@link ol.Coordinate} as argument and returns + * the transformed {@link ol.Coordinate}. + * @param {function(ol.Coordinate): ol.Coordinate} inverse The inverse transform + * function (that is, from the destination projection to the source + * projection) that takes a {@link ol.Coordinate} as argument and returns + * the transformed {@link ol.Coordinate}. + * @api stable + */ +ol.proj.addCoordinateTransforms = function(source, destination, forward, inverse) { + var sourceProj = ol.proj.get(source); + var destProj = ol.proj.get(destination); + ol.proj.transforms.add(sourceProj, destProj, + ol.proj.createTransformFromCoordinateTransform(forward)); + ol.proj.transforms.add(destProj, sourceProj, + ol.proj.createTransformFromCoordinateTransform(inverse)); +}; + + +/** + * Creates a {@link ol.TransformFunction} from a simple 2D coordinate transform + * function. + * @param {function(ol.Coordinate): ol.Coordinate} transform Coordinate + * transform. + * @return {ol.TransformFunction} Transform function. + */ +ol.proj.createTransformFromCoordinateTransform = function(transform) { + return ( + /** + * @param {Array.<number>} input Input. + * @param {Array.<number>=} opt_output Output. + * @param {number=} opt_dimension Dimension. + * @return {Array.<number>} Output. + */ + function(input, opt_output, opt_dimension) { + var length = input.length; + var dimension = opt_dimension !== undefined ? opt_dimension : 2; + var output = opt_output !== undefined ? opt_output : new Array(length); + var point, i, j; + for (i = 0; i < length; i += dimension) { + point = transform([input[i], input[i + 1]]); + output[i] = point[0]; + output[i + 1] = point[1]; + for (j = dimension - 1; j >= 2; --j) { + output[i + j] = input[i + j]; + } + } + return output; + }); +}; + + +/** + * Transforms a coordinate from longitude/latitude to a different projection. + * @param {ol.Coordinate} coordinate Coordinate as longitude and latitude, i.e. + * an array with longitude as 1st and latitude as 2nd element. + * @param {ol.ProjectionLike=} opt_projection Target projection. The + * default is Web Mercator, i.e. 'EPSG:3857'. + * @return {ol.Coordinate} Coordinate projected to the target projection. + * @api stable + */ +ol.proj.fromLonLat = function(coordinate, opt_projection) { + return ol.proj.transform(coordinate, 'EPSG:4326', + opt_projection !== undefined ? opt_projection : 'EPSG:3857'); +}; + + +/** + * Transforms a coordinate to longitude/latitude. + * @param {ol.Coordinate} coordinate Projected coordinate. + * @param {ol.ProjectionLike=} opt_projection Projection of the coordinate. + * The default is Web Mercator, i.e. 'EPSG:3857'. + * @return {ol.Coordinate} Coordinate as longitude and latitude, i.e. an array + * with longitude as 1st and latitude as 2nd element. + * @api stable + */ +ol.proj.toLonLat = function(coordinate, opt_projection) { + return ol.proj.transform(coordinate, + opt_projection !== undefined ? opt_projection : 'EPSG:3857', 'EPSG:4326'); +}; + + +/** + * Fetches a Projection object for the code specified. + * + * @param {ol.ProjectionLike} projectionLike Either a code string which is + * a combination of authority and identifier such as "EPSG:4326", or an + * existing projection object, or undefined. + * @return {ol.proj.Projection} Projection object, or null if not in list. + * @api stable + */ +ol.proj.get = function(projectionLike) { + var projection = null; + if (projectionLike instanceof ol.proj.Projection) { + projection = projectionLike; + } else if (typeof projectionLike === 'string') { + var code = projectionLike; + projection = ol.proj.projections.get(code); + if (ol.ENABLE_PROJ4JS) { + var proj4js = ol.proj.proj4.get(); + if (!projection && typeof proj4js == 'function' && + proj4js.defs(code) !== undefined) { + projection = new ol.proj.Projection({code: code}); + ol.proj.addProjection(projection); + } + } + } + return projection; +}; + + +/** + * Checks if two projections are the same, that is every coordinate in one + * projection does represent the same geographic point as the same coordinate in + * the other projection. + * + * @param {ol.proj.Projection} projection1 Projection 1. + * @param {ol.proj.Projection} projection2 Projection 2. + * @return {boolean} Equivalent. + * @api + */ +ol.proj.equivalent = function(projection1, projection2) { + if (projection1 === projection2) { + return true; + } + var equalUnits = projection1.getUnits() === projection2.getUnits(); + if (projection1.getCode() === projection2.getCode()) { + return equalUnits; + } else { + var transformFn = ol.proj.getTransformFromProjections( + projection1, projection2); + return transformFn === ol.proj.cloneTransform && equalUnits; + } +}; + + +/** + * Given the projection-like objects, searches for a transformation + * function to convert a coordinates array from the source projection to the + * destination projection. + * + * @param {ol.ProjectionLike} source Source. + * @param {ol.ProjectionLike} destination Destination. + * @return {ol.TransformFunction} Transform function. + * @api stable + */ +ol.proj.getTransform = function(source, destination) { + var sourceProjection = ol.proj.get(source); + var destinationProjection = ol.proj.get(destination); + return ol.proj.getTransformFromProjections( + sourceProjection, destinationProjection); +}; + + +/** + * Searches in the list of transform functions for the function for converting + * coordinates from the source projection to the destination projection. + * + * @param {ol.proj.Projection} sourceProjection Source Projection object. + * @param {ol.proj.Projection} destinationProjection Destination Projection + * object. + * @return {ol.TransformFunction} Transform function. + */ +ol.proj.getTransformFromProjections = function(sourceProjection, destinationProjection) { + var sourceCode = sourceProjection.getCode(); + var destinationCode = destinationProjection.getCode(); + var transform = ol.proj.transforms.get(sourceCode, destinationCode); + if (ol.ENABLE_PROJ4JS && !transform) { + var proj4js = ol.proj.proj4.get(); + if (typeof proj4js == 'function') { + var sourceDef = proj4js.defs(sourceCode); + var destinationDef = proj4js.defs(destinationCode); + + if (sourceDef !== undefined && destinationDef !== undefined) { + if (sourceDef === destinationDef) { + ol.proj.addEquivalentProjections([destinationProjection, sourceProjection]); + } else { + var proj4Transform = proj4js(destinationCode, sourceCode); + ol.proj.addCoordinateTransforms(destinationProjection, sourceProjection, + proj4Transform.forward, proj4Transform.inverse); + } + transform = ol.proj.transforms.get(sourceCode, destinationCode); + } + } + } + if (!transform) { + ol.DEBUG && console.assert(transform, 'transform should be defined'); + transform = ol.proj.identityTransform; + } + return transform; +}; + + +/** + * @param {Array.<number>} input Input coordinate array. + * @param {Array.<number>=} opt_output Output array of coordinate values. + * @param {number=} opt_dimension Dimension. + * @return {Array.<number>} Input coordinate array (same array as input). + */ +ol.proj.identityTransform = function(input, opt_output, opt_dimension) { + if (opt_output !== undefined && input !== opt_output) { + // TODO: consider making this a warning instead + ol.DEBUG && console.assert(false, 'This should not be used internally.'); + for (var i = 0, ii = input.length; i < ii; ++i) { + opt_output[i] = input[i]; + } + input = opt_output; + } + return input; +}; + + +/** + * @param {Array.<number>} input Input coordinate array. + * @param {Array.<number>=} opt_output Output array of coordinate values. + * @param {number=} opt_dimension Dimension. + * @return {Array.<number>} Output coordinate array (new array, same coordinate + * values). + */ +ol.proj.cloneTransform = function(input, opt_output, opt_dimension) { + var output; + if (opt_output !== undefined) { + for (var i = 0, ii = input.length; i < ii; ++i) { + opt_output[i] = input[i]; + } + output = opt_output; + } else { + output = input.slice(); + } + return output; +}; + + +/** + * Transforms a coordinate from source projection to destination projection. + * This returns a new coordinate (and does not modify the original). + * + * See {@link ol.proj.transformExtent} for extent transformation. + * See the transform method of {@link ol.geom.Geometry} and its subclasses for + * geometry transforms. + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.ProjectionLike} source Source projection-like. + * @param {ol.ProjectionLike} destination Destination projection-like. + * @return {ol.Coordinate} Coordinate. + * @api stable + */ +ol.proj.transform = function(coordinate, source, destination) { + var transformFn = ol.proj.getTransform(source, destination); + return transformFn(coordinate, undefined, coordinate.length); +}; + + +/** + * Transforms an extent from source projection to destination projection. This + * returns a new extent (and does not modify the original). + * + * @param {ol.Extent} extent The extent to transform. + * @param {ol.ProjectionLike} source Source projection-like. + * @param {ol.ProjectionLike} destination Destination projection-like. + * @return {ol.Extent} The transformed extent. + * @api stable + */ +ol.proj.transformExtent = function(extent, source, destination) { + var transformFn = ol.proj.getTransform(source, destination); + return ol.extent.applyTransform(extent, transformFn); +}; + + +/** + * Transforms the given point to the destination projection. + * + * @param {ol.Coordinate} point Point. + * @param {ol.proj.Projection} sourceProjection Source projection. + * @param {ol.proj.Projection} destinationProjection Destination projection. + * @return {ol.Coordinate} Point. + */ +ol.proj.transformWithProjections = function(point, sourceProjection, destinationProjection) { + var transformFn = ol.proj.getTransformFromProjections( + sourceProjection, destinationProjection); + return transformFn(point); +}; + +goog.provide('ol.geom.Geometry'); + +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.extent'); +goog.require('ol.functions'); +goog.require('ol.proj'); +goog.require('ol.proj.Units'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for vector geometries. + * + * To get notified of changes to the geometry, register a listener for the + * generic `change` event on your geometry instance. + * + * @constructor + * @extends {ol.Object} + * @api stable + */ +ol.geom.Geometry = function() { + + ol.Object.call(this); + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = ol.extent.createEmpty(); + + /** + * @private + * @type {number} + */ + this.extentRevision_ = -1; + + /** + * @protected + * @type {Object.<string, ol.geom.Geometry>} + */ + this.simplifiedGeometryCache = {}; + + /** + * @protected + * @type {number} + */ + this.simplifiedGeometryMaxMinSquaredTolerance = 0; + + /** + * @protected + * @type {number} + */ + this.simplifiedGeometryRevision = 0; + +}; +ol.inherits(ol.geom.Geometry, ol.Object); + + +/** + * Make a complete copy of the geometry. + * @abstract + * @return {!ol.geom.Geometry} Clone. + */ +ol.geom.Geometry.prototype.clone = function() {}; + + +/** + * @abstract + * @param {number} x X. + * @param {number} y Y. + * @param {ol.Coordinate} closestPoint Closest point. + * @param {number} minSquaredDistance Minimum squared distance. + * @return {number} Minimum squared distance. + */ +ol.geom.Geometry.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {}; + + +/** + * Return the closest point of the geometry to the passed point as + * {@link ol.Coordinate coordinate}. + * @param {ol.Coordinate} point Point. + * @param {ol.Coordinate=} opt_closestPoint Closest point. + * @return {ol.Coordinate} Closest point. + * @api stable + */ +ol.geom.Geometry.prototype.getClosestPoint = function(point, opt_closestPoint) { + var closestPoint = opt_closestPoint ? opt_closestPoint : [NaN, NaN]; + this.closestPointXY(point[0], point[1], closestPoint, Infinity); + return closestPoint; +}; + + +/** + * Returns true if this geometry includes the specified coordinate. If the + * coordinate is on the boundary of the geometry, returns false. + * @param {ol.Coordinate} coordinate Coordinate. + * @return {boolean} Contains coordinate. + * @api + */ +ol.geom.Geometry.prototype.intersectsCoordinate = function(coordinate) { + return this.containsXY(coordinate[0], coordinate[1]); +}; + + +/** + * @abstract + * @param {ol.Extent} extent Extent. + * @protected + * @return {ol.Extent} extent Extent. + */ +ol.geom.Geometry.prototype.computeExtent = function(extent) {}; + + +/** + * @param {number} x X. + * @param {number} y Y. + * @return {boolean} Contains (x, y). + */ +ol.geom.Geometry.prototype.containsXY = ol.functions.FALSE; + + +/** + * Get the extent of the geometry. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} extent Extent. + * @api stable + */ +ol.geom.Geometry.prototype.getExtent = function(opt_extent) { + if (this.extentRevision_ != this.getRevision()) { + this.extent_ = this.computeExtent(this.extent_); + this.extentRevision_ = this.getRevision(); + } + return ol.extent.returnOrUpdate(this.extent_, opt_extent); +}; + + +/** + * Rotate the geometry around a given coordinate. This modifies the geometry + * coordinates in place. + * @abstract + * @param {number} angle Rotation angle in radians. + * @param {ol.Coordinate} anchor The rotation center. + * @api + */ +ol.geom.Geometry.prototype.rotate = function(angle, anchor) {}; + + +/** + * Scale the geometry (with an optional origin). This modifies the geometry + * coordinates in place. + * @abstract + * @param {number} sx The scaling factor in the x-direction. + * @param {number=} opt_sy The scaling factor in the y-direction (defaults to + * sx). + * @param {ol.Coordinate=} opt_anchor The scale origin (defaults to the center + * of the geometry extent). + * @api + */ +ol.geom.Geometry.prototype.scale = function(sx, opt_sy, opt_anchor) {}; + + +/** + * Create a simplified version of this geometry. For linestrings, this uses + * the the {@link + * https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm + * Douglas Peucker} algorithm. For polygons, a quantization-based + * simplification is used to preserve topology. + * @function + * @param {number} tolerance The tolerance distance for simplification. + * @return {ol.geom.Geometry} A new, simplified version of the original + * geometry. + * @api + */ +ol.geom.Geometry.prototype.simplify = function(tolerance) { + return this.getSimplifiedGeometry(tolerance * tolerance); +}; + + +/** + * Create a simplified version of this geometry using the Douglas Peucker + * algorithm. + * @see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm + * @abstract + * @param {number} squaredTolerance Squared tolerance. + * @return {ol.geom.Geometry} Simplified geometry. + */ +ol.geom.Geometry.prototype.getSimplifiedGeometry = function(squaredTolerance) {}; + + +/** + * Get the type of this geometry. + * @abstract + * @return {ol.geom.GeometryType} Geometry type. + */ +ol.geom.Geometry.prototype.getType = function() {}; + + +/** + * Apply a transform function to each coordinate of the geometry. + * The geometry is modified in place. + * If you do not want the geometry modified in place, first `clone()` it and + * then use this function on the clone. + * @abstract + * @param {ol.TransformFunction} transformFn Transform. + */ +ol.geom.Geometry.prototype.applyTransform = function(transformFn) {}; + + +/** + * Test if the geometry and the passed extent intersect. + * @abstract + * @param {ol.Extent} extent Extent. + * @return {boolean} `true` if the geometry and the extent intersect. + */ +ol.geom.Geometry.prototype.intersectsExtent = function(extent) {}; + + +/** + * Translate the geometry. This modifies the geometry coordinates in place. If + * instead you want a new geometry, first `clone()` this geometry. + * @abstract + * @param {number} deltaX Delta X. + * @param {number} deltaY Delta Y. + */ +ol.geom.Geometry.prototype.translate = function(deltaX, deltaY) {}; + + +/** + * Transform each coordinate of the geometry from one coordinate reference + * system to another. The geometry is modified in place. + * For example, a line will be transformed to a line and a circle to a circle. + * If you do not want the geometry modified in place, first `clone()` it and + * then use this function on the clone. + * + * @param {ol.ProjectionLike} source The current projection. Can be a + * string identifier or a {@link ol.proj.Projection} object. + * @param {ol.ProjectionLike} destination The desired projection. Can be a + * string identifier or a {@link ol.proj.Projection} object. + * @return {ol.geom.Geometry} This geometry. Note that original geometry is + * modified in place. + * @api stable + */ +ol.geom.Geometry.prototype.transform = function(source, destination) { + ol.DEBUG && console.assert( + ol.proj.get(source).getUnits() !== ol.proj.Units.TILE_PIXELS && + ol.proj.get(destination).getUnits() !== ol.proj.Units.TILE_PIXELS, + 'cannot transform geometries with TILE_PIXELS units'); + this.applyTransform(ol.proj.getTransform(source, destination)); + return this; +}; + +goog.provide('ol.geom.flat.transform'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Transform} transform Transform. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed coordinates. + */ +ol.geom.flat.transform.transform2D = function(flatCoordinates, offset, end, stride, transform, opt_dest) { + var dest = opt_dest ? opt_dest : []; + var i = 0; + var j; + for (j = offset; j < end; j += stride) { + var x = flatCoordinates[j]; + var y = flatCoordinates[j + 1]; + dest[i++] = transform[0] * x + transform[2] * y + transform[4]; + dest[i++] = transform[1] * x + transform[3] * y + transform[5]; + } + if (opt_dest && dest.length != i) { + dest.length = i; + } + return dest; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} angle Angle. + * @param {Array.<number>} anchor Rotation anchor point. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed coordinates. + */ +ol.geom.flat.transform.rotate = function(flatCoordinates, offset, end, stride, angle, anchor, opt_dest) { + var dest = opt_dest ? opt_dest : []; + var cos = Math.cos(angle); + var sin = Math.sin(angle); + var anchorX = anchor[0]; + var anchorY = anchor[1]; + var i = 0; + for (var j = offset; j < end; j += stride) { + var deltaX = flatCoordinates[j] - anchorX; + var deltaY = flatCoordinates[j + 1] - anchorY; + dest[i++] = anchorX + deltaX * cos - deltaY * sin; + dest[i++] = anchorY + deltaX * sin + deltaY * cos; + for (var k = j + 2; k < j + stride; ++k) { + dest[i++] = flatCoordinates[k]; + } + } + if (opt_dest && dest.length != i) { + dest.length = i; + } + return dest; +}; + + +/** + * Scale the coordinates. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} sx Scale factor in the x-direction. + * @param {number} sy Scale factor in the y-direction. + * @param {Array.<number>} anchor Scale anchor point. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed coordinates. + */ +ol.geom.flat.transform.scale = function(flatCoordinates, offset, end, stride, sx, sy, anchor, opt_dest) { + var dest = opt_dest ? opt_dest : []; + var anchorX = anchor[0]; + var anchorY = anchor[1]; + var i = 0; + for (var j = offset; j < end; j += stride) { + var deltaX = flatCoordinates[j] - anchorX; + var deltaY = flatCoordinates[j + 1] - anchorY; + dest[i++] = anchorX + sx * deltaX; + dest[i++] = anchorY + sy * deltaY; + for (var k = j + 2; k < j + stride; ++k) { + dest[i++] = flatCoordinates[k]; + } + } + if (opt_dest && dest.length != i) { + dest.length = i; + } + return dest; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} deltaX Delta X. + * @param {number} deltaY Delta Y. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed coordinates. + */ +ol.geom.flat.transform.translate = function(flatCoordinates, offset, end, stride, deltaX, deltaY, opt_dest) { + var dest = opt_dest ? opt_dest : []; + var i = 0; + var j, k; + for (j = offset; j < end; j += stride) { + dest[i++] = flatCoordinates[j] + deltaX; + dest[i++] = flatCoordinates[j + 1] + deltaY; + for (k = j + 2; k < j + stride; ++k) { + dest[i++] = flatCoordinates[k]; + } + } + if (opt_dest && dest.length != i) { + dest.length = i; + } + return dest; +}; + +goog.provide('ol.geom.SimpleGeometry'); + +goog.require('ol'); +goog.require('ol.functions'); +goog.require('ol.extent'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.obj'); + + +/** + * @classdesc + * Abstract base class; only used for creating subclasses; do not instantiate + * in apps, as cannot be rendered. + * + * @constructor + * @extends {ol.geom.Geometry} + * @api stable + */ +ol.geom.SimpleGeometry = function() { + + ol.geom.Geometry.call(this); + + /** + * @protected + * @type {ol.geom.GeometryLayout} + */ + this.layout = ol.geom.GeometryLayout.XY; + + /** + * @protected + * @type {number} + */ + this.stride = 2; + + /** + * @protected + * @type {Array.<number>} + */ + this.flatCoordinates = null; + +}; +ol.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry); + + +/** + * @param {number} stride Stride. + * @private + * @return {ol.geom.GeometryLayout} layout Layout. + */ +ol.geom.SimpleGeometry.getLayoutForStride_ = function(stride) { + var layout; + if (stride == 2) { + layout = ol.geom.GeometryLayout.XY; + } else if (stride == 3) { + layout = ol.geom.GeometryLayout.XYZ; + } else if (stride == 4) { + layout = ol.geom.GeometryLayout.XYZM; + } + ol.DEBUG && console.assert(layout, 'unsupported stride: ' + stride); + return /** @type {ol.geom.GeometryLayout} */ (layout); +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @return {number} Stride. + */ +ol.geom.SimpleGeometry.getStrideForLayout = function(layout) { + var stride; + if (layout == ol.geom.GeometryLayout.XY) { + stride = 2; + } else if (layout == ol.geom.GeometryLayout.XYZ || layout == ol.geom.GeometryLayout.XYM) { + stride = 3; + } else if (layout == ol.geom.GeometryLayout.XYZM) { + stride = 4; + } + ol.DEBUG && console.assert(stride, 'unsupported layout: ' + layout); + return /** @type {number} */ (stride); +}; + + +/** + * @inheritDoc + */ +ol.geom.SimpleGeometry.prototype.containsXY = ol.functions.FALSE; + + +/** + * @inheritDoc + */ +ol.geom.SimpleGeometry.prototype.computeExtent = function(extent) { + return ol.extent.createOrUpdateFromFlatCoordinates( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + extent); +}; + + +/** + * @abstract + * @return {Array} Coordinates. + */ +ol.geom.SimpleGeometry.prototype.getCoordinates = function() {}; + + +/** + * Return the first coordinate of the geometry. + * @return {ol.Coordinate} First coordinate. + * @api stable + */ +ol.geom.SimpleGeometry.prototype.getFirstCoordinate = function() { + return this.flatCoordinates.slice(0, this.stride); +}; + + +/** + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.SimpleGeometry.prototype.getFlatCoordinates = function() { + return this.flatCoordinates; +}; + + +/** + * Return the last coordinate of the geometry. + * @return {ol.Coordinate} Last point. + * @api stable + */ +ol.geom.SimpleGeometry.prototype.getLastCoordinate = function() { + return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride); +}; + + +/** + * Return the {@link ol.geom.GeometryLayout layout} of the geometry. + * @return {ol.geom.GeometryLayout} Layout. + * @api stable + */ +ol.geom.SimpleGeometry.prototype.getLayout = function() { + return this.layout; +}; + + +/** + * @inheritDoc + */ +ol.geom.SimpleGeometry.prototype.getSimplifiedGeometry = function(squaredTolerance) { + if (this.simplifiedGeometryRevision != this.getRevision()) { + ol.obj.clear(this.simplifiedGeometryCache); + this.simplifiedGeometryMaxMinSquaredTolerance = 0; + this.simplifiedGeometryRevision = this.getRevision(); + } + // If squaredTolerance is negative or if we know that simplification will not + // have any effect then just return this. + if (squaredTolerance < 0 || + (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 && + squaredTolerance <= this.simplifiedGeometryMaxMinSquaredTolerance)) { + return this; + } + var key = squaredTolerance.toString(); + if (this.simplifiedGeometryCache.hasOwnProperty(key)) { + return this.simplifiedGeometryCache[key]; + } else { + var simplifiedGeometry = + this.getSimplifiedGeometryInternal(squaredTolerance); + var simplifiedFlatCoordinates = simplifiedGeometry.getFlatCoordinates(); + if (simplifiedFlatCoordinates.length < this.flatCoordinates.length) { + this.simplifiedGeometryCache[key] = simplifiedGeometry; + return simplifiedGeometry; + } else { + // Simplification did not actually remove any coordinates. We now know + // that any calls to getSimplifiedGeometry with a squaredTolerance less + // than or equal to the current squaredTolerance will also not have any + // effect. This allows us to short circuit simplification (saving CPU + // cycles) and prevents the cache of simplified geometries from filling + // up with useless identical copies of this geometry (saving memory). + this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance; + return this; + } + } +}; + + +/** + * @param {number} squaredTolerance Squared tolerance. + * @return {ol.geom.SimpleGeometry} Simplified geometry. + * @protected + */ +ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) { + return this; +}; + + +/** + * @return {number} Stride. + */ +ol.geom.SimpleGeometry.prototype.getStride = function() { + return this.stride; +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @protected + */ +ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal = function(layout, flatCoordinates) { + this.stride = ol.geom.SimpleGeometry.getStrideForLayout(layout); + this.layout = layout; + this.flatCoordinates = flatCoordinates; +}; + + +/** + * @abstract + * @param {Array} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + */ +ol.geom.SimpleGeometry.prototype.setCoordinates = function(coordinates, opt_layout) {}; + + +/** + * @param {ol.geom.GeometryLayout|undefined} layout Layout. + * @param {Array} coordinates Coordinates. + * @param {number} nesting Nesting. + * @protected + */ +ol.geom.SimpleGeometry.prototype.setLayout = function(layout, coordinates, nesting) { + /** @type {number} */ + var stride; + if (layout) { + stride = ol.geom.SimpleGeometry.getStrideForLayout(layout); + } else { + var i; + for (i = 0; i < nesting; ++i) { + if (coordinates.length === 0) { + this.layout = ol.geom.GeometryLayout.XY; + this.stride = 2; + return; + } else { + coordinates = /** @type {Array} */ (coordinates[0]); + } + } + stride = coordinates.length; + layout = ol.geom.SimpleGeometry.getLayoutForStride_(stride); + } + this.layout = layout; + this.stride = stride; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.SimpleGeometry.prototype.applyTransform = function(transformFn) { + if (this.flatCoordinates) { + transformFn(this.flatCoordinates, this.flatCoordinates, this.stride); + this.changed(); + } +}; + + +/** + * @inheritDoc + * @api + */ +ol.geom.SimpleGeometry.prototype.rotate = function(angle, anchor) { + var flatCoordinates = this.getFlatCoordinates(); + if (flatCoordinates) { + var stride = this.getStride(); + ol.geom.flat.transform.rotate( + flatCoordinates, 0, flatCoordinates.length, + stride, angle, anchor, flatCoordinates); + this.changed(); + } +}; + + +/** + * @inheritDoc + * @api + */ +ol.geom.SimpleGeometry.prototype.scale = function(sx, opt_sy, opt_anchor) { + var sy = opt_sy; + if (sy === undefined) { + sy = sx; + } + var anchor = opt_anchor; + if (!anchor) { + anchor = ol.extent.getCenter(this.getExtent()); + } + var flatCoordinates = this.getFlatCoordinates(); + if (flatCoordinates) { + var stride = this.getStride(); + ol.geom.flat.transform.scale( + flatCoordinates, 0, flatCoordinates.length, + stride, sx, sy, anchor, flatCoordinates); + this.changed(); + } +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.SimpleGeometry.prototype.translate = function(deltaX, deltaY) { + var flatCoordinates = this.getFlatCoordinates(); + if (flatCoordinates) { + var stride = this.getStride(); + ol.geom.flat.transform.translate( + flatCoordinates, 0, flatCoordinates.length, stride, + deltaX, deltaY, flatCoordinates); + this.changed(); + } +}; + + +/** + * @param {ol.geom.SimpleGeometry} simpleGeometry Simple geometry. + * @param {ol.Transform} transform Transform. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed flat coordinates. + */ +ol.geom.SimpleGeometry.transform2D = function(simpleGeometry, transform, opt_dest) { + var flatCoordinates = simpleGeometry.getFlatCoordinates(); + if (!flatCoordinates) { + return null; + } else { + var stride = simpleGeometry.getStride(); + return ol.geom.flat.transform.transform2D( + flatCoordinates, 0, flatCoordinates.length, stride, + transform, opt_dest); + } +}; + +goog.provide('ol.geom.flat.area'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} Area. + */ +ol.geom.flat.area.linearRing = function(flatCoordinates, offset, end, stride) { + var twiceArea = 0; + var x1 = flatCoordinates[end - stride]; + var y1 = flatCoordinates[end - stride + 1]; + for (; offset < end; offset += stride) { + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + twiceArea += y1 * x2 - x1 * y2; + x1 = x2; + y1 = y2; + } + return twiceArea / 2; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @return {number} Area. + */ +ol.geom.flat.area.linearRings = function(flatCoordinates, offset, ends, stride) { + var area = 0; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + area += ol.geom.flat.area.linearRing(flatCoordinates, offset, end, stride); + offset = end; + } + return area; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @return {number} Area. + */ +ol.geom.flat.area.linearRingss = function(flatCoordinates, offset, endss, stride) { + var area = 0; + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + area += + ol.geom.flat.area.linearRings(flatCoordinates, offset, ends, stride); + offset = ends[ends.length - 1]; + } + return area; +}; + +goog.provide('ol.geom.flat.closest'); + +goog.require('ol'); +goog.require('ol.math'); + + +/** + * Returns the point on the 2D line segment flatCoordinates[offset1] to + * flatCoordinates[offset2] that is closest to the point (x, y). Extra + * dimensions are linearly interpolated. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset1 Offset 1. + * @param {number} offset2 Offset 2. + * @param {number} stride Stride. + * @param {number} x X. + * @param {number} y Y. + * @param {Array.<number>} closestPoint Closest point. + */ +ol.geom.flat.closest.point = function(flatCoordinates, offset1, offset2, stride, x, y, closestPoint) { + var x1 = flatCoordinates[offset1]; + var y1 = flatCoordinates[offset1 + 1]; + var dx = flatCoordinates[offset2] - x1; + var dy = flatCoordinates[offset2 + 1] - y1; + var i, offset; + if (dx === 0 && dy === 0) { + offset = offset1; + } else { + var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy); + if (t > 1) { + offset = offset2; + } else if (t > 0) { + for (i = 0; i < stride; ++i) { + closestPoint[i] = ol.math.lerp(flatCoordinates[offset1 + i], + flatCoordinates[offset2 + i], t); + } + closestPoint.length = stride; + return; + } else { + offset = offset1; + } + } + for (i = 0; i < stride; ++i) { + closestPoint[i] = flatCoordinates[offset + i]; + } + closestPoint.length = stride; +}; + + +/** + * Return the squared of the largest distance between any pair of consecutive + * coordinates. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} maxSquaredDelta Max squared delta. + * @return {number} Max squared delta. + */ +ol.geom.flat.closest.getMaxSquaredDelta = function(flatCoordinates, offset, end, stride, maxSquaredDelta) { + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + for (offset += stride; offset < end; offset += stride) { + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + var squaredDelta = ol.math.squaredDistance(x1, y1, x2, y2); + if (squaredDelta > maxSquaredDelta) { + maxSquaredDelta = squaredDelta; + } + x1 = x2; + y1 = y2; + } + return maxSquaredDelta; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} maxSquaredDelta Max squared delta. + * @return {number} Max squared delta. + */ +ol.geom.flat.closest.getsMaxSquaredDelta = function(flatCoordinates, offset, ends, stride, maxSquaredDelta) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + maxSquaredDelta = ol.geom.flat.closest.getMaxSquaredDelta( + flatCoordinates, offset, end, stride, maxSquaredDelta); + offset = end; + } + return maxSquaredDelta; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} maxSquaredDelta Max squared delta. + * @return {number} Max squared delta. + */ +ol.geom.flat.closest.getssMaxSquaredDelta = function(flatCoordinates, offset, endss, stride, maxSquaredDelta) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + maxSquaredDelta = ol.geom.flat.closest.getsMaxSquaredDelta( + flatCoordinates, offset, ends, stride, maxSquaredDelta); + offset = ends[ends.length - 1]; + } + return maxSquaredDelta; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} maxDelta Max delta. + * @param {boolean} isRing Is ring. + * @param {number} x X. + * @param {number} y Y. + * @param {Array.<number>} closestPoint Closest point. + * @param {number} minSquaredDistance Minimum squared distance. + * @param {Array.<number>=} opt_tmpPoint Temporary point object. + * @return {number} Minimum squared distance. + */ +ol.geom.flat.closest.getClosestPoint = function(flatCoordinates, offset, end, + stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, + opt_tmpPoint) { + if (offset == end) { + return minSquaredDistance; + } + var i, squaredDistance; + if (maxDelta === 0) { + // All points are identical, so just test the first point. + squaredDistance = ol.math.squaredDistance( + x, y, flatCoordinates[offset], flatCoordinates[offset + 1]); + if (squaredDistance < minSquaredDistance) { + for (i = 0; i < stride; ++i) { + closestPoint[i] = flatCoordinates[offset + i]; + } + closestPoint.length = stride; + return squaredDistance; + } else { + return minSquaredDistance; + } + } + ol.DEBUG && console.assert(maxDelta > 0, 'maxDelta should be larger than 0'); + var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN]; + var index = offset + stride; + while (index < end) { + ol.geom.flat.closest.point( + flatCoordinates, index - stride, index, stride, x, y, tmpPoint); + squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]); + if (squaredDistance < minSquaredDistance) { + minSquaredDistance = squaredDistance; + for (i = 0; i < stride; ++i) { + closestPoint[i] = tmpPoint[i]; + } + closestPoint.length = stride; + index += stride; + } else { + // Skip ahead multiple points, because we know that all the skipped + // points cannot be any closer than the closest point we have found so + // far. We know this because we know how close the current point is, how + // close the closest point we have found so far is, and the maximum + // distance between consecutive points. For example, if we're currently + // at distance 10, the best we've found so far is 3, and that the maximum + // distance between consecutive points is 2, then we'll need to skip at + // least (10 - 3) / 2 == 3 (rounded down) points to have any chance of + // finding a closer point. We use Math.max(..., 1) to ensure that we + // always advance at least one point, to avoid an infinite loop. + index += stride * Math.max( + ((Math.sqrt(squaredDistance) - + Math.sqrt(minSquaredDistance)) / maxDelta) | 0, 1); + } + } + if (isRing) { + // Check the closing segment. + ol.geom.flat.closest.point( + flatCoordinates, end - stride, offset, stride, x, y, tmpPoint); + squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]); + if (squaredDistance < minSquaredDistance) { + minSquaredDistance = squaredDistance; + for (i = 0; i < stride; ++i) { + closestPoint[i] = tmpPoint[i]; + } + closestPoint.length = stride; + } + } + return minSquaredDistance; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} maxDelta Max delta. + * @param {boolean} isRing Is ring. + * @param {number} x X. + * @param {number} y Y. + * @param {Array.<number>} closestPoint Closest point. + * @param {number} minSquaredDistance Minimum squared distance. + * @param {Array.<number>=} opt_tmpPoint Temporary point object. + * @return {number} Minimum squared distance. + */ +ol.geom.flat.closest.getsClosestPoint = function(flatCoordinates, offset, ends, + stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, + opt_tmpPoint) { + var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN]; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + minSquaredDistance = ol.geom.flat.closest.getClosestPoint( + flatCoordinates, offset, end, stride, + maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint); + offset = end; + } + return minSquaredDistance; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} maxDelta Max delta. + * @param {boolean} isRing Is ring. + * @param {number} x X. + * @param {number} y Y. + * @param {Array.<number>} closestPoint Closest point. + * @param {number} minSquaredDistance Minimum squared distance. + * @param {Array.<number>=} opt_tmpPoint Temporary point object. + * @return {number} Minimum squared distance. + */ +ol.geom.flat.closest.getssClosestPoint = function(flatCoordinates, offset, + endss, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, + opt_tmpPoint) { + var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN]; + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + minSquaredDistance = ol.geom.flat.closest.getsClosestPoint( + flatCoordinates, offset, ends, stride, + maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint); + offset = ends[ends.length - 1]; + } + return minSquaredDistance; +}; + +goog.provide('ol.geom.flat.deflate'); + +goog.require('ol'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} stride Stride. + * @return {number} offset Offset. + */ +ol.geom.flat.deflate.coordinate = function(flatCoordinates, offset, coordinate, stride) { + ol.DEBUG && console.assert(coordinate.length == stride, + 'length of the coordinate array should match stride'); + var i, ii; + for (i = 0, ii = coordinate.length; i < ii; ++i) { + flatCoordinates[offset++] = coordinate[i]; + } + return offset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {number} stride Stride. + * @return {number} offset Offset. + */ +ol.geom.flat.deflate.coordinates = function(flatCoordinates, offset, coordinates, stride) { + var i, ii; + for (i = 0, ii = coordinates.length; i < ii; ++i) { + var coordinate = coordinates[i]; + ol.DEBUG && console.assert(coordinate.length == stride, + 'length of coordinate array should match stride'); + var j; + for (j = 0; j < stride; ++j) { + flatCoordinates[offset++] = coordinate[j]; + } + } + return offset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<ol.Coordinate>>} coordinatess Coordinatess. + * @param {number} stride Stride. + * @param {Array.<number>=} opt_ends Ends. + * @return {Array.<number>} Ends. + */ +ol.geom.flat.deflate.coordinatess = function(flatCoordinates, offset, coordinatess, stride, opt_ends) { + var ends = opt_ends ? opt_ends : []; + var i = 0; + var j, jj; + for (j = 0, jj = coordinatess.length; j < jj; ++j) { + var end = ol.geom.flat.deflate.coordinates( + flatCoordinates, offset, coordinatess[j], stride); + ends[i++] = end; + offset = end; + } + ends.length = i; + return ends; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinatesss Coordinatesss. + * @param {number} stride Stride. + * @param {Array.<Array.<number>>=} opt_endss Endss. + * @return {Array.<Array.<number>>} Endss. + */ +ol.geom.flat.deflate.coordinatesss = function(flatCoordinates, offset, coordinatesss, stride, opt_endss) { + var endss = opt_endss ? opt_endss : []; + var i = 0; + var j, jj; + for (j = 0, jj = coordinatesss.length; j < jj; ++j) { + var ends = ol.geom.flat.deflate.coordinatess( + flatCoordinates, offset, coordinatesss[j], stride, endss[i]); + endss[i++] = ends; + offset = ends[ends.length - 1]; + } + endss.length = i; + return endss; +}; + +goog.provide('ol.geom.flat.inflate'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {Array.<ol.Coordinate>=} opt_coordinates Coordinates. + * @return {Array.<ol.Coordinate>} Coordinates. + */ +ol.geom.flat.inflate.coordinates = function(flatCoordinates, offset, end, stride, opt_coordinates) { + var coordinates = opt_coordinates !== undefined ? opt_coordinates : []; + var i = 0; + var j; + for (j = offset; j < end; j += stride) { + coordinates[i++] = flatCoordinates.slice(j, j + stride); + } + coordinates.length = i; + return coordinates; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {Array.<Array.<ol.Coordinate>>=} opt_coordinatess Coordinatess. + * @return {Array.<Array.<ol.Coordinate>>} Coordinatess. + */ +ol.geom.flat.inflate.coordinatess = function(flatCoordinates, offset, ends, stride, opt_coordinatess) { + var coordinatess = opt_coordinatess !== undefined ? opt_coordinatess : []; + var i = 0; + var j, jj; + for (j = 0, jj = ends.length; j < jj; ++j) { + var end = ends[j]; + coordinatess[i++] = ol.geom.flat.inflate.coordinates( + flatCoordinates, offset, end, stride, coordinatess[i]); + offset = end; + } + coordinatess.length = i; + return coordinatess; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {Array.<Array.<Array.<ol.Coordinate>>>=} opt_coordinatesss + * Coordinatesss. + * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinatesss. + */ +ol.geom.flat.inflate.coordinatesss = function(flatCoordinates, offset, endss, stride, opt_coordinatesss) { + var coordinatesss = opt_coordinatesss !== undefined ? opt_coordinatesss : []; + var i = 0; + var j, jj; + for (j = 0, jj = endss.length; j < jj; ++j) { + var ends = endss[j]; + coordinatesss[i++] = ol.geom.flat.inflate.coordinatess( + flatCoordinates, offset, ends, stride, coordinatesss[i]); + offset = ends[ends.length - 1]; + } + coordinatesss.length = i; + return coordinatesss; +}; + +// Based on simplify-js https://github.com/mourner/simplify-js +// Copyright (c) 2012, Vladimir Agafonkin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.geom.flat.simplify'); + +goog.require('ol.math'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {boolean} highQuality Highest quality. + * @param {Array.<number>=} opt_simplifiedFlatCoordinates Simplified flat + * coordinates. + * @return {Array.<number>} Simplified line string. + */ +ol.geom.flat.simplify.lineString = function(flatCoordinates, offset, end, + stride, squaredTolerance, highQuality, opt_simplifiedFlatCoordinates) { + var simplifiedFlatCoordinates = opt_simplifiedFlatCoordinates !== undefined ? + opt_simplifiedFlatCoordinates : []; + if (!highQuality) { + end = ol.geom.flat.simplify.radialDistance(flatCoordinates, offset, end, + stride, squaredTolerance, + simplifiedFlatCoordinates, 0); + flatCoordinates = simplifiedFlatCoordinates; + offset = 0; + stride = 2; + } + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker( + flatCoordinates, offset, end, stride, squaredTolerance, + simplifiedFlatCoordinates, 0); + return simplifiedFlatCoordinates; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.douglasPeucker = function(flatCoordinates, offset, end, + stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) { + var n = (end - offset) / stride; + if (n < 3) { + for (; offset < end; offset += stride) { + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset]; + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset + 1]; + } + return simplifiedOffset; + } + /** @type {Array.<number>} */ + var markers = new Array(n); + markers[0] = 1; + markers[n - 1] = 1; + /** @type {Array.<number>} */ + var stack = [offset, end - stride]; + var index = 0; + var i; + while (stack.length > 0) { + var last = stack.pop(); + var first = stack.pop(); + var maxSquaredDistance = 0; + var x1 = flatCoordinates[first]; + var y1 = flatCoordinates[first + 1]; + var x2 = flatCoordinates[last]; + var y2 = flatCoordinates[last + 1]; + for (i = first + stride; i < last; i += stride) { + var x = flatCoordinates[i]; + var y = flatCoordinates[i + 1]; + var squaredDistance = ol.math.squaredSegmentDistance( + x, y, x1, y1, x2, y2); + if (squaredDistance > maxSquaredDistance) { + index = i; + maxSquaredDistance = squaredDistance; + } + } + if (maxSquaredDistance > squaredTolerance) { + markers[(index - offset) / stride] = 1; + if (first + stride < index) { + stack.push(first, index); + } + if (index + stride < last) { + stack.push(index, last); + } + } + } + for (i = 0; i < n; ++i) { + if (markers[i]) { + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset + i * stride]; + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset + i * stride + 1]; + } + } + return simplifiedOffset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @param {Array.<number>} simplifiedEnds Simplified ends. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.douglasPeuckers = function(flatCoordinates, offset, + ends, stride, squaredTolerance, simplifiedFlatCoordinates, + simplifiedOffset, simplifiedEnds) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + simplifiedOffset = ol.geom.flat.simplify.douglasPeucker( + flatCoordinates, offset, end, stride, squaredTolerance, + simplifiedFlatCoordinates, simplifiedOffset); + simplifiedEnds.push(simplifiedOffset); + offset = end; + } + return simplifiedOffset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.douglasPeuckerss = function( + flatCoordinates, offset, endss, stride, squaredTolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + var simplifiedEnds = []; + simplifiedOffset = ol.geom.flat.simplify.douglasPeuckers( + flatCoordinates, offset, ends, stride, squaredTolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds); + simplifiedEndss.push(simplifiedEnds); + offset = ends[ends.length - 1]; + } + return simplifiedOffset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.radialDistance = function(flatCoordinates, offset, end, + stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) { + if (end <= offset + stride) { + // zero or one point, no simplification possible, so copy and return + for (; offset < end; offset += stride) { + simplifiedFlatCoordinates[simplifiedOffset++] = flatCoordinates[offset]; + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset + 1]; + } + return simplifiedOffset; + } + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + // copy first point + simplifiedFlatCoordinates[simplifiedOffset++] = x1; + simplifiedFlatCoordinates[simplifiedOffset++] = y1; + var x2 = x1; + var y2 = y1; + for (offset += stride; offset < end; offset += stride) { + x2 = flatCoordinates[offset]; + y2 = flatCoordinates[offset + 1]; + if (ol.math.squaredDistance(x1, y1, x2, y2) > squaredTolerance) { + // copy point at offset + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + x1 = x2; + y1 = y2; + } + } + if (x2 != x1 || y2 != y1) { + // copy last point + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + } + return simplifiedOffset; +}; + + +/** + * @param {number} value Value. + * @param {number} tolerance Tolerance. + * @return {number} Rounded value. + */ +ol.geom.flat.simplify.snap = function(value, tolerance) { + return tolerance * Math.round(value / tolerance); +}; + + +/** + * Simplifies a line string using an algorithm designed by Tim Schaub. + * Coordinates are snapped to the nearest value in a virtual grid and + * consecutive duplicate coordinates are discarded. This effectively preserves + * topology as the simplification of any subsection of a line string is + * independent of the rest of the line string. This means that, for examples, + * the common edge between two polygons will be simplified to the same line + * string independently in both polygons. This implementation uses a single + * pass over the coordinates and eliminates intermediate collinear points. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} tolerance Tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.quantize = function(flatCoordinates, offset, end, stride, + tolerance, simplifiedFlatCoordinates, simplifiedOffset) { + // do nothing if the line is empty + if (offset == end) { + return simplifiedOffset; + } + // snap the first coordinate (P1) + var x1 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance); + var y1 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance); + offset += stride; + // add the first coordinate to the output + simplifiedFlatCoordinates[simplifiedOffset++] = x1; + simplifiedFlatCoordinates[simplifiedOffset++] = y1; + // find the next coordinate that does not snap to the same value as the first + // coordinate (P2) + var x2, y2; + do { + x2 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance); + y2 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance); + offset += stride; + if (offset == end) { + // all coordinates snap to the same value, the line collapses to a point + // push the last snapped value anyway to ensure that the output contains + // at least two points + // FIXME should we really return at least two points anyway? + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + return simplifiedOffset; + } + } while (x2 == x1 && y2 == y1); + while (offset < end) { + var x3, y3; + // snap the next coordinate (P3) + x3 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance); + y3 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance); + offset += stride; + // skip P3 if it is equal to P2 + if (x3 == x2 && y3 == y2) { + continue; + } + // calculate the delta between P1 and P2 + var dx1 = x2 - x1; + var dy1 = y2 - y1; + // calculate the delta between P3 and P1 + var dx2 = x3 - x1; + var dy2 = y3 - y1; + // if P1, P2, and P3 are colinear and P3 is further from P1 than P2 is from + // P1 in the same direction then P2 is on the straight line between P1 and + // P3 + if ((dx1 * dy2 == dy1 * dx2) && + ((dx1 < 0 && dx2 < dx1) || dx1 == dx2 || (dx1 > 0 && dx2 > dx1)) && + ((dy1 < 0 && dy2 < dy1) || dy1 == dy2 || (dy1 > 0 && dy2 > dy1))) { + // discard P2 and set P2 = P3 + x2 = x3; + y2 = y3; + continue; + } + // either P1, P2, and P3 are not colinear, or they are colinear but P3 is + // between P3 and P1 or on the opposite half of the line to P2. add P2, + // and continue with P1 = P2 and P2 = P3 + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + x1 = x2; + y1 = y2; + x2 = x3; + y2 = y3; + } + // add the last point (P2) + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + return simplifiedOffset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} tolerance Tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @param {Array.<number>} simplifiedEnds Simplified ends. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.quantizes = function( + flatCoordinates, offset, ends, stride, + tolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + simplifiedOffset = ol.geom.flat.simplify.quantize( + flatCoordinates, offset, end, stride, + tolerance, + simplifiedFlatCoordinates, simplifiedOffset); + simplifiedEnds.push(simplifiedOffset); + offset = end; + } + return simplifiedOffset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} tolerance Tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.quantizess = function( + flatCoordinates, offset, endss, stride, + tolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + var simplifiedEnds = []; + simplifiedOffset = ol.geom.flat.simplify.quantizes( + flatCoordinates, offset, ends, stride, + tolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds); + simplifiedEndss.push(simplifiedEnds); + offset = ends[ends.length - 1]; + } + return simplifiedOffset; +}; + +goog.provide('ol.geom.LinearRing'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.area'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.simplify'); + + +/** + * @classdesc + * Linear ring geometry. Only used as part of polygon; cannot be rendered + * on its own. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.LinearRing = function(coordinates, opt_layout) { + + ol.geom.SimpleGeometry.call(this); + + /** + * @private + * @type {number} + */ + this.maxDelta_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDeltaRevision_ = -1; + + this.setCoordinates(coordinates, opt_layout); + +}; +ol.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry); + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.LinearRing} Clone. + * @api stable + */ +ol.geom.LinearRing.prototype.clone = function() { + var linearRing = new ol.geom.LinearRing(null); + linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return linearRing; +}; + + +/** + * @inheritDoc + */ +ol.geom.LinearRing.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getClosestPoint( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); +}; + + +/** + * Return the area of the linear ring on projected plane. + * @return {number} Area (on projected plane). + * @api stable + */ +ol.geom.LinearRing.prototype.getArea = function() { + return ol.geom.flat.area.linearRing( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; + + +/** + * Return the coordinates of the linear ring. + * @return {Array.<ol.Coordinate>} Coordinates. + * @api stable + */ +ol.geom.LinearRing.prototype.getCoordinates = function() { + return ol.geom.flat.inflate.coordinates( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; + + +/** + * @inheritDoc + */ +ol.geom.LinearRing.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + squaredTolerance, simplifiedFlatCoordinates, 0); + var simplifiedLinearRing = new ol.geom.LinearRing(null); + simplifiedLinearRing.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates); + return simplifiedLinearRing; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.LinearRing.prototype.getType = function() { + return ol.geom.GeometryType.LINEAR_RING; +}; + + +/** + * Set the coordinates of the linear ring. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.LinearRing.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, coordinates, 1); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + this.flatCoordinates.length = ol.geom.flat.deflate.coordinates( + this.flatCoordinates, 0, coordinates, this.stride); + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + */ +ol.geom.LinearRing.prototype.setFlatCoordinates = function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; + +goog.provide('ol.geom.Point'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.math'); + + +/** + * @classdesc + * Point geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {ol.Coordinate} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.Point = function(coordinates, opt_layout) { + ol.geom.SimpleGeometry.call(this); + this.setCoordinates(coordinates, opt_layout); +}; +ol.inherits(ol.geom.Point, ol.geom.SimpleGeometry); + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.Point} Clone. + * @api stable + */ +ol.geom.Point.prototype.clone = function() { + var point = new ol.geom.Point(null); + point.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return point; +}; + + +/** + * @inheritDoc + */ +ol.geom.Point.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + var flatCoordinates = this.flatCoordinates; + var squaredDistance = ol.math.squaredDistance( + x, y, flatCoordinates[0], flatCoordinates[1]); + if (squaredDistance < minSquaredDistance) { + var stride = this.stride; + var i; + for (i = 0; i < stride; ++i) { + closestPoint[i] = flatCoordinates[i]; + } + closestPoint.length = stride; + return squaredDistance; + } else { + return minSquaredDistance; + } +}; + + +/** + * Return the coordinate of the point. + * @return {ol.Coordinate} Coordinates. + * @api stable + */ +ol.geom.Point.prototype.getCoordinates = function() { + return !this.flatCoordinates ? [] : this.flatCoordinates.slice(); +}; + + +/** + * @inheritDoc + */ +ol.geom.Point.prototype.computeExtent = function(extent) { + return ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates, extent); +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.Point.prototype.getType = function() { + return ol.geom.GeometryType.POINT; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.Point.prototype.intersectsExtent = function(extent) { + return ol.extent.containsXY(extent, + this.flatCoordinates[0], this.flatCoordinates[1]); +}; + + +/** + * Set the coordinate of the point. + * @param {ol.Coordinate} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.Point.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, coordinates, 0); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + this.flatCoordinates.length = ol.geom.flat.deflate.coordinate( + this.flatCoordinates, 0, coordinates, this.stride); + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + */ +ol.geom.Point.prototype.setFlatCoordinates = function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; + +goog.provide('ol.geom.flat.contains'); + +goog.require('ol'); +goog.require('ol.extent'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} Contains extent. + */ +ol.geom.flat.contains.linearRingContainsExtent = function(flatCoordinates, offset, end, stride, extent) { + var outside = ol.extent.forEachCorner(extent, + /** + * @param {ol.Coordinate} coordinate Coordinate. + * @return {boolean} Contains (x, y). + */ + function(coordinate) { + return !ol.geom.flat.contains.linearRingContainsXY(flatCoordinates, + offset, end, stride, coordinate[0], coordinate[1]); + }); + return !outside; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} x X. + * @param {number} y Y. + * @return {boolean} Contains (x, y). + */ +ol.geom.flat.contains.linearRingContainsXY = function(flatCoordinates, offset, end, stride, x, y) { + // http://geomalgorithms.com/a03-_inclusion.html + // Copyright 2000 softSurfer, 2012 Dan Sunday + // This code may be freely used and modified for any purpose + // providing that this copyright notice is included with it. + // SoftSurfer makes no warranty for this code, and cannot be held + // liable for any real or imagined damage resulting from its use. + // Users of this code must verify correctness for their application. + var wn = 0; + var x1 = flatCoordinates[end - stride]; + var y1 = flatCoordinates[end - stride + 1]; + for (; offset < end; offset += stride) { + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + if (y1 <= y) { + if (y2 > y && ((x2 - x1) * (y - y1)) - ((x - x1) * (y2 - y1)) > 0) { + wn++; + } + } else if (y2 <= y && ((x2 - x1) * (y - y1)) - ((x - x1) * (y2 - y1)) < 0) { + wn--; + } + x1 = x2; + y1 = y2; + } + return wn !== 0; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} x X. + * @param {number} y Y. + * @return {boolean} Contains (x, y). + */ +ol.geom.flat.contains.linearRingsContainsXY = function(flatCoordinates, offset, ends, stride, x, y) { + ol.DEBUG && console.assert(ends.length > 0, 'ends should not be an empty array'); + if (ends.length === 0) { + return false; + } + if (!ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, ends[0], stride, x, y)) { + return false; + } + var i, ii; + for (i = 1, ii = ends.length; i < ii; ++i) { + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, ends[i - 1], ends[i], stride, x, y)) { + return false; + } + } + return true; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} x X. + * @param {number} y Y. + * @return {boolean} Contains (x, y). + */ +ol.geom.flat.contains.linearRingssContainsXY = function(flatCoordinates, offset, endss, stride, x, y) { + ol.DEBUG && console.assert(endss.length > 0, 'endss should not be an empty array'); + if (endss.length === 0) { + return false; + } + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + if (ol.geom.flat.contains.linearRingsContainsXY( + flatCoordinates, offset, ends, stride, x, y)) { + return true; + } + offset = ends[ends.length - 1]; + } + return false; +}; + +goog.provide('ol.geom.flat.interiorpoint'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.geom.flat.contains'); + + +/** + * Calculates a point that is likely to lie in the interior of the linear rings. + * Inspired by JTS's com.vividsolutions.jts.geom.Geometry#getInteriorPoint. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {Array.<number>} flatCenters Flat centers. + * @param {number} flatCentersOffset Flat center offset. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Destination. + */ +ol.geom.flat.interiorpoint.linearRings = function(flatCoordinates, offset, + ends, stride, flatCenters, flatCentersOffset, opt_dest) { + var i, ii, x, x1, x2, y1, y2; + var y = flatCenters[flatCentersOffset + 1]; + /** @type {Array.<number>} */ + var intersections = []; + // Calculate intersections with the horizontal line + var end = ends[0]; + x1 = flatCoordinates[end - stride]; + y1 = flatCoordinates[end - stride + 1]; + for (i = offset; i < end; i += stride) { + x2 = flatCoordinates[i]; + y2 = flatCoordinates[i + 1]; + if ((y <= y1 && y2 <= y) || (y1 <= y && y <= y2)) { + x = (y - y1) / (y2 - y1) * (x2 - x1) + x1; + intersections.push(x); + } + x1 = x2; + y1 = y2; + } + // Find the longest segment of the horizontal line that has its center point + // inside the linear ring. + var pointX = NaN; + var maxSegmentLength = -Infinity; + intersections.sort(ol.array.numberSafeCompareFunction); + x1 = intersections[0]; + for (i = 1, ii = intersections.length; i < ii; ++i) { + x2 = intersections[i]; + var segmentLength = Math.abs(x2 - x1); + if (segmentLength > maxSegmentLength) { + x = (x1 + x2) / 2; + if (ol.geom.flat.contains.linearRingsContainsXY( + flatCoordinates, offset, ends, stride, x, y)) { + pointX = x; + maxSegmentLength = segmentLength; + } + } + x1 = x2; + } + if (isNaN(pointX)) { + // There is no horizontal line that has its center point inside the linear + // ring. Use the center of the the linear ring's extent. + pointX = flatCenters[flatCentersOffset]; + } + if (opt_dest) { + opt_dest.push(pointX, y); + return opt_dest; + } else { + return [pointX, y]; + } +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {Array.<number>} flatCenters Flat centers. + * @return {Array.<number>} Interior points. + */ +ol.geom.flat.interiorpoint.linearRingss = function(flatCoordinates, offset, endss, stride, flatCenters) { + ol.DEBUG && console.assert(2 * endss.length == flatCenters.length, + 'endss.length times 2 should be flatCenters.length'); + var interiorPoints = []; + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + interiorPoints = ol.geom.flat.interiorpoint.linearRings(flatCoordinates, + offset, ends, stride, flatCenters, 2 * i, interiorPoints); + offset = ends[ends.length - 1]; + } + return interiorPoints; +}; + +goog.provide('ol.geom.flat.segments'); + + +/** + * This function calls `callback` for each segment of the flat coordinates + * array. If the callback returns a truthy value the function returns that + * value immediately. Otherwise the function returns `false`. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function + * called for each segment. + * @param {S=} opt_this The object to be used as the value of 'this' + * within callback. + * @return {T|boolean} Value. + * @template T,S + */ +ol.geom.flat.segments.forEach = function(flatCoordinates, offset, end, stride, callback, opt_this) { + var point1 = [flatCoordinates[offset], flatCoordinates[offset + 1]]; + var point2 = []; + var ret; + for (; (offset + stride) < end; offset += stride) { + point2[0] = flatCoordinates[offset + stride]; + point2[1] = flatCoordinates[offset + stride + 1]; + ret = callback.call(opt_this, point1, point2); + if (ret) { + return ret; + } + point1[0] = point2[0]; + point1[1] = point2[1]; + } + return false; +}; + +goog.provide('ol.geom.flat.intersectsextent'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.flat.contains'); +goog.require('ol.geom.flat.segments'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. + */ +ol.geom.flat.intersectsextent.lineString = function(flatCoordinates, offset, end, stride, extent) { + var coordinatesExtent = ol.extent.extendFlatCoordinates( + ol.extent.createEmpty(), flatCoordinates, offset, end, stride); + if (!ol.extent.intersects(extent, coordinatesExtent)) { + return false; + } + if (ol.extent.containsExtent(extent, coordinatesExtent)) { + return true; + } + if (coordinatesExtent[0] >= extent[0] && + coordinatesExtent[2] <= extent[2]) { + return true; + } + if (coordinatesExtent[1] >= extent[1] && + coordinatesExtent[3] <= extent[3]) { + return true; + } + return ol.geom.flat.segments.forEach(flatCoordinates, offset, end, stride, + /** + * @param {ol.Coordinate} point1 Start point. + * @param {ol.Coordinate} point2 End point. + * @return {boolean} `true` if the segment and the extent intersect, + * `false` otherwise. + */ + function(point1, point2) { + return ol.extent.intersectsSegment(extent, point1, point2); + }); +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. + */ +ol.geom.flat.intersectsextent.lineStrings = function(flatCoordinates, offset, ends, stride, extent) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + if (ol.geom.flat.intersectsextent.lineString( + flatCoordinates, offset, ends[i], stride, extent)) { + return true; + } + offset = ends[i]; + } + return false; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. + */ +ol.geom.flat.intersectsextent.linearRing = function(flatCoordinates, offset, end, stride, extent) { + if (ol.geom.flat.intersectsextent.lineString( + flatCoordinates, offset, end, stride, extent)) { + return true; + } + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, end, stride, extent[0], extent[1])) { + return true; + } + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, end, stride, extent[0], extent[3])) { + return true; + } + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, end, stride, extent[2], extent[1])) { + return true; + } + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, end, stride, extent[2], extent[3])) { + return true; + } + return false; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. + */ +ol.geom.flat.intersectsextent.linearRings = function(flatCoordinates, offset, ends, stride, extent) { + ol.DEBUG && console.assert(ends.length > 0, 'ends should not be an empty array'); + if (!ol.geom.flat.intersectsextent.linearRing( + flatCoordinates, offset, ends[0], stride, extent)) { + return false; + } + if (ends.length === 1) { + return true; + } + var i, ii; + for (i = 1, ii = ends.length; i < ii; ++i) { + if (ol.geom.flat.contains.linearRingContainsExtent( + flatCoordinates, ends[i - 1], ends[i], stride, extent)) { + return false; + } + } + return true; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. + */ +ol.geom.flat.intersectsextent.linearRingss = function(flatCoordinates, offset, endss, stride, extent) { + ol.DEBUG && console.assert(endss.length > 0, 'endss should not be an empty array'); + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + if (ol.geom.flat.intersectsextent.linearRings( + flatCoordinates, offset, ends, stride, extent)) { + return true; + } + offset = ends[ends.length - 1]; + } + return false; +}; + +goog.provide('ol.geom.flat.reverse'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + */ +ol.geom.flat.reverse.coordinates = function(flatCoordinates, offset, end, stride) { + while (offset < end - stride) { + var i; + for (i = 0; i < stride; ++i) { + var tmp = flatCoordinates[offset + i]; + flatCoordinates[offset + i] = flatCoordinates[end - stride + i]; + flatCoordinates[end - stride + i] = tmp; + } + offset += stride; + end -= stride; + } +}; + +goog.provide('ol.geom.flat.orient'); + +goog.require('ol'); +goog.require('ol.geom.flat.reverse'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {boolean} Is clockwise. + */ +ol.geom.flat.orient.linearRingIsClockwise = function(flatCoordinates, offset, end, stride) { + // http://tinyurl.com/clockwise-method + // https://github.com/OSGeo/gdal/blob/trunk/gdal/ogr/ogrlinearring.cpp + var edge = 0; + var x1 = flatCoordinates[end - stride]; + var y1 = flatCoordinates[end - stride + 1]; + for (; offset < end; offset += stride) { + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + edge += (x2 - x1) * (y2 + y1); + x1 = x2; + y1 = y2; + } + return edge > 0; +}; + + +/** + * Determines if linear rings are oriented. By default, left-hand orientation + * is tested (first ring must be clockwise, remaining rings counter-clockwise). + * To test for right-hand orientation, use the `opt_right` argument. + * + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Array of end indexes. + * @param {number} stride Stride. + * @param {boolean=} opt_right Test for right-hand orientation + * (counter-clockwise exterior ring and clockwise interior rings). + * @return {boolean} Rings are correctly oriented. + */ +ol.geom.flat.orient.linearRingsAreOriented = function(flatCoordinates, offset, ends, stride, opt_right) { + var right = opt_right !== undefined ? opt_right : false; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var isClockwise = ol.geom.flat.orient.linearRingIsClockwise( + flatCoordinates, offset, end, stride); + if (i === 0) { + if ((right && isClockwise) || (!right && !isClockwise)) { + return false; + } + } else { + if ((right && !isClockwise) || (!right && isClockwise)) { + return false; + } + } + offset = end; + } + return true; +}; + + +/** + * Determines if linear rings are oriented. By default, left-hand orientation + * is tested (first ring must be clockwise, remaining rings counter-clockwise). + * To test for right-hand orientation, use the `opt_right` argument. + * + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Array of array of end indexes. + * @param {number} stride Stride. + * @param {boolean=} opt_right Test for right-hand orientation + * (counter-clockwise exterior ring and clockwise interior rings). + * @return {boolean} Rings are correctly oriented. + */ +ol.geom.flat.orient.linearRingssAreOriented = function(flatCoordinates, offset, endss, stride, opt_right) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + if (!ol.geom.flat.orient.linearRingsAreOriented( + flatCoordinates, offset, endss[i], stride, opt_right)) { + return false; + } + } + return true; +}; + + +/** + * Orient coordinates in a flat array of linear rings. By default, rings + * are oriented following the left-hand rule (clockwise for exterior and + * counter-clockwise for interior rings). To orient according to the + * right-hand rule, use the `opt_right` argument. + * + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {boolean=} opt_right Follow the right-hand rule for orientation. + * @return {number} End. + */ +ol.geom.flat.orient.orientLinearRings = function(flatCoordinates, offset, ends, stride, opt_right) { + var right = opt_right !== undefined ? opt_right : false; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var isClockwise = ol.geom.flat.orient.linearRingIsClockwise( + flatCoordinates, offset, end, stride); + var reverse = i === 0 ? + (right && isClockwise) || (!right && !isClockwise) : + (right && !isClockwise) || (!right && isClockwise); + if (reverse) { + ol.geom.flat.reverse.coordinates(flatCoordinates, offset, end, stride); + } + offset = end; + } + return offset; +}; + + +/** + * Orient coordinates in a flat array of linear rings. By default, rings + * are oriented following the left-hand rule (clockwise for exterior and + * counter-clockwise for interior rings). To orient according to the + * right-hand rule, use the `opt_right` argument. + * + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Array of array of end indexes. + * @param {number} stride Stride. + * @param {boolean=} opt_right Follow the right-hand rule for orientation. + * @return {number} End. + */ +ol.geom.flat.orient.orientLinearRingss = function(flatCoordinates, offset, endss, stride, opt_right) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + offset = ol.geom.flat.orient.orientLinearRings( + flatCoordinates, offset, endss[i], stride, opt_right); + } + return offset; +}; + +goog.provide('ol.geom.Polygon'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.area'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.contains'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.interiorpoint'); +goog.require('ol.geom.flat.intersectsextent'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.simplify'); +goog.require('ol.math'); + + +/** + * @classdesc + * Polygon geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.Polygon = function(coordinates, opt_layout) { + + ol.geom.SimpleGeometry.call(this); + + /** + * @type {Array.<number>} + * @private + */ + this.ends_ = []; + + /** + * @private + * @type {number} + */ + this.flatInteriorPointRevision_ = -1; + + /** + * @private + * @type {ol.Coordinate} + */ + this.flatInteriorPoint_ = null; + + /** + * @private + * @type {number} + */ + this.maxDelta_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDeltaRevision_ = -1; + + /** + * @private + * @type {number} + */ + this.orientedRevision_ = -1; + + /** + * @private + * @type {Array.<number>} + */ + this.orientedFlatCoordinates_ = null; + + this.setCoordinates(coordinates, opt_layout); + +}; +ol.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry); + + +/** + * Append the passed linear ring to this polygon. + * @param {ol.geom.LinearRing} linearRing Linear ring. + * @api stable + */ +ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) { + ol.DEBUG && console.assert(linearRing.getLayout() == this.layout, + 'layout of linearRing should match layout'); + if (!this.flatCoordinates) { + this.flatCoordinates = linearRing.getFlatCoordinates().slice(); + } else { + ol.array.extend(this.flatCoordinates, linearRing.getFlatCoordinates()); + } + this.ends_.push(this.flatCoordinates.length); + this.changed(); +}; + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.Polygon} Clone. + * @api stable + */ +ol.geom.Polygon.prototype.clone = function() { + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + this.layout, this.flatCoordinates.slice(), this.ends_.slice()); + return polygon; +}; + + +/** + * @inheritDoc + */ +ol.geom.Polygon.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta( + this.flatCoordinates, 0, this.ends_, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getsClosestPoint( + this.flatCoordinates, 0, this.ends_, this.stride, + this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); +}; + + +/** + * @inheritDoc + */ +ol.geom.Polygon.prototype.containsXY = function(x, y) { + return ol.geom.flat.contains.linearRingsContainsXY( + this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y); +}; + + +/** + * Return the area of the polygon on projected plane. + * @return {number} Area (on projected plane). + * @api stable + */ +ol.geom.Polygon.prototype.getArea = function() { + return ol.geom.flat.area.linearRings( + this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride); +}; + + +/** + * Get the coordinate array for this geometry. This array has the structure + * of a GeoJSON coordinate array for polygons. + * + * @param {boolean=} opt_right Orient coordinates according to the right-hand + * rule (counter-clockwise for exterior and clockwise for interior rings). + * If `false`, coordinates will be oriented according to the left-hand rule + * (clockwise for exterior and counter-clockwise for interior rings). + * By default, coordinate orientation will depend on how the geometry was + * constructed. + * @return {Array.<Array.<ol.Coordinate>>} Coordinates. + * @api stable + */ +ol.geom.Polygon.prototype.getCoordinates = function(opt_right) { + var flatCoordinates; + if (opt_right !== undefined) { + flatCoordinates = this.getOrientedFlatCoordinates().slice(); + ol.geom.flat.orient.orientLinearRings( + flatCoordinates, 0, this.ends_, this.stride, opt_right); + } else { + flatCoordinates = this.flatCoordinates; + } + + return ol.geom.flat.inflate.coordinatess( + flatCoordinates, 0, this.ends_, this.stride); +}; + + +/** + * @return {Array.<number>} Ends. + */ +ol.geom.Polygon.prototype.getEnds = function() { + return this.ends_; +}; + + +/** + * @return {Array.<number>} Interior point. + */ +ol.geom.Polygon.prototype.getFlatInteriorPoint = function() { + if (this.flatInteriorPointRevision_ != this.getRevision()) { + var flatCenter = ol.extent.getCenter(this.getExtent()); + this.flatInteriorPoint_ = ol.geom.flat.interiorpoint.linearRings( + this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, + flatCenter, 0); + this.flatInteriorPointRevision_ = this.getRevision(); + } + return this.flatInteriorPoint_; +}; + + +/** + * Return an interior point of the polygon. + * @return {ol.geom.Point} Interior point. + * @api stable + */ +ol.geom.Polygon.prototype.getInteriorPoint = function() { + return new ol.geom.Point(this.getFlatInteriorPoint()); +}; + + +/** + * Return the number of rings of the polygon, this includes the exterior + * ring and any interior rings. + * + * @return {number} Number of rings. + * @api + */ +ol.geom.Polygon.prototype.getLinearRingCount = function() { + return this.ends_.length; +}; + + +/** + * Return the Nth linear ring of the polygon geometry. Return `null` if the + * given index is out of range. + * The exterior linear ring is available at index `0` and the interior rings + * at index `1` and beyond. + * + * @param {number} index Index. + * @return {ol.geom.LinearRing} Linear ring. + * @api stable + */ +ol.geom.Polygon.prototype.getLinearRing = function(index) { + ol.DEBUG && console.assert(0 <= index && index < this.ends_.length, + 'index should be in between 0 and and length of this.ends_'); + if (index < 0 || this.ends_.length <= index) { + return null; + } + var linearRing = new ol.geom.LinearRing(null); + linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice( + index === 0 ? 0 : this.ends_[index - 1], this.ends_[index])); + return linearRing; +}; + + +/** + * Return the linear rings of the polygon. + * @return {Array.<ol.geom.LinearRing>} Linear rings. + * @api stable + */ +ol.geom.Polygon.prototype.getLinearRings = function() { + var layout = this.layout; + var flatCoordinates = this.flatCoordinates; + var ends = this.ends_; + var linearRings = []; + var offset = 0; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var linearRing = new ol.geom.LinearRing(null); + linearRing.setFlatCoordinates(layout, flatCoordinates.slice(offset, end)); + linearRings.push(linearRing); + offset = end; + } + return linearRings; +}; + + +/** + * @return {Array.<number>} Oriented flat coordinates. + */ +ol.geom.Polygon.prototype.getOrientedFlatCoordinates = function() { + if (this.orientedRevision_ != this.getRevision()) { + var flatCoordinates = this.flatCoordinates; + if (ol.geom.flat.orient.linearRingsAreOriented( + flatCoordinates, 0, this.ends_, this.stride)) { + this.orientedFlatCoordinates_ = flatCoordinates; + } else { + this.orientedFlatCoordinates_ = flatCoordinates.slice(); + this.orientedFlatCoordinates_.length = + ol.geom.flat.orient.orientLinearRings( + this.orientedFlatCoordinates_, 0, this.ends_, this.stride); + } + this.orientedRevision_ = this.getRevision(); + } + return this.orientedFlatCoordinates_; +}; + + +/** + * @inheritDoc + */ +ol.geom.Polygon.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + var simplifiedEnds = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizes( + this.flatCoordinates, 0, this.ends_, this.stride, + Math.sqrt(squaredTolerance), + simplifiedFlatCoordinates, 0, simplifiedEnds); + var simplifiedPolygon = new ol.geom.Polygon(null); + simplifiedPolygon.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds); + return simplifiedPolygon; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.Polygon.prototype.getType = function() { + return ol.geom.GeometryType.POLYGON; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.Polygon.prototype.intersectsExtent = function(extent) { + return ol.geom.flat.intersectsextent.linearRings( + this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent); +}; + + +/** + * Set the coordinates of the polygon. + * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.Polygon.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_); + } else { + this.setLayout(opt_layout, coordinates, 2); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + var ends = ol.geom.flat.deflate.coordinatess( + this.flatCoordinates, 0, coordinates, this.stride, this.ends_); + this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1]; + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<number>} ends Ends. + */ +ol.geom.Polygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) { + if (!flatCoordinates) { + ol.DEBUG && console.assert(ends && ends.length === 0, + 'ends must be an empty array'); + } else if (ends.length === 0) { + ol.DEBUG && console.assert(flatCoordinates.length === 0, + 'flatCoordinates should be an empty array'); + } else { + ol.DEBUG && console.assert(flatCoordinates.length == ends[ends.length - 1], + 'the length of flatCoordinates should be the last entry of ends'); + } + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.ends_ = ends; + this.changed(); +}; + + +/** + * Create an approximation of a circle on the surface of a sphere. + * @param {ol.Sphere} sphere The sphere. + * @param {ol.Coordinate} center Center (`[lon, lat]` in degrees). + * @param {number} radius The great-circle distance from the center to + * the polygon vertices. + * @param {number=} opt_n Optional number of vertices for the resulting + * polygon. Default is `32`. + * @return {ol.geom.Polygon} The "circular" polygon. + * @api stable + */ +ol.geom.Polygon.circular = function(sphere, center, radius, opt_n) { + var n = opt_n ? opt_n : 32; + /** @type {Array.<number>} */ + var flatCoordinates = []; + var i; + for (i = 0; i < n; ++i) { + ol.array.extend( + flatCoordinates, sphere.offset(center, radius, 2 * Math.PI * i / n)); + } + flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]); + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]); + return polygon; +}; + + +/** + * Create a polygon from an extent. The layout used is `XY`. + * @param {ol.Extent} extent The extent. + * @return {ol.geom.Polygon} The polygon. + * @api + */ +ol.geom.Polygon.fromExtent = function(extent) { + var minX = extent[0]; + var minY = extent[1]; + var maxX = extent[2]; + var maxY = extent[3]; + var flatCoordinates = + [minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY]; + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]); + return polygon; +}; + + +/** + * Create a regular polygon from a circle. + * @param {ol.geom.Circle} circle Circle geometry. + * @param {number=} opt_sides Number of sides of the polygon. Default is 32. + * @param {number=} opt_angle Start angle for the first vertex of the polygon in + * radians. Default is 0. + * @return {ol.geom.Polygon} Polygon geometry. + * @api + */ +ol.geom.Polygon.fromCircle = function(circle, opt_sides, opt_angle) { + var sides = opt_sides ? opt_sides : 32; + var stride = circle.getStride(); + var layout = circle.getLayout(); + var polygon = new ol.geom.Polygon(null, layout); + var arrayLength = stride * (sides + 1); + var flatCoordinates = new Array(arrayLength); + for (var i = 0; i < arrayLength; i++) { + flatCoordinates[i] = 0; + } + var ends = [flatCoordinates.length]; + polygon.setFlatCoordinates(layout, flatCoordinates, ends); + ol.geom.Polygon.makeRegular( + polygon, circle.getCenter(), circle.getRadius(), opt_angle); + return polygon; +}; + + +/** + * Modify the coordinates of a polygon to make it a regular polygon. + * @param {ol.geom.Polygon} polygon Polygon geometry. + * @param {ol.Coordinate} center Center of the regular polygon. + * @param {number} radius Radius of the regular polygon. + * @param {number=} opt_angle Start angle for the first vertex of the polygon in + * radians. Default is 0. + */ +ol.geom.Polygon.makeRegular = function(polygon, center, radius, opt_angle) { + var flatCoordinates = polygon.getFlatCoordinates(); + var layout = polygon.getLayout(); + var stride = polygon.getStride(); + var ends = polygon.getEnds(); + ol.DEBUG && console.assert(ends.length === 1, 'only 1 ring is supported'); + var sides = flatCoordinates.length / stride - 1; + var startAngle = opt_angle ? opt_angle : 0; + var angle, offset; + for (var i = 0; i <= sides; ++i) { + offset = i * stride; + angle = startAngle + (ol.math.modulo(i, sides) * 2 * Math.PI / sides); + flatCoordinates[offset] = center[0] + (radius * Math.cos(angle)); + flatCoordinates[offset + 1] = center[1] + (radius * Math.sin(angle)); + } + polygon.setFlatCoordinates(layout, flatCoordinates, ends); +}; + +goog.provide('ol.View'); + +goog.require('ol'); +goog.require('ol.CenterConstraint'); +goog.require('ol.Constraints'); +goog.require('ol.Object'); +goog.require('ol.ResolutionConstraint'); +goog.require('ol.RotationConstraint'); +goog.require('ol.array'); +goog.require('ol.asserts'); +goog.require('ol.coordinate'); +goog.require('ol.easing'); +goog.require('ol.extent'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.proj'); +goog.require('ol.proj.Units'); + + +/** + * @classdesc + * An ol.View object represents a simple 2D view of the map. + * + * This is the object to act upon to change the center, resolution, + * and rotation of the map. + * + * ### The view states + * + * An `ol.View` is determined by three states: `center`, `resolution`, + * and `rotation`. Each state has a corresponding getter and setter, e.g. + * `getCenter` and `setCenter` for the `center` state. + * + * An `ol.View` has a `projection`. The projection determines the + * coordinate system of the center, and its units determine the units of the + * resolution (projection units per pixel). The default projection is + * Spherical Mercator (EPSG:3857). + * + * ### The constraints + * + * `setCenter`, `setResolution` and `setRotation` can be used to change the + * states of the view. Any value can be passed to the setters. And the value + * that is passed to a setter will effectively be the value set in the view, + * and returned by the corresponding getter. + * + * But an `ol.View` object also has a *resolution constraint*, a + * *rotation constraint* and a *center constraint*. + * + * As said above, no constraints are applied when the setters are used to set + * new states for the view. Applying constraints is done explicitly through + * the use of the `constrain*` functions (`constrainResolution` and + * `constrainRotation` and `constrainCenter`). + * + * The main users of the constraints are the interactions and the + * controls. For example, double-clicking on the map changes the view to + * the "next" resolution. And releasing the fingers after pinch-zooming + * snaps to the closest resolution (with an animation). + * + * The *resolution constraint* snaps to specific resolutions. It is + * determined by the following options: `resolutions`, `maxResolution`, + * `maxZoom`, and `zoomFactor`. If `resolutions` is set, the other three + * options are ignored. See documentation for each option for more + * information. + * + * The *rotation constraint* snaps to specific angles. It is determined + * by the following options: `enableRotation` and `constrainRotation`. + * By default the rotation value is snapped to zero when approaching the + * horizontal. + * + * The *center constraint* is determined by the `extent` option. By + * default the center is not constrained at all. + * + * @constructor + * @extends {ol.Object} + * @param {olx.ViewOptions=} opt_options View options. + * @api stable + */ +ol.View = function(opt_options) { + ol.Object.call(this); + var options = opt_options || {}; + + /** + * @private + * @type {Array.<number>} + */ + this.hints_ = [0, 0]; + + /** + * @private + * @type {Array.<Array.<ol.ViewAnimation>>} + */ + this.animations_ = []; + + /** + * @private + * @type {number|undefined} + */ + this.updateAnimationKey_; + + this.updateAnimations_ = this.updateAnimations_.bind(this); + + /** + * @type {Object.<string, *>} + */ + var properties = {}; + properties[ol.View.Property.CENTER] = options.center !== undefined ? + options.center : null; + + /** + * @private + * @const + * @type {ol.proj.Projection} + */ + this.projection_ = ol.proj.createProjection(options.projection, 'EPSG:3857'); + + var resolutionConstraintInfo = ol.View.createResolutionConstraint_( + options); + + /** + * @private + * @type {number} + */ + this.maxResolution_ = resolutionConstraintInfo.maxResolution; + + /** + * @private + * @type {number} + */ + this.minResolution_ = resolutionConstraintInfo.minResolution; + + /** + * @private + * @type {number} + */ + this.zoomFactor_ = resolutionConstraintInfo.zoomFactor; + + /** + * @private + * @type {Array.<number>|undefined} + */ + this.resolutions_ = options.resolutions; + + /** + * @private + * @type {number} + */ + this.minZoom_ = resolutionConstraintInfo.minZoom; + + var centerConstraint = ol.View.createCenterConstraint_(options); + var resolutionConstraint = resolutionConstraintInfo.constraint; + var rotationConstraint = ol.View.createRotationConstraint_(options); + + /** + * @private + * @type {ol.Constraints} + */ + this.constraints_ = new ol.Constraints( + centerConstraint, resolutionConstraint, rotationConstraint); + + if (options.resolution !== undefined) { + properties[ol.View.Property.RESOLUTION] = options.resolution; + } else if (options.zoom !== undefined) { + properties[ol.View.Property.RESOLUTION] = this.constrainResolution( + this.maxResolution_, options.zoom - this.minZoom_); + } + properties[ol.View.Property.ROTATION] = + options.rotation !== undefined ? options.rotation : 0; + this.setProperties(properties); +}; +ol.inherits(ol.View, ol.Object); + + +/** + * Animate the view. The view's center, zoom (or resolution), and rotation + * can be animated for smooth transitions between view states. For example, + * to animate the view to a new zoom level: + * + * view.animate({zoom: view.getZoom() + 1}); + * + * By default, the animation lasts one second and uses in-and-out easing. You + * can customize this behavior by including `duration` (in milliseconds) and + * `easing` options (see {@link ol.easing}). + * + * To chain together multiple animations, call the method with multiple + * animation objects. For example, to first zoom and then pan: + * + * view.animate({zoom: 10}, {center: [0, 0]}); + * + * If you provide a function as the last argument to the animate method, it + * will get called at the end of an animation series. The callback will be + * called with `true` if the animation series completed on its own or `false` + * if it was cancelled. + * + * Animations are cancelled by user interactions (e.g. dragging the map) or by + * calling `view.setCenter()`, `view.setResolution()`, or `view.setRotation()` + * (or another method that calls one of these). + * + * @param {...(olx.AnimationOptions|function(boolean))} var_args Animation + * options. Multiple animations can be run in series by passing multiple + * options objects. To run multiple animations in parallel, call the method + * multiple times. An optional callback can be provided as a final + * argument. The callback will be called with a boolean indicating whether + * the animation completed without being cancelled. + * @api + */ +ol.View.prototype.animate = function(var_args) { + var start = Date.now(); + var center = this.getCenter().slice(); + var resolution = this.getResolution(); + var rotation = this.getRotation(); + var animationCount = arguments.length; + var callback; + if (animationCount > 1 && typeof arguments[animationCount - 1] === 'function') { + callback = arguments[animationCount - 1]; + --animationCount; + } + var series = []; + for (var i = 0; i < animationCount; ++i) { + var options = /** @type olx.AnimationOptions */ (arguments[i]); + + var animation = /** @type {ol.ViewAnimation} */ ({ + start: start, + complete: false, + anchor: options.anchor, + duration: options.duration !== undefined ? options.duration : 1000, + easing: options.easing || ol.easing.inAndOut + }); + + if (options.center) { + animation.sourceCenter = center; + animation.targetCenter = options.center; + center = animation.targetCenter; + } + + if (options.zoom !== undefined) { + animation.sourceResolution = resolution; + animation.targetResolution = this.constrainResolution( + this.maxResolution_, options.zoom - this.minZoom_, 0); + resolution = animation.targetResolution; + } else if (options.resolution) { + animation.sourceResolution = resolution; + animation.targetResolution = options.resolution; + resolution = animation.targetResolution; + } + + if (options.rotation !== undefined) { + animation.sourceRotation = rotation; + animation.targetRotation = options.rotation; + rotation = animation.targetRotation; + } + + animation.callback = callback; + start += animation.duration; + series.push(animation); + } + this.animations_.push(series); + this.setHint(ol.View.Hint.ANIMATING, 1); + this.updateAnimations_(); +}; + + +/** + * Determine if the view is being animated. + * @return {boolean} The view is being animated. + */ +ol.View.prototype.getAnimating = function() { + return this.getHints()[ol.View.Hint.ANIMATING] > 0; +}; + + +/** + * Cancel any ongoing animations. + */ +ol.View.prototype.cancelAnimations = function() { + this.setHint(ol.View.Hint.ANIMATING, -this.getHints()[ol.View.Hint.ANIMATING]); + for (var i = 0, ii = this.animations_.length; i < ii; ++i) { + var series = this.animations_[i]; + if (series[0].callback) { + series[0].callback(false); + } + } + this.animations_.length = 0; +}; + +/** + * Update all animations. + */ +ol.View.prototype.updateAnimations_ = function() { + if (this.updateAnimationKey_ !== undefined) { + cancelAnimationFrame(this.updateAnimationKey_); + this.updateAnimationKey_ = undefined; + } + if (!this.getAnimating()) { + return; + } + var now = Date.now(); + var more = false; + for (var i = this.animations_.length - 1; i >= 0; --i) { + var series = this.animations_[i]; + var seriesComplete = true; + for (var j = 0, jj = series.length; j < jj; ++j) { + var animation = series[j]; + if (animation.complete) { + continue; + } + var elapsed = now - animation.start; + var fraction = animation.duration > 0 ? elapsed / animation.duration : 1; + if (fraction >= 1) { + animation.complete = true; + fraction = 1; + } else { + seriesComplete = false; + } + var progress = animation.easing(fraction); + if (animation.sourceCenter) { + var x0 = animation.sourceCenter[0]; + var y0 = animation.sourceCenter[1]; + var x1 = animation.targetCenter[0]; + var y1 = animation.targetCenter[1]; + var x = x0 + progress * (x1 - x0); + var y = y0 + progress * (y1 - y0); + this.set(ol.View.Property.CENTER, [x, y]); + } + if (animation.sourceResolution) { + var resolution = animation.sourceResolution + + progress * (animation.targetResolution - animation.sourceResolution); + if (animation.anchor) { + this.set(ol.View.Property.CENTER, + this.calculateCenterZoom(resolution, animation.anchor)); + } + this.set(ol.View.Property.RESOLUTION, resolution); + } + if (animation.sourceRotation !== undefined) { + var rotation = animation.sourceRotation + + progress * (animation.targetRotation - animation.sourceRotation); + if (animation.anchor) { + this.set(ol.View.Property.CENTER, + this.calculateCenterRotate(rotation, animation.anchor)); + } + this.set(ol.View.Property.ROTATION, rotation); + } + more = true; + if (!animation.complete) { + break; + } + } + if (seriesComplete) { + this.animations_[i] = null; + this.setHint(ol.View.Hint.ANIMATING, -1); + var callback = series[0].callback; + if (callback) { + callback(true); + } + } + } + // prune completed series + this.animations_ = this.animations_.filter(Boolean); + if (more && this.updateAnimationKey_ === undefined) { + this.updateAnimationKey_ = requestAnimationFrame(this.updateAnimations_); + } +}; + +/** + * @param {number} rotation Target rotation. + * @param {ol.Coordinate} anchor Rotation anchor. + * @return {ol.Coordinate|undefined} Center for rotation and anchor. + */ +ol.View.prototype.calculateCenterRotate = function(rotation, anchor) { + var center; + var currentCenter = this.getCenter(); + if (currentCenter !== undefined) { + center = [currentCenter[0] - anchor[0], currentCenter[1] - anchor[1]]; + ol.coordinate.rotate(center, rotation - this.getRotation()); + ol.coordinate.add(center, anchor); + } + return center; +}; + + +/** + * @param {number} resolution Target resolution. + * @param {ol.Coordinate} anchor Zoom anchor. + * @return {ol.Coordinate|undefined} Center for resolution and anchor. + */ +ol.View.prototype.calculateCenterZoom = function(resolution, anchor) { + var center; + var currentCenter = this.getCenter(); + var currentResolution = this.getResolution(); + if (currentCenter !== undefined && currentResolution !== undefined) { + var x = anchor[0] - + resolution * (anchor[0] - currentCenter[0]) / currentResolution; + var y = anchor[1] - + resolution * (anchor[1] - currentCenter[1]) / currentResolution; + center = [x, y]; + } + return center; +}; + + +/** + * Get the constrained center of this view. + * @param {ol.Coordinate|undefined} center Center. + * @return {ol.Coordinate|undefined} Constrained center. + * @api + */ +ol.View.prototype.constrainCenter = function(center) { + return this.constraints_.center(center); +}; + + +/** + * Get the constrained resolution of this view. + * @param {number|undefined} resolution Resolution. + * @param {number=} opt_delta Delta. Default is `0`. + * @param {number=} opt_direction Direction. Default is `0`. + * @return {number|undefined} Constrained resolution. + * @api + */ +ol.View.prototype.constrainResolution = function( + resolution, opt_delta, opt_direction) { + var delta = opt_delta || 0; + var direction = opt_direction || 0; + return this.constraints_.resolution(resolution, delta, direction); +}; + + +/** + * Get the constrained rotation of this view. + * @param {number|undefined} rotation Rotation. + * @param {number=} opt_delta Delta. Default is `0`. + * @return {number|undefined} Constrained rotation. + * @api + */ +ol.View.prototype.constrainRotation = function(rotation, opt_delta) { + var delta = opt_delta || 0; + return this.constraints_.rotation(rotation, delta); +}; + + +/** + * Get the view center. + * @return {ol.Coordinate|undefined} The center of the view. + * @observable + * @api stable + */ +ol.View.prototype.getCenter = function() { + return /** @type {ol.Coordinate|undefined} */ ( + this.get(ol.View.Property.CENTER)); +}; + + +/** + * @param {Array.<number>=} opt_hints Destination array. + * @return {Array.<number>} Hint. + */ +ol.View.prototype.getHints = function(opt_hints) { + if (opt_hints !== undefined) { + opt_hints[0] = this.hints_[0]; + opt_hints[1] = this.hints_[1]; + return opt_hints; + } else { + return this.hints_.slice(); + } +}; + + +/** + * Calculate the extent for the current view state and the passed size. + * The size is the pixel dimensions of the box into which the calculated extent + * should fit. In most cases you want to get the extent of the entire map, + * that is `map.getSize()`. + * @param {ol.Size} size Box pixel size. + * @return {ol.Extent} Extent. + * @api stable + */ +ol.View.prototype.calculateExtent = function(size) { + var center = /** @type {!ol.Coordinate} */ (this.getCenter()); + ol.asserts.assert(center, 1); // The view center is not defined + var resolution = /** @type {!number} */ (this.getResolution()); + ol.asserts.assert(resolution !== undefined, 2); // The view resolution is not defined + var rotation = /** @type {!number} */ (this.getRotation()); + ol.asserts.assert(rotation !== undefined, 3); // The view rotation is not defined + + return ol.extent.getForViewAndSize(center, resolution, rotation, size); +}; + + +/** + * Get the maximum resolution of the view. + * @return {number} The maximum resolution of the view. + * @api + */ +ol.View.prototype.getMaxResolution = function() { + return this.maxResolution_; +}; + + +/** + * Get the minimum resolution of the view. + * @return {number} The minimum resolution of the view. + * @api + */ +ol.View.prototype.getMinResolution = function() { + return this.minResolution_; +}; + + +/** + * Get the view projection. + * @return {ol.proj.Projection} The projection of the view. + * @api stable + */ +ol.View.prototype.getProjection = function() { + return this.projection_; +}; + + +/** + * Get the view resolution. + * @return {number|undefined} The resolution of the view. + * @observable + * @api stable + */ +ol.View.prototype.getResolution = function() { + return /** @type {number|undefined} */ ( + this.get(ol.View.Property.RESOLUTION)); +}; + + +/** + * Get the resolutions for the view. This returns the array of resolutions + * passed to the constructor of the {ol.View}, or undefined if none were given. + * @return {Array.<number>|undefined} The resolutions of the view. + * @api stable + */ +ol.View.prototype.getResolutions = function() { + return this.resolutions_; +}; + + +/** + * Get the resolution for a provided extent (in map units) and size (in pixels). + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Box pixel size. + * @return {number} The resolution at which the provided extent will render at + * the given size. + */ +ol.View.prototype.getResolutionForExtent = function(extent, size) { + var xResolution = ol.extent.getWidth(extent) / size[0]; + var yResolution = ol.extent.getHeight(extent) / size[1]; + return Math.max(xResolution, yResolution); +}; + + +/** + * Return a function that returns a value between 0 and 1 for a + * resolution. Exponential scaling is assumed. + * @param {number=} opt_power Power. + * @return {function(number): number} Resolution for value function. + */ +ol.View.prototype.getResolutionForValueFunction = function(opt_power) { + var power = opt_power || 2; + var maxResolution = this.maxResolution_; + var minResolution = this.minResolution_; + var max = Math.log(maxResolution / minResolution) / Math.log(power); + return ( + /** + * @param {number} value Value. + * @return {number} Resolution. + */ + function(value) { + var resolution = maxResolution / Math.pow(power, value * max); + ol.DEBUG && console.assert(resolution >= minResolution && + resolution <= maxResolution, + 'calculated resolution outside allowed bounds (%s <= %s <= %s)', + minResolution, resolution, maxResolution); + return resolution; + }); +}; + + +/** + * Get the view rotation. + * @return {number} The rotation of the view in radians. + * @observable + * @api stable + */ +ol.View.prototype.getRotation = function() { + return /** @type {number} */ (this.get(ol.View.Property.ROTATION)); +}; + + +/** + * Return a function that returns a resolution for a value between + * 0 and 1. Exponential scaling is assumed. + * @param {number=} opt_power Power. + * @return {function(number): number} Value for resolution function. + */ +ol.View.prototype.getValueForResolutionFunction = function(opt_power) { + var power = opt_power || 2; + var maxResolution = this.maxResolution_; + var minResolution = this.minResolution_; + var max = Math.log(maxResolution / minResolution) / Math.log(power); + return ( + /** + * @param {number} resolution Resolution. + * @return {number} Value. + */ + function(resolution) { + var value = + (Math.log(maxResolution / resolution) / Math.log(power)) / max; + ol.DEBUG && console.assert(value >= 0 && value <= 1, + 'calculated value (%s) ouside allowed range (0-1)', value); + return value; + }); +}; + + +/** + * @return {olx.ViewState} View state. + */ +ol.View.prototype.getState = function() { + ol.DEBUG && console.assert(this.isDef(), + 'the view was not defined (had no center and/or resolution)'); + var center = /** @type {ol.Coordinate} */ (this.getCenter()); + var projection = this.getProjection(); + var resolution = /** @type {number} */ (this.getResolution()); + var rotation = this.getRotation(); + return /** @type {olx.ViewState} */ ({ + center: center.slice(), + projection: projection !== undefined ? projection : null, + resolution: resolution, + rotation: rotation + }); +}; + + +/** + * Get the current zoom level. Return undefined if the current + * resolution is undefined or not within the "resolution constraints". + * @return {number|undefined} Zoom. + * @api stable + */ +ol.View.prototype.getZoom = function() { + var zoom; + var resolution = this.getResolution(); + if (resolution !== undefined && + resolution >= this.minResolution_ && resolution <= this.maxResolution_) { + var offset = this.minZoom_ || 0; + var max, zoomFactor; + if (this.resolutions_) { + var nearest = ol.array.linearFindNearest(this.resolutions_, resolution, 1); + offset += nearest; + if (nearest == this.resolutions_.length - 1) { + return offset; + } + max = this.resolutions_[nearest]; + zoomFactor = max / this.resolutions_[nearest + 1]; + } else { + max = this.maxResolution_; + zoomFactor = this.zoomFactor_; + } + zoom = offset + Math.log(max / resolution) / Math.log(zoomFactor); + } + return zoom; +}; + + +/** + * Fit the given geometry or extent based on the given map size and border. + * The size is pixel dimensions of the box to fit the extent into. + * In most cases you will want to use the map size, that is `map.getSize()`. + * Takes care of the map angle. + * @param {ol.geom.SimpleGeometry|ol.Extent} geometry Geometry. + * @param {ol.Size} size Box pixel size. + * @param {olx.view.FitOptions=} opt_options Options. + * @api + */ +ol.View.prototype.fit = function(geometry, size, opt_options) { + if (!(geometry instanceof ol.geom.SimpleGeometry)) { + ol.asserts.assert(Array.isArray(geometry), + 24); // Invalid extent or geometry provided as `geometry` + ol.asserts.assert(!ol.extent.isEmpty(geometry), + 25); // Cannot fit empty extent provided as `geometry` + geometry = ol.geom.Polygon.fromExtent(geometry); + } + + var options = opt_options || {}; + + var padding = options.padding !== undefined ? options.padding : [0, 0, 0, 0]; + var constrainResolution = options.constrainResolution !== undefined ? + options.constrainResolution : true; + var nearest = options.nearest !== undefined ? options.nearest : false; + var minResolution; + if (options.minResolution !== undefined) { + minResolution = options.minResolution; + } else if (options.maxZoom !== undefined) { + minResolution = this.constrainResolution( + this.maxResolution_, options.maxZoom - this.minZoom_, 0); + } else { + minResolution = 0; + } + var coords = geometry.getFlatCoordinates(); + + // calculate rotated extent + var rotation = this.getRotation(); + ol.DEBUG && console.assert(rotation !== undefined, 'rotation was not defined'); + var cosAngle = Math.cos(-rotation); + var sinAngle = Math.sin(-rotation); + var minRotX = +Infinity; + var minRotY = +Infinity; + var maxRotX = -Infinity; + var maxRotY = -Infinity; + var stride = geometry.getStride(); + for (var i = 0, ii = coords.length; i < ii; i += stride) { + var rotX = coords[i] * cosAngle - coords[i + 1] * sinAngle; + var rotY = coords[i] * sinAngle + coords[i + 1] * cosAngle; + minRotX = Math.min(minRotX, rotX); + minRotY = Math.min(minRotY, rotY); + maxRotX = Math.max(maxRotX, rotX); + maxRotY = Math.max(maxRotY, rotY); + } + + // calculate resolution + var resolution = this.getResolutionForExtent( + [minRotX, minRotY, maxRotX, maxRotY], + [size[0] - padding[1] - padding[3], size[1] - padding[0] - padding[2]]); + resolution = isNaN(resolution) ? minResolution : + Math.max(resolution, minResolution); + if (constrainResolution) { + var constrainedResolution = this.constrainResolution(resolution, 0, 0); + if (!nearest && constrainedResolution < resolution) { + constrainedResolution = this.constrainResolution( + constrainedResolution, -1, 0); + } + resolution = constrainedResolution; + } + + // calculate center + sinAngle = -sinAngle; // go back to original rotation + var centerRotX = (minRotX + maxRotX) / 2; + var centerRotY = (minRotY + maxRotY) / 2; + centerRotX += (padding[1] - padding[3]) / 2 * resolution; + centerRotY += (padding[0] - padding[2]) / 2 * resolution; + var centerX = centerRotX * cosAngle - centerRotY * sinAngle; + var centerY = centerRotY * cosAngle + centerRotX * sinAngle; + var center = [centerX, centerY]; + + if (options.duration !== undefined) { + this.animate({ + resolution: resolution, + center: center, + duration: options.duration, + easing: options.easing + }); + } else { + this.setResolution(resolution); + this.setCenter(center); + } +}; + + +/** + * Center on coordinate and view position. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.Size} size Box pixel size. + * @param {ol.Pixel} position Position on the view to center on. + * @api + */ +ol.View.prototype.centerOn = function(coordinate, size, position) { + // calculate rotated position + var rotation = this.getRotation(); + var cosAngle = Math.cos(-rotation); + var sinAngle = Math.sin(-rotation); + var rotX = coordinate[0] * cosAngle - coordinate[1] * sinAngle; + var rotY = coordinate[1] * cosAngle + coordinate[0] * sinAngle; + var resolution = this.getResolution(); + rotX += (size[0] / 2 - position[0]) * resolution; + rotY += (position[1] - size[1] / 2) * resolution; + + // go back to original angle + sinAngle = -sinAngle; // go back to original rotation + var centerX = rotX * cosAngle - rotY * sinAngle; + var centerY = rotY * cosAngle + rotX * sinAngle; + + this.setCenter([centerX, centerY]); +}; + + +/** + * @return {boolean} Is defined. + */ +ol.View.prototype.isDef = function() { + return !!this.getCenter() && this.getResolution() !== undefined; +}; + + +/** + * Rotate the view around a given coordinate. + * @param {number} rotation New rotation value for the view. + * @param {ol.Coordinate=} opt_anchor The rotation center. + * @api stable + */ +ol.View.prototype.rotate = function(rotation, opt_anchor) { + if (opt_anchor !== undefined) { + var center = this.calculateCenterRotate(rotation, opt_anchor); + this.setCenter(center); + } + this.setRotation(rotation); +}; + + +/** + * Set the center of the current view. + * @param {ol.Coordinate|undefined} center The center of the view. + * @observable + * @api stable + */ +ol.View.prototype.setCenter = function(center) { + this.set(ol.View.Property.CENTER, center); + if (this.getAnimating()) { + this.cancelAnimations(); + } +}; + + +/** + * @param {ol.View.Hint} hint Hint. + * @param {number} delta Delta. + * @return {number} New value. + */ +ol.View.prototype.setHint = function(hint, delta) { + ol.DEBUG && console.assert(0 <= hint && hint < this.hints_.length, + 'illegal hint (%s), must be between 0 and %s', hint, this.hints_.length); + this.hints_[hint] += delta; + ol.DEBUG && console.assert(this.hints_[hint] >= 0, + 'Hint at %s must be positive, was %s', hint, this.hints_[hint]); + this.changed(); + return this.hints_[hint]; +}; + + +/** + * Set the resolution for this view. + * @param {number|undefined} resolution The resolution of the view. + * @observable + * @api stable + */ +ol.View.prototype.setResolution = function(resolution) { + this.set(ol.View.Property.RESOLUTION, resolution); + if (this.getAnimating()) { + this.cancelAnimations(); + } +}; + + +/** + * Set the rotation for this view. + * @param {number} rotation The rotation of the view in radians. + * @observable + * @api stable + */ +ol.View.prototype.setRotation = function(rotation) { + this.set(ol.View.Property.ROTATION, rotation); + if (this.getAnimating()) { + this.cancelAnimations(); + } +}; + + +/** + * Zoom to a specific zoom level. + * @param {number} zoom Zoom level. + * @api stable + */ +ol.View.prototype.setZoom = function(zoom) { + var resolution = this.constrainResolution( + this.maxResolution_, zoom - this.minZoom_, 0); + this.setResolution(resolution); +}; + + +/** + * @param {olx.ViewOptions} options View options. + * @private + * @return {ol.CenterConstraintType} The constraint. + */ +ol.View.createCenterConstraint_ = function(options) { + if (options.extent !== undefined) { + return ol.CenterConstraint.createExtent(options.extent); + } else { + return ol.CenterConstraint.none; + } +}; + + +/** + * @private + * @param {olx.ViewOptions} options View options. + * @return {{constraint: ol.ResolutionConstraintType, maxResolution: number, + * minResolution: number, zoomFactor: number}} The constraint. + */ +ol.View.createResolutionConstraint_ = function(options) { + var resolutionConstraint; + var maxResolution; + var minResolution; + + // TODO: move these to be ol constants + // see https://github.com/openlayers/ol3/issues/2076 + var defaultMaxZoom = 28; + var defaultZoomFactor = 2; + + var minZoom = options.minZoom !== undefined ? + options.minZoom : ol.DEFAULT_MIN_ZOOM; + + var maxZoom = options.maxZoom !== undefined ? + options.maxZoom : defaultMaxZoom; + + var zoomFactor = options.zoomFactor !== undefined ? + options.zoomFactor : defaultZoomFactor; + + if (options.resolutions !== undefined) { + var resolutions = options.resolutions; + maxResolution = resolutions[0]; + minResolution = resolutions[resolutions.length - 1]; + resolutionConstraint = ol.ResolutionConstraint.createSnapToResolutions( + resolutions); + } else { + // calculate the default min and max resolution + var projection = ol.proj.createProjection(options.projection, 'EPSG:3857'); + var extent = projection.getExtent(); + var size = !extent ? + // use an extent that can fit the whole world if need be + 360 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] / + projection.getMetersPerUnit() : + Math.max(ol.extent.getWidth(extent), ol.extent.getHeight(extent)); + + var defaultMaxResolution = size / ol.DEFAULT_TILE_SIZE / Math.pow( + defaultZoomFactor, ol.DEFAULT_MIN_ZOOM); + + var defaultMinResolution = defaultMaxResolution / Math.pow( + defaultZoomFactor, defaultMaxZoom - ol.DEFAULT_MIN_ZOOM); + + // user provided maxResolution takes precedence + maxResolution = options.maxResolution; + if (maxResolution !== undefined) { + minZoom = 0; + } else { + maxResolution = defaultMaxResolution / Math.pow(zoomFactor, minZoom); + } + + // user provided minResolution takes precedence + minResolution = options.minResolution; + if (minResolution === undefined) { + if (options.maxZoom !== undefined) { + if (options.maxResolution !== undefined) { + minResolution = maxResolution / Math.pow(zoomFactor, maxZoom); + } else { + minResolution = defaultMaxResolution / Math.pow(zoomFactor, maxZoom); + } + } else { + minResolution = defaultMinResolution; + } + } + + // given discrete zoom levels, minResolution may be different than provided + maxZoom = minZoom + Math.floor( + Math.log(maxResolution / minResolution) / Math.log(zoomFactor)); + minResolution = maxResolution / Math.pow(zoomFactor, maxZoom - minZoom); + + resolutionConstraint = ol.ResolutionConstraint.createSnapToPower( + zoomFactor, maxResolution, maxZoom - minZoom); + } + return {constraint: resolutionConstraint, maxResolution: maxResolution, + minResolution: minResolution, minZoom: minZoom, zoomFactor: zoomFactor}; +}; + + +/** + * @private + * @param {olx.ViewOptions} options View options. + * @return {ol.RotationConstraintType} Rotation constraint. + */ +ol.View.createRotationConstraint_ = function(options) { + var enableRotation = options.enableRotation !== undefined ? + options.enableRotation : true; + if (enableRotation) { + var constrainRotation = options.constrainRotation; + if (constrainRotation === undefined || constrainRotation === true) { + return ol.RotationConstraint.createSnapToZero(); + } else if (constrainRotation === false) { + return ol.RotationConstraint.none; + } else if (typeof constrainRotation === 'number') { + return ol.RotationConstraint.createSnapToN(constrainRotation); + } else { + ol.DEBUG && console.assert(false, + 'illegal option for constrainRotation (%s)', constrainRotation); + return ol.RotationConstraint.none; + } + } else { + return ol.RotationConstraint.disable; + } +}; + + +/** + * @enum {string} + */ +ol.View.Property = { + CENTER: 'center', + RESOLUTION: 'resolution', + ROTATION: 'rotation' +}; + + +/** + * @enum {number} + */ +ol.View.Hint = { + ANIMATING: 0, + INTERACTING: 1 +}; + +goog.provide('ol.animation'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.coordinate'); +goog.require('ol.easing'); + + +/** + * Deprecated (use {@link ol.View#animate} instead). + * Generate an animated transition that will "bounce" the resolution as it + * approaches the final value. + * @param {olx.animation.BounceOptions} options Bounce options. + * @return {ol.PreRenderFunction} Pre-render function. + * @api + */ +ol.animation.bounce = function(options) { + ol.DEBUG && console.warn('ol.animation.bounce() is deprecated. Use view.animate() instead.'); + var resolution = options.resolution; + var start = options.start ? options.start : Date.now(); + var duration = options.duration !== undefined ? options.duration : 1000; + var easing = options.easing ? + options.easing : ol.easing.upAndDown; + return ( + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + * @return {boolean} Run this function in the next frame. + */ + function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = easing((frameState.time - start) / duration); + var deltaResolution = resolution - frameState.viewState.resolution; + frameState.animate = true; + frameState.viewState.resolution += delta * deltaResolution; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else { + return false; + } + }); +}; + + +/** + * Deprecated (use {@link ol.View#animate} instead). + * Generate an animated transition while updating the view center. + * @param {olx.animation.PanOptions} options Pan options. + * @return {ol.PreRenderFunction} Pre-render function. + * @api + */ +ol.animation.pan = function(options) { + ol.DEBUG && console.warn('ol.animation.pan() is deprecated. Use view.animate() instead.'); + var source = options.source; + var start = options.start ? options.start : Date.now(); + var sourceX = source[0]; + var sourceY = source[1]; + var duration = options.duration !== undefined ? options.duration : 1000; + var easing = options.easing ? + options.easing : ol.easing.inAndOut; + return ( + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + * @return {boolean} Run this function in the next frame. + */ + function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = 1 - easing((frameState.time - start) / duration); + var deltaX = sourceX - frameState.viewState.center[0]; + var deltaY = sourceY - frameState.viewState.center[1]; + frameState.animate = true; + frameState.viewState.center[0] += delta * deltaX; + frameState.viewState.center[1] += delta * deltaY; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else { + return false; + } + }); +}; + + +/** + * Deprecated (use {@link ol.View#animate} instead). + * Generate an animated transition while updating the view rotation. + * @param {olx.animation.RotateOptions} options Rotate options. + * @return {ol.PreRenderFunction} Pre-render function. + * @api + */ +ol.animation.rotate = function(options) { + ol.DEBUG && console.warn('ol.animation.rotate() is deprecated. Use view.animate() instead.'); + var sourceRotation = options.rotation ? options.rotation : 0; + var start = options.start ? options.start : Date.now(); + var duration = options.duration !== undefined ? options.duration : 1000; + var easing = options.easing ? + options.easing : ol.easing.inAndOut; + var anchor = options.anchor ? + options.anchor : null; + + return ( + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + * @return {boolean} Run this function in the next frame. + */ + function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = 1 - easing((frameState.time - start) / duration); + var deltaRotation = + (sourceRotation - frameState.viewState.rotation) * delta; + frameState.animate = true; + frameState.viewState.rotation += deltaRotation; + if (anchor) { + var center = frameState.viewState.center; + ol.coordinate.sub(center, anchor); + ol.coordinate.rotate(center, deltaRotation); + ol.coordinate.add(center, anchor); + } + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else { + return false; + } + }); +}; + + +/** + * Deprecated (use {@link ol.View#animate} instead). + * Generate an animated transition while updating the view resolution. + * @param {olx.animation.ZoomOptions} options Zoom options. + * @return {ol.PreRenderFunction} Pre-render function. + * @api + */ +ol.animation.zoom = function(options) { + ol.DEBUG && console.warn('ol.animation.zoom() is deprecated. Use view.animate() instead.'); + var sourceResolution = options.resolution; + var start = options.start ? options.start : Date.now(); + var duration = options.duration !== undefined ? options.duration : 1000; + var easing = options.easing ? + options.easing : ol.easing.inAndOut; + return ( + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + * @return {boolean} Run this function in the next frame. + */ + function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = 1 - easing((frameState.time - start) / duration); + var deltaResolution = + sourceResolution - frameState.viewState.resolution; + frameState.animate = true; + frameState.viewState.resolution += delta * deltaResolution; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else { + return false; + } + }); +}; + +goog.provide('ol.TileRange'); + + +/** + * A representation of a contiguous block of tiles. A tile range is specified + * by its min/max tile coordinates and is inclusive of coordinates. + * + * @constructor + * @param {number} minX Minimum X. + * @param {number} maxX Maximum X. + * @param {number} minY Minimum Y. + * @param {number} maxY Maximum Y. + * @struct + */ +ol.TileRange = function(minX, maxX, minY, maxY) { + + /** + * @type {number} + */ + this.minX = minX; + + /** + * @type {number} + */ + this.maxX = maxX; + + /** + * @type {number} + */ + this.minY = minY; + + /** + * @type {number} + */ + this.maxY = maxY; + +}; + + +/** + * @param {number} minX Minimum X. + * @param {number} maxX Maximum X. + * @param {number} minY Minimum Y. + * @param {number} maxY Maximum Y. + * @param {ol.TileRange|undefined} tileRange TileRange. + * @return {ol.TileRange} Tile range. + */ +ol.TileRange.createOrUpdate = function(minX, maxX, minY, maxY, tileRange) { + if (tileRange !== undefined) { + tileRange.minX = minX; + tileRange.maxX = maxX; + tileRange.minY = minY; + tileRange.maxY = maxY; + return tileRange; + } else { + return new ol.TileRange(minX, maxX, minY, maxY); + } +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {boolean} Contains tile coordinate. + */ +ol.TileRange.prototype.contains = function(tileCoord) { + return this.containsXY(tileCoord[1], tileCoord[2]); +}; + + +/** + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} Contains. + */ +ol.TileRange.prototype.containsTileRange = function(tileRange) { + return this.minX <= tileRange.minX && tileRange.maxX <= this.maxX && + this.minY <= tileRange.minY && tileRange.maxY <= this.maxY; +}; + + +/** + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @return {boolean} Contains coordinate. + */ +ol.TileRange.prototype.containsXY = function(x, y) { + return this.minX <= x && x <= this.maxX && this.minY <= y && y <= this.maxY; +}; + + +/** + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} Equals. + */ +ol.TileRange.prototype.equals = function(tileRange) { + return this.minX == tileRange.minX && this.minY == tileRange.minY && + this.maxX == tileRange.maxX && this.maxY == tileRange.maxY; +}; + + +/** + * @param {ol.TileRange} tileRange Tile range. + */ +ol.TileRange.prototype.extend = function(tileRange) { + if (tileRange.minX < this.minX) { + this.minX = tileRange.minX; + } + if (tileRange.maxX > this.maxX) { + this.maxX = tileRange.maxX; + } + if (tileRange.minY < this.minY) { + this.minY = tileRange.minY; + } + if (tileRange.maxY > this.maxY) { + this.maxY = tileRange.maxY; + } +}; + + +/** + * @return {number} Height. + */ +ol.TileRange.prototype.getHeight = function() { + return this.maxY - this.minY + 1; +}; + + +/** + * @return {ol.Size} Size. + */ +ol.TileRange.prototype.getSize = function() { + return [this.getWidth(), this.getHeight()]; +}; + + +/** + * @return {number} Width. + */ +ol.TileRange.prototype.getWidth = function() { + return this.maxX - this.minX + 1; +}; + + +/** + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} Intersects. + */ +ol.TileRange.prototype.intersects = function(tileRange) { + return this.minX <= tileRange.maxX && + this.maxX >= tileRange.minX && + this.minY <= tileRange.maxY && + this.maxY >= tileRange.minY; +}; + +goog.provide('ol.size'); + + +/** + * Returns a buffered size. + * @param {ol.Size} size Size. + * @param {number} buffer Buffer. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} The buffered size. + */ +ol.size.buffer = function(size, buffer, opt_size) { + if (opt_size === undefined) { + opt_size = [0, 0]; + } + opt_size[0] = size[0] + 2 * buffer; + opt_size[1] = size[1] + 2 * buffer; + return opt_size; +}; + + +/** + * Determines if a size has a positive area. + * @param {ol.Size} size The size to test. + * @return {boolean} The size has a positive area. + */ +ol.size.hasArea = function(size) { + return size[0] > 0 && size[1] > 0; +}; + + +/** + * Returns a size scaled by a ratio. The result will be an array of integers. + * @param {ol.Size} size Size. + * @param {number} ratio Ratio. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} The scaled size. + */ +ol.size.scale = function(size, ratio, opt_size) { + if (opt_size === undefined) { + opt_size = [0, 0]; + } + opt_size[0] = (size[0] * ratio + 0.5) | 0; + opt_size[1] = (size[1] * ratio + 0.5) | 0; + return opt_size; +}; + + +/** + * Returns an `ol.Size` array for the passed in number (meaning: square) or + * `ol.Size` array. + * (meaning: non-square), + * @param {number|ol.Size} size Width and height. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} Size. + * @api stable + */ +ol.size.toSize = function(size, opt_size) { + if (Array.isArray(size)) { + return size; + } else { + if (opt_size === undefined) { + opt_size = [size, size]; + } else { + opt_size[0] = opt_size[1] = /** @type {number} */ (size); + } + return opt_size; + } +}; + +goog.provide('ol.tilecoord'); + + +/** + * @param {number} z Z. + * @param {number} x X. + * @param {number} y Y. + * @param {ol.TileCoord=} opt_tileCoord Tile coordinate. + * @return {ol.TileCoord} Tile coordinate. + */ +ol.tilecoord.createOrUpdate = function(z, x, y, opt_tileCoord) { + if (opt_tileCoord !== undefined) { + opt_tileCoord[0] = z; + opt_tileCoord[1] = x; + opt_tileCoord[2] = y; + return opt_tileCoord; + } else { + return [z, x, y]; + } +}; + + +/** + * @param {number} z Z. + * @param {number} x X. + * @param {number} y Y. + * @return {string} Key. + */ +ol.tilecoord.getKeyZXY = function(z, x, y) { + return z + '/' + x + '/' + y; +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coord. + * @return {number} Hash. + */ +ol.tilecoord.hash = function(tileCoord) { + return (tileCoord[1] << tileCoord[0]) + tileCoord[2]; +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coord. + * @return {string} Quad key. + */ +ol.tilecoord.quadKey = function(tileCoord) { + var z = tileCoord[0]; + var digits = new Array(z); + var mask = 1 << (z - 1); + var i, charCode; + for (i = 0; i < z; ++i) { + // 48 is charCode for 0 - '0'.charCodeAt(0) + charCode = 48; + if (tileCoord[1] & mask) { + charCode += 1; + } + if (tileCoord[2] & mask) { + charCode += 2; + } + digits[i] = String.fromCharCode(charCode); + mask >>= 1; + } + return digits.join(''); +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid. + * @return {boolean} Tile coordinate is within extent and zoom level range. + */ +ol.tilecoord.withinExtentAndZ = function(tileCoord, tileGrid) { + var z = tileCoord[0]; + var x = tileCoord[1]; + var y = tileCoord[2]; + + if (tileGrid.getMinZoom() > z || z > tileGrid.getMaxZoom()) { + return false; + } + var extent = tileGrid.getExtent(); + var tileRange; + if (!extent) { + tileRange = tileGrid.getFullTileRange(z); + } else { + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + } + if (!tileRange) { + return true; + } else { + return tileRange.containsXY(x, y); + } +}; + +goog.provide('ol.tilegrid.TileGrid'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.TileRange'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.math'); +goog.require('ol.size'); +goog.require('ol.tilecoord'); + + +/** + * @classdesc + * Base class for setting the grid pattern for sources accessing tiled-image + * servers. + * + * @constructor + * @param {olx.tilegrid.TileGridOptions} options Tile grid options. + * @struct + * @api stable + */ +ol.tilegrid.TileGrid = function(options) { + + /** + * @protected + * @type {number} + */ + this.minZoom = options.minZoom !== undefined ? options.minZoom : 0; + + /** + * @private + * @type {!Array.<number>} + */ + this.resolutions_ = options.resolutions; + ol.asserts.assert(ol.array.isSorted(this.resolutions_, function(a, b) { + return b - a; + }, true), 17); // `resolutions` must be sorted in descending order + + /** + * @protected + * @type {number} + */ + this.maxZoom = this.resolutions_.length - 1; + + /** + * @private + * @type {ol.Coordinate} + */ + this.origin_ = options.origin !== undefined ? options.origin : null; + + /** + * @private + * @type {Array.<ol.Coordinate>} + */ + this.origins_ = null; + if (options.origins !== undefined) { + this.origins_ = options.origins; + ol.asserts.assert(this.origins_.length == this.resolutions_.length, + 20); // Number of `origins` and `resolutions` must be equal + } + + var extent = options.extent; + + if (extent !== undefined && + !this.origin_ && !this.origins_) { + this.origin_ = ol.extent.getTopLeft(extent); + } + + ol.asserts.assert( + (!this.origin_ && this.origins_) || (this.origin_ && !this.origins_), + 18); // Either `origin` or `origins` must be configured, never both + + /** + * @private + * @type {Array.<number|ol.Size>} + */ + this.tileSizes_ = null; + if (options.tileSizes !== undefined) { + this.tileSizes_ = options.tileSizes; + ol.asserts.assert(this.tileSizes_.length == this.resolutions_.length, + 19); // Number of `tileSizes` and `resolutions` must be equal + } + + /** + * @private + * @type {number|ol.Size} + */ + this.tileSize_ = options.tileSize !== undefined ? + options.tileSize : + !this.tileSizes_ ? ol.DEFAULT_TILE_SIZE : null; + ol.asserts.assert( + (!this.tileSize_ && this.tileSizes_) || + (this.tileSize_ && !this.tileSizes_), + 22); // Either `tileSize` or `tileSizes` must be configured, never both + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = extent !== undefined ? extent : null; + + + /** + * @private + * @type {Array.<ol.TileRange>} + */ + this.fullTileRanges_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; + + if (options.sizes !== undefined) { + ol.DEBUG && console.assert(options.sizes.length == this.resolutions_.length, + 'number of sizes and resolutions must be equal'); + this.fullTileRanges_ = options.sizes.map(function(size, z) { + ol.DEBUG && console.assert(size[0] !== 0, 'width must not be 0'); + ol.DEBUG && console.assert(size[1] !== 0, 'height must not be 0'); + var tileRange = new ol.TileRange( + Math.min(0, size[0]), Math.max(size[0] - 1, -1), + Math.min(0, size[1]), Math.max(size[1] - 1, -1)); + return tileRange; + }, this); + } else if (extent) { + this.calculateTileRanges_(extent); + } + +}; + + +/** + * @private + * @type {ol.TileCoord} + */ +ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0]; + + +/** + * Call a function with each tile coordinate for a given extent and zoom level. + * + * @param {ol.Extent} extent Extent. + * @param {number} zoom Zoom level. + * @param {function(ol.TileCoord)} callback Function called with each tile coordinate. + * @api + */ +ol.tilegrid.TileGrid.prototype.forEachTileCoord = function(extent, zoom, callback) { + var tileRange = this.getTileRangeForExtentAndZ(extent, zoom); + for (var i = tileRange.minX, ii = tileRange.maxX; i <= ii; ++i) { + for (var j = tileRange.minY, jj = tileRange.maxY; j <= jj; ++j) { + callback([zoom, i, j]); + } + } +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {function(this: T, number, ol.TileRange): boolean} callback Callback. + * @param {T=} opt_this The object to use as `this` in `callback`. + * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. + * @param {ol.Extent=} opt_extent Temporary ol.Extent object. + * @return {boolean} Callback succeeded. + * @template T + */ +ol.tilegrid.TileGrid.prototype.forEachTileCoordParentTileRange = function(tileCoord, callback, opt_this, opt_tileRange, opt_extent) { + var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent); + var z = tileCoord[0] - 1; + while (z >= this.minZoom) { + if (callback.call(opt_this, z, + this.getTileRangeForExtentAndZ(tileCoordExtent, z, opt_tileRange))) { + return true; + } + --z; + } + return false; +}; + + +/** + * Get the extent for this tile grid, if it was configured. + * @return {ol.Extent} Extent. + */ +ol.tilegrid.TileGrid.prototype.getExtent = function() { + return this.extent_; +}; + + +/** + * Get the maximum zoom level for the grid. + * @return {number} Max zoom. + * @api + */ +ol.tilegrid.TileGrid.prototype.getMaxZoom = function() { + return this.maxZoom; +}; + + +/** + * Get the minimum zoom level for the grid. + * @return {number} Min zoom. + * @api + */ +ol.tilegrid.TileGrid.prototype.getMinZoom = function() { + return this.minZoom; +}; + + +/** + * Get the origin for the grid at the given zoom level. + * @param {number} z Z. + * @return {ol.Coordinate} Origin. + * @api stable + */ +ol.tilegrid.TileGrid.prototype.getOrigin = function(z) { + if (this.origin_) { + return this.origin_; + } else { + ol.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom, + 'given z is not in allowed range (%s <= %s <= %s)', + this.minZoom, z, this.maxZoom); + return this.origins_[z]; + } +}; + + +/** + * Get the resolution for the given zoom level. + * @param {number} z Z. + * @return {number} Resolution. + * @api stable + */ +ol.tilegrid.TileGrid.prototype.getResolution = function(z) { + ol.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom, + 'given z is not in allowed range (%s <= %s <= %s)', + this.minZoom, z, this.maxZoom); + return this.resolutions_[z]; +}; + + +/** + * Get the list of resolutions for the tile grid. + * @return {Array.<number>} Resolutions. + * @api stable + */ +ol.tilegrid.TileGrid.prototype.getResolutions = function() { + return this.resolutions_; +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. + * @param {ol.Extent=} opt_extent Temporary ol.Extent object. + * @return {ol.TileRange} Tile range. + */ +ol.tilegrid.TileGrid.prototype.getTileCoordChildTileRange = function(tileCoord, opt_tileRange, opt_extent) { + if (tileCoord[0] < this.maxZoom) { + var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent); + return this.getTileRangeForExtentAndZ( + tileCoordExtent, tileCoord[0] + 1, opt_tileRange); + } else { + return null; + } +}; + + +/** + * @param {number} z Z. + * @param {ol.TileRange} tileRange Tile range. + * @param {ol.Extent=} opt_extent Temporary ol.Extent object. + * @return {ol.Extent} Extent. + */ +ol.tilegrid.TileGrid.prototype.getTileRangeExtent = function(z, tileRange, opt_extent) { + var origin = this.getOrigin(z); + var resolution = this.getResolution(z); + var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); + var minX = origin[0] + tileRange.minX * tileSize[0] * resolution; + var maxX = origin[0] + (tileRange.maxX + 1) * tileSize[0] * resolution; + var minY = origin[1] + tileRange.minY * tileSize[1] * resolution; + var maxY = origin[1] + (tileRange.maxY + 1) * tileSize[1] * resolution; + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {ol.TileRange=} opt_tileRange Temporary tile range object. + * @return {ol.TileRange} Tile range. + */ +ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndResolution = function(extent, resolution, opt_tileRange) { + var tileCoord = ol.tilegrid.TileGrid.tmpTileCoord_; + this.getTileCoordForXYAndResolution_( + extent[0], extent[1], resolution, false, tileCoord); + var minX = tileCoord[1]; + var minY = tileCoord[2]; + this.getTileCoordForXYAndResolution_( + extent[2], extent[3], resolution, true, tileCoord); + return ol.TileRange.createOrUpdate( + minX, tileCoord[1], minY, tileCoord[2], opt_tileRange); +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} z Z. + * @param {ol.TileRange=} opt_tileRange Temporary tile range object. + * @return {ol.TileRange} Tile range. + */ +ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ = function(extent, z, opt_tileRange) { + var resolution = this.getResolution(z); + return this.getTileRangeForExtentAndResolution( + extent, resolution, opt_tileRange); +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {ol.Coordinate} Tile center. + */ +ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) { + var origin = this.getOrigin(tileCoord[0]); + var resolution = this.getResolution(tileCoord[0]); + var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_); + return [ + origin[0] + (tileCoord[1] + 0.5) * tileSize[0] * resolution, + origin[1] + (tileCoord[2] + 0.5) * tileSize[1] * resolution + ]; +}; + + +/** + * Get the extent of a tile coordinate. + * + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Extent=} opt_extent Temporary extent object. + * @return {ol.Extent} Extent. + * @api + */ +ol.tilegrid.TileGrid.prototype.getTileCoordExtent = function(tileCoord, opt_extent) { + var origin = this.getOrigin(tileCoord[0]); + var resolution = this.getResolution(tileCoord[0]); + var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_); + var minX = origin[0] + tileCoord[1] * tileSize[0] * resolution; + var minY = origin[1] + tileCoord[2] * tileSize[1] * resolution; + var maxX = minX + tileSize[0] * resolution; + var maxY = minY + tileSize[1] * resolution; + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); +}; + + +/** + * Get the tile coordinate for the given map coordinate and resolution. This + * method considers that coordinates that intersect tile boundaries should be + * assigned the higher tile coordinate. + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object. + * @return {ol.TileCoord} Tile coordinate. + * @api + */ +ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function(coordinate, resolution, opt_tileCoord) { + return this.getTileCoordForXYAndResolution_( + coordinate[0], coordinate[1], resolution, false, opt_tileCoord); +}; + + +/** + * @param {number} x X. + * @param {number} y Y. + * @param {number} resolution Resolution. + * @param {boolean} reverseIntersectionPolicy Instead of letting edge + * intersections go to the higher tile coordinate, let edge intersections + * go to the lower tile coordinate. + * @param {ol.TileCoord=} opt_tileCoord Temporary ol.TileCoord object. + * @return {ol.TileCoord} Tile coordinate. + * @private + */ +ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function( + x, y, resolution, reverseIntersectionPolicy, opt_tileCoord) { + var z = this.getZForResolution(resolution); + var scale = resolution / this.getResolution(z); + var origin = this.getOrigin(z); + var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); + + var adjustX = reverseIntersectionPolicy ? 0.5 : 0; + var adjustY = reverseIntersectionPolicy ? 0 : 0.5; + var xFromOrigin = Math.floor((x - origin[0]) / resolution + adjustX); + var yFromOrigin = Math.floor((y - origin[1]) / resolution + adjustY); + var tileCoordX = scale * xFromOrigin / tileSize[0]; + var tileCoordY = scale * yFromOrigin / tileSize[1]; + + if (reverseIntersectionPolicy) { + tileCoordX = Math.ceil(tileCoordX) - 1; + tileCoordY = Math.ceil(tileCoordY) - 1; + } else { + tileCoordX = Math.floor(tileCoordX); + tileCoordY = Math.floor(tileCoordY); + } + + return ol.tilecoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord); +}; + + +/** + * Get a tile coordinate given a map coordinate and zoom level. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} z Zoom level. + * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object. + * @return {ol.TileCoord} Tile coordinate. + * @api + */ +ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ = function(coordinate, z, opt_tileCoord) { + var resolution = this.getResolution(z); + return this.getTileCoordForXYAndResolution_( + coordinate[0], coordinate[1], resolution, false, opt_tileCoord); +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {number} Tile resolution. + */ +ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) { + ol.DEBUG && console.assert( + this.minZoom <= tileCoord[0] && tileCoord[0] <= this.maxZoom, + 'z of given tilecoord is not in allowed range (%s <= %s <= %s', + this.minZoom, tileCoord[0], this.maxZoom); + return this.resolutions_[tileCoord[0]]; +}; + + +/** + * Get the tile size for a zoom level. The type of the return value matches the + * `tileSize` or `tileSizes` that the tile grid was configured with. To always + * get an `ol.Size`, run the result through `ol.size.toSize()`. + * @param {number} z Z. + * @return {number|ol.Size} Tile size. + * @api stable + */ +ol.tilegrid.TileGrid.prototype.getTileSize = function(z) { + if (this.tileSize_) { + return this.tileSize_; + } else { + ol.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom, + 'z is not in allowed range (%s <= %s <= %s', + this.minZoom, z, this.maxZoom); + return this.tileSizes_[z]; + } +}; + + +/** + * @param {number} z Zoom level. + * @return {ol.TileRange} Extent tile range for the specified zoom level. + */ +ol.tilegrid.TileGrid.prototype.getFullTileRange = function(z) { + if (!this.fullTileRanges_) { + return null; + } else { + ol.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom, + 'z is not in allowed range (%s <= %s <= %s', + this.minZoom, z, this.maxZoom); + return this.fullTileRanges_[z]; + } +}; + + +/** + * @param {number} resolution Resolution. + * @param {number=} opt_direction If 0, the nearest resolution will be used. + * If 1, the nearest lower resolution will be used. If -1, the nearest + * higher resolution will be used. Default is 0. + * @return {number} Z. + * @api + */ +ol.tilegrid.TileGrid.prototype.getZForResolution = function( + resolution, opt_direction) { + var z = ol.array.linearFindNearest(this.resolutions_, resolution, + opt_direction || 0); + return ol.math.clamp(z, this.minZoom, this.maxZoom); +}; + + +/** + * @param {!ol.Extent} extent Extent for this tile grid. + * @private + */ +ol.tilegrid.TileGrid.prototype.calculateTileRanges_ = function(extent) { + var length = this.resolutions_.length; + var fullTileRanges = new Array(length); + for (var z = this.minZoom; z < length; ++z) { + fullTileRanges[z] = this.getTileRangeForExtentAndZ(extent, z); + } + this.fullTileRanges_ = fullTileRanges; +}; + +goog.provide('ol.tilegrid'); + +goog.require('ol'); +goog.require('ol.size'); +goog.require('ol.extent'); +goog.require('ol.extent.Corner'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.proj.Units'); +goog.require('ol.tilegrid.TileGrid'); + + +/** + * @param {ol.proj.Projection} projection Projection. + * @return {!ol.tilegrid.TileGrid} Default tile grid for the passed projection. + */ +ol.tilegrid.getForProjection = function(projection) { + var tileGrid = projection.getDefaultTileGrid(); + if (!tileGrid) { + tileGrid = ol.tilegrid.createForProjection(projection); + projection.setDefaultTileGrid(tileGrid); + } + return tileGrid; +}; + + +/** + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.TileCoord} Tile coordinate. + */ +ol.tilegrid.wrapX = function(tileGrid, tileCoord, projection) { + var z = tileCoord[0]; + var center = tileGrid.getTileCoordCenter(tileCoord); + var projectionExtent = ol.tilegrid.extentFromProjection(projection); + if (!ol.extent.containsCoordinate(projectionExtent, center)) { + var worldWidth = ol.extent.getWidth(projectionExtent); + var worldsAway = Math.ceil((projectionExtent[0] - center[0]) / worldWidth); + center[0] += worldWidth * worldsAway; + return tileGrid.getTileCoordForCoordAndZ(center, z); + } else { + return tileCoord; + } +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number=} opt_maxZoom Maximum zoom level (default is + * ol.DEFAULT_MAX_ZOOM). + * @param {number|ol.Size=} opt_tileSize Tile size (default uses + * ol.DEFAULT_TILE_SIZE). + * @param {ol.extent.Corner=} opt_corner Extent corner (default is + * ol.extent.Corner.TOP_LEFT). + * @return {!ol.tilegrid.TileGrid} TileGrid instance. + */ +ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_corner) { + var corner = opt_corner !== undefined ? + opt_corner : ol.extent.Corner.TOP_LEFT; + + var resolutions = ol.tilegrid.resolutionsFromExtent( + extent, opt_maxZoom, opt_tileSize); + + return new ol.tilegrid.TileGrid({ + extent: extent, + origin: ol.extent.getCorner(extent, corner), + resolutions: resolutions, + tileSize: opt_tileSize + }); +}; + + +/** + * Creates a tile grid with a standard XYZ tiling scheme. + * @param {olx.tilegrid.XYZOptions=} opt_options Tile grid options. + * @return {ol.tilegrid.TileGrid} Tile grid instance. + * @api + */ +ol.tilegrid.createXYZ = function(opt_options) { + var options = /** @type {olx.tilegrid.TileGridOptions} */ ({}); + ol.obj.assign(options, opt_options !== undefined ? + opt_options : /** @type {olx.tilegrid.XYZOptions} */ ({})); + if (options.extent === undefined) { + options.extent = ol.proj.get('EPSG:3857').getExtent(); + } + options.resolutions = ol.tilegrid.resolutionsFromExtent( + options.extent, options.maxZoom, options.tileSize); + delete options.maxZoom; + + return new ol.tilegrid.TileGrid(options); +}; + + +/** + * Create a resolutions array from an extent. A zoom factor of 2 is assumed. + * @param {ol.Extent} extent Extent. + * @param {number=} opt_maxZoom Maximum zoom level (default is + * ol.DEFAULT_MAX_ZOOM). + * @param {number|ol.Size=} opt_tileSize Tile size (default uses + * ol.DEFAULT_TILE_SIZE). + * @return {!Array.<number>} Resolutions array. + */ +ol.tilegrid.resolutionsFromExtent = function(extent, opt_maxZoom, opt_tileSize) { + var maxZoom = opt_maxZoom !== undefined ? + opt_maxZoom : ol.DEFAULT_MAX_ZOOM; + + var height = ol.extent.getHeight(extent); + var width = ol.extent.getWidth(extent); + + var tileSize = ol.size.toSize(opt_tileSize !== undefined ? + opt_tileSize : ol.DEFAULT_TILE_SIZE); + var maxResolution = Math.max( + width / tileSize[0], height / tileSize[1]); + + var length = maxZoom + 1; + var resolutions = new Array(length); + for (var z = 0; z < length; ++z) { + resolutions[z] = maxResolution / Math.pow(2, z); + } + return resolutions; +}; + + +/** + * @param {ol.ProjectionLike} projection Projection. + * @param {number=} opt_maxZoom Maximum zoom level (default is + * ol.DEFAULT_MAX_ZOOM). + * @param {ol.Size=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). + * @param {ol.extent.Corner=} opt_corner Extent corner (default is + * ol.extent.Corner.BOTTOM_LEFT). + * @return {!ol.tilegrid.TileGrid} TileGrid instance. + */ +ol.tilegrid.createForProjection = function(projection, opt_maxZoom, opt_tileSize, opt_corner) { + var extent = ol.tilegrid.extentFromProjection(projection); + return ol.tilegrid.createForExtent( + extent, opt_maxZoom, opt_tileSize, opt_corner); +}; + + +/** + * Generate a tile grid extent from a projection. If the projection has an + * extent, it is used. If not, a global extent is assumed. + * @param {ol.ProjectionLike} projection Projection. + * @return {ol.Extent} Extent. + */ +ol.tilegrid.extentFromProjection = function(projection) { + projection = ol.proj.get(projection); + var extent = projection.getExtent(); + if (!extent) { + var half = 180 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] / + projection.getMetersPerUnit(); + extent = ol.extent.createOrUpdate(-half, -half, half, half); + } + return extent; +}; + +goog.provide('ol.Attribution'); + +goog.require('ol.TileRange'); +goog.require('ol.math'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * An attribution for a layer source. + * + * Example: + * + * source: new ol.source.OSM({ + * attributions: [ + * new ol.Attribution({ + * html: 'All maps © ' + + * '<a href="https://www.opencyclemap.org/">OpenCycleMap</a>' + * }), + * ol.source.OSM.ATTRIBUTION + * ], + * .. + * + * @constructor + * @param {olx.AttributionOptions} options Attribution options. + * @struct + * @api stable + */ +ol.Attribution = function(options) { + + /** + * @private + * @type {string} + */ + this.html_ = options.html; + + /** + * @private + * @type {Object.<string, Array.<ol.TileRange>>} + */ + this.tileRanges_ = options.tileRanges ? options.tileRanges : null; + +}; + + +/** + * Get the attribution markup. + * @return {string} The attribution HTML. + * @api stable + */ +ol.Attribution.prototype.getHTML = function() { + return this.html_; +}; + + +/** + * @param {Object.<string, ol.TileRange>} tileRanges Tile ranges. + * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {!ol.proj.Projection} projection Projection. + * @return {boolean} Intersects any tile range. + */ +ol.Attribution.prototype.intersectsAnyTileRange = function(tileRanges, tileGrid, projection) { + if (!this.tileRanges_) { + return true; + } + var i, ii, tileRange, zKey; + for (zKey in tileRanges) { + if (!(zKey in this.tileRanges_)) { + continue; + } + tileRange = tileRanges[zKey]; + var testTileRange; + for (i = 0, ii = this.tileRanges_[zKey].length; i < ii; ++i) { + testTileRange = this.tileRanges_[zKey][i]; + if (testTileRange.intersects(tileRange)) { + return true; + } + var extentTileRange = tileGrid.getTileRangeForExtentAndZ( + ol.tilegrid.extentFromProjection(projection), parseInt(zKey, 10)); + var width = extentTileRange.getWidth(); + if (tileRange.minX < extentTileRange.minX || + tileRange.maxX > extentTileRange.maxX) { + if (testTileRange.intersects(new ol.TileRange( + ol.math.modulo(tileRange.minX, width), + ol.math.modulo(tileRange.maxX, width), + tileRange.minY, tileRange.maxY))) { + return true; + } + if (tileRange.getWidth() > width && + testTileRange.intersects(extentTileRange)) { + return true; + } + } + } + } + return false; +}; + +/** + * An implementation of Google Maps' MVCArray. + * @see https://developers.google.com/maps/documentation/javascript/reference + */ + +goog.provide('ol.Collection'); + +goog.require('ol'); +goog.require('ol.events.Event'); +goog.require('ol.Object'); + + +/** + * @classdesc + * An expanded version of standard JS Array, adding convenience methods for + * manipulation. Add and remove changes to the Collection trigger a Collection + * event. Note that this does not cover changes to the objects _within_ the + * Collection; they trigger events on the appropriate object, not on the + * Collection as a whole. + * + * @constructor + * @extends {ol.Object} + * @fires ol.Collection.Event + * @param {!Array.<T>=} opt_array Array. + * @template T + * @api stable + */ +ol.Collection = function(opt_array) { + + ol.Object.call(this); + + /** + * @private + * @type {!Array.<T>} + */ + this.array_ = opt_array ? opt_array : []; + + this.updateLength_(); + +}; +ol.inherits(ol.Collection, ol.Object); + + +/** + * Remove all elements from the collection. + * @api stable + */ +ol.Collection.prototype.clear = function() { + while (this.getLength() > 0) { + this.pop(); + } +}; + + +/** + * Add elements to the collection. This pushes each item in the provided array + * to the end of the collection. + * @param {!Array.<T>} arr Array. + * @return {ol.Collection.<T>} This collection. + * @api stable + */ +ol.Collection.prototype.extend = function(arr) { + var i, ii; + for (i = 0, ii = arr.length; i < ii; ++i) { + this.push(arr[i]); + } + return this; +}; + + +/** + * Iterate over each element, calling the provided callback. + * @param {function(this: S, T, number, Array.<T>): *} f The function to call + * for every element. This function takes 3 arguments (the element, the + * index and the array). The return value is ignored. + * @param {S=} opt_this The object to use as `this` in `f`. + * @template S + * @api stable + */ +ol.Collection.prototype.forEach = function(f, opt_this) { + this.array_.forEach(f, opt_this); +}; + + +/** + * Get a reference to the underlying Array object. Warning: if the array + * is mutated, no events will be dispatched by the collection, and the + * collection's "length" property won't be in sync with the actual length + * of the array. + * @return {!Array.<T>} Array. + * @api stable + */ +ol.Collection.prototype.getArray = function() { + return this.array_; +}; + + +/** + * Get the element at the provided index. + * @param {number} index Index. + * @return {T} Element. + * @api stable + */ +ol.Collection.prototype.item = function(index) { + return this.array_[index]; +}; + + +/** + * Get the length of this collection. + * @return {number} The length of the array. + * @observable + * @api stable + */ +ol.Collection.prototype.getLength = function() { + return /** @type {number} */ (this.get(ol.Collection.Property.LENGTH)); +}; + + +/** + * Insert an element at the provided index. + * @param {number} index Index. + * @param {T} elem Element. + * @api stable + */ +ol.Collection.prototype.insertAt = function(index, elem) { + this.array_.splice(index, 0, elem); + this.updateLength_(); + this.dispatchEvent( + new ol.Collection.Event(ol.Collection.EventType.ADD, elem)); +}; + + +/** + * Remove the last element of the collection and return it. + * Return `undefined` if the collection is empty. + * @return {T|undefined} Element. + * @api stable + */ +ol.Collection.prototype.pop = function() { + return this.removeAt(this.getLength() - 1); +}; + + +/** + * Insert the provided element at the end of the collection. + * @param {T} elem Element. + * @return {number} New length of the collection. + * @api stable + */ +ol.Collection.prototype.push = function(elem) { + var n = this.getLength(); + this.insertAt(n, elem); + return this.getLength(); +}; + + +/** + * Remove the first occurrence of an element from the collection. + * @param {T} elem Element. + * @return {T|undefined} The removed element or undefined if none found. + * @api stable + */ +ol.Collection.prototype.remove = function(elem) { + var arr = this.array_; + var i, ii; + for (i = 0, ii = arr.length; i < ii; ++i) { + if (arr[i] === elem) { + return this.removeAt(i); + } + } + return undefined; +}; + + +/** + * Remove the element at the provided index and return it. + * Return `undefined` if the collection does not contain this index. + * @param {number} index Index. + * @return {T|undefined} Value. + * @api stable + */ +ol.Collection.prototype.removeAt = function(index) { + var prev = this.array_[index]; + this.array_.splice(index, 1); + this.updateLength_(); + this.dispatchEvent( + new ol.Collection.Event(ol.Collection.EventType.REMOVE, prev)); + return prev; +}; + + +/** + * Set the element at the provided index. + * @param {number} index Index. + * @param {T} elem Element. + * @api stable + */ +ol.Collection.prototype.setAt = function(index, elem) { + var n = this.getLength(); + if (index < n) { + var prev = this.array_[index]; + this.array_[index] = elem; + this.dispatchEvent( + new ol.Collection.Event(ol.Collection.EventType.REMOVE, prev)); + this.dispatchEvent( + new ol.Collection.Event(ol.Collection.EventType.ADD, elem)); + } else { + var j; + for (j = n; j < index; ++j) { + this.insertAt(j, undefined); + } + this.insertAt(index, elem); + } +}; + + +/** + * @private + */ +ol.Collection.prototype.updateLength_ = function() { + this.set(ol.Collection.Property.LENGTH, this.array_.length); +}; + + +/** + * @enum {string} + */ +ol.Collection.Property = { + LENGTH: 'length' +}; + + +/** + * @enum {string} + */ +ol.Collection.EventType = { + /** + * Triggered when an item is added to the collection. + * @event ol.Collection.Event#add + * @api stable + */ + ADD: 'add', + /** + * Triggered when an item is removed from the collection. + * @event ol.Collection.Event#remove + * @api stable + */ + REMOVE: 'remove' +}; + + +/** + * @classdesc + * Events emitted by {@link ol.Collection} instances are instances of this + * type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.Collection.Event} + * @param {ol.Collection.EventType} type Type. + * @param {*=} opt_element Element. + */ +ol.Collection.Event = function(type, opt_element) { + + ol.events.Event.call(this, type); + + /** + * The element that is added to or removed from the collection. + * @type {*} + * @api stable + */ + this.element = opt_element; + +}; +ol.inherits(ol.Collection.Event, ol.events.Event); + +goog.provide('ol.color'); + +goog.require('ol.asserts'); +goog.require('ol.math'); + + +/** + * This RegExp matches # followed by 3 or 6 hex digits. + * @const + * @type {RegExp} + * @private + */ +ol.color.HEX_COLOR_RE_ = /^#(?:[0-9a-f]{3}){1,2}$/i; + + +/** + * Regular expression for matching potential named color style strings. + * @const + * @type {RegExp} + * @private + */ +ol.color.NAMED_COLOR_RE_ = /^([a-z]*)$/i; + + +/** + * Return the color as an array. This function maintains a cache of calculated + * arrays which means the result should not be modified. + * @param {ol.Color|string} color Color. + * @return {ol.Color} Color. + * @api + */ +ol.color.asArray = function(color) { + if (Array.isArray(color)) { + return color; + } else { + return ol.color.fromString(/** @type {string} */ (color)); + } +}; + + +/** + * Return the color as an rgba string. + * @param {ol.Color|string} color Color. + * @return {string} Rgba string. + * @api + */ +ol.color.asString = function(color) { + if (typeof color === 'string') { + return color; + } else { + return ol.color.toString(color); + } +}; + +/** + * Return named color as an rgba string. + * @param {string} color Named color. + * @return {string} Rgb string. + */ +ol.color.fromNamed = function(color) { + var el = document.createElement('div'); + el.style.color = color; + document.body.appendChild(el); + var rgb = getComputedStyle(el).color; + document.body.removeChild(el); + return rgb; +}; + + +/** + * @param {string} s String. + * @return {ol.Color} Color. + */ +ol.color.fromString = ( + function() { + + // We maintain a small cache of parsed strings. To provide cheap LRU-like + // semantics, whenever the cache grows too large we simply delete an + // arbitrary 25% of the entries. + + /** + * @const + * @type {number} + */ + var MAX_CACHE_SIZE = 1024; + + /** + * @type {Object.<string, ol.Color>} + */ + var cache = {}; + + /** + * @type {number} + */ + var cacheSize = 0; + + return ( + /** + * @param {string} s String. + * @return {ol.Color} Color. + */ + function(s) { + var color; + if (cache.hasOwnProperty(s)) { + color = cache[s]; + } else { + if (cacheSize >= MAX_CACHE_SIZE) { + var i = 0; + var key; + for (key in cache) { + if ((i++ & 3) === 0) { + delete cache[key]; + --cacheSize; + } + } + } + color = ol.color.fromStringInternal_(s); + cache[s] = color; + ++cacheSize; + } + return color; + }); + + })(); + + +/** + * @param {string} s String. + * @private + * @return {ol.Color} Color. + */ +ol.color.fromStringInternal_ = function(s) { + var r, g, b, a, color, parts; + + if (ol.color.NAMED_COLOR_RE_.exec(s)) { + s = ol.color.fromNamed(s); + } + + if (ol.color.HEX_COLOR_RE_.exec(s)) { // hex + var n = s.length - 1; // number of hex digits + ol.asserts.assert(n == 3 || n == 6, 54); // Hex color should have 3 or 6 digits + var d = n == 3 ? 1 : 2; // number of digits per channel + r = parseInt(s.substr(1 + 0 * d, d), 16); + g = parseInt(s.substr(1 + 1 * d, d), 16); + b = parseInt(s.substr(1 + 2 * d, d), 16); + if (d == 1) { + r = (r << 4) + r; + g = (g << 4) + g; + b = (b << 4) + b; + } + a = 1; + color = [r, g, b, a]; + } else if (s.indexOf('rgba(') == 0) { // rgba() + parts = s.slice(5, -1).split(',').map(Number); + color = ol.color.normalize(parts); + } else if (s.indexOf('rgb(') == 0) { // rgb() + parts = s.slice(4, -1).split(',').map(Number); + parts.push(1); + color = ol.color.normalize(parts); + } else { + ol.asserts.assert(false, 14); // Invalid color + } + return /** @type {ol.Color} */ (color); +}; + + +/** + * @param {ol.Color} color Color. + * @param {ol.Color=} opt_color Color. + * @return {ol.Color} Clamped color. + */ +ol.color.normalize = function(color, opt_color) { + var result = opt_color || []; + result[0] = ol.math.clamp((color[0] + 0.5) | 0, 0, 255); + result[1] = ol.math.clamp((color[1] + 0.5) | 0, 0, 255); + result[2] = ol.math.clamp((color[2] + 0.5) | 0, 0, 255); + result[3] = ol.math.clamp(color[3], 0, 1); + return result; +}; + + +/** + * @param {ol.Color} color Color. + * @return {string} String. + */ +ol.color.toString = function(color) { + var r = color[0]; + if (r != (r | 0)) { + r = (r + 0.5) | 0; + } + var g = color[1]; + if (g != (g | 0)) { + g = (g + 0.5) | 0; + } + var b = color[2]; + if (b != (b | 0)) { + b = (b + 0.5) | 0; + } + var a = color[3] === undefined ? 1 : color[3]; + return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; +}; + +goog.provide('ol.colorlike'); + +goog.require('ol.color'); + + +/** + * @param {ol.Color|ol.ColorLike} color Color. + * @return {ol.ColorLike} The color as an ol.ColorLike + * @api + */ +ol.colorlike.asColorLike = function(color) { + if (ol.colorlike.isColorLike(color)) { + return /** @type {string|CanvasPattern|CanvasGradient} */ (color); + } else { + return ol.color.asString(/** @type {ol.Color} */ (color)); + } +}; + + +/** + * @param {?} color The value that is potentially an ol.ColorLike + * @return {boolean} Whether the color is an ol.ColorLike + */ +ol.colorlike.isColorLike = function(color) { + return ( + typeof color === 'string' || + color instanceof CanvasPattern || + color instanceof CanvasGradient + ); +}; + +goog.provide('ol.dom'); + + +/** + * Create an html canvas element and returns its 2d context. + * @param {number=} opt_width Canvas width. + * @param {number=} opt_height Canvas height. + * @return {CanvasRenderingContext2D} The context. + */ +ol.dom.createCanvasContext2D = function(opt_width, opt_height) { + var canvas = document.createElement('CANVAS'); + if (opt_width) { + canvas.width = opt_width; + } + if (opt_height) { + canvas.height = opt_height; + } + return canvas.getContext('2d'); +}; + + +/** + * Get the current computed width for the given element including margin, + * padding and border. + * Equivalent to jQuery's `$(el).outerWidth(true)`. + * @param {!Element} element Element. + * @return {number} The width. + */ +ol.dom.outerWidth = function(element) { + var width = element.offsetWidth; + var style = element.currentStyle || getComputedStyle(element); + width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10); + + return width; +}; + + +/** + * Get the current computed height for the given element including margin, + * padding and border. + * Equivalent to jQuery's `$(el).outerHeight(true)`. + * @param {!Element} element Element. + * @return {number} The height. + */ +ol.dom.outerHeight = function(element) { + var height = element.offsetHeight; + var style = element.currentStyle || getComputedStyle(element); + height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10); + + return height; +}; + +/** + * @param {Node} newNode Node to replace old node + * @param {Node} oldNode The node to be replaced + */ +ol.dom.replaceNode = function(newNode, oldNode) { + var parent = oldNode.parentNode; + if (parent) { + parent.replaceChild(newNode, oldNode); + } +}; + +/** + * @param {Node} node The node to remove. + * @returns {Node} The node that was removed or null. + */ +ol.dom.removeNode = function(node) { + return node && node.parentNode ? node.parentNode.removeChild(node) : null; +}; + +/** + * @param {Node} node The node to remove the children from. + */ +ol.dom.removeChildren = function(node) { + while (node.lastChild) { + node.removeChild(node.lastChild); + } +}; + +goog.provide('ol.MapEvent'); + +goog.require('ol'); +goog.require('ol.events.Event'); + + +/** + * @classdesc + * Events emitted as map events are instances of this type. + * See {@link ol.Map} for which events trigger a map event. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.MapEvent} + * @param {string} type Event type. + * @param {ol.Map} map Map. + * @param {?olx.FrameState=} opt_frameState Frame state. + */ +ol.MapEvent = function(type, map, opt_frameState) { + + ol.events.Event.call(this, type); + + /** + * The map where the event occurred. + * @type {ol.Map} + * @api stable + */ + this.map = map; + + /** + * The frame state at the time of the event. + * @type {?olx.FrameState} + * @api + */ + this.frameState = opt_frameState !== undefined ? opt_frameState : null; + +}; +ol.inherits(ol.MapEvent, ol.events.Event); + + +/** + * @enum {string} + */ +ol.MapEvent.Type = { + + /** + * Triggered after a map frame is rendered. + * @event ol.MapEvent#postrender + * @api + */ + POSTRENDER: 'postrender', + + /** + * Triggered after the map is moved. + * @event ol.MapEvent#moveend + * @api stable + */ + MOVEEND: 'moveend' + +}; + +goog.provide('ol.control.Control'); + +goog.require('ol.events'); +goog.require('ol'); +goog.require('ol.MapEvent'); +goog.require('ol.Object'); +goog.require('ol.dom'); + + +/** + * @classdesc + * A control is a visible widget with a DOM element in a fixed position on the + * screen. They can involve user input (buttons), or be informational only; + * the position is determined using CSS. By default these are placed in the + * container with CSS class name `ol-overlaycontainer-stopevent`, but can use + * any outside DOM element. + * + * This is the base class for controls. You can use it for simple custom + * controls by creating the element with listeners, creating an instance: + * ```js + * var myControl = new ol.control.Control({element: myElement}); + * ``` + * and then adding this to the map. + * + * The main advantage of having this as a control rather than a simple separate + * DOM element is that preventing propagation is handled for you. Controls + * will also be `ol.Object`s in a `ol.Collection`, so you can use their + * methods. + * + * You can also extend this base for your own control class. See + * examples/custom-controls for an example of how to do this. + * + * @constructor + * @extends {ol.Object} + * @implements {oli.control.Control} + * @param {olx.control.ControlOptions} options Control options. + * @api stable + */ +ol.control.Control = function(options) { + + ol.Object.call(this); + + /** + * @protected + * @type {Element} + */ + this.element = options.element ? options.element : null; + + /** + * @private + * @type {Element} + */ + this.target_ = null; + + /** + * @private + * @type {ol.Map} + */ + this.map_ = null; + + /** + * @protected + * @type {!Array.<ol.EventsKey>} + */ + this.listenerKeys = []; + + /** + * @type {function(ol.MapEvent)} + */ + this.render = options.render ? options.render : ol.nullFunction; + + if (options.target) { + this.setTarget(options.target); + } + +}; +ol.inherits(ol.control.Control, ol.Object); + + +/** + * @inheritDoc + */ +ol.control.Control.prototype.disposeInternal = function() { + ol.dom.removeNode(this.element); + ol.Object.prototype.disposeInternal.call(this); +}; + + +/** + * Get the map associated with this control. + * @return {ol.Map} Map. + * @api stable + */ +ol.control.Control.prototype.getMap = function() { + return this.map_; +}; + + +/** + * Remove the control from its current map and attach it to the new map. + * Subclasses may set up event handlers to get notified about changes to + * the map here. + * @param {ol.Map} map Map. + * @api stable + */ +ol.control.Control.prototype.setMap = function(map) { + if (this.map_) { + ol.dom.removeNode(this.element); + } + for (var i = 0, ii = this.listenerKeys.length; i < ii; ++i) { + ol.events.unlistenByKey(this.listenerKeys[i]); + } + this.listenerKeys.length = 0; + this.map_ = map; + if (this.map_) { + var target = this.target_ ? + this.target_ : map.getOverlayContainerStopEvent(); + target.appendChild(this.element); + if (this.render !== ol.nullFunction) { + this.listenerKeys.push(ol.events.listen(map, + ol.MapEvent.Type.POSTRENDER, this.render, this)); + } + map.render(); + } +}; + + +/** + * This function is used to set a target element for the control. It has no + * effect if it is called after the control has been added to the map (i.e. + * after `setMap` is called on the control). If no `target` is set in the + * options passed to the control constructor and if `setTarget` is not called + * then the control is added to the map's overlay container. + * @param {Element|string} target Target. + * @api + */ +ol.control.Control.prototype.setTarget = function(target) { + this.target_ = typeof target === 'string' ? + document.getElementById(target) : + target; +}; + +goog.provide('ol.css'); + + +/** + * The CSS class for hidden feature. + * + * @const + * @type {string} + */ +ol.css.CLASS_HIDDEN = 'ol-hidden'; + + +/** + * The CSS class that we'll give the DOM elements to have them unselectable. + * + * @const + * @type {string} + */ +ol.css.CLASS_UNSELECTABLE = 'ol-unselectable'; + + +/** + * The CSS class for unsupported feature. + * + * @const + * @type {string} + */ +ol.css.CLASS_UNSUPPORTED = 'ol-unsupported'; + + +/** + * The CSS class for controls. + * + * @const + * @type {string} + */ +ol.css.CLASS_CONTROL = 'ol-control'; + +// FIXME handle date line wrap + +goog.provide('ol.control.Attribution'); + +goog.require('ol'); +goog.require('ol.dom'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.obj'); + + +/** + * @classdesc + * Control to show all the attributions associated with the layer sources + * in the map. This control is one of the default controls included in maps. + * By default it will show in the bottom right portion of the map, but this can + * be changed by using a css selector for `.ol-attribution`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.AttributionOptions=} opt_options Attribution options. + * @api stable + */ +ol.control.Attribution = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {Element} + */ + this.ulElement_ = document.createElement('UL'); + + /** + * @private + * @type {Element} + */ + this.logoLi_ = document.createElement('LI'); + + this.ulElement_.appendChild(this.logoLi_); + this.logoLi_.style.display = 'none'; + + /** + * @private + * @type {boolean} + */ + this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true; + + /** + * @private + * @type {boolean} + */ + this.collapsible_ = options.collapsible !== undefined ? + options.collapsible : true; + + if (!this.collapsible_) { + this.collapsed_ = false; + } + + var className = options.className !== undefined ? options.className : 'ol-attribution'; + + var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Attributions'; + + var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00BB'; + + if (typeof collapseLabel === 'string') { + /** + * @private + * @type {Node} + */ + this.collapseLabel_ = document.createElement('span'); + this.collapseLabel_.textContent = collapseLabel; + } else { + this.collapseLabel_ = collapseLabel; + } + + var label = options.label !== undefined ? options.label : 'i'; + + if (typeof label === 'string') { + /** + * @private + * @type {Node} + */ + this.label_ = document.createElement('span'); + this.label_.textContent = label; + } else { + this.label_ = label; + } + + + var activeLabel = (this.collapsible_ && !this.collapsed_) ? + this.collapseLabel_ : this.label_; + var button = document.createElement('button'); + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild(activeLabel); + + ol.events.listen(button, ol.events.EventType.CLICK, this.handleClick_, this); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL + + (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') + + (this.collapsible_ ? '' : ' ol-uncollapsible'); + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(this.ulElement_); + element.appendChild(button); + + var render = options.render ? options.render : ol.control.Attribution.render; + + ol.control.Control.call(this, { + element: element, + render: render, + target: options.target + }); + + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = true; + + /** + * @private + * @type {Object.<string, Element>} + */ + this.attributionElements_ = {}; + + /** + * @private + * @type {Object.<string, boolean>} + */ + this.attributionElementRenderedVisible_ = {}; + + /** + * @private + * @type {Object.<string, Element>} + */ + this.logoElements_ = {}; + +}; +ol.inherits(ol.control.Attribution, ol.control.Control); + + +/** + * @param {?olx.FrameState} frameState Frame state. + * @return {Array.<Object.<string, ol.Attribution>>} Attributions. + */ +ol.control.Attribution.prototype.getSourceAttributions = function(frameState) { + var i, ii, j, jj, tileRanges, source, sourceAttribution, + sourceAttributionKey, sourceAttributions, sourceKey; + var intersectsTileRange; + var layerStatesArray = frameState.layerStatesArray; + /** @type {Object.<string, ol.Attribution>} */ + var attributions = ol.obj.assign({}, frameState.attributions); + /** @type {Object.<string, ol.Attribution>} */ + var hiddenAttributions = {}; + var uniqueAttributions = {}; + var projection = /** @type {!ol.proj.Projection} */ (frameState.viewState.projection); + for (i = 0, ii = layerStatesArray.length; i < ii; i++) { + source = layerStatesArray[i].layer.getSource(); + if (!source) { + continue; + } + sourceKey = ol.getUid(source).toString(); + sourceAttributions = source.getAttributions(); + if (!sourceAttributions) { + continue; + } + for (j = 0, jj = sourceAttributions.length; j < jj; j++) { + sourceAttribution = sourceAttributions[j]; + sourceAttributionKey = ol.getUid(sourceAttribution).toString(); + if (sourceAttributionKey in attributions) { + continue; + } + tileRanges = frameState.usedTiles[sourceKey]; + if (tileRanges) { + var tileGrid = /** @type {ol.source.Tile} */ (source).getTileGridForProjection(projection); + intersectsTileRange = sourceAttribution.intersectsAnyTileRange( + tileRanges, tileGrid, projection); + } else { + intersectsTileRange = false; + } + if (intersectsTileRange) { + if (sourceAttributionKey in hiddenAttributions) { + delete hiddenAttributions[sourceAttributionKey]; + } + var html = sourceAttribution.getHTML(); + if (!(html in uniqueAttributions)) { + uniqueAttributions[html] = true; + attributions[sourceAttributionKey] = sourceAttribution; + } + } else { + hiddenAttributions[sourceAttributionKey] = sourceAttribution; + } + } + } + return [attributions, hiddenAttributions]; +}; + + +/** + * Update the attribution element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.Attribution} + * @api + */ +ol.control.Attribution.render = function(mapEvent) { + this.updateElement_(mapEvent.frameState); +}; + + +/** + * @private + * @param {?olx.FrameState} frameState Frame state. + */ +ol.control.Attribution.prototype.updateElement_ = function(frameState) { + + if (!frameState) { + if (this.renderedVisible_) { + this.element.style.display = 'none'; + this.renderedVisible_ = false; + } + return; + } + + var attributions = this.getSourceAttributions(frameState); + /** @type {Object.<string, ol.Attribution>} */ + var visibleAttributions = attributions[0]; + /** @type {Object.<string, ol.Attribution>} */ + var hiddenAttributions = attributions[1]; + + var attributionElement, attributionKey; + for (attributionKey in this.attributionElements_) { + if (attributionKey in visibleAttributions) { + if (!this.attributionElementRenderedVisible_[attributionKey]) { + this.attributionElements_[attributionKey].style.display = ''; + this.attributionElementRenderedVisible_[attributionKey] = true; + } + delete visibleAttributions[attributionKey]; + } else if (attributionKey in hiddenAttributions) { + if (this.attributionElementRenderedVisible_[attributionKey]) { + this.attributionElements_[attributionKey].style.display = 'none'; + delete this.attributionElementRenderedVisible_[attributionKey]; + } + delete hiddenAttributions[attributionKey]; + } else { + ol.dom.removeNode(this.attributionElements_[attributionKey]); + delete this.attributionElements_[attributionKey]; + delete this.attributionElementRenderedVisible_[attributionKey]; + } + } + for (attributionKey in visibleAttributions) { + attributionElement = document.createElement('LI'); + attributionElement.innerHTML = + visibleAttributions[attributionKey].getHTML(); + this.ulElement_.appendChild(attributionElement); + this.attributionElements_[attributionKey] = attributionElement; + this.attributionElementRenderedVisible_[attributionKey] = true; + } + for (attributionKey in hiddenAttributions) { + attributionElement = document.createElement('LI'); + attributionElement.innerHTML = + hiddenAttributions[attributionKey].getHTML(); + attributionElement.style.display = 'none'; + this.ulElement_.appendChild(attributionElement); + this.attributionElements_[attributionKey] = attributionElement; + } + + var renderVisible = + !ol.obj.isEmpty(this.attributionElementRenderedVisible_) || + !ol.obj.isEmpty(frameState.logos); + if (this.renderedVisible_ != renderVisible) { + this.element.style.display = renderVisible ? '' : 'none'; + this.renderedVisible_ = renderVisible; + } + if (renderVisible && + ol.obj.isEmpty(this.attributionElementRenderedVisible_)) { + this.element.classList.add('ol-logo-only'); + } else { + this.element.classList.remove('ol-logo-only'); + } + + this.insertLogos_(frameState); + +}; + + +/** + * @param {?olx.FrameState} frameState Frame state. + * @private + */ +ol.control.Attribution.prototype.insertLogos_ = function(frameState) { + + var logo; + var logos = frameState.logos; + var logoElements = this.logoElements_; + + for (logo in logoElements) { + if (!(logo in logos)) { + ol.dom.removeNode(logoElements[logo]); + delete logoElements[logo]; + } + } + + var image, logoElement, logoKey; + for (logoKey in logos) { + var logoValue = logos[logoKey]; + if (logoValue instanceof HTMLElement) { + this.logoLi_.appendChild(logoValue); + logoElements[logoKey] = logoValue; + } + if (!(logoKey in logoElements)) { + image = new Image(); + image.src = logoKey; + if (logoValue === '') { + logoElement = image; + } else { + logoElement = document.createElement('a'); + logoElement.href = logoValue; + logoElement.appendChild(image); + } + this.logoLi_.appendChild(logoElement); + logoElements[logoKey] = logoElement; + } + } + + this.logoLi_.style.display = !ol.obj.isEmpty(logos) ? '' : 'none'; + +}; + + +/** + * @param {Event} event The event to handle + * @private + */ +ol.control.Attribution.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleToggle_(); +}; + + +/** + * @private + */ +ol.control.Attribution.prototype.handleToggle_ = function() { + this.element.classList.toggle('ol-collapsed'); + if (this.collapsed_) { + ol.dom.replaceNode(this.collapseLabel_, this.label_); + } else { + ol.dom.replaceNode(this.label_, this.collapseLabel_); + } + this.collapsed_ = !this.collapsed_; +}; + + +/** + * Return `true` if the attribution is collapsible, `false` otherwise. + * @return {boolean} True if the widget is collapsible. + * @api stable + */ +ol.control.Attribution.prototype.getCollapsible = function() { + return this.collapsible_; +}; + + +/** + * Set whether the attribution should be collapsible. + * @param {boolean} collapsible True if the widget is collapsible. + * @api stable + */ +ol.control.Attribution.prototype.setCollapsible = function(collapsible) { + if (this.collapsible_ === collapsible) { + return; + } + this.collapsible_ = collapsible; + this.element.classList.toggle('ol-uncollapsible'); + if (!collapsible && this.collapsed_) { + this.handleToggle_(); + } +}; + + +/** + * Collapse or expand the attribution according to the passed parameter. Will + * not do anything if the attribution isn't collapsible or if the current + * collapsed state is already the one requested. + * @param {boolean} collapsed True if the widget is collapsed. + * @api stable + */ +ol.control.Attribution.prototype.setCollapsed = function(collapsed) { + if (!this.collapsible_ || this.collapsed_ === collapsed) { + return; + } + this.handleToggle_(); +}; + + +/** + * Return `true` when the attribution is currently collapsed or `false` + * otherwise. + * @return {boolean} True if the widget is collapsed. + * @api stable + */ +ol.control.Attribution.prototype.getCollapsed = function() { + return this.collapsed_; +}; + +goog.provide('ol.control.FullScreen'); + +goog.require('ol'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); + + +/** + * @classdesc + * Provides a button that when clicked fills up the full screen with the map. + * The full screen source element is by default the element containing the map viewport unless + * overriden by providing the `source` option. In which case, the dom + * element introduced using this parameter will be displayed in full screen. + * + * When in full screen mode, a close button is shown to exit full screen mode. + * The [Fullscreen API](http://www.w3.org/TR/fullscreen/) is used to + * toggle the map in full screen mode. + * + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.FullScreenOptions=} opt_options Options. + * @api stable + */ +ol.control.FullScreen = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {string} + */ + this.cssClassName_ = options.className !== undefined ? options.className : + 'ol-full-screen'; + + var label = options.label !== undefined ? options.label : '\u2922'; + + /** + * @private + * @type {Node} + */ + this.labelNode_ = typeof label === 'string' ? + document.createTextNode(label) : label; + + var labelActive = options.labelActive !== undefined ? options.labelActive : '\u00d7'; + + /** + * @private + * @type {Node} + */ + this.labelActiveNode_ = typeof labelActive === 'string' ? + document.createTextNode(labelActive) : labelActive; + + var tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen'; + var button = document.createElement('button'); + button.className = this.cssClassName_ + '-' + ol.control.FullScreen.isFullScreen(); + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild(this.labelNode_); + + ol.events.listen(button, ol.events.EventType.CLICK, + this.handleClick_, this); + + var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE + + ' ' + ol.css.CLASS_CONTROL + ' ' + + (!ol.control.FullScreen.isFullScreenSupported() ? ol.css.CLASS_UNSUPPORTED : ''); + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(button); + + ol.control.Control.call(this, { + element: element, + target: options.target + }); + + /** + * @private + * @type {boolean} + */ + this.keys_ = options.keys !== undefined ? options.keys : false; + + /** + * @private + * @type {Element|string|undefined} + */ + this.source_ = options.source; + +}; +ol.inherits(ol.control.FullScreen, ol.control.Control); + + +/** + * @param {Event} event The event to handle + * @private + */ +ol.control.FullScreen.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleFullScreen_(); +}; + + +/** + * @private + */ +ol.control.FullScreen.prototype.handleFullScreen_ = function() { + if (!ol.control.FullScreen.isFullScreenSupported()) { + return; + } + var map = this.getMap(); + if (!map) { + return; + } + if (ol.control.FullScreen.isFullScreen()) { + ol.control.FullScreen.exitFullScreen(); + } else { + var element; + if (this.source_) { + element = typeof this.source_ === 'string' ? + document.getElementById(this.source_) : + this.source_; + } else { + element = map.getTargetElement(); + } + if (this.keys_) { + ol.control.FullScreen.requestFullScreenWithKeys(element); + + } else { + ol.control.FullScreen.requestFullScreen(element); + } + } +}; + + +/** + * @private + */ +ol.control.FullScreen.prototype.handleFullScreenChange_ = function() { + var button = this.element.firstElementChild; + var map = this.getMap(); + if (ol.control.FullScreen.isFullScreen()) { + button.className = this.cssClassName_ + '-true'; + ol.dom.replaceNode(this.labelActiveNode_, this.labelNode_); + } else { + button.className = this.cssClassName_ + '-false'; + ol.dom.replaceNode(this.labelNode_, this.labelActiveNode_); + } + if (map) { + map.updateSize(); + } +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.control.FullScreen.prototype.setMap = function(map) { + ol.control.Control.prototype.setMap.call(this, map); + if (map) { + this.listenerKeys.push(ol.events.listen(document, + ol.control.FullScreen.getChangeType_(), + this.handleFullScreenChange_, this) + ); + } +}; + +/** + * @return {boolean} Fullscreen is supported by the current platform. + */ +ol.control.FullScreen.isFullScreenSupported = function() { + var body = document.body; + return !!( + body.webkitRequestFullscreen || + (body.mozRequestFullScreen && document.mozFullScreenEnabled) || + (body.msRequestFullscreen && document.msFullscreenEnabled) || + (body.requestFullscreen && document.fullscreenEnabled) + ); +}; + +/** + * @return {boolean} Element is currently in fullscreen. + */ +ol.control.FullScreen.isFullScreen = function() { + return !!( + document.webkitIsFullScreen || document.mozFullScreen || + document.msFullscreenElement || document.fullscreenElement + ); +}; + +/** + * Request to fullscreen an element. + * @param {Node} element Element to request fullscreen + */ +ol.control.FullScreen.requestFullScreen = function(element) { + if (element.requestFullscreen) { + element.requestFullscreen(); + } else if (element.msRequestFullscreen) { + element.msRequestFullscreen(); + } else if (element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if (element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(); + } +}; + +/** + * Request to fullscreen an element with keyboard input. + * @param {Node} element Element to request fullscreen + */ +ol.control.FullScreen.requestFullScreenWithKeys = function(element) { + if (element.mozRequestFullScreenWithKeys) { + element.mozRequestFullScreenWithKeys(); + } else if (element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else { + ol.control.FullScreen.requestFullScreen(element); + } +}; + +/** + * Exit fullscreen. + */ +ol.control.FullScreen.exitFullScreen = function() { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } +}; + +/** + * @return {string} Change type. + * @private + */ +ol.control.FullScreen.getChangeType_ = (function() { + var changeType; + return function() { + if (!changeType) { + var body = document.body; + if (body.webkitRequestFullscreen) { + changeType = 'webkitfullscreenchange'; + } else if (body.mozRequestFullScreen) { + changeType = 'mozfullscreenchange'; + } else if (body.msRequestFullscreen) { + changeType = 'MSFullscreenChange'; + } else if (body.requestFullscreen) { + changeType = 'fullscreenchange'; + } + } + return changeType; + }; +})(); + +goog.provide('ol.control.Rotate'); + +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.easing'); + + +/** + * @classdesc + * A button control to reset rotation to 0. + * To style this control use css selector `.ol-rotate`. A `.ol-hidden` css + * selector is added to the button when the rotation is 0. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.RotateOptions=} opt_options Rotate options. + * @api stable + */ +ol.control.Rotate = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var className = options.className !== undefined ? options.className : 'ol-rotate'; + + var label = options.label !== undefined ? options.label : '\u21E7'; + + /** + * @type {Element} + * @private + */ + this.label_ = null; + + if (typeof label === 'string') { + this.label_ = document.createElement('span'); + this.label_.className = 'ol-compass'; + this.label_.textContent = label; + } else { + this.label_ = label; + this.label_.classList.add('ol-compass'); + } + + var tipLabel = options.tipLabel ? options.tipLabel : 'Reset rotation'; + + var button = document.createElement('button'); + button.className = className + '-reset'; + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild(this.label_); + + ol.events.listen(button, ol.events.EventType.CLICK, + ol.control.Rotate.prototype.handleClick_, this); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(button); + + var render = options.render ? options.render : ol.control.Rotate.render; + + this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined; + + ol.control.Control.call(this, { + element: element, + render: render, + target: options.target + }); + + /** + * @type {number} + * @private + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; + + /** + * @type {boolean} + * @private + */ + this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true; + + /** + * @private + * @type {number|undefined} + */ + this.rotation_ = undefined; + + if (this.autoHide_) { + this.element.classList.add(ol.css.CLASS_HIDDEN); + } + +}; +ol.inherits(ol.control.Rotate, ol.control.Control); + + +/** + * @param {Event} event The event to handle + * @private + */ +ol.control.Rotate.prototype.handleClick_ = function(event) { + event.preventDefault(); + if (this.callResetNorth_ !== undefined) { + this.callResetNorth_(); + } else { + this.resetNorth_(); + } +}; + + +/** + * @private + */ +ol.control.Rotate.prototype.resetNorth_ = function() { + var map = this.getMap(); + var view = map.getView(); + if (!view) { + // the map does not have a view, so we can't act + // upon it + return; + } + var currentRotation = view.getRotation(); + if (currentRotation !== undefined) { + if (this.duration_ > 0) { + currentRotation = currentRotation % (2 * Math.PI); + if (currentRotation < -Math.PI) { + currentRotation += 2 * Math.PI; + } + if (currentRotation > Math.PI) { + currentRotation -= 2 * Math.PI; + } + view.animate({ + rotation: 0, + duration: this.duration_, + easing: ol.easing.easeOut + }); + } else { + view.setRotation(0); + } + } +}; + + +/** + * Update the rotate control element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.Rotate} + * @api + */ +ol.control.Rotate.render = function(mapEvent) { + var frameState = mapEvent.frameState; + if (!frameState) { + return; + } + var rotation = frameState.viewState.rotation; + if (rotation != this.rotation_) { + var transform = 'rotate(' + rotation + 'rad)'; + if (this.autoHide_) { + var contains = this.element.classList.contains(ol.css.CLASS_HIDDEN); + if (!contains && rotation === 0) { + this.element.classList.add(ol.css.CLASS_HIDDEN); + } else if (contains && rotation !== 0) { + this.element.classList.remove(ol.css.CLASS_HIDDEN); + } + } + this.label_.style.msTransform = transform; + this.label_.style.webkitTransform = transform; + this.label_.style.transform = transform; + } + this.rotation_ = rotation; +}; + +goog.provide('ol.control.Zoom'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.easing'); + + +/** + * @classdesc + * A control with 2 buttons, one for zoom in and one for zoom out. + * This control is one of the default controls of a map. To style this control + * use css selectors `.ol-zoom-in` and `.ol-zoom-out`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ZoomOptions=} opt_options Zoom options. + * @api stable + */ +ol.control.Zoom = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var className = options.className !== undefined ? options.className : 'ol-zoom'; + + var delta = options.delta !== undefined ? options.delta : 1; + + var zoomInLabel = options.zoomInLabel !== undefined ? options.zoomInLabel : '+'; + var zoomOutLabel = options.zoomOutLabel !== undefined ? options.zoomOutLabel : '\u2212'; + + var zoomInTipLabel = options.zoomInTipLabel !== undefined ? + options.zoomInTipLabel : 'Zoom in'; + var zoomOutTipLabel = options.zoomOutTipLabel !== undefined ? + options.zoomOutTipLabel : 'Zoom out'; + + var inElement = document.createElement('button'); + inElement.className = className + '-in'; + inElement.setAttribute('type', 'button'); + inElement.title = zoomInTipLabel; + inElement.appendChild( + typeof zoomInLabel === 'string' ? document.createTextNode(zoomInLabel) : zoomInLabel + ); + + ol.events.listen(inElement, ol.events.EventType.CLICK, + ol.control.Zoom.prototype.handleClick_.bind(this, delta)); + + var outElement = document.createElement('button'); + outElement.className = className + '-out'; + outElement.setAttribute('type', 'button'); + outElement.title = zoomOutTipLabel; + outElement.appendChild( + typeof zoomOutLabel === 'string' ? document.createTextNode(zoomOutLabel) : zoomOutLabel + ); + + ol.events.listen(outElement, ol.events.EventType.CLICK, + ol.control.Zoom.prototype.handleClick_.bind(this, -delta)); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(inElement); + element.appendChild(outElement); + + ol.control.Control.call(this, { + element: element, + target: options.target + }); + + /** + * @type {number} + * @private + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; + +}; +ol.inherits(ol.control.Zoom, ol.control.Control); + + +/** + * @param {number} delta Zoom delta. + * @param {Event} event The event to handle + * @private + */ +ol.control.Zoom.prototype.handleClick_ = function(delta, event) { + event.preventDefault(); + this.zoomByDelta_(delta); +}; + + +/** + * @param {number} delta Zoom delta. + * @private + */ +ol.control.Zoom.prototype.zoomByDelta_ = function(delta) { + var map = this.getMap(); + var view = map.getView(); + if (!view) { + // the map does not have a view, so we can't act + // upon it + return; + } + var currentResolution = view.getResolution(); + if (currentResolution) { + var newResolution = view.constrainResolution(currentResolution, delta); + if (this.duration_ > 0) { + if (view.getAnimating()) { + view.cancelAnimations(); + } + view.animate({ + resolution: newResolution, + duration: this.duration_, + easing: ol.easing.easeOut + }); + } else { + view.setResolution(newResolution); + } + } +}; + +goog.provide('ol.control'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.control.Attribution'); +goog.require('ol.control.Rotate'); +goog.require('ol.control.Zoom'); + + +/** + * Set of controls included in maps by default. Unless configured otherwise, + * this returns a collection containing an instance of each of the following + * controls: + * * {@link ol.control.Zoom} + * * {@link ol.control.Rotate} + * * {@link ol.control.Attribution} + * + * @param {olx.control.DefaultsOptions=} opt_options Defaults options. + * @return {ol.Collection.<ol.control.Control>} Controls. + * @api stable + */ +ol.control.defaults = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var controls = new ol.Collection(); + + var zoomControl = options.zoom !== undefined ? options.zoom : true; + if (zoomControl) { + controls.push(new ol.control.Zoom(options.zoomOptions)); + } + + var rotateControl = options.rotate !== undefined ? options.rotate : true; + if (rotateControl) { + controls.push(new ol.control.Rotate(options.rotateOptions)); + } + + var attributionControl = options.attribution !== undefined ? + options.attribution : true; + if (attributionControl) { + controls.push(new ol.control.Attribution(options.attributionOptions)); + } + + return controls; + +}; + +// FIXME should listen on appropriate pane, once it is defined + +goog.provide('ol.control.MousePosition'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.Object'); +goog.require('ol.control.Control'); +goog.require('ol.proj'); + + +/** + * @classdesc + * A control to show the 2D coordinates of the mouse cursor. By default, these + * are in the view projection, but can be in any supported projection. + * By default the control is shown in the top right corner of the map, but this + * can be changed by using the css selector `.ol-mouse-position`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.MousePositionOptions=} opt_options Mouse position + * options. + * @api stable + */ +ol.control.MousePosition = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var element = document.createElement('DIV'); + element.className = options.className !== undefined ? options.className : 'ol-mouse-position'; + + var render = options.render ? + options.render : ol.control.MousePosition.render; + + ol.control.Control.call(this, { + element: element, + render: render, + target: options.target + }); + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.control.MousePosition.Property.PROJECTION), + this.handleProjectionChanged_, this); + + if (options.coordinateFormat) { + this.setCoordinateFormat(options.coordinateFormat); + } + if (options.projection) { + this.setProjection(ol.proj.get(options.projection)); + } + + /** + * @private + * @type {string} + */ + this.undefinedHTML_ = options.undefinedHTML !== undefined ? options.undefinedHTML : ''; + + /** + * @private + * @type {string} + */ + this.renderedHTML_ = element.innerHTML; + + /** + * @private + * @type {ol.proj.Projection} + */ + this.mapProjection_ = null; + + /** + * @private + * @type {?ol.TransformFunction} + */ + this.transform_ = null; + + /** + * @private + * @type {ol.Pixel} + */ + this.lastMouseMovePixel_ = null; + +}; +ol.inherits(ol.control.MousePosition, ol.control.Control); + + +/** + * Update the mouseposition element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.MousePosition} + * @api + */ +ol.control.MousePosition.render = function(mapEvent) { + var frameState = mapEvent.frameState; + if (!frameState) { + this.mapProjection_ = null; + } else { + if (this.mapProjection_ != frameState.viewState.projection) { + this.mapProjection_ = frameState.viewState.projection; + this.transform_ = null; + } + } + this.updateHTML_(this.lastMouseMovePixel_); +}; + + +/** + * @private + */ +ol.control.MousePosition.prototype.handleProjectionChanged_ = function() { + this.transform_ = null; +}; + + +/** + * Return the coordinate format type used to render the current position or + * undefined. + * @return {ol.CoordinateFormatType|undefined} The format to render the current + * position in. + * @observable + * @api stable + */ +ol.control.MousePosition.prototype.getCoordinateFormat = function() { + return /** @type {ol.CoordinateFormatType|undefined} */ ( + this.get(ol.control.MousePosition.Property.COORDINATE_FORMAT)); +}; + + +/** + * Return the projection that is used to report the mouse position. + * @return {ol.proj.Projection|undefined} The projection to report mouse + * position in. + * @observable + * @api stable + */ +ol.control.MousePosition.prototype.getProjection = function() { + return /** @type {ol.proj.Projection|undefined} */ ( + this.get(ol.control.MousePosition.Property.PROJECTION)); +}; + + +/** + * @param {Event} event Browser event. + * @protected + */ +ol.control.MousePosition.prototype.handleMouseMove = function(event) { + var map = this.getMap(); + this.lastMouseMovePixel_ = map.getEventPixel(event); + this.updateHTML_(this.lastMouseMovePixel_); +}; + + +/** + * @param {Event} event Browser event. + * @protected + */ +ol.control.MousePosition.prototype.handleMouseOut = function(event) { + this.updateHTML_(null); + this.lastMouseMovePixel_ = null; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.control.MousePosition.prototype.setMap = function(map) { + ol.control.Control.prototype.setMap.call(this, map); + if (map) { + var viewport = map.getViewport(); + this.listenerKeys.push( + ol.events.listen(viewport, ol.events.EventType.MOUSEMOVE, + this.handleMouseMove, this), + ol.events.listen(viewport, ol.events.EventType.MOUSEOUT, + this.handleMouseOut, this) + ); + } +}; + + +/** + * Set the coordinate format type used to render the current position. + * @param {ol.CoordinateFormatType} format The format to render the current + * position in. + * @observable + * @api stable + */ +ol.control.MousePosition.prototype.setCoordinateFormat = function(format) { + this.set(ol.control.MousePosition.Property.COORDINATE_FORMAT, format); +}; + + +/** + * Set the projection that is used to report the mouse position. + * @param {ol.proj.Projection} projection The projection to report mouse + * position in. + * @observable + * @api stable + */ +ol.control.MousePosition.prototype.setProjection = function(projection) { + this.set(ol.control.MousePosition.Property.PROJECTION, projection); +}; + + +/** + * @param {?ol.Pixel} pixel Pixel. + * @private + */ +ol.control.MousePosition.prototype.updateHTML_ = function(pixel) { + var html = this.undefinedHTML_; + if (pixel && this.mapProjection_) { + if (!this.transform_) { + var projection = this.getProjection(); + if (projection) { + this.transform_ = ol.proj.getTransformFromProjections( + this.mapProjection_, projection); + } else { + this.transform_ = ol.proj.identityTransform; + } + } + var map = this.getMap(); + var coordinate = map.getCoordinateFromPixel(pixel); + if (coordinate) { + this.transform_(coordinate, coordinate); + var coordinateFormat = this.getCoordinateFormat(); + if (coordinateFormat) { + html = coordinateFormat(coordinate); + } else { + html = coordinate.toString(); + } + } + } + if (!this.renderedHTML_ || html != this.renderedHTML_) { + this.element.innerHTML = html; + this.renderedHTML_ = html; + } +}; + + +/** + * @enum {string} + */ +ol.control.MousePosition.Property = { + PROJECTION: 'projection', + COORDINATE_FORMAT: 'coordinateFormat' +}; + +goog.provide('ol.MapBrowserEvent'); + +goog.require('ol'); +goog.require('ol.MapEvent'); +goog.require('ol.events.EventType'); + + +/** + * @classdesc + * Events emitted as map browser events are instances of this type. + * See {@link ol.Map} for which events trigger a map browser event. + * + * @constructor + * @extends {ol.MapEvent} + * @implements {oli.MapBrowserEvent} + * @param {string} type Event type. + * @param {ol.Map} map Map. + * @param {Event} browserEvent Browser event. + * @param {boolean=} opt_dragging Is the map currently being dragged? + * @param {?olx.FrameState=} opt_frameState Frame state. + */ +ol.MapBrowserEvent = function(type, map, browserEvent, opt_dragging, + opt_frameState) { + + ol.MapEvent.call(this, type, map, opt_frameState); + + /** + * The original browser event. + * @const + * @type {Event} + * @api stable + */ + this.originalEvent = browserEvent; + + /** + * The map pixel relative to the viewport corresponding to the original browser event. + * @type {ol.Pixel} + * @api stable + */ + this.pixel = map.getEventPixel(browserEvent); + + /** + * The coordinate in view projection corresponding to the original browser event. + * @type {ol.Coordinate} + * @api stable + */ + this.coordinate = map.getCoordinateFromPixel(this.pixel); + + /** + * Indicates if the map is currently being dragged. Only set for + * `POINTERDRAG` and `POINTERMOVE` events. Default is `false`. + * + * @type {boolean} + * @api stable + */ + this.dragging = opt_dragging !== undefined ? opt_dragging : false; + +}; +ol.inherits(ol.MapBrowserEvent, ol.MapEvent); + + +/** + * Prevents the default browser action. + * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault + * @override + * @api stable + */ +ol.MapBrowserEvent.prototype.preventDefault = function() { + ol.MapEvent.prototype.preventDefault.call(this); + this.originalEvent.preventDefault(); +}; + + +/** + * Prevents further propagation of the current event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation + * @override + * @api stable + */ +ol.MapBrowserEvent.prototype.stopPropagation = function() { + ol.MapEvent.prototype.stopPropagation.call(this); + this.originalEvent.stopPropagation(); +}; + + +/** + * Constants for event names. + * @enum {string} + */ +ol.MapBrowserEvent.EventType = { + + /** + * A true single click with no dragging and no double click. Note that this + * event is delayed by 250 ms to ensure that it is not a double click. + * @event ol.MapBrowserEvent#singleclick + * @api stable + */ + SINGLECLICK: 'singleclick', + + /** + * A click with no dragging. A double click will fire two of this. + * @event ol.MapBrowserEvent#click + * @api stable + */ + CLICK: ol.events.EventType.CLICK, + + /** + * A true double click, with no dragging. + * @event ol.MapBrowserEvent#dblclick + * @api stable + */ + DBLCLICK: ol.events.EventType.DBLCLICK, + + /** + * Triggered when a pointer is dragged. + * @event ol.MapBrowserEvent#pointerdrag + * @api + */ + POINTERDRAG: 'pointerdrag', + + /** + * Triggered when a pointer is moved. Note that on touch devices this is + * triggered when the map is panned, so is not the same as mousemove. + * @event ol.MapBrowserEvent#pointermove + * @api stable + */ + POINTERMOVE: 'pointermove', + + POINTERDOWN: 'pointerdown', + POINTERUP: 'pointerup', + POINTEROVER: 'pointerover', + POINTEROUT: 'pointerout', + POINTERENTER: 'pointerenter', + POINTERLEAVE: 'pointerleave', + POINTERCANCEL: 'pointercancel' +}; + +goog.provide('ol.MapBrowserPointerEvent'); + +goog.require('ol'); +goog.require('ol.MapBrowserEvent'); + + +/** + * @constructor + * @extends {ol.MapBrowserEvent} + * @param {string} type Event type. + * @param {ol.Map} map Map. + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @param {boolean=} opt_dragging Is the map currently being dragged? + * @param {?olx.FrameState=} opt_frameState Frame state. + */ +ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_dragging, + opt_frameState) { + + ol.MapBrowserEvent.call(this, type, map, pointerEvent.originalEvent, opt_dragging, + opt_frameState); + + /** + * @const + * @type {ol.pointer.PointerEvent} + */ + this.pointerEvent = pointerEvent; + +}; +ol.inherits(ol.MapBrowserPointerEvent, ol.MapBrowserEvent); + +goog.provide('ol.pointer.EventType'); + + +/** + * Constants for event names. + * @enum {string} + */ +ol.pointer.EventType = { + POINTERMOVE: 'pointermove', + POINTERDOWN: 'pointerdown', + POINTERUP: 'pointerup', + POINTEROVER: 'pointerover', + POINTEROUT: 'pointerout', + POINTERENTER: 'pointerenter', + POINTERLEAVE: 'pointerleave', + POINTERCANCEL: 'pointercancel' +}; + +goog.provide('ol.webgl'); + + +/** Constants taken from goog.webgl + */ + + +/** + * @const + * @type {number} + */ +ol.webgl.ONE = 1; + + +/** + * @const + * @type {number} + */ +ol.webgl.SRC_ALPHA = 0x0302; + + +/** + * @const + * @type {number} + */ +ol.webgl.COLOR_ATTACHMENT0 = 0x8CE0; + + +/** + * @const + * @type {number} + */ +ol.webgl.COLOR_BUFFER_BIT = 0x00004000; + + +/** + * @const + * @type {number} + */ +ol.webgl.TRIANGLES = 0x0004; + + +/** + * @const + * @type {number} + */ +ol.webgl.TRIANGLE_STRIP = 0x0005; + + +/** + * @const + * @type {number} + */ +ol.webgl.ONE_MINUS_SRC_ALPHA = 0x0303; + + +/** + * @const + * @type {number} + */ +ol.webgl.ARRAY_BUFFER = 0x8892; + + +/** + * @const + * @type {number} + */ +ol.webgl.ELEMENT_ARRAY_BUFFER = 0x8893; + + +/** + * @const + * @type {number} + */ +ol.webgl.STREAM_DRAW = 0x88E0; + + +/** + * @const + * @type {number} + */ +ol.webgl.STATIC_DRAW = 0x88E4; + + +/** + * @const + * @type {number} + */ +ol.webgl.DYNAMIC_DRAW = 0x88E8; + + +/** + * @const + * @type {number} + */ +ol.webgl.CULL_FACE = 0x0B44; + + +/** + * @const + * @type {number} + */ +ol.webgl.BLEND = 0x0BE2; + + +/** + * @const + * @type {number} + */ +ol.webgl.STENCIL_TEST = 0x0B90; + + +/** + * @const + * @type {number} + */ +ol.webgl.DEPTH_TEST = 0x0B71; + + +/** + * @const + * @type {number} + */ +ol.webgl.SCISSOR_TEST = 0x0C11; + + +/** + * @const + * @type {number} + */ +ol.webgl.UNSIGNED_BYTE = 0x1401; + + +/** + * @const + * @type {number} + */ +ol.webgl.UNSIGNED_SHORT = 0x1403; + + +/** + * @const + * @type {number} + */ +ol.webgl.UNSIGNED_INT = 0x1405; + + +/** + * @const + * @type {number} + */ +ol.webgl.FLOAT = 0x1406; + + +/** + * @const + * @type {number} + */ +ol.webgl.RGBA = 0x1908; + + +/** + * @const + * @type {number} + */ +ol.webgl.FRAGMENT_SHADER = 0x8B30; + + +/** + * @const + * @type {number} + */ +ol.webgl.VERTEX_SHADER = 0x8B31; + + +/** + * @const + * @type {number} + */ +ol.webgl.LINK_STATUS = 0x8B82; + + +/** + * @const + * @type {number} + */ +ol.webgl.LINEAR = 0x2601; + + +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_MAG_FILTER = 0x2800; + + +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_MIN_FILTER = 0x2801; + + +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_WRAP_S = 0x2802; + + +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_WRAP_T = 0x2803; + + +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_2D = 0x0DE1; + + +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE0 = 0x84C0; + + +/** + * @const + * @type {number} + */ +ol.webgl.CLAMP_TO_EDGE = 0x812F; + + +/** + * @const + * @type {number} + */ +ol.webgl.COMPILE_STATUS = 0x8B81; + + +/** + * @const + * @type {number} + */ +ol.webgl.FRAMEBUFFER = 0x8D40; + + +/** end of goog.webgl constants + */ + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.webgl.CONTEXT_IDS_ = [ + 'experimental-webgl', + 'webgl', + 'webkit-3d', + 'moz-webgl' +]; + + +/** + * @param {HTMLCanvasElement} canvas Canvas. + * @param {Object=} opt_attributes Attributes. + * @return {WebGLRenderingContext} WebGL rendering context. + */ +ol.webgl.getContext = function(canvas, opt_attributes) { + var context, i, ii = ol.webgl.CONTEXT_IDS_.length; + for (i = 0; i < ii; ++i) { + try { + context = canvas.getContext(ol.webgl.CONTEXT_IDS_[i], opt_attributes); + if (context) { + return /** @type {!WebGLRenderingContext} */ (context); + } + } catch (e) { + // pass + } + } + return null; +}; + +goog.provide('ol.has'); + +goog.require('ol'); +goog.require('ol.webgl'); + +var ua = typeof navigator !== 'undefined' ? + navigator.userAgent.toLowerCase() : ''; + +/** + * User agent string says we are dealing with Firefox as browser. + * @type {boolean} + */ +ol.has.FIREFOX = ua.indexOf('firefox') !== -1; + +/** + * User agent string says we are dealing with Safari as browser. + * @type {boolean} + */ +ol.has.SAFARI = ua.indexOf('safari') !== -1 && ua.indexOf('chrom') == -1; + +/** + * User agent string says we are dealing with a WebKit engine. + * @type {boolean} + */ +ol.has.WEBKIT = ua.indexOf('webkit') !== -1 && ua.indexOf('edge') == -1; + +/** + * User agent string says we are dealing with a Mac as platform. + * @type {boolean} + */ +ol.has.MAC = ua.indexOf('macintosh') !== -1; + + +/** + * The ratio between physical pixels and device-independent pixels + * (dips) on the device (`window.devicePixelRatio`). + * @const + * @type {number} + * @api stable + */ +ol.has.DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1; + + +/** + * True if the browser's Canvas implementation implements {get,set}LineDash. + * @type {boolean} + */ +ol.has.CANVAS_LINE_DASH = false; + + +/** + * True if both the library and browser support Canvas. Always `false` + * if `ol.ENABLE_CANVAS` is set to `false` at compile time. + * @const + * @type {boolean} + * @api stable + */ +ol.has.CANVAS = ol.ENABLE_CANVAS && ( + /** + * @return {boolean} Canvas supported. + */ + function() { + if (!('HTMLCanvasElement' in window)) { + return false; + } + try { + var context = document.createElement('CANVAS').getContext('2d'); + if (!context) { + return false; + } else { + if (context.setLineDash !== undefined) { + ol.has.CANVAS_LINE_DASH = true; + } + return true; + } + } catch (e) { + return false; + } + })(); + + +/** + * Indicates if DeviceOrientation is supported in the user's browser. + * @const + * @type {boolean} + * @api stable + */ +ol.has.DEVICE_ORIENTATION = 'DeviceOrientationEvent' in window; + + +/** + * Is HTML5 geolocation supported in the current browser? + * @const + * @type {boolean} + * @api stable + */ +ol.has.GEOLOCATION = 'geolocation' in navigator; + + +/** + * True if browser supports touch events. + * @const + * @type {boolean} + * @api stable + */ +ol.has.TOUCH = ol.ASSUME_TOUCH || 'ontouchstart' in window; + + +/** + * True if browser supports pointer events. + * @const + * @type {boolean} + */ +ol.has.POINTER = 'PointerEvent' in window; + + +/** + * True if browser supports ms pointer events (IE 10). + * @const + * @type {boolean} + */ +ol.has.MSPOINTER = !!(navigator.msPointerEnabled); + + +/** + * True if both OpenLayers and browser support WebGL. Always `false` + * if `ol.ENABLE_WEBGL` is set to `false` at compile time. + * @const + * @type {boolean} + * @api stable + */ +ol.has.WEBGL; + + +(function() { + if (ol.ENABLE_WEBGL) { + var hasWebGL = false; + var textureSize; + var /** @type {Array.<string>} */ extensions = []; + + if ('WebGLRenderingContext' in window) { + try { + var canvas = /** @type {HTMLCanvasElement} */ + (document.createElement('CANVAS')); + var gl = ol.webgl.getContext(canvas, { + failIfMajorPerformanceCaveat: true + }); + if (gl) { + hasWebGL = true; + textureSize = /** @type {number} */ + (gl.getParameter(gl.MAX_TEXTURE_SIZE)); + extensions = gl.getSupportedExtensions(); + } + } catch (e) { + // pass + } + } + ol.has.WEBGL = hasWebGL; + ol.WEBGL_EXTENSIONS = extensions; + ol.WEBGL_MAX_TEXTURE_SIZE = textureSize; + } +})(); + +goog.provide('ol.pointer.EventSource'); + + +/** + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @param {!Object.<string, function(Event)>} mapping Event + * mapping. + * @constructor + */ +ol.pointer.EventSource = function(dispatcher, mapping) { + /** + * @type {ol.pointer.PointerEventHandler} + */ + this.dispatcher = dispatcher; + + /** + * @private + * @const + * @type {!Object.<string, function(Event)>} + */ + this.mapping_ = mapping; +}; + + +/** + * List of events supported by this source. + * @return {Array.<string>} Event names + */ +ol.pointer.EventSource.prototype.getEvents = function() { + return Object.keys(this.mapping_); +}; + + +/** + * Returns a mapping between the supported event types and + * the handlers that should handle an event. + * @return {Object.<string, function(Event)>} + * Event/Handler mapping + */ +ol.pointer.EventSource.prototype.getMapping = function() { + return this.mapping_; +}; + + +/** + * Returns the handler that should handle a given event type. + * @param {string} eventType The event type. + * @return {function(Event)} Handler + */ +ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) { + return this.mapping_[eventType]; +}; + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.MouseSource'); + +goog.require('ol'); +goog.require('ol.pointer.EventSource'); + + +/** + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @constructor + * @extends {ol.pointer.EventSource} + */ +ol.pointer.MouseSource = function(dispatcher) { + var mapping = { + 'mousedown': this.mousedown, + 'mousemove': this.mousemove, + 'mouseup': this.mouseup, + 'mouseover': this.mouseover, + 'mouseout': this.mouseout + }; + ol.pointer.EventSource.call(this, dispatcher, mapping); + + /** + * @const + * @type {!Object.<string, Event|Object>} + */ + this.pointerMap = dispatcher.pointerMap; + + /** + * @const + * @type {Array.<ol.Pixel>} + */ + this.lastTouches = []; +}; +ol.inherits(ol.pointer.MouseSource, ol.pointer.EventSource); + + +/** + * @const + * @type {number} + */ +ol.pointer.MouseSource.POINTER_ID = 1; + + +/** + * @const + * @type {string} + */ +ol.pointer.MouseSource.POINTER_TYPE = 'mouse'; + + +/** + * Radius around touchend that swallows mouse events. + * + * @const + * @type {number} + */ +ol.pointer.MouseSource.DEDUP_DIST = 25; + + +/** + * Detect if a mouse event was simulated from a touch by + * checking if previously there was a touch event at the + * same position. + * + * FIXME - Known problem with the native Android browser on + * Samsung GT-I9100 (Android 4.1.2): + * In case the page is scrolled, this function does not work + * correctly when a canvas is used (WebGL or canvas renderer). + * Mouse listeners on canvas elements (for this browser), create + * two mouse events: One 'good' and one 'bad' one (on other browsers or + * when a div is used, there is only one event). For the 'bad' one, + * clientX/clientY and also pageX/pageY are wrong when the page + * is scrolled. Because of that, this function can not detect if + * the events were simulated from a touch event. As result, a + * pointer event at a wrong position is dispatched, which confuses + * the map interactions. + * It is unclear, how one can get the correct position for the event + * or detect that the positions are invalid. + * + * @private + * @param {Event} inEvent The in event. + * @return {boolean} True, if the event was generated by a touch. + */ +ol.pointer.MouseSource.prototype.isEventSimulatedFromTouch_ = function(inEvent) { + var lts = this.lastTouches; + var x = inEvent.clientX, y = inEvent.clientY; + for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { + // simulated mouse events will be swallowed near a primary touchend + var dx = Math.abs(x - t[0]), dy = Math.abs(y - t[1]); + if (dx <= ol.pointer.MouseSource.DEDUP_DIST && + dy <= ol.pointer.MouseSource.DEDUP_DIST) { + return true; + } + } + return false; +}; + + +/** + * Creates a copy of the original event that will be used + * for the fake pointer event. + * + * @param {Event} inEvent The in event. + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @return {Object} The copied event. + */ +ol.pointer.MouseSource.prepareEvent = function(inEvent, dispatcher) { + var e = dispatcher.cloneEvent(inEvent, inEvent); + + // forward mouse preventDefault + var pd = e.preventDefault; + e.preventDefault = function() { + inEvent.preventDefault(); + pd(); + }; + + e.pointerId = ol.pointer.MouseSource.POINTER_ID; + e.isPrimary = true; + e.pointerType = ol.pointer.MouseSource.POINTER_TYPE; + + return e; +}; + + +/** + * Handler for `mousedown`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MouseSource.prototype.mousedown = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + // TODO(dfreedman) workaround for some elements not sending mouseup + // http://crbug/149091 + if (ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap) { + this.cancel(inEvent); + } + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()] = inEvent; + this.dispatcher.down(e, inEvent); + } +}; + + +/** + * Handler for `mousemove`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MouseSource.prototype.mousemove = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.move(e, inEvent); + } +}; + + +/** + * Handler for `mouseup`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MouseSource.prototype.mouseup = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + var p = this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()]; + + if (p && p.button === inEvent.button) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.up(e, inEvent); + this.cleanupMouse(); + } + } +}; + + +/** + * Handler for `mouseover`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MouseSource.prototype.mouseover = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.enterOver(e, inEvent); + } +}; + + +/** + * Handler for `mouseout`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MouseSource.prototype.mouseout = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.leaveOut(e, inEvent); + } +}; + + +/** + * Dispatches a `pointercancel` event. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MouseSource.prototype.cancel = function(inEvent) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.cancel(e, inEvent); + this.cleanupMouse(); +}; + + +/** + * Remove the mouse from the list of active pointers. + */ +ol.pointer.MouseSource.prototype.cleanupMouse = function() { + delete this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()]; +}; + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.MsSource'); + +goog.require('ol'); +goog.require('ol.pointer.EventSource'); + + +/** + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @constructor + * @extends {ol.pointer.EventSource} + */ +ol.pointer.MsSource = function(dispatcher) { + var mapping = { + 'MSPointerDown': this.msPointerDown, + 'MSPointerMove': this.msPointerMove, + 'MSPointerUp': this.msPointerUp, + 'MSPointerOut': this.msPointerOut, + 'MSPointerOver': this.msPointerOver, + 'MSPointerCancel': this.msPointerCancel, + 'MSGotPointerCapture': this.msGotPointerCapture, + 'MSLostPointerCapture': this.msLostPointerCapture + }; + ol.pointer.EventSource.call(this, dispatcher, mapping); + + /** + * @const + * @type {!Object.<string, Event|Object>} + */ + this.pointerMap = dispatcher.pointerMap; + + /** + * @const + * @type {Array.<string>} + */ + this.POINTER_TYPES = [ + '', + 'unavailable', + 'touch', + 'pen', + 'mouse' + ]; +}; +ol.inherits(ol.pointer.MsSource, ol.pointer.EventSource); + + +/** + * Creates a copy of the original event that will be used + * for the fake pointer event. + * + * @private + * @param {Event} inEvent The in event. + * @return {Object} The copied event. + */ +ol.pointer.MsSource.prototype.prepareEvent_ = function(inEvent) { + var e = inEvent; + if (typeof inEvent.pointerType === 'number') { + e = this.dispatcher.cloneEvent(inEvent, inEvent); + e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; + } + + return e; +}; + + +/** + * Remove this pointer from the list of active pointers. + * @param {number} pointerId Pointer identifier. + */ +ol.pointer.MsSource.prototype.cleanup = function(pointerId) { + delete this.pointerMap[pointerId.toString()]; +}; + + +/** + * Handler for `msPointerDown`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) { + this.pointerMap[inEvent.pointerId.toString()] = inEvent; + var e = this.prepareEvent_(inEvent); + this.dispatcher.down(e, inEvent); +}; + + +/** + * Handler for `msPointerMove`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msPointerMove = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.move(e, inEvent); +}; + + +/** + * Handler for `msPointerUp`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msPointerUp = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.up(e, inEvent); + this.cleanup(inEvent.pointerId); +}; + + +/** + * Handler for `msPointerOut`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msPointerOut = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.leaveOut(e, inEvent); +}; + + +/** + * Handler for `msPointerOver`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msPointerOver = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.enterOver(e, inEvent); +}; + + +/** + * Handler for `msPointerCancel`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msPointerCancel = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.cancel(e, inEvent); + this.cleanup(inEvent.pointerId); +}; + + +/** + * Handler for `msLostPointerCapture`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msLostPointerCapture = function(inEvent) { + var e = this.dispatcher.makeEvent('lostpointercapture', + inEvent, inEvent); + this.dispatcher.dispatchEvent(e); +}; + + +/** + * Handler for `msGotPointerCapture`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msGotPointerCapture = function(inEvent) { + var e = this.dispatcher.makeEvent('gotpointercapture', + inEvent, inEvent); + this.dispatcher.dispatchEvent(e); +}; + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.NativeSource'); + +goog.require('ol'); +goog.require('ol.pointer.EventSource'); + + +/** + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @constructor + * @extends {ol.pointer.EventSource} + */ +ol.pointer.NativeSource = function(dispatcher) { + var mapping = { + 'pointerdown': this.pointerDown, + 'pointermove': this.pointerMove, + 'pointerup': this.pointerUp, + 'pointerout': this.pointerOut, + 'pointerover': this.pointerOver, + 'pointercancel': this.pointerCancel, + 'gotpointercapture': this.gotPointerCapture, + 'lostpointercapture': this.lostPointerCapture + }; + ol.pointer.EventSource.call(this, dispatcher, mapping); +}; +ol.inherits(ol.pointer.NativeSource, ol.pointer.EventSource); + + +/** + * Handler for `pointerdown`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.pointerDown = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `pointermove`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.pointerMove = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `pointerup`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.pointerUp = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `pointerout`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.pointerOut = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `pointerover`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.pointerOver = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `pointercancel`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.pointerCancel = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `lostpointercapture`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.lostPointerCapture = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `gotpointercapture`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.gotPointerCapture = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.PointerEvent'); + + +goog.require('ol'); +goog.require('ol.events.Event'); + + +/** + * A class for pointer events. + * + * This class is used as an abstraction for mouse events, + * touch events and even native pointer events. + * + * @constructor + * @extends {ol.events.Event} + * @param {string} type The type of the event to create. + * @param {Event} originalEvent The event. + * @param {Object.<string, ?>=} opt_eventDict An optional dictionary of + * initial event properties. + */ +ol.pointer.PointerEvent = function(type, originalEvent, opt_eventDict) { + ol.events.Event.call(this, type); + + /** + * @const + * @type {Event} + */ + this.originalEvent = originalEvent; + + var eventDict = opt_eventDict ? opt_eventDict : {}; + + /** + * @type {number} + */ + this.buttons = this.getButtons_(eventDict); + + /** + * @type {number} + */ + this.pressure = this.getPressure_(eventDict, this.buttons); + + // MouseEvent related properties + + /** + * @type {boolean} + */ + this.bubbles = 'bubbles' in eventDict ? eventDict['bubbles'] : false; + + /** + * @type {boolean} + */ + this.cancelable = 'cancelable' in eventDict ? eventDict['cancelable'] : false; + + /** + * @type {Object} + */ + this.view = 'view' in eventDict ? eventDict['view'] : null; + + /** + * @type {number} + */ + this.detail = 'detail' in eventDict ? eventDict['detail'] : null; + + /** + * @type {number} + */ + this.screenX = 'screenX' in eventDict ? eventDict['screenX'] : 0; + + /** + * @type {number} + */ + this.screenY = 'screenY' in eventDict ? eventDict['screenY'] : 0; + + /** + * @type {number} + */ + this.clientX = 'clientX' in eventDict ? eventDict['clientX'] : 0; + + /** + * @type {number} + */ + this.clientY = 'clientY' in eventDict ? eventDict['clientY'] : 0; + + /** + * @type {boolean} + */ + this.ctrlKey = 'ctrlKey' in eventDict ? eventDict['ctrlKey'] : false; + + /** + * @type {boolean} + */ + this.altKey = 'altKey' in eventDict ? eventDict['altKey'] : false; + + /** + * @type {boolean} + */ + this.shiftKey = 'shiftKey' in eventDict ? eventDict['shiftKey'] : false; + + /** + * @type {boolean} + */ + this.metaKey = 'metaKey' in eventDict ? eventDict['metaKey'] : false; + + /** + * @type {number} + */ + this.button = 'button' in eventDict ? eventDict['button'] : 0; + + /** + * @type {Node} + */ + this.relatedTarget = 'relatedTarget' in eventDict ? + eventDict['relatedTarget'] : null; + + // PointerEvent related properties + + /** + * @const + * @type {number} + */ + this.pointerId = 'pointerId' in eventDict ? eventDict['pointerId'] : 0; + + /** + * @type {number} + */ + this.width = 'width' in eventDict ? eventDict['width'] : 0; + + /** + * @type {number} + */ + this.height = 'height' in eventDict ? eventDict['height'] : 0; + + /** + * @type {number} + */ + this.tiltX = 'tiltX' in eventDict ? eventDict['tiltX'] : 0; + + /** + * @type {number} + */ + this.tiltY = 'tiltY' in eventDict ? eventDict['tiltY'] : 0; + + /** + * @type {string} + */ + this.pointerType = 'pointerType' in eventDict ? eventDict['pointerType'] : ''; + + /** + * @type {number} + */ + this.hwTimestamp = 'hwTimestamp' in eventDict ? eventDict['hwTimestamp'] : 0; + + /** + * @type {boolean} + */ + this.isPrimary = 'isPrimary' in eventDict ? eventDict['isPrimary'] : false; + + // keep the semantics of preventDefault + if (originalEvent.preventDefault) { + this.preventDefault = function() { + originalEvent.preventDefault(); + }; + } +}; +ol.inherits(ol.pointer.PointerEvent, ol.events.Event); + + +/** + * @private + * @param {Object.<string, ?>} eventDict The event dictionary. + * @return {number} Button indicator. + */ +ol.pointer.PointerEvent.prototype.getButtons_ = function(eventDict) { + // According to the w3c spec, + // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-button + // MouseEvent.button == 0 can mean either no mouse button depressed, or the + // left mouse button depressed. + // + // As of now, the only way to distinguish between the two states of + // MouseEvent.button is by using the deprecated MouseEvent.which property, as + // this maps mouse buttons to positive integers > 0, and uses 0 to mean that + // no mouse button is held. + // + // MouseEvent.which is derived from MouseEvent.button at MouseEvent creation, + // but initMouseEvent does not expose an argument with which to set + // MouseEvent.which. Calling initMouseEvent with a buttonArg of 0 will set + // MouseEvent.button == 0 and MouseEvent.which == 1, breaking the expectations + // of app developers. + // + // The only way to propagate the correct state of MouseEvent.which and + // MouseEvent.button to a new MouseEvent.button == 0 and MouseEvent.which == 0 + // is to call initMouseEvent with a buttonArg value of -1. + // + // This is fixed with DOM Level 4's use of buttons + var buttons; + if (eventDict.buttons || ol.pointer.PointerEvent.HAS_BUTTONS) { + buttons = eventDict.buttons; + } else { + switch (eventDict.which) { + case 1: buttons = 1; break; + case 2: buttons = 4; break; + case 3: buttons = 2; break; + default: buttons = 0; + } + } + return buttons; +}; + + +/** + * @private + * @param {Object.<string, ?>} eventDict The event dictionary. + * @param {number} buttons Button indicator. + * @return {number} The pressure. + */ +ol.pointer.PointerEvent.prototype.getPressure_ = function(eventDict, buttons) { + // Spec requires that pointers without pressure specified use 0.5 for down + // state and 0 for up state. + var pressure = 0; + if (eventDict.pressure) { + pressure = eventDict.pressure; + } else { + pressure = buttons ? 0.5 : 0; + } + return pressure; +}; + + +/** + * Is the `buttons` property supported? + * @type {boolean} + */ +ol.pointer.PointerEvent.HAS_BUTTONS = false; + + +/** + * Checks if the `buttons` property is supported. + */ +(function() { + try { + var ev = new MouseEvent('click', {buttons: 1}); + ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1; + } catch (e) { + // pass + } +})(); + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.TouchSource'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.pointer.EventSource'); +goog.require('ol.pointer.MouseSource'); + + +/** + * @constructor + * @param {ol.pointer.PointerEventHandler} dispatcher The event handler. + * @param {ol.pointer.MouseSource} mouseSource Mouse source. + * @extends {ol.pointer.EventSource} + */ +ol.pointer.TouchSource = function(dispatcher, mouseSource) { + var mapping = { + 'touchstart': this.touchstart, + 'touchmove': this.touchmove, + 'touchend': this.touchend, + 'touchcancel': this.touchcancel + }; + ol.pointer.EventSource.call(this, dispatcher, mapping); + + /** + * @const + * @type {!Object.<string, Event|Object>} + */ + this.pointerMap = dispatcher.pointerMap; + + /** + * @const + * @type {ol.pointer.MouseSource} + */ + this.mouseSource = mouseSource; + + /** + * @private + * @type {number|undefined} + */ + this.firstTouchId_ = undefined; + + /** + * @private + * @type {number} + */ + this.clickCount_ = 0; + + /** + * @private + * @type {number|undefined} + */ + this.resetId_ = undefined; +}; +ol.inherits(ol.pointer.TouchSource, ol.pointer.EventSource); + + +/** + * Mouse event timeout: This should be long enough to + * ignore compat mouse events made by touch. + * @const + * @type {number} + */ +ol.pointer.TouchSource.DEDUP_TIMEOUT = 2500; + + +/** + * @const + * @type {number} + */ +ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT = 200; + + +/** + * @const + * @type {string} + */ +ol.pointer.TouchSource.POINTER_TYPE = 'touch'; + + +/** + * @private + * @param {Touch} inTouch The in touch. + * @return {boolean} True, if this is the primary touch. + */ +ol.pointer.TouchSource.prototype.isPrimaryTouch_ = function(inTouch) { + return this.firstTouchId_ === inTouch.identifier; +}; + + +/** + * Set primary touch if there are no pointers, or the only pointer is the mouse. + * @param {Touch} inTouch The in touch. + * @private + */ +ol.pointer.TouchSource.prototype.setPrimaryTouch_ = function(inTouch) { + var count = Object.keys(this.pointerMap).length; + if (count === 0 || (count === 1 && + ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap)) { + this.firstTouchId_ = inTouch.identifier; + this.cancelResetClickCount_(); + } +}; + + +/** + * @private + * @param {Object} inPointer The in pointer object. + */ +ol.pointer.TouchSource.prototype.removePrimaryPointer_ = function(inPointer) { + if (inPointer.isPrimary) { + this.firstTouchId_ = undefined; + this.resetClickCount_(); + } +}; + + +/** + * @private + */ +ol.pointer.TouchSource.prototype.resetClickCount_ = function() { + this.resetId_ = setTimeout( + this.resetClickCountHandler_.bind(this), + ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT); +}; + + +/** + * @private + */ +ol.pointer.TouchSource.prototype.resetClickCountHandler_ = function() { + this.clickCount_ = 0; + this.resetId_ = undefined; +}; + + +/** + * @private + */ +ol.pointer.TouchSource.prototype.cancelResetClickCount_ = function() { + if (this.resetId_ !== undefined) { + clearTimeout(this.resetId_); + } +}; + + +/** + * @private + * @param {Event} browserEvent Browser event + * @param {Touch} inTouch Touch event + * @return {Object} A pointer object. + */ +ol.pointer.TouchSource.prototype.touchToPointer_ = function(browserEvent, inTouch) { + var e = this.dispatcher.cloneEvent(browserEvent, inTouch); + // Spec specifies that pointerId 1 is reserved for Mouse. + // Touch identifiers can start at 0. + // Add 2 to the touch identifier for compatibility. + e.pointerId = inTouch.identifier + 2; + // TODO: check if this is necessary? + //e.target = findTarget(e); + e.bubbles = true; + e.cancelable = true; + e.detail = this.clickCount_; + e.button = 0; + e.buttons = 1; + e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; + e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; + e.pressure = inTouch.webkitForce || inTouch.force || 0.5; + e.isPrimary = this.isPrimaryTouch_(inTouch); + e.pointerType = ol.pointer.TouchSource.POINTER_TYPE; + + // make sure that the properties that are different for + // each `Touch` object are not copied from the BrowserEvent object + e.clientX = inTouch.clientX; + e.clientY = inTouch.clientY; + e.screenX = inTouch.screenX; + e.screenY = inTouch.screenY; + + return e; +}; + + +/** + * @private + * @param {Event} inEvent Touch event + * @param {function(Event, Object)} inFunction In function. + */ +ol.pointer.TouchSource.prototype.processTouches_ = function(inEvent, inFunction) { + var touches = Array.prototype.slice.call( + inEvent.changedTouches); + var count = touches.length; + function preventDefault() { + inEvent.preventDefault(); + } + var i, pointer; + for (i = 0; i < count; ++i) { + pointer = this.touchToPointer_(inEvent, touches[i]); + // forward touch preventDefaults + pointer.preventDefault = preventDefault; + inFunction.call(this, inEvent, pointer); + } +}; + + +/** + * @private + * @param {TouchList} touchList The touch list. + * @param {number} searchId Search identifier. + * @return {boolean} True, if the `Touch` with the given id is in the list. + */ +ol.pointer.TouchSource.prototype.findTouch_ = function(touchList, searchId) { + var l = touchList.length; + var touch; + for (var i = 0; i < l; i++) { + touch = touchList[i]; + if (touch.identifier === searchId) { + return true; + } + } + return false; +}; + + +/** + * In some instances, a touchstart can happen without a touchend. This + * leaves the pointermap in a broken state. + * Therefore, on every touchstart, we remove the touches that did not fire a + * touchend event. + * To keep state globally consistent, we fire a pointercancel for + * this "abandoned" touch + * + * @private + * @param {Event} inEvent The in event. + */ +ol.pointer.TouchSource.prototype.vacuumTouches_ = function(inEvent) { + var touchList = inEvent.touches; + // pointerMap.getCount() should be < touchList.length here, + // as the touchstart has not been processed yet. + var keys = Object.keys(this.pointerMap); + var count = keys.length; + if (count >= touchList.length) { + var d = []; + var i, key, value; + for (i = 0; i < count; ++i) { + key = keys[i]; + value = this.pointerMap[key]; + // Never remove pointerId == 1, which is mouse. + // Touch identifiers are 2 smaller than their pointerId, which is the + // index in pointermap. + if (key != ol.pointer.MouseSource.POINTER_ID && + !this.findTouch_(touchList, key - 2)) { + d.push(value.out); + } + } + for (i = 0; i < d.length; ++i) { + this.cancelOut_(inEvent, d[i]); + } + } +}; + + +/** + * Handler for `touchstart`, triggers `pointerover`, + * `pointerenter` and `pointerdown` events. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.TouchSource.prototype.touchstart = function(inEvent) { + this.vacuumTouches_(inEvent); + this.setPrimaryTouch_(inEvent.changedTouches[0]); + this.dedupSynthMouse_(inEvent); + this.clickCount_++; + this.processTouches_(inEvent, this.overDown_); +}; + + +/** + * @private + * @param {Event} browserEvent The event. + * @param {Object} inPointer The in pointer object. + */ +ol.pointer.TouchSource.prototype.overDown_ = function(browserEvent, inPointer) { + this.pointerMap[inPointer.pointerId] = { + target: inPointer.target, + out: inPointer, + outTarget: inPointer.target + }; + this.dispatcher.over(inPointer, browserEvent); + this.dispatcher.enter(inPointer, browserEvent); + this.dispatcher.down(inPointer, browserEvent); +}; + + +/** + * Handler for `touchmove`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.TouchSource.prototype.touchmove = function(inEvent) { + inEvent.preventDefault(); + this.processTouches_(inEvent, this.moveOverOut_); +}; + + +/** + * @private + * @param {Event} browserEvent The event. + * @param {Object} inPointer The in pointer. + */ +ol.pointer.TouchSource.prototype.moveOverOut_ = function(browserEvent, inPointer) { + var event = inPointer; + var pointer = this.pointerMap[event.pointerId]; + // a finger drifted off the screen, ignore it + if (!pointer) { + return; + } + var outEvent = pointer.out; + var outTarget = pointer.outTarget; + this.dispatcher.move(event, browserEvent); + if (outEvent && outTarget !== event.target) { + outEvent.relatedTarget = event.target; + event.relatedTarget = outTarget; + // recover from retargeting by shadow + outEvent.target = outTarget; + if (event.target) { + this.dispatcher.leaveOut(outEvent, browserEvent); + this.dispatcher.enterOver(event, browserEvent); + } else { + // clean up case when finger leaves the screen + event.target = outTarget; + event.relatedTarget = null; + this.cancelOut_(browserEvent, event); + } + } + pointer.out = event; + pointer.outTarget = event.target; +}; + + +/** + * Handler for `touchend`, triggers `pointerup`, + * `pointerout` and `pointerleave` events. + * + * @param {Event} inEvent The event. + */ +ol.pointer.TouchSource.prototype.touchend = function(inEvent) { + this.dedupSynthMouse_(inEvent); + this.processTouches_(inEvent, this.upOut_); +}; + + +/** + * @private + * @param {Event} browserEvent An event. + * @param {Object} inPointer The inPointer object. + */ +ol.pointer.TouchSource.prototype.upOut_ = function(browserEvent, inPointer) { + this.dispatcher.up(inPointer, browserEvent); + this.dispatcher.out(inPointer, browserEvent); + this.dispatcher.leave(inPointer, browserEvent); + this.cleanUpPointer_(inPointer); +}; + + +/** + * Handler for `touchcancel`, triggers `pointercancel`, + * `pointerout` and `pointerleave` events. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.TouchSource.prototype.touchcancel = function(inEvent) { + this.processTouches_(inEvent, this.cancelOut_); +}; + + +/** + * @private + * @param {Event} browserEvent The event. + * @param {Object} inPointer The in pointer. + */ +ol.pointer.TouchSource.prototype.cancelOut_ = function(browserEvent, inPointer) { + this.dispatcher.cancel(inPointer, browserEvent); + this.dispatcher.out(inPointer, browserEvent); + this.dispatcher.leave(inPointer, browserEvent); + this.cleanUpPointer_(inPointer); +}; + + +/** + * @private + * @param {Object} inPointer The inPointer object. + */ +ol.pointer.TouchSource.prototype.cleanUpPointer_ = function(inPointer) { + delete this.pointerMap[inPointer.pointerId]; + this.removePrimaryPointer_(inPointer); +}; + + +/** + * Prevent synth mouse events from creating pointer events. + * + * @private + * @param {Event} inEvent The in event. + */ +ol.pointer.TouchSource.prototype.dedupSynthMouse_ = function(inEvent) { + var lts = this.mouseSource.lastTouches; + var t = inEvent.changedTouches[0]; + // only the primary finger will synth mouse events + if (this.isPrimaryTouch_(t)) { + // remember x/y of last touch + var lt = [t.clientX, t.clientY]; + lts.push(lt); + + setTimeout(function() { + // remove touch after timeout + ol.array.remove(lts, lt); + }, ol.pointer.TouchSource.DEDUP_TIMEOUT); + } +}; + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.PointerEventHandler'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); + +goog.require('ol.has'); +goog.require('ol.pointer.EventType'); +goog.require('ol.pointer.MouseSource'); +goog.require('ol.pointer.MsSource'); +goog.require('ol.pointer.NativeSource'); +goog.require('ol.pointer.PointerEvent'); +goog.require('ol.pointer.TouchSource'); + + +/** + * @constructor + * @extends {ol.events.EventTarget} + * @param {Element|HTMLDocument} element Viewport element. + */ +ol.pointer.PointerEventHandler = function(element) { + ol.events.EventTarget.call(this); + + /** + * @const + * @private + * @type {Element|HTMLDocument} + */ + this.element_ = element; + + /** + * @const + * @type {!Object.<string, Event|Object>} + */ + this.pointerMap = {}; + + /** + * @type {Object.<string, function(Event)>} + * @private + */ + this.eventMap_ = {}; + + /** + * @type {Array.<ol.pointer.EventSource>} + * @private + */ + this.eventSourceList_ = []; + + this.registerSources(); +}; +ol.inherits(ol.pointer.PointerEventHandler, ol.events.EventTarget); + + +/** + * Set up the event sources (mouse, touch and native pointers) + * that generate pointer events. + */ +ol.pointer.PointerEventHandler.prototype.registerSources = function() { + if (ol.has.POINTER) { + this.registerSource('native', new ol.pointer.NativeSource(this)); + } else if (ol.has.MSPOINTER) { + this.registerSource('ms', new ol.pointer.MsSource(this)); + } else { + var mouseSource = new ol.pointer.MouseSource(this); + this.registerSource('mouse', mouseSource); + + if (ol.has.TOUCH) { + this.registerSource('touch', + new ol.pointer.TouchSource(this, mouseSource)); + } + } + + // register events on the viewport element + this.register_(); +}; + + +/** + * Add a new event source that will generate pointer events. + * + * @param {string} name A name for the event source + * @param {ol.pointer.EventSource} source The source event. + */ +ol.pointer.PointerEventHandler.prototype.registerSource = function(name, source) { + var s = source; + var newEvents = s.getEvents(); + + if (newEvents) { + newEvents.forEach(function(e) { + var handler = s.getHandlerForEvent(e); + + if (handler) { + this.eventMap_[e] = handler.bind(s); + } + }, this); + this.eventSourceList_.push(s); + } +}; + + +/** + * Set up the events for all registered event sources. + * @private + */ +ol.pointer.PointerEventHandler.prototype.register_ = function() { + var l = this.eventSourceList_.length; + var eventSource; + for (var i = 0; i < l; i++) { + eventSource = this.eventSourceList_[i]; + this.addEvents_(eventSource.getEvents()); + } +}; + + +/** + * Remove all registered events. + * @private + */ +ol.pointer.PointerEventHandler.prototype.unregister_ = function() { + var l = this.eventSourceList_.length; + var eventSource; + for (var i = 0; i < l; i++) { + eventSource = this.eventSourceList_[i]; + this.removeEvents_(eventSource.getEvents()); + } +}; + + +/** + * Calls the right handler for a new event. + * @private + * @param {Event} inEvent Browser event. + */ +ol.pointer.PointerEventHandler.prototype.eventHandler_ = function(inEvent) { + var type = inEvent.type; + var handler = this.eventMap_[type]; + if (handler) { + handler(inEvent); + } +}; + + +/** + * Setup listeners for the given events. + * @private + * @param {Array.<string>} events List of events. + */ +ol.pointer.PointerEventHandler.prototype.addEvents_ = function(events) { + events.forEach(function(eventName) { + ol.events.listen(this.element_, eventName, this.eventHandler_, this); + }, this); +}; + + +/** + * Unregister listeners for the given events. + * @private + * @param {Array.<string>} events List of events. + */ +ol.pointer.PointerEventHandler.prototype.removeEvents_ = function(events) { + events.forEach(function(e) { + ol.events.unlisten(this.element_, e, this.eventHandler_, this); + }, this); +}; + + +/** + * Returns a snapshot of inEvent, with writable properties. + * + * @param {Event} event Browser event. + * @param {Event|Touch} inEvent An event that contains + * properties to copy. + * @return {Object} An object containing shallow copies of + * `inEvent`'s properties. + */ +ol.pointer.PointerEventHandler.prototype.cloneEvent = function(event, inEvent) { + var eventCopy = {}, p; + for (var i = 0, ii = ol.pointer.PointerEventHandler.CLONE_PROPS.length; i < ii; i++) { + p = ol.pointer.PointerEventHandler.CLONE_PROPS[i][0]; + eventCopy[p] = event[p] || inEvent[p] || ol.pointer.PointerEventHandler.CLONE_PROPS[i][1]; + } + + return eventCopy; +}; + + +// EVENTS + + +/** + * Triggers a 'pointerdown' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.down = function(data, event) { + this.fireEvent(ol.pointer.EventType.POINTERDOWN, data, event); +}; + + +/** + * Triggers a 'pointermove' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.move = function(data, event) { + this.fireEvent(ol.pointer.EventType.POINTERMOVE, data, event); +}; + + +/** + * Triggers a 'pointerup' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.up = function(data, event) { + this.fireEvent(ol.pointer.EventType.POINTERUP, data, event); +}; + + +/** + * Triggers a 'pointerenter' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.enter = function(data, event) { + data.bubbles = false; + this.fireEvent(ol.pointer.EventType.POINTERENTER, data, event); +}; + + +/** + * Triggers a 'pointerleave' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.leave = function(data, event) { + data.bubbles = false; + this.fireEvent(ol.pointer.EventType.POINTERLEAVE, data, event); +}; + + +/** + * Triggers a 'pointerover' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.over = function(data, event) { + data.bubbles = true; + this.fireEvent(ol.pointer.EventType.POINTEROVER, data, event); +}; + + +/** + * Triggers a 'pointerout' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.out = function(data, event) { + data.bubbles = true; + this.fireEvent(ol.pointer.EventType.POINTEROUT, data, event); +}; + + +/** + * Triggers a 'pointercancel' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.cancel = function(data, event) { + this.fireEvent(ol.pointer.EventType.POINTERCANCEL, data, event); +}; + + +/** + * Triggers a combination of 'pointerout' and 'pointerleave' events. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.leaveOut = function(data, event) { + this.out(data, event); + if (!this.contains_(data.target, data.relatedTarget)) { + this.leave(data, event); + } +}; + + +/** + * Triggers a combination of 'pointerover' and 'pointerevents' events. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.enterOver = function(data, event) { + this.over(data, event); + if (!this.contains_(data.target, data.relatedTarget)) { + this.enter(data, event); + } +}; + + +/** + * @private + * @param {Element} container The container element. + * @param {Element} contained The contained element. + * @return {boolean} Returns true if the container element + * contains the other element. + */ +ol.pointer.PointerEventHandler.prototype.contains_ = function(container, contained) { + if (!container || !contained) { + return false; + } + return container.contains(contained); +}; + + +// EVENT CREATION AND TRACKING +/** + * Creates a new Event of type `inType`, based on the information in + * `data`. + * + * @param {string} inType A string representing the type of event to create. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + * @return {ol.pointer.PointerEvent} A PointerEvent of type `inType`. + */ +ol.pointer.PointerEventHandler.prototype.makeEvent = function(inType, data, event) { + return new ol.pointer.PointerEvent(inType, event, data); +}; + + +/** + * Make and dispatch an event in one call. + * @param {string} inType A string representing the type of event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.fireEvent = function(inType, data, event) { + var e = this.makeEvent(inType, data, event); + this.dispatchEvent(e); +}; + + +/** + * Creates a pointer event from a native pointer event + * and dispatches this event. + * @param {Event} event A platform event with a target. + */ +ol.pointer.PointerEventHandler.prototype.fireNativeEvent = function(event) { + var e = this.makeEvent(event.type, event, event); + this.dispatchEvent(e); +}; + + +/** + * Wrap a native mouse event into a pointer event. + * This proxy method is required for the legacy IE support. + * @param {string} eventType The pointer event type. + * @param {Event} event The event. + * @return {ol.pointer.PointerEvent} The wrapped event. + */ +ol.pointer.PointerEventHandler.prototype.wrapMouseEvent = function(eventType, event) { + var pointerEvent = this.makeEvent( + eventType, ol.pointer.MouseSource.prepareEvent(event, this), event); + return pointerEvent; +}; + + +/** + * @inheritDoc + */ +ol.pointer.PointerEventHandler.prototype.disposeInternal = function() { + this.unregister_(); + ol.events.EventTarget.prototype.disposeInternal.call(this); +}; + + +/** + * Properties to copy when cloning an event, with default values. + * @type {Array.<Array>} + */ +ol.pointer.PointerEventHandler.CLONE_PROPS = [ + // MouseEvent + ['bubbles', false], + ['cancelable', false], + ['view', null], + ['detail', null], + ['screenX', 0], + ['screenY', 0], + ['clientX', 0], + ['clientY', 0], + ['ctrlKey', false], + ['altKey', false], + ['shiftKey', false], + ['metaKey', false], + ['button', 0], + ['relatedTarget', null], + // DOM Level 3 + ['buttons', 0], + // PointerEvent + ['pointerId', 0], + ['width', 0], + ['height', 0], + ['pressure', 0], + ['tiltX', 0], + ['tiltY', 0], + ['pointerType', ''], + ['hwTimestamp', 0], + ['isPrimary', false], + // event instance + ['type', ''], + ['target', null], + ['currentTarget', null], + ['which', 0] +]; + +goog.provide('ol.MapBrowserEventHandler'); + +goog.require('ol'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserPointerEvent'); +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); +goog.require('ol.pointer.EventType'); +goog.require('ol.pointer.PointerEventHandler'); + + +/** + * @param {ol.Map} map The map with the viewport to listen to events on. + * @constructor + * @extends {ol.events.EventTarget} + */ +ol.MapBrowserEventHandler = function(map) { + + ol.events.EventTarget.call(this); + + /** + * This is the element that we will listen to the real events on. + * @type {ol.Map} + * @private + */ + this.map_ = map; + + /** + * @type {number} + * @private + */ + this.clickTimeoutId_ = 0; + + /** + * @type {boolean} + * @private + */ + this.dragging_ = false; + + /** + * @type {!Array.<ol.EventsKey>} + * @private + */ + this.dragListenerKeys_ = []; + + /** + * The most recent "down" type event (or null if none have occurred). + * Set on pointerdown. + * @type {ol.pointer.PointerEvent} + * @private + */ + this.down_ = null; + + var element = this.map_.getViewport(); + + /** + * @type {number} + * @private + */ + this.activePointers_ = 0; + + /** + * @type {!Object.<number, boolean>} + * @private + */ + this.trackedTouches_ = {}; + + /** + * Event handler which generates pointer events for + * the viewport element. + * + * @type {ol.pointer.PointerEventHandler} + * @private + */ + this.pointerEventHandler_ = new ol.pointer.PointerEventHandler(element); + + /** + * Event handler which generates pointer events for + * the document (used when dragging). + * + * @type {ol.pointer.PointerEventHandler} + * @private + */ + this.documentPointerEventHandler_ = null; + + /** + * @type {?ol.EventsKey} + * @private + */ + this.pointerdownListenerKey_ = ol.events.listen(this.pointerEventHandler_, + ol.pointer.EventType.POINTERDOWN, + this.handlePointerDown_, this); + + /** + * @type {?ol.EventsKey} + * @private + */ + this.relayedListenerKey_ = ol.events.listen(this.pointerEventHandler_, + ol.pointer.EventType.POINTERMOVE, + this.relayEvent_, this); + +}; +ol.inherits(ol.MapBrowserEventHandler, ol.events.EventTarget); + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private + */ +ol.MapBrowserEventHandler.prototype.emulateClick_ = function(pointerEvent) { + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.CLICK, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + if (this.clickTimeoutId_ !== 0) { + // double-click + clearTimeout(this.clickTimeoutId_); + this.clickTimeoutId_ = 0; + newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.DBLCLICK, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + } else { + // click + this.clickTimeoutId_ = setTimeout(function() { + this.clickTimeoutId_ = 0; + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.SINGLECLICK, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + }.bind(this), 250); + } +}; + + +/** + * Keeps track on how many pointers are currently active. + * + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private + */ +ol.MapBrowserEventHandler.prototype.updateActivePointers_ = function(pointerEvent) { + var event = pointerEvent; + + if (event.type == ol.MapBrowserEvent.EventType.POINTERUP || + event.type == ol.MapBrowserEvent.EventType.POINTERCANCEL) { + delete this.trackedTouches_[event.pointerId]; + } else if (event.type == ol.MapBrowserEvent.EventType.POINTERDOWN) { + this.trackedTouches_[event.pointerId] = true; + } + this.activePointers_ = Object.keys(this.trackedTouches_).length; +}; + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private + */ +ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) { + this.updateActivePointers_(pointerEvent); + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.POINTERUP, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + + // We emulate click events on left mouse button click, touch contact, and pen + // contact. isMouseActionButton returns true in these cases (evt.button is set + // to 0). + // See http://www.w3.org/TR/pointerevents/#button-states + if (!this.dragging_ && this.isMouseActionButton_(pointerEvent)) { + ol.DEBUG && console.assert(this.down_, 'this.down_ must be truthy'); + this.emulateClick_(this.down_); + } + + ol.DEBUG && console.assert(this.activePointers_ >= 0, + 'this.activePointers_ should be equal to or larger than 0'); + if (this.activePointers_ === 0) { + this.dragListenerKeys_.forEach(ol.events.unlistenByKey); + this.dragListenerKeys_.length = 0; + this.dragging_ = false; + this.down_ = null; + this.documentPointerEventHandler_.dispose(); + this.documentPointerEventHandler_ = null; + } +}; + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @return {boolean} If the left mouse button was pressed. + * @private + */ +ol.MapBrowserEventHandler.prototype.isMouseActionButton_ = function(pointerEvent) { + return pointerEvent.button === 0; +}; + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private + */ +ol.MapBrowserEventHandler.prototype.handlePointerDown_ = function(pointerEvent) { + this.updateActivePointers_(pointerEvent); + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.POINTERDOWN, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + + this.down_ = pointerEvent; + + if (this.dragListenerKeys_.length === 0) { + /* Set up a pointer event handler on the `document`, + * which is required when the pointer is moved outside + * the viewport when dragging. + */ + this.documentPointerEventHandler_ = + new ol.pointer.PointerEventHandler(document); + + this.dragListenerKeys_.push( + ol.events.listen(this.documentPointerEventHandler_, + ol.MapBrowserEvent.EventType.POINTERMOVE, + this.handlePointerMove_, this), + ol.events.listen(this.documentPointerEventHandler_, + ol.MapBrowserEvent.EventType.POINTERUP, + this.handlePointerUp_, this), + /* Note that the listener for `pointercancel is set up on + * `pointerEventHandler_` and not `documentPointerEventHandler_` like + * the `pointerup` and `pointermove` listeners. + * + * The reason for this is the following: `TouchSource.vacuumTouches_()` + * issues `pointercancel` events, when there was no `touchend` for a + * `touchstart`. Now, let's say a first `touchstart` is registered on + * `pointerEventHandler_`. The `documentPointerEventHandler_` is set up. + * But `documentPointerEventHandler_` doesn't know about the first + * `touchstart`. If there is no `touchend` for the `touchstart`, we can + * only receive a `touchcancel` from `pointerEventHandler_`, because it is + * only registered there. + */ + ol.events.listen(this.pointerEventHandler_, + ol.MapBrowserEvent.EventType.POINTERCANCEL, + this.handlePointerUp_, this) + ); + } +}; + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private + */ +ol.MapBrowserEventHandler.prototype.handlePointerMove_ = function(pointerEvent) { + // Fix IE10 on windows Surface : When you tap the tablet, it triggers + // multiple pointermove events between pointerdown and pointerup with + // the exact same coordinates of the pointerdown event. To avoid a + // 'false' touchmove event to be dispatched , we test if the pointer + // effectively moved. + if (this.isMoving_(pointerEvent)) { + this.dragging_ = true; + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.POINTERDRAG, this.map_, pointerEvent, + this.dragging_); + this.dispatchEvent(newEvent); + } + + // Some native android browser triggers mousemove events during small period + // of time. See: https://code.google.com/p/android/issues/detail?id=5491 or + // https://code.google.com/p/android/issues/detail?id=19827 + // ex: Galaxy Tab P3110 + Android 4.1.1 + pointerEvent.preventDefault(); +}; + + +/** + * Wrap and relay a pointer event. Note that this requires that the type + * string for the MapBrowserPointerEvent matches the PointerEvent type. + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private + */ +ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) { + var dragging = !!(this.down_ && this.isMoving_(pointerEvent)); + this.dispatchEvent(new ol.MapBrowserPointerEvent( + pointerEvent.type, this.map_, pointerEvent, dragging)); +}; + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @return {boolean} Is moving. + * @private + */ +ol.MapBrowserEventHandler.prototype.isMoving_ = function(pointerEvent) { + return pointerEvent.clientX != this.down_.clientX || + pointerEvent.clientY != this.down_.clientY; +}; + + +/** + * @inheritDoc + */ +ol.MapBrowserEventHandler.prototype.disposeInternal = function() { + if (this.relayedListenerKey_) { + ol.events.unlistenByKey(this.relayedListenerKey_); + this.relayedListenerKey_ = null; + } + if (this.pointerdownListenerKey_) { + ol.events.unlistenByKey(this.pointerdownListenerKey_); + this.pointerdownListenerKey_ = null; + } + + this.dragListenerKeys_.forEach(ol.events.unlistenByKey); + this.dragListenerKeys_.length = 0; + + if (this.documentPointerEventHandler_) { + this.documentPointerEventHandler_.dispose(); + this.documentPointerEventHandler_ = null; + } + if (this.pointerEventHandler_) { + this.pointerEventHandler_.dispose(); + this.pointerEventHandler_ = null; + } + ol.events.EventTarget.prototype.disposeInternal.call(this); +}; + +goog.provide('ol.Tile'); + +goog.require('ol'); +goog.require('ol.events.EventTarget'); +goog.require('ol.events.EventType'); + + +/** + * @classdesc + * Base class for tiles. + * + * @constructor + * @extends {ol.events.EventTarget} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Tile.State} state State. + */ +ol.Tile = function(tileCoord, state) { + + ol.events.EventTarget.call(this); + + /** + * @type {ol.TileCoord} + */ + this.tileCoord = tileCoord; + + /** + * @protected + * @type {ol.Tile.State} + */ + this.state = state; + + /** + * An "interim" tile for this tile. The interim tile may be used while this + * one is loading, for "smooth" transitions when changing params/dimensions + * on the source. + * @type {ol.Tile} + */ + this.interimTile = null; + + /** + * A key assigned to the tile. This is used by the tile source to determine + * if this tile can effectively be used, or if a new tile should be created + * and this one be used as an interim tile for this new tile. + * @type {string} + */ + this.key = ''; + +}; +ol.inherits(ol.Tile, ol.events.EventTarget); + + +/** + * @protected + */ +ol.Tile.prototype.changed = function() { + this.dispatchEvent(ol.events.EventType.CHANGE); +}; + + +/** + * Get the HTML image element for this tile (may be a Canvas, Image, or Video). + * @abstract + * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image. + */ +ol.Tile.prototype.getImage = function() {}; + + +/** + * @return {string} Key. + */ +ol.Tile.prototype.getKey = function() { + return this.key + '/' + this.tileCoord; +}; + +/** + * Get the interim tile most suitable for rendering using the chain of interim + * tiles. This corresponds to the most recent tile that has been loaded, if no + * such tile exists, the original tile is returned. + * @return {!ol.Tile} Best tile for rendering. + */ +ol.Tile.prototype.getInterimTile = function() { + if (!this.interimTile) { + //empty chain + return this; + } + var tile = this.interimTile; + + // find the first loaded tile and return it. Since the chain is sorted in + // decreasing order of creation time, there is no need to search the remainder + // of the list (all those tiles correspond to older requests and will be + // cleaned up by refreshInterimChain) + do { + if (tile.getState() == ol.Tile.State.LOADED) { + return tile; + } + tile = tile.interimTile; + } while (tile); + + // we can not find a better tile + return this; +}; + +/** + * Goes through the chain of interim tiles and discards sections of the chain + * that are no longer relevant. + */ +ol.Tile.prototype.refreshInterimChain = function() { + if (!this.interimTile) { + return; + } + + var tile = this.interimTile; + var prev = this; + + do { + if (tile.getState() == ol.Tile.State.LOADED) { + //we have a loaded tile, we can discard the rest of the list + //we would could abort any LOADING tile request + //older than this tile (i.e. any LOADING tile following this entry in the chain) + tile.interimTile = null; + break; + } else if (tile.getState() == ol.Tile.State.LOADING) { + //keep this LOADING tile any loaded tiles later in the chain are + //older than this tile, so we're still interested in the request + prev = tile; + } else if (tile.getState() == ol.Tile.State.IDLE) { + //the head of the list is the most current tile, we don't need + //to start any other requests for this chain + prev.interimTile = tile.interimTile; + } else { + prev = tile; + } + tile = prev.interimTile; + } while (tile); +}; + +/** + * Get the tile coordinate for this tile. + * @return {ol.TileCoord} The tile coordinate. + * @api + */ +ol.Tile.prototype.getTileCoord = function() { + return this.tileCoord; +}; + + +/** + * @return {ol.Tile.State} State. + */ +ol.Tile.prototype.getState = function() { + return this.state; +}; + + +/** + * Load the image or retry if loading previously failed. + * Loading is taken care of by the tile queue, and calling this method is + * only needed for preloading or for reloading in case of an error. + * @abstract + * @api + */ +ol.Tile.prototype.load = function() {}; + + +/** + * @enum {number} + */ +ol.Tile.State = { + IDLE: 0, + LOADING: 1, + LOADED: 2, + ERROR: 3, + EMPTY: 4, + ABORT: 5 +}; + +goog.provide('ol.structs.PriorityQueue'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.obj'); + + +/** + * Priority queue. + * + * The implementation is inspired from the Closure Library's Heap class and + * Python's heapq module. + * + * @see http://closure-library.googlecode.com/svn/docs/closure_goog_structs_heap.js.source.html + * @see http://hg.python.org/cpython/file/2.7/Lib/heapq.py + * + * @constructor + * @param {function(T): number} priorityFunction Priority function. + * @param {function(T): string} keyFunction Key function. + * @struct + * @template T + */ +ol.structs.PriorityQueue = function(priorityFunction, keyFunction) { + + /** + * @type {function(T): number} + * @private + */ + this.priorityFunction_ = priorityFunction; + + /** + * @type {function(T): string} + * @private + */ + this.keyFunction_ = keyFunction; + + /** + * @type {Array.<T>} + * @private + */ + this.elements_ = []; + + /** + * @type {Array.<number>} + * @private + */ + this.priorities_ = []; + + /** + * @type {Object.<string, boolean>} + * @private + */ + this.queuedElements_ = {}; + +}; + + +/** + * @const + * @type {number} + */ +ol.structs.PriorityQueue.DROP = Infinity; + + +if (ol.DEBUG) { + /** + * FIXME empty description for jsdoc + */ + ol.structs.PriorityQueue.prototype.assertValid = function() { + var elements = this.elements_; + var priorities = this.priorities_; + var n = elements.length; + console.assert(priorities.length == n); + var i, priority; + for (i = 0; i < (n >> 1) - 1; ++i) { + priority = priorities[i]; + console.assert(priority <= priorities[this.getLeftChildIndex_(i)], + 'priority smaller than or equal to priority of left child (%s <= %s)', + priority, priorities[this.getLeftChildIndex_(i)]); + console.assert(priority <= priorities[this.getRightChildIndex_(i)], + 'priority smaller than or equal to priority of right child (%s <= %s)', + priority, priorities[this.getRightChildIndex_(i)]); + } + }; +} + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.PriorityQueue.prototype.clear = function() { + this.elements_.length = 0; + this.priorities_.length = 0; + ol.obj.clear(this.queuedElements_); +}; + + +/** + * Remove and return the highest-priority element. O(log N). + * @return {T} Element. + */ +ol.structs.PriorityQueue.prototype.dequeue = function() { + var elements = this.elements_; + ol.DEBUG && console.assert(elements.length > 0, + 'must have elements in order to be able to dequeue'); + var priorities = this.priorities_; + var element = elements[0]; + if (elements.length == 1) { + elements.length = 0; + priorities.length = 0; + } else { + elements[0] = elements.pop(); + priorities[0] = priorities.pop(); + this.siftUp_(0); + } + var elementKey = this.keyFunction_(element); + ol.DEBUG && console.assert(elementKey in this.queuedElements_, + 'key %s is not listed as queued', elementKey); + delete this.queuedElements_[elementKey]; + return element; +}; + + +/** + * Enqueue an element. O(log N). + * @param {T} element Element. + * @return {boolean} The element was added to the queue. + */ +ol.structs.PriorityQueue.prototype.enqueue = function(element) { + ol.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_), + 31); // Tried to enqueue an `element` that was already added to the queue + var priority = this.priorityFunction_(element); + if (priority != ol.structs.PriorityQueue.DROP) { + this.elements_.push(element); + this.priorities_.push(priority); + this.queuedElements_[this.keyFunction_(element)] = true; + this.siftDown_(0, this.elements_.length - 1); + return true; + } + return false; +}; + + +/** + * @return {number} Count. + */ +ol.structs.PriorityQueue.prototype.getCount = function() { + return this.elements_.length; +}; + + +/** + * Gets the index of the left child of the node at the given index. + * @param {number} index The index of the node to get the left child for. + * @return {number} The index of the left child. + * @private + */ +ol.structs.PriorityQueue.prototype.getLeftChildIndex_ = function(index) { + return index * 2 + 1; +}; + + +/** + * Gets the index of the right child of the node at the given index. + * @param {number} index The index of the node to get the right child for. + * @return {number} The index of the right child. + * @private + */ +ol.structs.PriorityQueue.prototype.getRightChildIndex_ = function(index) { + return index * 2 + 2; +}; + + +/** + * Gets the index of the parent of the node at the given index. + * @param {number} index The index of the node to get the parent for. + * @return {number} The index of the parent. + * @private + */ +ol.structs.PriorityQueue.prototype.getParentIndex_ = function(index) { + return (index - 1) >> 1; +}; + + +/** + * Make this a heap. O(N). + * @private + */ +ol.structs.PriorityQueue.prototype.heapify_ = function() { + var i; + for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) { + this.siftUp_(i); + } +}; + + +/** + * @return {boolean} Is empty. + */ +ol.structs.PriorityQueue.prototype.isEmpty = function() { + return this.elements_.length === 0; +}; + + +/** + * @param {string} key Key. + * @return {boolean} Is key queued. + */ +ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) { + return key in this.queuedElements_; +}; + + +/** + * @param {T} element Element. + * @return {boolean} Is queued. + */ +ol.structs.PriorityQueue.prototype.isQueued = function(element) { + return this.isKeyQueued(this.keyFunction_(element)); +}; + + +/** + * @param {number} index The index of the node to move down. + * @private + */ +ol.structs.PriorityQueue.prototype.siftUp_ = function(index) { + var elements = this.elements_; + var priorities = this.priorities_; + var count = elements.length; + var element = elements[index]; + var priority = priorities[index]; + var startIndex = index; + + while (index < (count >> 1)) { + var lIndex = this.getLeftChildIndex_(index); + var rIndex = this.getRightChildIndex_(index); + + var smallerChildIndex = rIndex < count && + priorities[rIndex] < priorities[lIndex] ? + rIndex : lIndex; + + elements[index] = elements[smallerChildIndex]; + priorities[index] = priorities[smallerChildIndex]; + index = smallerChildIndex; + } + + elements[index] = element; + priorities[index] = priority; + this.siftDown_(startIndex, index); +}; + + +/** + * @param {number} startIndex The index of the root. + * @param {number} index The index of the node to move up. + * @private + */ +ol.structs.PriorityQueue.prototype.siftDown_ = function(startIndex, index) { + var elements = this.elements_; + var priorities = this.priorities_; + var element = elements[index]; + var priority = priorities[index]; + + while (index > startIndex) { + var parentIndex = this.getParentIndex_(index); + if (priorities[parentIndex] > priority) { + elements[index] = elements[parentIndex]; + priorities[index] = priorities[parentIndex]; + index = parentIndex; + } else { + break; + } + } + elements[index] = element; + priorities[index] = priority; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.PriorityQueue.prototype.reprioritize = function() { + var priorityFunction = this.priorityFunction_; + var elements = this.elements_; + var priorities = this.priorities_; + var index = 0; + var n = elements.length; + var element, i, priority; + for (i = 0; i < n; ++i) { + element = elements[i]; + priority = priorityFunction(element); + if (priority == ol.structs.PriorityQueue.DROP) { + delete this.queuedElements_[this.keyFunction_(element)]; + } else { + priorities[index] = priority; + elements[index++] = element; + } + } + elements.length = index; + priorities.length = index; + this.heapify_(); +}; + +goog.provide('ol.TileQueue'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.structs.PriorityQueue'); + + +/** + * @constructor + * @extends {ol.structs.PriorityQueue.<Array>} + * @param {ol.TilePriorityFunction} tilePriorityFunction + * Tile priority function. + * @param {function(): ?} tileChangeCallback + * Function called on each tile change event. + * @struct + */ +ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) { + + ol.structs.PriorityQueue.call( + this, + /** + * @param {Array} element Element. + * @return {number} Priority. + */ + function(element) { + return tilePriorityFunction.apply(null, element); + }, + /** + * @param {Array} element Element. + * @return {string} Key. + */ + function(element) { + return /** @type {ol.Tile} */ (element[0]).getKey(); + }); + + /** + * @private + * @type {function(): ?} + */ + this.tileChangeCallback_ = tileChangeCallback; + + /** + * @private + * @type {number} + */ + this.tilesLoading_ = 0; + + /** + * @private + * @type {!Object.<string,boolean>} + */ + this.tilesLoadingKeys_ = {}; + +}; +ol.inherits(ol.TileQueue, ol.structs.PriorityQueue); + + +/** + * @inheritDoc + */ +ol.TileQueue.prototype.enqueue = function(element) { + var added = ol.structs.PriorityQueue.prototype.enqueue.call(this, element); + if (added) { + var tile = element[0]; + ol.events.listen(tile, ol.events.EventType.CHANGE, + this.handleTileChange, this); + } + return added; +}; + + +/** + * @return {number} Number of tiles loading. + */ +ol.TileQueue.prototype.getTilesLoading = function() { + return this.tilesLoading_; +}; + + +/** + * @param {ol.events.Event} event Event. + * @protected + */ +ol.TileQueue.prototype.handleTileChange = function(event) { + var tile = /** @type {ol.Tile} */ (event.target); + var state = tile.getState(); + if (state === ol.Tile.State.LOADED || state === ol.Tile.State.ERROR || + state === ol.Tile.State.EMPTY || state === ol.Tile.State.ABORT) { + ol.events.unlisten(tile, ol.events.EventType.CHANGE, + this.handleTileChange, this); + var tileKey = tile.getKey(); + if (tileKey in this.tilesLoadingKeys_) { + delete this.tilesLoadingKeys_[tileKey]; + --this.tilesLoading_; + } + this.tileChangeCallback_(); + } + ol.DEBUG && console.assert(Object.keys(this.tilesLoadingKeys_).length === this.tilesLoading_); +}; + + +/** + * @param {number} maxTotalLoading Maximum number tiles to load simultaneously. + * @param {number} maxNewLoads Maximum number of new tiles to load. + */ +ol.TileQueue.prototype.loadMoreTiles = function(maxTotalLoading, maxNewLoads) { + var newLoads = 0; + var tile, tileKey; + while (this.tilesLoading_ < maxTotalLoading && newLoads < maxNewLoads && + this.getCount() > 0) { + tile = /** @type {ol.Tile} */ (this.dequeue()[0]); + tileKey = tile.getKey(); + if (tile.getState() === ol.Tile.State.IDLE && !(tileKey in this.tilesLoadingKeys_)) { + this.tilesLoadingKeys_[tileKey] = true; + ++this.tilesLoading_; + ++newLoads; + tile.load(); + } + ol.DEBUG && console.assert(Object.keys(this.tilesLoadingKeys_).length === this.tilesLoading_); + } +}; + +goog.provide('ol.Kinetic'); + + +/** + * @classdesc + * Implementation of inertial deceleration for map movement. + * + * @constructor + * @param {number} decay Rate of decay (must be negative). + * @param {number} minVelocity Minimum velocity (pixels/millisecond). + * @param {number} delay Delay to consider to calculate the kinetic + * initial values (milliseconds). + * @struct + * @api + */ +ol.Kinetic = function(decay, minVelocity, delay) { + + /** + * @private + * @type {number} + */ + this.decay_ = decay; + + /** + * @private + * @type {number} + */ + this.minVelocity_ = minVelocity; + + /** + * @private + * @type {number} + */ + this.delay_ = delay; + + /** + * @private + * @type {Array.<number>} + */ + this.points_ = []; + + /** + * @private + * @type {number} + */ + this.angle_ = 0; + + /** + * @private + * @type {number} + */ + this.initialVelocity_ = 0; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.Kinetic.prototype.begin = function() { + this.points_.length = 0; + this.angle_ = 0; + this.initialVelocity_ = 0; +}; + + +/** + * @param {number} x X. + * @param {number} y Y. + */ +ol.Kinetic.prototype.update = function(x, y) { + this.points_.push(x, y, Date.now()); +}; + + +/** + * @return {boolean} Whether we should do kinetic animation. + */ +ol.Kinetic.prototype.end = function() { + if (this.points_.length < 6) { + // at least 2 points are required (i.e. there must be at least 6 elements + // in the array) + return false; + } + var delay = Date.now() - this.delay_; + var lastIndex = this.points_.length - 3; + if (this.points_[lastIndex + 2] < delay) { + // the last tracked point is too old, which means that the user stopped + // panning before releasing the map + return false; + } + + // get the first point which still falls into the delay time + var firstIndex = lastIndex - 3; + while (firstIndex > 0 && this.points_[firstIndex + 2] > delay) { + firstIndex -= 3; + } + var duration = this.points_[lastIndex + 2] - this.points_[firstIndex + 2]; + var dx = this.points_[lastIndex] - this.points_[firstIndex]; + var dy = this.points_[lastIndex + 1] - this.points_[firstIndex + 1]; + this.angle_ = Math.atan2(dy, dx); + this.initialVelocity_ = Math.sqrt(dx * dx + dy * dy) / duration; + return this.initialVelocity_ > this.minVelocity_; +}; + + +/** + * @private + * @return {number} Duration of animation (milliseconds). + */ +ol.Kinetic.prototype.getDuration_ = function() { + return Math.log(this.minVelocity_ / this.initialVelocity_) / this.decay_; +}; + + +/** + * @return {number} Total distance travelled (pixels). + */ +ol.Kinetic.prototype.getDistance = function() { + return (this.minVelocity_ - this.initialVelocity_) / this.decay_; +}; + + +/** + * @return {number} Angle of the kinetic panning animation (radians). + */ +ol.Kinetic.prototype.getAngle = function() { + return this.angle_; +}; + +// FIXME factor out key precondition (shift et. al) + +goog.provide('ol.interaction.Interaction'); + +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.easing'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * User actions that change the state of the map. Some are similar to controls, + * but are not associated with a DOM element. + * For example, {@link ol.interaction.KeyboardZoom} is functionally the same as + * {@link ol.control.Zoom}, but triggered by a keyboard event not a button + * element event. + * Although interactions do not have a DOM element, some of them do render + * vectors and so are visible on the screen. + * + * @constructor + * @param {olx.interaction.InteractionOptions} options Options. + * @extends {ol.Object} + * @api + */ +ol.interaction.Interaction = function(options) { + + ol.Object.call(this); + + /** + * @private + * @type {ol.Map} + */ + this.map_ = null; + + this.setActive(true); + + /** + * @type {function(ol.MapBrowserEvent):boolean} + */ + this.handleEvent = options.handleEvent; + +}; +ol.inherits(ol.interaction.Interaction, ol.Object); + + +/** + * Return whether the interaction is currently active. + * @return {boolean} `true` if the interaction is active, `false` otherwise. + * @observable + * @api + */ +ol.interaction.Interaction.prototype.getActive = function() { + return /** @type {boolean} */ ( + this.get(ol.interaction.Interaction.Property.ACTIVE)); +}; + + +/** + * Get the map associated with this interaction. + * @return {ol.Map} Map. + * @api + */ +ol.interaction.Interaction.prototype.getMap = function() { + return this.map_; +}; + + +/** + * Activate or deactivate the interaction. + * @param {boolean} active Active. + * @observable + * @api + */ +ol.interaction.Interaction.prototype.setActive = function(active) { + this.set(ol.interaction.Interaction.Property.ACTIVE, active); +}; + + +/** + * Remove the interaction from its current map and attach it to the new map. + * Subclasses may set up event handlers to get notified about changes to + * the map here. + * @param {ol.Map} map Map. + */ +ol.interaction.Interaction.prototype.setMap = function(map) { + this.map_ = map; +}; + + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {ol.Coordinate} delta Delta. + * @param {number=} opt_duration Duration. + */ +ol.interaction.Interaction.pan = function(map, view, delta, opt_duration) { + var currentCenter = view.getCenter(); + if (currentCenter) { + var center = view.constrainCenter( + [currentCenter[0] + delta[0], currentCenter[1] + delta[1]]); + if (opt_duration) { + view.animate({ + duration: opt_duration, + easing: ol.easing.linear, + center: center + }); + } else { + view.setCenter(center); + } + } +}; + + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} rotation Rotation. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + */ +ol.interaction.Interaction.rotate = function(map, view, rotation, opt_anchor, opt_duration) { + rotation = view.constrainRotation(rotation, 0); + ol.interaction.Interaction.rotateWithoutConstraints( + map, view, rotation, opt_anchor, opt_duration); +}; + + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} rotation Rotation. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + */ +ol.interaction.Interaction.rotateWithoutConstraints = function(map, view, rotation, opt_anchor, opt_duration) { + if (rotation !== undefined) { + var currentRotation = view.getRotation(); + var currentCenter = view.getCenter(); + if (currentRotation !== undefined && currentCenter && opt_duration > 0) { + view.animate({ + rotation: rotation, + anchor: opt_anchor, + duration: opt_duration, + easing: ol.easing.easeOut + }); + } else { + view.rotate(rotation, opt_anchor); + } + } +}; + + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} resolution Resolution to go to. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + * @param {number=} opt_direction Zooming direction; > 0 indicates + * zooming out, in which case the constraints system will select + * the largest nearest resolution; < 0 indicates zooming in, in + * which case the constraints system will select the smallest + * nearest resolution; == 0 indicates that the zooming direction + * is unknown/not relevant, in which case the constraints system + * will select the nearest resolution. If not defined 0 is + * assumed. + */ +ol.interaction.Interaction.zoom = function(map, view, resolution, opt_anchor, opt_duration, opt_direction) { + resolution = view.constrainResolution(resolution, 0, opt_direction); + ol.interaction.Interaction.zoomWithoutConstraints( + map, view, resolution, opt_anchor, opt_duration); +}; + + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number} delta Delta from previous zoom level. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + */ +ol.interaction.Interaction.zoomByDelta = function(map, view, delta, opt_anchor, opt_duration) { + var currentResolution = view.getResolution(); + var resolution = view.constrainResolution(currentResolution, delta, 0); + ol.interaction.Interaction.zoomWithoutConstraints( + map, view, resolution, opt_anchor, opt_duration); +}; + + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} resolution Resolution to go to. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + */ +ol.interaction.Interaction.zoomWithoutConstraints = function(map, view, resolution, opt_anchor, opt_duration) { + if (resolution) { + var currentResolution = view.getResolution(); + var currentCenter = view.getCenter(); + if (currentResolution !== undefined && currentCenter && + resolution !== currentResolution && opt_duration) { + view.animate({ + resolution: resolution, + anchor: opt_anchor, + duration: opt_duration, + easing: ol.easing.easeOut + }); + } else { + if (opt_anchor) { + var center = view.calculateCenterZoom(resolution, opt_anchor); + view.setCenter(center); + } + view.setResolution(resolution); + } + } +}; + + +/** + * @enum {string} + */ +ol.interaction.Interaction.Property = { + ACTIVE: 'active' +}; + +goog.provide('ol.interaction.DoubleClickZoom'); + +goog.require('ol'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.interaction.Interaction'); + + +/** + * @classdesc + * Allows the user to zoom by double-clicking on the map. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.DoubleClickZoomOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DoubleClickZoom = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {number} + */ + this.delta_ = options.delta ? options.delta : 1; + + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.DoubleClickZoom.handleEvent + }); + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; + +}; +ol.inherits(ol.interaction.DoubleClickZoom, ol.interaction.Interaction); + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} (if it was a + * doubleclick) and eventually zooms the map. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.DoubleClickZoom} + * @api + */ +ol.interaction.DoubleClickZoom.handleEvent = function(mapBrowserEvent) { + var stopEvent = false; + var browserEvent = mapBrowserEvent.originalEvent; + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK) { + var map = mapBrowserEvent.map; + var anchor = mapBrowserEvent.coordinate; + var delta = browserEvent.shiftKey ? -this.delta_ : this.delta_; + var view = map.getView(); + ol.interaction.Interaction.zoomByDelta( + map, view, delta, anchor, this.duration_); + mapBrowserEvent.preventDefault(); + stopEvent = true; + } + return !stopEvent; +}; + +goog.provide('ol.events.condition'); + +goog.require('ol.MapBrowserEvent'); +goog.require('ol.asserts'); +goog.require('ol.functions'); +goog.require('ol.has'); + + +/** + * Return `true` if only the alt-key is pressed, `false` otherwise (e.g. when + * additionally the shift-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the alt key is pressed. + * @api stable + */ +ol.events.condition.altKeyOnly = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + originalEvent.altKey && + !(originalEvent.metaKey || originalEvent.ctrlKey) && + !originalEvent.shiftKey); +}; + + +/** + * Return `true` if only the alt-key and shift-key is pressed, `false` otherwise + * (e.g. when additionally the platform-modifier-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the alt and shift keys are pressed. + * @api stable + */ +ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + originalEvent.altKey && + !(originalEvent.metaKey || originalEvent.ctrlKey) && + originalEvent.shiftKey); +}; + + +/** + * Return always true. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True. + * @function + * @api stable + */ +ol.events.condition.always = ol.functions.TRUE; + + +/** + * Return `true` if the event is a `click` event, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event is a map `click` event. + * @api stable + */ +ol.events.condition.click = function(mapBrowserEvent) { + return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.CLICK; +}; + + +/** + * Return `true` if the event has an "action"-producing mouse button. + * + * By definition, this includes left-click on windows/linux, and left-click + * without the ctrl key on Macs. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} The result. + */ +ol.events.condition.mouseActionButton = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return originalEvent.button == 0 && + !(ol.has.WEBKIT && ol.has.MAC && originalEvent.ctrlKey); +}; + + +/** + * Return always false. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} False. + * @function + * @api stable + */ +ol.events.condition.never = ol.functions.FALSE; + + +/** + * Return `true` if the browser event is a `pointermove` event, `false` + * otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the browser event is a `pointermove` event. + * @api + */ +ol.events.condition.pointerMove = function(mapBrowserEvent) { + return mapBrowserEvent.type == 'pointermove'; +}; + + +/** + * Return `true` if the event is a map `singleclick` event, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event is a map `singleclick` event. + * @api stable + */ +ol.events.condition.singleClick = function(mapBrowserEvent) { + return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK; +}; + + +/** + * Return `true` if the event is a map `dblclick` event, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event is a map `dblclick` event. + * @api stable + */ +ol.events.condition.doubleClick = function(mapBrowserEvent) { + return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK; +}; + + +/** + * Return `true` if no modifier key (alt-, shift- or platform-modifier-key) is + * pressed. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True only if there no modifier keys are pressed. + * @api stable + */ +ol.events.condition.noModifierKeys = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + !originalEvent.altKey && + !(originalEvent.metaKey || originalEvent.ctrlKey) && + !originalEvent.shiftKey); +}; + + +/** + * Return `true` if only the platform-modifier-key (the meta-key on Mac, + * ctrl-key otherwise) is pressed, `false` otherwise (e.g. when additionally + * the shift-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the platform modifier key is pressed. + * @api stable + */ +ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + !originalEvent.altKey && + (ol.has.MAC ? originalEvent.metaKey : originalEvent.ctrlKey) && + !originalEvent.shiftKey); +}; + + +/** + * Return `true` if only the shift-key is pressed, `false` otherwise (e.g. when + * additionally the alt-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the shift key is pressed. + * @api stable + */ +ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + !originalEvent.altKey && + !(originalEvent.metaKey || originalEvent.ctrlKey) && + originalEvent.shiftKey); +}; + + +/** + * Return `true` if the target element is not editable, i.e. not a `<input>`-, + * `<select>`- or `<textarea>`-element, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True only if the target element is not editable. + * @api + */ +ol.events.condition.targetNotEditable = function(mapBrowserEvent) { + var target = mapBrowserEvent.originalEvent.target; + var tagName = target.tagName; + return ( + tagName !== 'INPUT' && + tagName !== 'SELECT' && + tagName !== 'TEXTAREA'); +}; + + +/** + * Return `true` if the event originates from a mouse device. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event originates from a mouse device. + * @api stable + */ +ol.events.condition.mouseOnly = function(mapBrowserEvent) { + ol.asserts.assert(mapBrowserEvent.pointerEvent, 56); // mapBrowserEvent must originate from a pointer event + // see http://www.w3.org/TR/pointerevents/#widl-PointerEvent-pointerType + return /** @type {ol.MapBrowserEvent} */ (mapBrowserEvent).pointerEvent.pointerType == 'mouse'; +}; + + +/** + * Return `true` if the event originates from a primary pointer in + * contact with the surface or if the left mouse button is pressed. + * @see http://www.w3.org/TR/pointerevents/#button-states + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event originates from a primary pointer. + * @api + */ +ol.events.condition.primaryAction = function(mapBrowserEvent) { + var pointerEvent = mapBrowserEvent.pointerEvent; + return pointerEvent.isPrimary && pointerEvent.button === 0; +}; + +goog.provide('ol.interaction.Pointer'); + +goog.require('ol'); +goog.require('ol.functions'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserPointerEvent'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.obj'); + + +/** + * @classdesc + * Base class that calls user-defined functions on `down`, `move` and `up` + * events. This class also manages "drag sequences". + * + * When the `handleDownEvent` user function returns `true` a drag sequence is + * started. During a drag sequence the `handleDragEvent` user function is + * called on `move` events. The drag sequence ends when the `handleUpEvent` + * user function is called and returns `false`. + * + * @constructor + * @param {olx.interaction.PointerOptions=} opt_options Options. + * @extends {ol.interaction.Interaction} + * @api + */ +ol.interaction.Pointer = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var handleEvent = options.handleEvent ? + options.handleEvent : ol.interaction.Pointer.handleEvent; + + ol.interaction.Interaction.call(this, { + handleEvent: handleEvent + }); + + /** + * @type {function(ol.MapBrowserPointerEvent):boolean} + * @private + */ + this.handleDownEvent_ = options.handleDownEvent ? + options.handleDownEvent : ol.interaction.Pointer.handleDownEvent; + + /** + * @type {function(ol.MapBrowserPointerEvent)} + * @private + */ + this.handleDragEvent_ = options.handleDragEvent ? + options.handleDragEvent : ol.interaction.Pointer.handleDragEvent; + + /** + * @type {function(ol.MapBrowserPointerEvent)} + * @private + */ + this.handleMoveEvent_ = options.handleMoveEvent ? + options.handleMoveEvent : ol.interaction.Pointer.handleMoveEvent; + + /** + * @type {function(ol.MapBrowserPointerEvent):boolean} + * @private + */ + this.handleUpEvent_ = options.handleUpEvent ? + options.handleUpEvent : ol.interaction.Pointer.handleUpEvent; + + /** + * @type {boolean} + * @protected + */ + this.handlingDownUpSequence = false; + + /** + * @type {Object.<number, ol.pointer.PointerEvent>} + * @private + */ + this.trackedPointers_ = {}; + + /** + * @type {Array.<ol.pointer.PointerEvent>} + * @protected + */ + this.targetPointers = []; + +}; +ol.inherits(ol.interaction.Pointer, ol.interaction.Interaction); + + +/** + * @param {Array.<ol.pointer.PointerEvent>} pointerEvents List of events. + * @return {ol.Pixel} Centroid pixel. + */ +ol.interaction.Pointer.centroid = function(pointerEvents) { + var length = pointerEvents.length; + var clientX = 0; + var clientY = 0; + for (var i = 0; i < length; i++) { + clientX += pointerEvents[i].clientX; + clientY += pointerEvents[i].clientY; + } + return [clientX / length, clientY / length]; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Whether the event is a pointerdown, pointerdrag + * or pointerup event. + * @private + */ +ol.interaction.Pointer.prototype.isPointerDraggingEvent_ = function(mapBrowserEvent) { + var type = mapBrowserEvent.type; + return ( + type === ol.MapBrowserEvent.EventType.POINTERDOWN || + type === ol.MapBrowserEvent.EventType.POINTERDRAG || + type === ol.MapBrowserEvent.EventType.POINTERUP); +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @private + */ +ol.interaction.Pointer.prototype.updateTrackedPointers_ = function(mapBrowserEvent) { + if (this.isPointerDraggingEvent_(mapBrowserEvent)) { + var event = mapBrowserEvent.pointerEvent; + + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) { + delete this.trackedPointers_[event.pointerId]; + } else if (mapBrowserEvent.type == + ol.MapBrowserEvent.EventType.POINTERDOWN) { + this.trackedPointers_[event.pointerId] = event; + } else if (event.pointerId in this.trackedPointers_) { + // update only when there was a pointerdown event for this pointer + this.trackedPointers_[event.pointerId] = event; + } + this.targetPointers = ol.obj.getValues(this.trackedPointers_); + } +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.Pointer} + */ +ol.interaction.Pointer.handleDragEvent = ol.nullFunction; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Capture dragging. + * @this {ol.interaction.Pointer} + */ +ol.interaction.Pointer.handleUpEvent = ol.functions.FALSE; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Capture dragging. + * @this {ol.interaction.Pointer} + */ +ol.interaction.Pointer.handleDownEvent = ol.functions.FALSE; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.Pointer} + */ +ol.interaction.Pointer.handleMoveEvent = ol.nullFunction; + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} and may call into + * other functions, if event sequences like e.g. 'drag' or 'down-up' etc. are + * detected. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.Pointer} + * @api + */ +ol.interaction.Pointer.handleEvent = function(mapBrowserEvent) { + if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) { + return true; + } + + var stopEvent = false; + this.updateTrackedPointers_(mapBrowserEvent); + if (this.handlingDownUpSequence) { + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERDRAG) { + this.handleDragEvent_(mapBrowserEvent); + } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) { + this.handlingDownUpSequence = this.handleUpEvent_(mapBrowserEvent); + } + } + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERDOWN) { + var handled = this.handleDownEvent_(mapBrowserEvent); + this.handlingDownUpSequence = handled; + stopEvent = this.shouldStopEvent(handled); + } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE) { + this.handleMoveEvent_(mapBrowserEvent); + } + return !stopEvent; +}; + + +/** + * This method is used to determine if "down" events should be propagated to + * other interactions or should be stopped. + * + * The method receives the return code of the "handleDownEvent" function. + * + * By default this function is the "identity" function. It's overidden in + * child classes. + * + * @param {boolean} handled Was the event handled by the interaction? + * @return {boolean} Should the event be stopped? + * @protected + */ +ol.interaction.Pointer.prototype.shouldStopEvent = function(handled) { + return handled; +}; + +goog.provide('ol.interaction.DragPan'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.coordinate'); +goog.require('ol.easing'); +goog.require('ol.events.condition'); +goog.require('ol.functions'); +goog.require('ol.interaction.Pointer'); + + +/** + * @classdesc + * Allows the user to pan the map by dragging the map. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.DragPanOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragPan = function(opt_options) { + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.DragPan.handleDownEvent_, + handleDragEvent: ol.interaction.DragPan.handleDragEvent_, + handleUpEvent: ol.interaction.DragPan.handleUpEvent_ + }); + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {ol.Kinetic|undefined} + */ + this.kinetic_ = options.kinetic; + + /** + * @type {ol.Pixel} + */ + this.lastCentroid = null; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.noModifierKeys; + + /** + * @private + * @type {boolean} + */ + this.noKinetic_ = false; + +}; +ol.inherits(ol.interaction.DragPan, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.DragPan} + * @private + */ +ol.interaction.DragPan.handleDragEvent_ = function(mapBrowserEvent) { + ol.DEBUG && console.assert(this.targetPointers.length >= 1, + 'the length of this.targetPointers should be more than 1'); + var centroid = + ol.interaction.Pointer.centroid(this.targetPointers); + if (this.kinetic_) { + this.kinetic_.update(centroid[0], centroid[1]); + } + if (this.lastCentroid) { + var deltaX = this.lastCentroid[0] - centroid[0]; + var deltaY = centroid[1] - this.lastCentroid[1]; + var map = mapBrowserEvent.map; + var view = map.getView(); + var viewState = view.getState(); + var center = [deltaX, deltaY]; + ol.coordinate.scale(center, viewState.resolution); + ol.coordinate.rotate(center, viewState.rotation); + ol.coordinate.add(center, viewState.center); + center = view.constrainCenter(center); + view.setCenter(center); + } + this.lastCentroid = centroid; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.DragPan} + * @private + */ +ol.interaction.DragPan.handleUpEvent_ = function(mapBrowserEvent) { + var map = mapBrowserEvent.map; + var view = map.getView(); + if (this.targetPointers.length === 0) { + if (!this.noKinetic_ && this.kinetic_ && this.kinetic_.end()) { + var distance = this.kinetic_.getDistance(); + var angle = this.kinetic_.getAngle(); + var center = /** @type {!ol.Coordinate} */ (view.getCenter()); + var centerpx = map.getPixelFromCoordinate(center); + var dest = map.getCoordinateFromPixel([ + centerpx[0] - distance * Math.cos(angle), + centerpx[1] - distance * Math.sin(angle) + ]); + view.animate({ + center: view.constrainCenter(dest), + duration: 500, + easing: ol.easing.easeOut + }); + } + view.setHint(ol.View.Hint.INTERACTING, -1); + return false; + } else { + this.lastCentroid = null; + return true; + } +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.DragPan} + * @private + */ +ol.interaction.DragPan.handleDownEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length > 0 && this.condition_(mapBrowserEvent)) { + var map = mapBrowserEvent.map; + var view = map.getView(); + this.lastCentroid = null; + if (!this.handlingDownUpSequence) { + view.setHint(ol.View.Hint.INTERACTING, 1); + } + // stop any current animation + view.setCenter(mapBrowserEvent.frameState.viewState.center); + if (this.kinetic_) { + this.kinetic_.begin(); + } + // No kinetic as soon as more than one pointer on the screen is + // detected. This is to prevent nasty pans after pinch. + this.noKinetic_ = this.targetPointers.length > 1; + return true; + } else { + return false; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.DragPan.prototype.shouldStopEvent = ol.functions.FALSE; + +goog.provide('ol.interaction.DragRotate'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.events.condition'); +goog.require('ol.functions'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); + + +/** + * @classdesc + * Allows the user to rotate the map by clicking and dragging on the map, + * normally combined with an {@link ol.events.condition} that limits + * it to when the alt and shift keys are held down. + * + * This interaction is only supported for mouse devices. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.DragRotateOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragRotate = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.DragRotate.handleDownEvent_, + handleDragEvent: ol.interaction.DragRotate.handleDragEvent_, + handleUpEvent: ol.interaction.DragRotate.handleUpEvent_ + }); + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.altShiftKeysOnly; + + /** + * @private + * @type {number|undefined} + */ + this.lastAngle_ = undefined; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; +}; +ol.inherits(ol.interaction.DragRotate, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.DragRotate} + * @private + */ +ol.interaction.DragRotate.handleDragEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return; + } + + var map = mapBrowserEvent.map; + var size = map.getSize(); + var offset = mapBrowserEvent.pixel; + var theta = + Math.atan2(size[1] / 2 - offset[1], offset[0] - size[0] / 2); + if (this.lastAngle_ !== undefined) { + var delta = theta - this.lastAngle_; + var view = map.getView(); + var rotation = view.getRotation(); + ol.interaction.Interaction.rotateWithoutConstraints( + map, view, rotation - delta); + } + this.lastAngle_ = theta; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.DragRotate} + * @private + */ +ol.interaction.DragRotate.handleUpEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return true; + } + + var map = mapBrowserEvent.map; + var view = map.getView(); + view.setHint(ol.View.Hint.INTERACTING, -1); + var rotation = view.getRotation(); + ol.interaction.Interaction.rotate(map, view, rotation, + undefined, this.duration_); + return false; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.DragRotate} + * @private + */ +ol.interaction.DragRotate.handleDownEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return false; + } + + if (ol.events.condition.mouseActionButton(mapBrowserEvent) && + this.condition_(mapBrowserEvent)) { + var map = mapBrowserEvent.map; + map.getView().setHint(ol.View.Hint.INTERACTING, 1); + this.lastAngle_ = undefined; + return true; + } else { + return false; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.DragRotate.prototype.shouldStopEvent = ol.functions.FALSE; + +// FIXME add rotation + +goog.provide('ol.render.Box'); + +goog.require('ol'); +goog.require('ol.Disposable'); +goog.require('ol.geom.Polygon'); + + +/** + * @constructor + * @extends {ol.Disposable} + * @param {string} className CSS class name. + */ +ol.render.Box = function(className) { + + /** + * @type {ol.geom.Polygon} + * @private + */ + this.geometry_ = null; + + /** + * @type {HTMLDivElement} + * @private + */ + this.element_ = /** @type {HTMLDivElement} */ (document.createElement('div')); + this.element_.style.position = 'absolute'; + this.element_.className = 'ol-box ' + className; + + /** + * @private + * @type {ol.Map} + */ + this.map_ = null; + + /** + * @private + * @type {ol.Pixel} + */ + this.startPixel_ = null; + + /** + * @private + * @type {ol.Pixel} + */ + this.endPixel_ = null; + +}; +ol.inherits(ol.render.Box, ol.Disposable); + + +/** + * @inheritDoc + */ +ol.render.Box.prototype.disposeInternal = function() { + this.setMap(null); +}; + + +/** + * @private + */ +ol.render.Box.prototype.render_ = function() { + var startPixel = this.startPixel_; + var endPixel = this.endPixel_; + var px = 'px'; + var style = this.element_.style; + style.left = Math.min(startPixel[0], endPixel[0]) + px; + style.top = Math.min(startPixel[1], endPixel[1]) + px; + style.width = Math.abs(endPixel[0] - startPixel[0]) + px; + style.height = Math.abs(endPixel[1] - startPixel[1]) + px; +}; + + +/** + * @param {ol.Map} map Map. + */ +ol.render.Box.prototype.setMap = function(map) { + if (this.map_) { + this.map_.getOverlayContainer().removeChild(this.element_); + var style = this.element_.style; + style.left = style.top = style.width = style.height = 'inherit'; + } + this.map_ = map; + if (this.map_) { + this.map_.getOverlayContainer().appendChild(this.element_); + } +}; + + +/** + * @param {ol.Pixel} startPixel Start pixel. + * @param {ol.Pixel} endPixel End pixel. + */ +ol.render.Box.prototype.setPixels = function(startPixel, endPixel) { + this.startPixel_ = startPixel; + this.endPixel_ = endPixel; + this.createOrUpdateGeometry(); + this.render_(); +}; + + +/** + * Creates or updates the cached geometry. + */ +ol.render.Box.prototype.createOrUpdateGeometry = function() { + var startPixel = this.startPixel_; + var endPixel = this.endPixel_; + var pixels = [ + startPixel, + [startPixel[0], endPixel[1]], + endPixel, + [endPixel[0], startPixel[1]] + ]; + var coordinates = pixels.map(this.map_.getCoordinateFromPixel, this.map_); + // close the polygon + coordinates[4] = coordinates[0].slice(); + if (!this.geometry_) { + this.geometry_ = new ol.geom.Polygon([coordinates]); + } else { + this.geometry_.setCoordinates([coordinates]); + } +}; + + +/** + * @return {ol.geom.Polygon} Geometry. + */ +ol.render.Box.prototype.getGeometry = function() { + return this.geometry_; +}; + +// FIXME draw drag box +goog.provide('ol.interaction.DragBox'); + +goog.require('ol.events.Event'); +goog.require('ol'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.render.Box'); + + +/** + * @const + * @type {number} + */ +ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED = + ol.DRAG_BOX_HYSTERESIS_PIXELS * + ol.DRAG_BOX_HYSTERESIS_PIXELS; + + +/** + * @classdesc + * Allows the user to draw a vector box by clicking and dragging on the map, + * normally combined with an {@link ol.events.condition} that limits + * it to when the shift or other key is held down. This is used, for example, + * for zooming to a specific area of the map + * (see {@link ol.interaction.DragZoom} and + * {@link ol.interaction.DragRotateAndZoom}). + * + * This interaction is only supported for mouse devices. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @fires ol.interaction.DragBox.Event + * @param {olx.interaction.DragBoxOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragBox = function(opt_options) { + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.DragBox.handleDownEvent_, + handleDragEvent: ol.interaction.DragBox.handleDragEvent_, + handleUpEvent: ol.interaction.DragBox.handleUpEvent_ + }); + + var options = opt_options ? opt_options : {}; + + /** + * @type {ol.render.Box} + * @private + */ + this.box_ = new ol.render.Box(options.className || 'ol-dragbox'); + + /** + * @type {ol.Pixel} + * @private + */ + this.startPixel_ = null; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.always; + + /** + * @private + * @type {ol.DragBoxEndConditionType} + */ + this.boxEndCondition_ = options.boxEndCondition ? + options.boxEndCondition : ol.interaction.DragBox.defaultBoxEndCondition; +}; +ol.inherits(ol.interaction.DragBox, ol.interaction.Pointer); + + +/** + * The default condition for determining whether the boxend event + * should fire. + * @param {ol.MapBrowserEvent} mapBrowserEvent The originating MapBrowserEvent + * leading to the box end. + * @param {ol.Pixel} startPixel The starting pixel of the box. + * @param {ol.Pixel} endPixel The end pixel of the box. + * @return {boolean} Whether or not the boxend condition should be fired. + */ +ol.interaction.DragBox.defaultBoxEndCondition = function(mapBrowserEvent, + startPixel, endPixel) { + var width = endPixel[0] - startPixel[0]; + var height = endPixel[1] - startPixel[1]; + return width * width + height * height >= + ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.DragBox} + * @private + */ +ol.interaction.DragBox.handleDragEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return; + } + + this.box_.setPixels(this.startPixel_, mapBrowserEvent.pixel); + + this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType.BOXDRAG, + mapBrowserEvent.coordinate, mapBrowserEvent)); +}; + + +/** + * Returns geometry of last drawn box. + * @return {ol.geom.Polygon} Geometry. + * @api stable + */ +ol.interaction.DragBox.prototype.getGeometry = function() { + return this.box_.getGeometry(); +}; + + +/** + * To be overriden by child classes. + * FIXME: use constructor option instead of relying on overridding. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @protected + */ +ol.interaction.DragBox.prototype.onBoxEnd = ol.nullFunction; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.DragBox} + * @private + */ +ol.interaction.DragBox.handleUpEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return true; + } + + this.box_.setMap(null); + + if (this.boxEndCondition_(mapBrowserEvent, + this.startPixel_, mapBrowserEvent.pixel)) { + this.onBoxEnd(mapBrowserEvent); + this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType.BOXEND, + mapBrowserEvent.coordinate, mapBrowserEvent)); + } + return false; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.DragBox} + * @private + */ +ol.interaction.DragBox.handleDownEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return false; + } + + if (ol.events.condition.mouseActionButton(mapBrowserEvent) && + this.condition_(mapBrowserEvent)) { + this.startPixel_ = mapBrowserEvent.pixel; + this.box_.setMap(mapBrowserEvent.map); + this.box_.setPixels(this.startPixel_, this.startPixel_); + this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType.BOXSTART, + mapBrowserEvent.coordinate, mapBrowserEvent)); + return true; + } else { + return false; + } +}; + + +/** + * @enum {string} + */ +ol.interaction.DragBox.EventType = { + /** + * Triggered upon drag box start. + * @event ol.interaction.DragBox.Event#boxstart + * @api stable + */ + BOXSTART: 'boxstart', + + /** + * Triggered on drag when box is active. + * @event ol.interaction.DragBox.Event#boxdrag + * @api + */ + BOXDRAG: 'boxdrag', + + /** + * Triggered upon drag box end. + * @event ol.interaction.DragBox.Event#boxend + * @api stable + */ + BOXEND: 'boxend' +}; + + +/** + * @classdesc + * Events emitted by {@link ol.interaction.DragBox} instances are instances of + * this type. + * + * @param {string} type The event type. + * @param {ol.Coordinate} coordinate The event coordinate. + * @param {ol.MapBrowserEvent} mapBrowserEvent Originating event. + * @extends {ol.events.Event} + * @constructor + * @implements {oli.DragBoxEvent} + */ +ol.interaction.DragBox.Event = function(type, coordinate, mapBrowserEvent) { + ol.events.Event.call(this, type); + + /** + * The coordinate of the drag event. + * @const + * @type {ol.Coordinate} + * @api stable + */ + this.coordinate = coordinate; + + /** + * @const + * @type {ol.MapBrowserEvent} + * @api + */ + this.mapBrowserEvent = mapBrowserEvent; + +}; +ol.inherits(ol.interaction.DragBox.Event, ol.events.Event); + +goog.provide('ol.interaction.DragZoom'); + +goog.require('ol'); +goog.require('ol.easing'); +goog.require('ol.events.condition'); +goog.require('ol.extent'); +goog.require('ol.interaction.DragBox'); + + +/** + * @classdesc + * Allows the user to zoom the map by clicking and dragging on the map, + * normally combined with an {@link ol.events.condition} that limits + * it to when a key, shift by default, is held down. + * + * To change the style of the box, use CSS and the `.ol-dragzoom` selector, or + * your custom one configured with `className`. + * + * @constructor + * @extends {ol.interaction.DragBox} + * @param {olx.interaction.DragZoomOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragZoom = function(opt_options) { + var options = opt_options ? opt_options : {}; + + var condition = options.condition ? + options.condition : ol.events.condition.shiftKeyOnly; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 200; + + /** + * @private + * @type {boolean} + */ + this.out_ = options.out !== undefined ? options.out : false; + + ol.interaction.DragBox.call(this, { + condition: condition, + className: options.className || 'ol-dragzoom' + }); + +}; +ol.inherits(ol.interaction.DragZoom, ol.interaction.DragBox); + + +/** + * @inheritDoc + */ +ol.interaction.DragZoom.prototype.onBoxEnd = function() { + var map = this.getMap(); + + var view = /** @type {!ol.View} */ (map.getView()); + + var size = /** @type {!ol.Size} */ (map.getSize()); + + var extent = this.getGeometry().getExtent(); + + if (this.out_) { + var mapExtent = view.calculateExtent(size); + var boxPixelExtent = ol.extent.createOrUpdateFromCoordinates([ + map.getPixelFromCoordinate(ol.extent.getBottomLeft(extent)), + map.getPixelFromCoordinate(ol.extent.getTopRight(extent))]); + var factor = view.getResolutionForExtent(boxPixelExtent, size); + + ol.extent.scaleFromCenter(mapExtent, 1 / factor); + extent = mapExtent; + } + + var resolution = view.constrainResolution( + view.getResolutionForExtent(extent, size)); + + view.animate({ + resolution: resolution, + center: ol.extent.getCenter(extent), + duration: this.duration_, + easing: ol.easing.easeOut + }); + +}; + +goog.provide('ol.events.KeyCode'); + +/** + * @enum {number} + * @const + */ +ol.events.KeyCode = { + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40 +}; + +goog.provide('ol.interaction.KeyboardPan'); + +goog.require('ol'); +goog.require('ol.coordinate'); +goog.require('ol.events.EventType'); +goog.require('ol.events.KeyCode'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Interaction'); + + +/** + * @classdesc + * Allows the user to pan the map using keyboard arrows. + * Note that, although this interaction is by default included in maps, + * the keys can only be used when browser focus is on the element to which + * the keyboard events are attached. By default, this is the map div, + * though you can change this with the `keyboardEventTarget` in + * {@link ol.Map}. `document` never loses focus but, for any other element, + * focus will have to be on, and returned to, this element if the keys are to + * function. + * See also {@link ol.interaction.KeyboardZoom}. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.KeyboardPanOptions=} opt_options Options. + * @api stable + */ +ol.interaction.KeyboardPan = function(opt_options) { + + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.KeyboardPan.handleEvent + }); + + var options = opt_options || {}; + + /** + * @private + * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event. + * @return {boolean} Combined condition result. + */ + this.defaultCondition_ = function(mapBrowserEvent) { + return ol.events.condition.noModifierKeys(mapBrowserEvent) && + ol.events.condition.targetNotEditable(mapBrowserEvent); + }; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition !== undefined ? + options.condition : this.defaultCondition_; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 100; + + /** + * @private + * @type {number} + */ + this.pixelDelta_ = options.pixelDelta !== undefined ? + options.pixelDelta : 128; + +}; +ol.inherits(ol.interaction.KeyboardPan, ol.interaction.Interaction); + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} if it was a + * `KeyEvent`, and decides the direction to pan to (if an arrow key was + * pressed). + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.KeyboardPan} + * @api + */ +ol.interaction.KeyboardPan.handleEvent = function(mapBrowserEvent) { + var stopEvent = false; + if (mapBrowserEvent.type == ol.events.EventType.KEYDOWN) { + var keyEvent = mapBrowserEvent.originalEvent; + var keyCode = keyEvent.keyCode; + if (this.condition_(mapBrowserEvent) && + (keyCode == ol.events.KeyCode.DOWN || + keyCode == ol.events.KeyCode.LEFT || + keyCode == ol.events.KeyCode.RIGHT || + keyCode == ol.events.KeyCode.UP)) { + var map = mapBrowserEvent.map; + var view = map.getView(); + var mapUnitsDelta = view.getResolution() * this.pixelDelta_; + var deltaX = 0, deltaY = 0; + if (keyCode == ol.events.KeyCode.DOWN) { + deltaY = -mapUnitsDelta; + } else if (keyCode == ol.events.KeyCode.LEFT) { + deltaX = -mapUnitsDelta; + } else if (keyCode == ol.events.KeyCode.RIGHT) { + deltaX = mapUnitsDelta; + } else { + deltaY = mapUnitsDelta; + } + var delta = [deltaX, deltaY]; + ol.coordinate.rotate(delta, view.getRotation()); + ol.interaction.Interaction.pan(map, view, delta, this.duration_); + mapBrowserEvent.preventDefault(); + stopEvent = true; + } + } + return !stopEvent; +}; + +goog.provide('ol.interaction.KeyboardZoom'); + +goog.require('ol'); +goog.require('ol.events.EventType'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Interaction'); + + +/** + * @classdesc + * Allows the user to zoom the map using keyboard + and -. + * Note that, although this interaction is by default included in maps, + * the keys can only be used when browser focus is on the element to which + * the keyboard events are attached. By default, this is the map div, + * though you can change this with the `keyboardEventTarget` in + * {@link ol.Map}. `document` never loses focus but, for any other element, + * focus will have to be on, and returned to, this element if the keys are to + * function. + * See also {@link ol.interaction.KeyboardPan}. + * + * @constructor + * @param {olx.interaction.KeyboardZoomOptions=} opt_options Options. + * @extends {ol.interaction.Interaction} + * @api stable + */ +ol.interaction.KeyboardZoom = function(opt_options) { + + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.KeyboardZoom.handleEvent + }); + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? options.condition : + ol.events.condition.targetNotEditable; + + /** + * @private + * @type {number} + */ + this.delta_ = options.delta ? options.delta : 1; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 100; + +}; +ol.inherits(ol.interaction.KeyboardZoom, ol.interaction.Interaction); + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} if it was a + * `KeyEvent`, and decides whether to zoom in or out (depending on whether the + * key pressed was '+' or '-'). + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.KeyboardZoom} + * @api + */ +ol.interaction.KeyboardZoom.handleEvent = function(mapBrowserEvent) { + var stopEvent = false; + if (mapBrowserEvent.type == ol.events.EventType.KEYDOWN || + mapBrowserEvent.type == ol.events.EventType.KEYPRESS) { + var keyEvent = mapBrowserEvent.originalEvent; + var charCode = keyEvent.charCode; + if (this.condition_(mapBrowserEvent) && + (charCode == '+'.charCodeAt(0) || charCode == '-'.charCodeAt(0))) { + var map = mapBrowserEvent.map; + var delta = (charCode == '+'.charCodeAt(0)) ? this.delta_ : -this.delta_; + var view = map.getView(); + ol.interaction.Interaction.zoomByDelta( + map, view, delta, undefined, this.duration_); + mapBrowserEvent.preventDefault(); + stopEvent = true; + } + } + return !stopEvent; +}; + +goog.provide('ol.interaction.MouseWheelZoom'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.easing'); +goog.require('ol.events.EventType'); +goog.require('ol.has'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.math'); + + +/** + * @classdesc + * Allows the user to zoom the map by scrolling the mouse wheel. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.MouseWheelZoomOptions=} opt_options Options. + * @api stable + */ +ol.interaction.MouseWheelZoom = function(opt_options) { + + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.MouseWheelZoom.handleEvent + }); + + var options = opt_options || {}; + + /** + * @private + * @type {number} + */ + this.delta_ = 0; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; + + /** + * @private + * @type {number} + */ + this.timeout_ = options.timeout !== undefined ? options.timeout : 80; + + /** + * @private + * @type {boolean} + */ + this.useAnchor_ = options.useAnchor !== undefined ? options.useAnchor : true; + + /** + * @private + * @type {?ol.Coordinate} + */ + this.lastAnchor_ = null; + + /** + * @private + * @type {number|undefined} + */ + this.startTime_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.timeoutId_ = undefined; + + /** + * @private + * @type {ol.interaction.MouseWheelZoom.Mode|undefined} + */ + this.mode_ = undefined; + + /** + * Trackpad events separated by this delay will be considered separate + * interactions. + * @type {number} + */ + this.trackpadEventGap_ = 400; + + /** + * @type {number|undefined} + */ + this.trackpadTimeoutId_ = undefined; + + /** + * The number of delta values per zoom level + * @private + * @type {number} + */ + this.trackpadDeltaPerZoom_ = 300; + + /** + * The zoom factor by which scroll zooming is allowed to exceed the limits. + * @private + * @type {number} + */ + this.trackpadZoomBuffer_ = 1.5; + +}; +ol.inherits(ol.interaction.MouseWheelZoom, ol.interaction.Interaction); + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} (if it was a + * mousewheel-event) and eventually zooms the map. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} Allow event propagation. + * @this {ol.interaction.MouseWheelZoom} + * @api + */ +ol.interaction.MouseWheelZoom.handleEvent = function(mapBrowserEvent) { + var type = mapBrowserEvent.type; + if (type !== ol.events.EventType.WHEEL && type !== ol.events.EventType.MOUSEWHEEL) { + return true; + } + + mapBrowserEvent.preventDefault(); + + var map = mapBrowserEvent.map; + var wheelEvent = /** @type {WheelEvent} */ (mapBrowserEvent.originalEvent); + + if (this.useAnchor_) { + this.lastAnchor_ = mapBrowserEvent.coordinate; + } + + // Delta normalisation inspired by + // https://github.com/mapbox/mapbox-gl-js/blob/001c7b9/js/ui/handler/scroll_zoom.js + var delta; + if (mapBrowserEvent.type == ol.events.EventType.WHEEL) { + delta = wheelEvent.deltaY; + if (ol.has.FIREFOX && + wheelEvent.deltaMode === WheelEvent.DOM_DELTA_PIXEL) { + delta /= ol.has.DEVICE_PIXEL_RATIO; + } + if (wheelEvent.deltaMode === WheelEvent.DOM_DELTA_LINE) { + delta *= 40; + } + } else if (mapBrowserEvent.type == ol.events.EventType.MOUSEWHEEL) { + delta = -wheelEvent.wheelDeltaY; + if (ol.has.SAFARI) { + delta /= 3; + } + } + + if (delta === 0) { + return false; + } + + var now = Date.now(); + + if (this.startTime_ === undefined) { + this.startTime_ = now; + } + + if (!this.mode_ || now - this.startTime_ > this.trackpadEventGap_) { + this.mode_ = Math.abs(delta) < 4 ? + ol.interaction.MouseWheelZoom.Mode.TRACKPAD : + ol.interaction.MouseWheelZoom.Mode.WHEEL; + } + + if (this.mode_ === ol.interaction.MouseWheelZoom.Mode.TRACKPAD) { + var view = map.getView(); + if (this.trackpadTimeoutId_) { + clearTimeout(this.trackpadTimeoutId_); + } else { + view.setHint(ol.View.Hint.INTERACTING, 1); + } + this.trackpadTimeoutId_ = setTimeout(this.decrementInteractingHint_.bind(this), this.trackpadEventGap_); + var resolution = view.getResolution() * Math.pow(2, delta / this.trackpadDeltaPerZoom_); + var minResolution = view.getMinResolution(); + var maxResolution = view.getMaxResolution(); + var rebound = 0; + if (resolution < minResolution) { + resolution = Math.max(resolution, minResolution / this.trackpadZoomBuffer_); + rebound = 1; + } else if (resolution > maxResolution) { + resolution = Math.min(resolution, maxResolution * this.trackpadZoomBuffer_); + rebound = -1; + } + if (this.lastAnchor_) { + var center = view.calculateCenterZoom(resolution, this.lastAnchor_); + view.setCenter(center); + } + view.setResolution(resolution); + if (rebound > 0) { + view.animate({ + resolution: minResolution, + easing: ol.easing.easeOut, + anchor: this.lastAnchor_, + duration: 500 + }); + } else if (rebound < 0) { + view.animate({ + resolution: maxResolution, + easing: ol.easing.easeOut, + anchor: this.lastAnchor_, + duration: 500 + }); + } + this.startTime_ = now; + return false; + } + + this.delta_ += delta; + + var timeLeft = Math.max(this.timeout_ - (now - this.startTime_), 0); + + clearTimeout(this.timeoutId_); + this.timeoutId_ = setTimeout(this.handleWheelZoom_.bind(this, map), timeLeft); + + return false; +}; + + +/** + * @private + */ +ol.interaction.MouseWheelZoom.prototype.decrementInteractingHint_ = function() { + this.trackpadTimeoutId_ = undefined; + var view = this.getMap().getView(); + view.setHint(ol.View.Hint.INTERACTING, -1); +}; + + +/** + * @private + * @param {ol.Map} map Map. + */ +ol.interaction.MouseWheelZoom.prototype.handleWheelZoom_ = function(map) { + var view = map.getView(); + if (view.getAnimating()) { + view.cancelAnimations(); + } + var maxDelta = ol.MOUSEWHEELZOOM_MAXDELTA; + var delta = ol.math.clamp(this.delta_, -maxDelta, maxDelta); + ol.interaction.Interaction.zoomByDelta(map, view, -delta, this.lastAnchor_, + this.duration_); + this.mode_ = undefined; + this.delta_ = 0; + this.lastAnchor_ = null; + this.startTime_ = undefined; + this.timeoutId_ = undefined; +}; + + +/** + * Enable or disable using the mouse's location as an anchor when zooming + * @param {boolean} useAnchor true to zoom to the mouse's location, false + * to zoom to the center of the map + * @api + */ +ol.interaction.MouseWheelZoom.prototype.setMouseAnchor = function(useAnchor) { + this.useAnchor_ = useAnchor; + if (!useAnchor) { + this.lastAnchor_ = null; + } +}; + + +/** + * @enum {string} + */ +ol.interaction.MouseWheelZoom.Mode = { + TRACKPAD: 'trackpad', + WHEEL: 'wheel' +}; + +goog.provide('ol.interaction.PinchRotate'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.functions'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); + + +/** + * @classdesc + * Allows the user to rotate the map by twisting with two fingers + * on a touch screen. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.PinchRotateOptions=} opt_options Options. + * @api stable + */ +ol.interaction.PinchRotate = function(opt_options) { + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.PinchRotate.handleDownEvent_, + handleDragEvent: ol.interaction.PinchRotate.handleDragEvent_, + handleUpEvent: ol.interaction.PinchRotate.handleUpEvent_ + }); + + var options = opt_options || {}; + + /** + * @private + * @type {ol.Coordinate} + */ + this.anchor_ = null; + + /** + * @private + * @type {number|undefined} + */ + this.lastAngle_ = undefined; + + /** + * @private + * @type {boolean} + */ + this.rotating_ = false; + + /** + * @private + * @type {number} + */ + this.rotationDelta_ = 0.0; + + /** + * @private + * @type {number} + */ + this.threshold_ = options.threshold !== undefined ? options.threshold : 0.3; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; + +}; +ol.inherits(ol.interaction.PinchRotate, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.PinchRotate} + * @private + */ +ol.interaction.PinchRotate.handleDragEvent_ = function(mapBrowserEvent) { + ol.DEBUG && console.assert(this.targetPointers.length >= 2, + 'length of this.targetPointers should be greater than or equal to 2'); + var rotationDelta = 0.0; + + var touch0 = this.targetPointers[0]; + var touch1 = this.targetPointers[1]; + + // angle between touches + var angle = Math.atan2( + touch1.clientY - touch0.clientY, + touch1.clientX - touch0.clientX); + + if (this.lastAngle_ !== undefined) { + var delta = angle - this.lastAngle_; + this.rotationDelta_ += delta; + if (!this.rotating_ && + Math.abs(this.rotationDelta_) > this.threshold_) { + this.rotating_ = true; + } + rotationDelta = delta; + } + this.lastAngle_ = angle; + + var map = mapBrowserEvent.map; + + // rotate anchor point. + // FIXME: should be the intersection point between the lines: + // touch0,touch1 and previousTouch0,previousTouch1 + var viewportPosition = map.getViewport().getBoundingClientRect(); + var centroid = ol.interaction.Pointer.centroid(this.targetPointers); + centroid[0] -= viewportPosition.left; + centroid[1] -= viewportPosition.top; + this.anchor_ = map.getCoordinateFromPixel(centroid); + + // rotate + if (this.rotating_) { + var view = map.getView(); + var rotation = view.getRotation(); + map.render(); + ol.interaction.Interaction.rotateWithoutConstraints(map, view, + rotation + rotationDelta, this.anchor_); + } +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.PinchRotate} + * @private + */ +ol.interaction.PinchRotate.handleUpEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length < 2) { + var map = mapBrowserEvent.map; + var view = map.getView(); + view.setHint(ol.View.Hint.INTERACTING, -1); + if (this.rotating_) { + var rotation = view.getRotation(); + ol.interaction.Interaction.rotate( + map, view, rotation, this.anchor_, this.duration_); + } + return false; + } else { + return true; + } +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.PinchRotate} + * @private + */ +ol.interaction.PinchRotate.handleDownEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length >= 2) { + var map = mapBrowserEvent.map; + this.anchor_ = null; + this.lastAngle_ = undefined; + this.rotating_ = false; + this.rotationDelta_ = 0.0; + if (!this.handlingDownUpSequence) { + map.getView().setHint(ol.View.Hint.INTERACTING, 1); + } + return true; + } else { + return false; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.PinchRotate.prototype.shouldStopEvent = ol.functions.FALSE; + +goog.provide('ol.interaction.PinchZoom'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.functions'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); + + +/** + * @classdesc + * Allows the user to zoom the map by pinching with two fingers + * on a touch screen. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.PinchZoomOptions=} opt_options Options. + * @api stable + */ +ol.interaction.PinchZoom = function(opt_options) { + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.PinchZoom.handleDownEvent_, + handleDragEvent: ol.interaction.PinchZoom.handleDragEvent_, + handleUpEvent: ol.interaction.PinchZoom.handleUpEvent_ + }); + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {boolean} + */ + this.constrainResolution_ = options.constrainResolution || false; + + /** + * @private + * @type {ol.Coordinate} + */ + this.anchor_ = null; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 400; + + /** + * @private + * @type {number|undefined} + */ + this.lastDistance_ = undefined; + + /** + * @private + * @type {number} + */ + this.lastScaleDelta_ = 1; + +}; +ol.inherits(ol.interaction.PinchZoom, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.PinchZoom} + * @private + */ +ol.interaction.PinchZoom.handleDragEvent_ = function(mapBrowserEvent) { + ol.DEBUG && console.assert(this.targetPointers.length >= 2, + 'length of this.targetPointers should be 2 or more'); + var scaleDelta = 1.0; + + var touch0 = this.targetPointers[0]; + var touch1 = this.targetPointers[1]; + var dx = touch0.clientX - touch1.clientX; + var dy = touch0.clientY - touch1.clientY; + + // distance between touches + var distance = Math.sqrt(dx * dx + dy * dy); + + if (this.lastDistance_ !== undefined) { + scaleDelta = this.lastDistance_ / distance; + } + this.lastDistance_ = distance; + if (scaleDelta != 1.0) { + this.lastScaleDelta_ = scaleDelta; + } + + var map = mapBrowserEvent.map; + var view = map.getView(); + var resolution = view.getResolution(); + + // scale anchor point. + var viewportPosition = map.getViewport().getBoundingClientRect(); + var centroid = ol.interaction.Pointer.centroid(this.targetPointers); + centroid[0] -= viewportPosition.left; + centroid[1] -= viewportPosition.top; + this.anchor_ = map.getCoordinateFromPixel(centroid); + + // scale, bypass the resolution constraint + map.render(); + ol.interaction.Interaction.zoomWithoutConstraints( + map, view, resolution * scaleDelta, this.anchor_); + +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.PinchZoom} + * @private + */ +ol.interaction.PinchZoom.handleUpEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length < 2) { + var map = mapBrowserEvent.map; + var view = map.getView(); + view.setHint(ol.View.Hint.INTERACTING, -1); + if (this.constrainResolution_) { + var resolution = view.getResolution(); + // Zoom to final resolution, with an animation, and provide a + // direction not to zoom out/in if user was pinching in/out. + // Direction is > 0 if pinching out, and < 0 if pinching in. + var direction = this.lastScaleDelta_ - 1; + ol.interaction.Interaction.zoom(map, view, resolution, + this.anchor_, this.duration_, direction); + } + return false; + } else { + return true; + } +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.PinchZoom} + * @private + */ +ol.interaction.PinchZoom.handleDownEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length >= 2) { + var map = mapBrowserEvent.map; + this.anchor_ = null; + this.lastDistance_ = undefined; + this.lastScaleDelta_ = 1; + if (!this.handlingDownUpSequence) { + map.getView().setHint(ol.View.Hint.INTERACTING, 1); + } + return true; + } else { + return false; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.PinchZoom.prototype.shouldStopEvent = ol.functions.FALSE; + +goog.provide('ol.interaction'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.Kinetic'); +goog.require('ol.interaction.DoubleClickZoom'); +goog.require('ol.interaction.DragPan'); +goog.require('ol.interaction.DragRotate'); +goog.require('ol.interaction.DragZoom'); +goog.require('ol.interaction.KeyboardPan'); +goog.require('ol.interaction.KeyboardZoom'); +goog.require('ol.interaction.MouseWheelZoom'); +goog.require('ol.interaction.PinchRotate'); +goog.require('ol.interaction.PinchZoom'); + + +/** + * Set of interactions included in maps by default. Specific interactions can be + * excluded by setting the appropriate option to false in the constructor + * options, but the order of the interactions is fixed. If you want to specify + * a different order for interactions, you will need to create your own + * {@link ol.interaction.Interaction} instances and insert them into a + * {@link ol.Collection} in the order you want before creating your + * {@link ol.Map} instance. The default set of interactions, in sequence, is: + * * {@link ol.interaction.DragRotate} + * * {@link ol.interaction.DoubleClickZoom} + * * {@link ol.interaction.DragPan} + * * {@link ol.interaction.PinchRotate} + * * {@link ol.interaction.PinchZoom} + * * {@link ol.interaction.KeyboardPan} + * * {@link ol.interaction.KeyboardZoom} + * * {@link ol.interaction.MouseWheelZoom} + * * {@link ol.interaction.DragZoom} + * + * @param {olx.interaction.DefaultsOptions=} opt_options Defaults options. + * @return {ol.Collection.<ol.interaction.Interaction>} A collection of + * interactions to be used with the ol.Map constructor's interactions option. + * @api stable + */ +ol.interaction.defaults = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var interactions = new ol.Collection(); + + var kinetic = new ol.Kinetic(-0.005, 0.05, 100); + + var altShiftDragRotate = options.altShiftDragRotate !== undefined ? + options.altShiftDragRotate : true; + if (altShiftDragRotate) { + interactions.push(new ol.interaction.DragRotate()); + } + + var doubleClickZoom = options.doubleClickZoom !== undefined ? + options.doubleClickZoom : true; + if (doubleClickZoom) { + interactions.push(new ol.interaction.DoubleClickZoom({ + delta: options.zoomDelta, + duration: options.zoomDuration + })); + } + + var dragPan = options.dragPan !== undefined ? options.dragPan : true; + if (dragPan) { + interactions.push(new ol.interaction.DragPan({ + kinetic: kinetic + })); + } + + var pinchRotate = options.pinchRotate !== undefined ? options.pinchRotate : + true; + if (pinchRotate) { + interactions.push(new ol.interaction.PinchRotate()); + } + + var pinchZoom = options.pinchZoom !== undefined ? options.pinchZoom : true; + if (pinchZoom) { + interactions.push(new ol.interaction.PinchZoom({ + duration: options.zoomDuration + })); + } + + var keyboard = options.keyboard !== undefined ? options.keyboard : true; + if (keyboard) { + interactions.push(new ol.interaction.KeyboardPan()); + interactions.push(new ol.interaction.KeyboardZoom({ + delta: options.zoomDelta, + duration: options.zoomDuration + })); + } + + var mouseWheelZoom = options.mouseWheelZoom !== undefined ? + options.mouseWheelZoom : true; + if (mouseWheelZoom) { + interactions.push(new ol.interaction.MouseWheelZoom({ + duration: options.zoomDuration + })); + } + + var shiftDragZoom = options.shiftDragZoom !== undefined ? + options.shiftDragZoom : true; + if (shiftDragZoom) { + interactions.push(new ol.interaction.DragZoom({ + duration: options.zoomDuration + })); + } + + return interactions; + +}; + +goog.provide('ol.layer.Base'); + +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.math'); +goog.require('ol.obj'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Note that with `ol.layer.Base` and all its subclasses, any property set in + * the options is set as a {@link ol.Object} property on the layer object, so + * is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.Object} + * @param {olx.layer.BaseOptions} options Layer options. + * @api stable + */ +ol.layer.Base = function(options) { + + ol.Object.call(this); + + /** + * @type {Object.<string, *>} + */ + var properties = ol.obj.assign({}, options); + properties[ol.layer.Base.Property.OPACITY] = + options.opacity !== undefined ? options.opacity : 1; + properties[ol.layer.Base.Property.VISIBLE] = + options.visible !== undefined ? options.visible : true; + properties[ol.layer.Base.Property.Z_INDEX] = + options.zIndex !== undefined ? options.zIndex : 0; + properties[ol.layer.Base.Property.MAX_RESOLUTION] = + options.maxResolution !== undefined ? options.maxResolution : Infinity; + properties[ol.layer.Base.Property.MIN_RESOLUTION] = + options.minResolution !== undefined ? options.minResolution : 0; + + this.setProperties(properties); + + /** + * @type {ol.LayerState} + * @private + */ + this.state_ = /** @type {ol.LayerState} */ ({ + layer: /** @type {ol.layer.Layer} */ (this), + managed: true + }); + +}; +ol.inherits(ol.layer.Base, ol.Object); + + +/** + * @return {ol.LayerState} Layer state. + */ +ol.layer.Base.prototype.getLayerState = function() { + this.state_.opacity = ol.math.clamp(this.getOpacity(), 0, 1); + this.state_.sourceState = this.getSourceState(); + this.state_.visible = this.getVisible(); + this.state_.extent = this.getExtent(); + this.state_.zIndex = this.getZIndex(); + this.state_.maxResolution = this.getMaxResolution(); + this.state_.minResolution = Math.max(this.getMinResolution(), 0); + + return this.state_; +}; + + +/** + * @abstract + * @param {Array.<ol.layer.Layer>=} opt_array Array of layers (to be + * modified in place). + * @return {Array.<ol.layer.Layer>} Array of layers. + */ +ol.layer.Base.prototype.getLayersArray = function(opt_array) {}; + + +/** + * @abstract + * @param {Array.<ol.LayerState>=} opt_states Optional list of layer + * states (to be modified in place). + * @return {Array.<ol.LayerState>} List of layer states. + */ +ol.layer.Base.prototype.getLayerStatesArray = function(opt_states) {}; + + +/** + * Return the {@link ol.Extent extent} of the layer or `undefined` if it + * will be visible regardless of extent. + * @return {ol.Extent|undefined} The layer extent. + * @observable + * @api stable + */ +ol.layer.Base.prototype.getExtent = function() { + return /** @type {ol.Extent|undefined} */ ( + this.get(ol.layer.Base.Property.EXTENT)); +}; + + +/** + * Return the maximum resolution of the layer. + * @return {number} The maximum resolution of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.getMaxResolution = function() { + return /** @type {number} */ ( + this.get(ol.layer.Base.Property.MAX_RESOLUTION)); +}; + + +/** + * Return the minimum resolution of the layer. + * @return {number} The minimum resolution of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.getMinResolution = function() { + return /** @type {number} */ ( + this.get(ol.layer.Base.Property.MIN_RESOLUTION)); +}; + + +/** + * Return the opacity of the layer (between 0 and 1). + * @return {number} The opacity of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.getOpacity = function() { + return /** @type {number} */ (this.get(ol.layer.Base.Property.OPACITY)); +}; + + +/** + * @abstract + * @return {ol.source.State} Source state. + */ +ol.layer.Base.prototype.getSourceState = function() {}; + + +/** + * Return the visibility of the layer (`true` or `false`). + * @return {boolean} The visibility of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.getVisible = function() { + return /** @type {boolean} */ (this.get(ol.layer.Base.Property.VISIBLE)); +}; + + +/** + * Return the Z-index of the layer, which is used to order layers before + * rendering. The default Z-index is 0. + * @return {number} The Z-index of the layer. + * @observable + * @api + */ +ol.layer.Base.prototype.getZIndex = function() { + return /** @type {number} */ (this.get(ol.layer.Base.Property.Z_INDEX)); +}; + + +/** + * Set the extent at which the layer is visible. If `undefined`, the layer + * will be visible at all extents. + * @param {ol.Extent|undefined} extent The extent of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.setExtent = function(extent) { + this.set(ol.layer.Base.Property.EXTENT, extent); +}; + + +/** + * Set the maximum resolution at which the layer is visible. + * @param {number} maxResolution The maximum resolution of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.setMaxResolution = function(maxResolution) { + this.set(ol.layer.Base.Property.MAX_RESOLUTION, maxResolution); +}; + + +/** + * Set the minimum resolution at which the layer is visible. + * @param {number} minResolution The minimum resolution of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.setMinResolution = function(minResolution) { + this.set(ol.layer.Base.Property.MIN_RESOLUTION, minResolution); +}; + + +/** + * Set the opacity of the layer, allowed values range from 0 to 1. + * @param {number} opacity The opacity of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.setOpacity = function(opacity) { + this.set(ol.layer.Base.Property.OPACITY, opacity); +}; + + +/** + * Set the visibility of the layer (`true` or `false`). + * @param {boolean} visible The visibility of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.setVisible = function(visible) { + this.set(ol.layer.Base.Property.VISIBLE, visible); +}; + + +/** + * Set Z-index of the layer, which is used to order layers before rendering. + * The default Z-index is 0. + * @param {number} zindex The z-index of the layer. + * @observable + * @api + */ +ol.layer.Base.prototype.setZIndex = function(zindex) { + this.set(ol.layer.Base.Property.Z_INDEX, zindex); +}; + + +/** + * @enum {string} + */ +ol.layer.Base.Property = { + OPACITY: 'opacity', + VISIBLE: 'visible', + EXTENT: 'extent', + Z_INDEX: 'zIndex', + MAX_RESOLUTION: 'maxResolution', + MIN_RESOLUTION: 'minResolution', + SOURCE: 'source' +}; + +goog.provide('ol.source.State'); + + +/** + * State of the source, one of 'undefined', 'loading', 'ready' or 'error'. + * @enum {string} + */ +ol.source.State = { + UNDEFINED: 'undefined', + LOADING: 'loading', + READY: 'ready', + ERROR: 'error' +}; + + +goog.provide('ol.layer.Group'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.Collection'); +goog.require('ol.Object'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.layer.Base'); +goog.require('ol.obj'); +goog.require('ol.source.State'); + + +/** + * @classdesc + * A {@link ol.Collection} of layers that are handled together. + * + * A generic `change` event is triggered when the group/Collection changes. + * + * @constructor + * @extends {ol.layer.Base} + * @param {olx.layer.GroupOptions=} opt_options Layer options. + * @api stable + */ +ol.layer.Group = function(opt_options) { + + var options = opt_options || {}; + var baseOptions = /** @type {olx.layer.GroupOptions} */ + (ol.obj.assign({}, options)); + delete baseOptions.layers; + + var layers = options.layers; + + ol.layer.Base.call(this, baseOptions); + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.layersListenerKeys_ = []; + + /** + * @private + * @type {Object.<string, Array.<ol.EventsKey>>} + */ + this.listenerKeys_ = {}; + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.Group.Property.LAYERS), + this.handleLayersChanged_, this); + + if (layers) { + if (Array.isArray(layers)) { + layers = new ol.Collection(layers.slice()); + } else { + ol.asserts.assert(layers instanceof ol.Collection, + 43); // Expected `layers` to be an array or an `ol.Collection` + layers = layers; + } + } else { + layers = new ol.Collection(); + } + + this.setLayers(layers); + +}; +ol.inherits(ol.layer.Group, ol.layer.Base); + + +/** + * @private + */ +ol.layer.Group.prototype.handleLayerChange_ = function() { + if (this.getVisible()) { + this.changed(); + } +}; + + +/** + * @param {ol.events.Event} event Event. + * @private + */ +ol.layer.Group.prototype.handleLayersChanged_ = function(event) { + this.layersListenerKeys_.forEach(ol.events.unlistenByKey); + this.layersListenerKeys_.length = 0; + + var layers = this.getLayers(); + this.layersListenerKeys_.push( + ol.events.listen(layers, ol.Collection.EventType.ADD, + this.handleLayersAdd_, this), + ol.events.listen(layers, ol.Collection.EventType.REMOVE, + this.handleLayersRemove_, this)); + + for (var id in this.listenerKeys_) { + this.listenerKeys_[id].forEach(ol.events.unlistenByKey); + } + ol.obj.clear(this.listenerKeys_); + + var layersArray = layers.getArray(); + var i, ii, layer; + for (i = 0, ii = layersArray.length; i < ii; i++) { + layer = layersArray[i]; + this.listenerKeys_[ol.getUid(layer).toString()] = [ + ol.events.listen(layer, ol.Object.EventType.PROPERTYCHANGE, + this.handleLayerChange_, this), + ol.events.listen(layer, ol.events.EventType.CHANGE, + this.handleLayerChange_, this) + ]; + } + + this.changed(); +}; + + +/** + * @param {ol.Collection.Event} collectionEvent Collection event. + * @private + */ +ol.layer.Group.prototype.handleLayersAdd_ = function(collectionEvent) { + var layer = /** @type {ol.layer.Base} */ (collectionEvent.element); + var key = ol.getUid(layer).toString(); + ol.DEBUG && console.assert(!(key in this.listenerKeys_), + 'listeners already registered'); + this.listenerKeys_[key] = [ + ol.events.listen(layer, ol.Object.EventType.PROPERTYCHANGE, + this.handleLayerChange_, this), + ol.events.listen(layer, ol.events.EventType.CHANGE, + this.handleLayerChange_, this) + ]; + this.changed(); +}; + + +/** + * @param {ol.Collection.Event} collectionEvent Collection event. + * @private + */ +ol.layer.Group.prototype.handleLayersRemove_ = function(collectionEvent) { + var layer = /** @type {ol.layer.Base} */ (collectionEvent.element); + var key = ol.getUid(layer).toString(); + ol.DEBUG && console.assert(key in this.listenerKeys_, 'no listeners to unregister'); + this.listenerKeys_[key].forEach(ol.events.unlistenByKey); + delete this.listenerKeys_[key]; + this.changed(); +}; + + +/** + * Returns the {@link ol.Collection collection} of {@link ol.layer.Layer layers} + * in this group. + * @return {!ol.Collection.<ol.layer.Base>} Collection of + * {@link ol.layer.Base layers} that are part of this group. + * @observable + * @api stable + */ +ol.layer.Group.prototype.getLayers = function() { + return /** @type {!ol.Collection.<ol.layer.Base>} */ (this.get( + ol.layer.Group.Property.LAYERS)); +}; + + +/** + * Set the {@link ol.Collection collection} of {@link ol.layer.Layer layers} + * in this group. + * @param {!ol.Collection.<ol.layer.Base>} layers Collection of + * {@link ol.layer.Base layers} that are part of this group. + * @observable + * @api stable + */ +ol.layer.Group.prototype.setLayers = function(layers) { + this.set(ol.layer.Group.Property.LAYERS, layers); +}; + + +/** + * @inheritDoc + */ +ol.layer.Group.prototype.getLayersArray = function(opt_array) { + var array = opt_array !== undefined ? opt_array : []; + this.getLayers().forEach(function(layer) { + layer.getLayersArray(array); + }); + return array; +}; + + +/** + * @inheritDoc + */ +ol.layer.Group.prototype.getLayerStatesArray = function(opt_states) { + var states = opt_states !== undefined ? opt_states : []; + + var pos = states.length; + + this.getLayers().forEach(function(layer) { + layer.getLayerStatesArray(states); + }); + + var ownLayerState = this.getLayerState(); + var i, ii, layerState; + for (i = pos, ii = states.length; i < ii; i++) { + layerState = states[i]; + layerState.opacity *= ownLayerState.opacity; + layerState.visible = layerState.visible && ownLayerState.visible; + layerState.maxResolution = Math.min( + layerState.maxResolution, ownLayerState.maxResolution); + layerState.minResolution = Math.max( + layerState.minResolution, ownLayerState.minResolution); + if (ownLayerState.extent !== undefined) { + if (layerState.extent !== undefined) { + layerState.extent = ol.extent.getIntersection( + layerState.extent, ownLayerState.extent); + } else { + layerState.extent = ownLayerState.extent; + } + } + } + + return states; +}; + + +/** + * @inheritDoc + */ +ol.layer.Group.prototype.getSourceState = function() { + return ol.source.State.READY; +}; + +/** + * @enum {string} + */ +ol.layer.Group.Property = { + LAYERS: 'layers' +}; + +goog.provide('ol.proj.EPSG3857'); + +goog.require('ol'); +goog.require('ol.math'); +goog.require('ol.proj'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); + + +/** + * @classdesc + * Projection object for web/spherical Mercator (EPSG:3857). + * + * @constructor + * @extends {ol.proj.Projection} + * @param {string} code Code. + * @private + */ +ol.proj.EPSG3857_ = function(code) { + ol.proj.Projection.call(this, { + code: code, + units: ol.proj.Units.METERS, + extent: ol.proj.EPSG3857.EXTENT, + global: true, + worldExtent: ol.proj.EPSG3857.WORLD_EXTENT, + getPointResolution: function(resolution, point) { + return resolution / ol.math.cosh(point[1] / ol.proj.EPSG3857.RADIUS); + } + }); +}; +ol.inherits(ol.proj.EPSG3857_, ol.proj.Projection); + + +/** + * @const + * @type {number} + */ +ol.proj.EPSG3857.RADIUS = 6378137; + + +/** + * @const + * @type {number} + */ +ol.proj.EPSG3857.HALF_SIZE = Math.PI * ol.proj.EPSG3857.RADIUS; + + +/** + * @const + * @type {ol.Extent} + */ +ol.proj.EPSG3857.EXTENT = [ + -ol.proj.EPSG3857.HALF_SIZE, -ol.proj.EPSG3857.HALF_SIZE, + ol.proj.EPSG3857.HALF_SIZE, ol.proj.EPSG3857.HALF_SIZE +]; + + +/** + * @const + * @type {ol.Extent} + */ +ol.proj.EPSG3857.WORLD_EXTENT = [-180, -85, 180, 85]; + + +/** + * Lists several projection codes with the same meaning as EPSG:3857. + * + * @type {Array.<string>} + */ +ol.proj.EPSG3857.CODES = [ + 'EPSG:3857', + 'EPSG:102100', + 'EPSG:102113', + 'EPSG:900913', + 'urn:ogc:def:crs:EPSG:6.18:3:3857', + 'urn:ogc:def:crs:EPSG::3857', + 'http://www.opengis.net/gml/srs/epsg.xml#3857' +]; + + +/** + * Projections equal to EPSG:3857. + * + * @const + * @type {Array.<ol.proj.Projection>} + */ +ol.proj.EPSG3857.PROJECTIONS = ol.proj.EPSG3857.CODES.map(function(code) { + return new ol.proj.EPSG3857_(code); +}); + + +/** + * Transformation from EPSG:4326 to EPSG:3857. + * + * @param {Array.<number>} input Input array of coordinate values. + * @param {Array.<number>=} opt_output Output array of coordinate values. + * @param {number=} opt_dimension Dimension (default is `2`). + * @return {Array.<number>} Output array of coordinate values. + */ +ol.proj.EPSG3857.fromEPSG4326 = function(input, opt_output, opt_dimension) { + var length = input.length, + dimension = opt_dimension > 1 ? opt_dimension : 2, + output = opt_output; + if (output === undefined) { + if (dimension > 2) { + // preserve values beyond second dimension + output = input.slice(); + } else { + output = new Array(length); + } + } + ol.DEBUG && console.assert(output.length % dimension === 0, + 'modulus of output.length with dimension should be 0'); + var halfSize = ol.proj.EPSG3857.HALF_SIZE; + for (var i = 0; i < length; i += dimension) { + output[i] = halfSize * input[i] / 180; + var y = ol.proj.EPSG3857.RADIUS * + Math.log(Math.tan(Math.PI * (input[i + 1] + 90) / 360)); + if (y > halfSize) { + y = halfSize; + } else if (y < -halfSize) { + y = -halfSize; + } + output[i + 1] = y; + } + return output; +}; + + +/** + * Transformation from EPSG:3857 to EPSG:4326. + * + * @param {Array.<number>} input Input array of coordinate values. + * @param {Array.<number>=} opt_output Output array of coordinate values. + * @param {number=} opt_dimension Dimension (default is `2`). + * @return {Array.<number>} Output array of coordinate values. + */ +ol.proj.EPSG3857.toEPSG4326 = function(input, opt_output, opt_dimension) { + var length = input.length, + dimension = opt_dimension > 1 ? opt_dimension : 2, + output = opt_output; + if (output === undefined) { + if (dimension > 2) { + // preserve values beyond second dimension + output = input.slice(); + } else { + output = new Array(length); + } + } + ol.DEBUG && console.assert(output.length % dimension === 0, + 'modulus of output.length with dimension should be 0'); + for (var i = 0; i < length; i += dimension) { + output[i] = 180 * input[i] / ol.proj.EPSG3857.HALF_SIZE; + output[i + 1] = 360 * Math.atan( + Math.exp(input[i + 1] / ol.proj.EPSG3857.RADIUS)) / Math.PI - 90; + } + return output; +}; + +goog.provide('ol.sphere.WGS84'); + +goog.require('ol.Sphere'); + + +/** + * A sphere with radius equal to the semi-major axis of the WGS84 ellipsoid. + * @const + * @type {ol.Sphere} + */ +ol.sphere.WGS84 = new ol.Sphere(6378137); + +goog.provide('ol.proj.EPSG4326'); + +goog.require('ol'); +goog.require('ol.proj'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); +goog.require('ol.sphere.WGS84'); + + +/** + * @classdesc + * Projection object for WGS84 geographic coordinates (EPSG:4326). + * + * Note that OpenLayers does not strictly comply with the EPSG definition. + * The EPSG registry defines 4326 as a CRS for Latitude,Longitude (y,x). + * OpenLayers treats EPSG:4326 as a pseudo-projection, with x,y coordinates. + * + * @constructor + * @extends {ol.proj.Projection} + * @param {string} code Code. + * @param {string=} opt_axisOrientation Axis orientation. + * @private + */ +ol.proj.EPSG4326_ = function(code, opt_axisOrientation) { + ol.proj.Projection.call(this, { + code: code, + units: ol.proj.Units.DEGREES, + extent: ol.proj.EPSG4326.EXTENT, + axisOrientation: opt_axisOrientation, + global: true, + metersPerUnit: ol.proj.EPSG4326.METERS_PER_UNIT, + worldExtent: ol.proj.EPSG4326.EXTENT + }); +}; +ol.inherits(ol.proj.EPSG4326_, ol.proj.Projection); + + +/** + * Extent of the EPSG:4326 projection which is the whole world. + * + * @const + * @type {ol.Extent} + */ +ol.proj.EPSG4326.EXTENT = [-180, -90, 180, 90]; + + +/** + * @const + * @type {number} + */ +ol.proj.EPSG4326.METERS_PER_UNIT = Math.PI * ol.sphere.WGS84.radius / 180; + + +/** + * Projections equal to EPSG:4326. + * + * @const + * @type {Array.<ol.proj.Projection>} + */ +ol.proj.EPSG4326.PROJECTIONS = [ + new ol.proj.EPSG4326_('CRS:84'), + new ol.proj.EPSG4326_('EPSG:4326', 'neu'), + new ol.proj.EPSG4326_('urn:ogc:def:crs:EPSG::4326', 'neu'), + new ol.proj.EPSG4326_('urn:ogc:def:crs:EPSG:6.6:4326', 'neu'), + new ol.proj.EPSG4326_('urn:ogc:def:crs:OGC:1.3:CRS84'), + new ol.proj.EPSG4326_('urn:ogc:def:crs:OGC:2:84'), + new ol.proj.EPSG4326_('http://www.opengis.net/gml/srs/epsg.xml#4326', 'neu'), + new ol.proj.EPSG4326_('urn:x-ogc:def:crs:EPSG:4326', 'neu') +]; + +goog.provide('ol.proj.common'); + +goog.require('ol.proj'); +goog.require('ol.proj.EPSG3857'); +goog.require('ol.proj.EPSG4326'); + + +/** + * FIXME empty description for jsdoc + * @api + */ +ol.proj.common.add = function() { + // Add transformations that don't alter coordinates to convert within set of + // projections with equal meaning. + ol.proj.addEquivalentProjections(ol.proj.EPSG3857.PROJECTIONS); + ol.proj.addEquivalentProjections(ol.proj.EPSG4326.PROJECTIONS); + // Add transformations to convert EPSG:4326 like coordinates to EPSG:3857 like + // coordinates and back. + ol.proj.addEquivalentTransforms( + ol.proj.EPSG4326.PROJECTIONS, + ol.proj.EPSG3857.PROJECTIONS, + ol.proj.EPSG3857.fromEPSG4326, + ol.proj.EPSG3857.toEPSG4326); +}; + +goog.provide('ol.renderer.Type'); + + +/** + * Available renderers: `'canvas'` or `'webgl'`. + * @enum {string} + */ +ol.renderer.Type = { + CANVAS: 'canvas', + WEBGL: 'webgl' +}; + +goog.provide('ol.render.Event'); + +goog.require('ol'); +goog.require('ol.events.Event'); + + +/** + * @constructor + * @extends {ol.events.Event} + * @implements {oli.render.Event} + * @param {ol.render.Event.Type} type Type. + * @param {ol.render.VectorContext=} opt_vectorContext Vector context. + * @param {olx.FrameState=} opt_frameState Frame state. + * @param {?CanvasRenderingContext2D=} opt_context Context. + * @param {?ol.webgl.Context=} opt_glContext WebGL Context. + */ +ol.render.Event = function( + type, opt_vectorContext, opt_frameState, opt_context, + opt_glContext) { + + ol.events.Event.call(this, type); + + /** + * For canvas, this is an instance of {@link ol.render.canvas.Immediate}. + * @type {ol.render.VectorContext|undefined} + * @api + */ + this.vectorContext = opt_vectorContext; + + /** + * An object representing the current render frame state. + * @type {olx.FrameState|undefined} + * @api + */ + this.frameState = opt_frameState; + + /** + * Canvas context. Only available when a Canvas renderer is used, null + * otherwise. + * @type {CanvasRenderingContext2D|null|undefined} + * @api + */ + this.context = opt_context; + + /** + * WebGL context. Only available when a WebGL renderer is used, null + * otherwise. + * @type {ol.webgl.Context|null|undefined} + * @api + */ + this.glContext = opt_glContext; + +}; +ol.inherits(ol.render.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.render.Event.Type = { + /** + * @event ol.render.Event#postcompose + * @api + */ + POSTCOMPOSE: 'postcompose', + /** + * @event ol.render.Event#precompose + * @api + */ + PRECOMPOSE: 'precompose', + /** + * @event ol.render.Event#render + * @api + */ + RENDER: 'render' +}; + +goog.provide('ol.layer.Layer'); + +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.layer.Base'); +goog.require('ol.obj'); +goog.require('ol.render.Event'); +goog.require('ol.source.State'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * A visual representation of raster or vector map data. + * Layers group together those properties that pertain to how the data is to be + * displayed, irrespective of the source of that data. + * + * Layers are usually added to a map with {@link ol.Map#addLayer}. Components + * like {@link ol.interaction.Select} use unmanaged layers internally. These + * unmanaged layers are associated with the map using + * {@link ol.layer.Layer#setMap} instead. + * + * A generic `change` event is fired when the state of the source changes. + * + * @constructor + * @extends {ol.layer.Base} + * @fires ol.render.Event + * @param {olx.layer.LayerOptions} options Layer options. + * @api stable + */ +ol.layer.Layer = function(options) { + + var baseOptions = ol.obj.assign({}, options); + delete baseOptions.source; + + ol.layer.Base.call(this, /** @type {olx.layer.BaseOptions} */ (baseOptions)); + + /** + * @private + * @type {?ol.EventsKey} + */ + this.mapPrecomposeKey_ = null; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.mapRenderKey_ = null; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.sourceChangeKey_ = null; + + if (options.map) { + this.setMap(options.map); + } + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.Base.Property.SOURCE), + this.handleSourcePropertyChange_, this); + + var source = options.source ? options.source : null; + this.setSource(source); +}; +ol.inherits(ol.layer.Layer, ol.layer.Base); + + +/** + * Return `true` if the layer is visible, and if the passed resolution is + * between the layer's minResolution and maxResolution. The comparison is + * inclusive for `minResolution` and exclusive for `maxResolution`. + * @param {ol.LayerState} layerState Layer state. + * @param {number} resolution Resolution. + * @return {boolean} The layer is visible at the given resolution. + */ +ol.layer.Layer.visibleAtResolution = function(layerState, resolution) { + return layerState.visible && resolution >= layerState.minResolution && + resolution < layerState.maxResolution; +}; + + +/** + * @inheritDoc + */ +ol.layer.Layer.prototype.getLayersArray = function(opt_array) { + var array = opt_array ? opt_array : []; + array.push(this); + return array; +}; + + +/** + * @inheritDoc + */ +ol.layer.Layer.prototype.getLayerStatesArray = function(opt_states) { + var states = opt_states ? opt_states : []; + states.push(this.getLayerState()); + return states; +}; + + +/** + * Get the layer source. + * @return {ol.source.Source} The layer source (or `null` if not yet set). + * @observable + * @api stable + */ +ol.layer.Layer.prototype.getSource = function() { + var source = this.get(ol.layer.Base.Property.SOURCE); + return /** @type {ol.source.Source} */ (source) || null; +}; + + +/** + * @inheritDoc + */ +ol.layer.Layer.prototype.getSourceState = function() { + var source = this.getSource(); + return !source ? ol.source.State.UNDEFINED : source.getState(); +}; + + +/** + * @private + */ +ol.layer.Layer.prototype.handleSourceChange_ = function() { + this.changed(); +}; + + +/** + * @private + */ +ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() { + if (this.sourceChangeKey_) { + ol.events.unlistenByKey(this.sourceChangeKey_); + this.sourceChangeKey_ = null; + } + var source = this.getSource(); + if (source) { + this.sourceChangeKey_ = ol.events.listen(source, + ol.events.EventType.CHANGE, this.handleSourceChange_, this); + } + this.changed(); +}; + + +/** + * Sets the layer to be rendered on top of other layers on a map. The map will + * not manage this layer in its layers collection, and the callback in + * {@link ol.Map#forEachLayerAtPixel} will receive `null` as layer. This + * is useful for temporary layers. To remove an unmanaged layer from the map, + * use `#setMap(null)`. + * + * To add the layer to a map and have it managed by the map, use + * {@link ol.Map#addLayer} instead. + * @param {ol.Map} map Map. + * @api + */ +ol.layer.Layer.prototype.setMap = function(map) { + if (this.mapPrecomposeKey_) { + ol.events.unlistenByKey(this.mapPrecomposeKey_); + this.mapPrecomposeKey_ = null; + } + if (!map) { + this.changed(); + } + if (this.mapRenderKey_) { + ol.events.unlistenByKey(this.mapRenderKey_); + this.mapRenderKey_ = null; + } + if (map) { + this.mapPrecomposeKey_ = ol.events.listen( + map, ol.render.Event.Type.PRECOMPOSE, function(evt) { + var layerState = this.getLayerState(); + layerState.managed = false; + layerState.zIndex = Infinity; + evt.frameState.layerStatesArray.push(layerState); + evt.frameState.layerStates[ol.getUid(this)] = layerState; + }, this); + this.mapRenderKey_ = ol.events.listen( + this, ol.events.EventType.CHANGE, map.render, map); + this.changed(); + } +}; + + +/** + * Set the layer source. + * @param {ol.source.Source} source The layer source. + * @observable + * @api stable + */ +ol.layer.Layer.prototype.setSource = function(source) { + this.set(ol.layer.Base.Property.SOURCE, source); +}; + +goog.provide('ol.style.IconImageCache'); + +goog.require('ol'); +goog.require('ol.color'); + + +/** + * @constructor + */ +ol.style.IconImageCache = function() { + + /** + * @type {Object.<string, ol.style.IconImage>} + * @private + */ + this.cache_ = {}; + + /** + * @type {number} + * @private + */ + this.cacheSize_ = 0; + + /** + * @const + * @type {number} + * @private + */ + this.maxCacheSize_ = 32; +}; + + +/** + * @param {string} src Src. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Color} color Color. + * @return {string} Cache key. + */ +ol.style.IconImageCache.getKey = function(src, crossOrigin, color) { + ol.DEBUG && console.assert(crossOrigin !== undefined, + 'argument crossOrigin must be defined'); + var colorString = color ? ol.color.asString(color) : 'null'; + return crossOrigin + ':' + src + ':' + colorString; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.style.IconImageCache.prototype.clear = function() { + this.cache_ = {}; + this.cacheSize_ = 0; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.style.IconImageCache.prototype.expire = function() { + if (this.cacheSize_ > this.maxCacheSize_) { + var i = 0; + var key, iconImage; + for (key in this.cache_) { + iconImage = this.cache_[key]; + if ((i++ & 3) === 0 && !iconImage.hasListener()) { + delete this.cache_[key]; + --this.cacheSize_; + } + } + } +}; + + +/** + * @param {string} src Src. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Color} color Color. + * @return {ol.style.IconImage} Icon image. + */ +ol.style.IconImageCache.prototype.get = function(src, crossOrigin, color) { + var key = ol.style.IconImageCache.getKey(src, crossOrigin, color); + return key in this.cache_ ? this.cache_[key] : null; +}; + + +/** + * @param {string} src Src. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Color} color Color. + * @param {ol.style.IconImage} iconImage Icon image. + */ +ol.style.IconImageCache.prototype.set = function(src, crossOrigin, color, + iconImage) { + var key = ol.style.IconImageCache.getKey(src, crossOrigin, color); + this.cache_[key] = iconImage; + ++this.cacheSize_; +}; + +goog.provide('ol.style'); + +goog.require('ol.style.IconImageCache'); + +ol.style.iconImageCache = new ol.style.IconImageCache(); + +goog.provide('ol.transform'); + +goog.require('ol.asserts'); + + +/** + * Collection of affine 2d transformation functions. The functions work on an + * array of 6 elements. The element order is compatible with the [SVGMatrix + * interface](https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix) and is + * a subset (elements a to f) of a 3x3 martrix: + * ``` + * [ a c e ] + * [ b d f ] + * [ 0 0 1 ] + * ``` + */ + + +/** + * @private + * @type {ol.Transform} + */ +ol.transform.tmp_ = new Array(6); + + +/** + * Create an identity transform. + * @return {!ol.Transform} Identity transform. + */ +ol.transform.create = function() { + return [1, 0, 0, 1, 0, 0]; +}; + + +/** + * Resets the given transform to an identity transform. + * @param {!ol.Transform} transform Transform. + * @return {!ol.Transform} Transform. + */ +ol.transform.reset = function(transform) { + return ol.transform.set(transform, 1, 0, 0, 1, 0, 0); +}; + + +/** + * Multiply the underlying matrices of two transforms and return the result in + * the first transform. + * @param {!ol.Transform} transform1 Transform parameters of matrix 1. + * @param {!ol.Transform} transform2 Transform parameters of matrix 2. + * @return {!ol.Transform} transform1 multiplied with transform2. + */ +ol.transform.multiply = function(transform1, transform2) { + var a1 = transform1[0]; + var b1 = transform1[1]; + var c1 = transform1[2]; + var d1 = transform1[3]; + var e1 = transform1[4]; + var f1 = transform1[5]; + var a2 = transform2[0]; + var b2 = transform2[1]; + var c2 = transform2[2]; + var d2 = transform2[3]; + var e2 = transform2[4]; + var f2 = transform2[5]; + + transform1[0] = a1 * a2 + c1 * b2; + transform1[1] = b1 * a2 + d1 * b2; + transform1[2] = a1 * c2 + c1 * d2; + transform1[3] = b1 * c2 + d1 * d2; + transform1[4] = a1 * e2 + c1 * f2 + e1; + transform1[5] = b1 * e2 + d1 * f2 + f1; + + return transform1; +}; + +/** + * Set the transform components a-f on a given transform. + * @param {!ol.Transform} transform Transform. + * @param {number} a The a component of the transform. + * @param {number} b The b component of the transform. + * @param {number} c The c component of the transform. + * @param {number} d The d component of the transform. + * @param {number} e The e component of the transform. + * @param {number} f The f component of the transform. + * @return {!ol.Transform} Matrix with transform applied. + */ +ol.transform.set = function(transform, a, b, c, d, e, f) { + transform[0] = a; + transform[1] = b; + transform[2] = c; + transform[3] = d; + transform[4] = e; + transform[5] = f; + return transform; +}; + + +/** + * Set transform on one matrix from another matrix. + * @param {!ol.Transform} transform1 Matrix to set transform to. + * @param {!ol.Transform} transform2 Matrix to set transform from. + * @return {!ol.Transform} transform1 with transform from transform2 applied. + */ +ol.transform.setFromArray = function(transform1, transform2) { + transform1[0] = transform2[0]; + transform1[1] = transform2[1]; + transform1[2] = transform2[2]; + transform1[3] = transform2[3]; + transform1[4] = transform2[4]; + transform1[5] = transform2[5]; + return transform1; +}; + + +/** + * Transforms the given coordinate with the given transform returning the + * resulting, transformed coordinate. The coordinate will be modified in-place. + * + * @param {ol.Transform} transform The transformation. + * @param {ol.Coordinate|ol.Pixel} coordinate The coordinate to transform. + * @return {ol.Coordinate|ol.Pixel} return coordinate so that operations can be + * chained together. + */ +ol.transform.apply = function(transform, coordinate) { + var x = coordinate[0], y = coordinate[1]; + coordinate[0] = transform[0] * x + transform[2] * y + transform[4]; + coordinate[1] = transform[1] * x + transform[3] * y + transform[5]; + return coordinate; +}; + + +/** + * Applies rotation to the given transform. + * @param {!ol.Transform} transform Transform. + * @param {number} angle Angle in radians. + * @return {!ol.Transform} The rotated transform. + */ +ol.transform.rotate = function(transform, angle) { + var cos = Math.cos(angle); + var sin = Math.sin(angle); + return ol.transform.multiply(transform, + ol.transform.set(ol.transform.tmp_, cos, sin, -sin, cos, 0, 0)); +}; + + +/** + * Applies scale to a given transform. + * @param {!ol.Transform} transform Transform. + * @param {number} x Scale factor x. + * @param {number} y Scale factor y. + * @return {!ol.Transform} The scaled transform. + */ +ol.transform.scale = function(transform, x, y) { + return ol.transform.multiply(transform, + ol.transform.set(ol.transform.tmp_, x, 0, 0, y, 0, 0)); +}; + + +/** + * Applies translation to the given transform. + * @param {!ol.Transform} transform Transform. + * @param {number} dx Translation x. + * @param {number} dy Translation y. + * @return {!ol.Transform} The translated transform. + */ +ol.transform.translate = function(transform, dx, dy) { + return ol.transform.multiply(transform, + ol.transform.set(ol.transform.tmp_, 1, 0, 0, 1, dx, dy)); +}; + + +/** + * Creates a composite transform given an initial translation, scale, rotation, and + * final translation (in that order only, not commutative). + * @param {!ol.Transform} transform The transform (will be modified in place). + * @param {number} dx1 Initial translation x. + * @param {number} dy1 Initial translation y. + * @param {number} sx Scale factor x. + * @param {number} sy Scale factor y. + * @param {number} angle Rotation (in counter-clockwise radians). + * @param {number} dx2 Final translation x. + * @param {number} dy2 Final translation y. + * @return {!ol.Transform} The composite transform. + */ +ol.transform.compose = function(transform, dx1, dy1, sx, sy, angle, dx2, dy2) { + var sin = Math.sin(angle); + var cos = Math.cos(angle); + transform[0] = sx * cos; + transform[1] = sy * sin; + transform[2] = -sx * sin; + transform[3] = sy * cos; + transform[4] = dx2 * sx * cos - dy2 * sx * sin + dx1; + transform[5] = dx2 * sy * sin + dy2 * sy * cos + dy1; + return transform; +}; + + +/** + * Invert the given transform. + * @param {!ol.Transform} transform Transform. + * @return {!ol.Transform} Inverse of the transform. + */ +ol.transform.invert = function(transform) { + var det = ol.transform.determinant(transform); + ol.asserts.assert(det !== 0, 32); // Transformation matrix cannot be inverted + + var a = transform[0]; + var b = transform[1]; + var c = transform[2]; + var d = transform[3]; + var e = transform[4]; + var f = transform[5]; + + transform[0] = d / det; + transform[1] = -b / det; + transform[2] = -c / det; + transform[3] = a / det; + transform[4] = (c * f - d * e) / det; + transform[5] = -(a * f - b * e) / det; + + return transform; +}; + + +/** + * Returns the determinant of the given matrix. + * @param {!ol.Transform} mat Matrix. + * @return {number} Determinant. + */ +ol.transform.determinant = function(mat) { + return mat[0] * mat[3] - mat[1] * mat[2]; +}; + +goog.provide('ol.renderer.Map'); + +goog.require('ol'); +goog.require('ol.Disposable'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.functions'); +goog.require('ol.layer.Layer'); +goog.require('ol.style'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.Disposable} + * @param {Element} container Container. + * @param {ol.Map} map Map. + * @struct + */ +ol.renderer.Map = function(container, map) { + + ol.Disposable.call(this); + + + /** + * @private + * @type {ol.Map} + */ + this.map_ = map; + + /** + * @private + * @type {Object.<string, ol.renderer.Layer>} + */ + this.layerRenderers_ = {}; + + /** + * @private + * @type {Object.<string, ol.EventsKey>} + */ + this.layerRendererListeners_ = {}; + +}; +ol.inherits(ol.renderer.Map, ol.Disposable); + + +/** + * @param {olx.FrameState} frameState FrameState. + * @protected + */ +ol.renderer.Map.prototype.calculateMatrices2D = function(frameState) { + var viewState = frameState.viewState; + var coordinateToPixelTransform = frameState.coordinateToPixelTransform; + var pixelToCoordinateTransform = frameState.pixelToCoordinateTransform; + ol.DEBUG && console.assert(coordinateToPixelTransform, + 'frameState has a coordinateToPixelTransform'); + + ol.transform.compose(coordinateToPixelTransform, + frameState.size[0] / 2, frameState.size[1] / 2, + 1 / viewState.resolution, -1 / viewState.resolution, + -viewState.rotation, + -viewState.center[0], -viewState.center[1]); + + ol.transform.invert( + ol.transform.setFromArray(pixelToCoordinateTransform, coordinateToPixelTransform)); +}; + + +/** + * @abstract + * @param {ol.layer.Layer} layer Layer. + * @protected + * @return {ol.renderer.Layer} layerRenderer Layer renderer. + */ +ol.renderer.Map.prototype.createLayerRenderer = function(layer) {}; + + +/** + * @inheritDoc + */ +ol.renderer.Map.prototype.disposeInternal = function() { + for (var id in this.layerRenderers_) { + this.layerRenderers_[id].dispose(); + } +}; + + +/** + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.Map.expireIconCache_ = function(map, frameState) { + var cache = ol.style.iconImageCache; + cache.expire(); +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState FrameState. + * @param {number} hitTolerance Hit tolerance in pixels. + * @param {function(this: S, (ol.Feature|ol.render.Feature), + * ol.layer.Layer): T} callback Feature callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result. + * @template S,T,U + */ +ol.renderer.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg, + layerFilter, thisArg2) { + var result; + var viewState = frameState.viewState; + var viewResolution = viewState.resolution; + + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.layer.Layer} layer Layer. + * @return {?} Callback result. + */ + function forEachFeatureAtCoordinate(feature, layer) { + var key = ol.getUid(feature).toString(); + var managed = frameState.layerStates[ol.getUid(layer)].managed; + if (!(key in frameState.skippedFeatureUids && !managed)) { + return callback.call(thisArg, feature, managed ? layer : null); + } + } + + var projection = viewState.projection; + + var translatedCoordinate = coordinate; + if (projection.canWrapX()) { + var projectionExtent = projection.getExtent(); + var worldWidth = ol.extent.getWidth(projectionExtent); + var x = coordinate[0]; + if (x < projectionExtent[0] || x > projectionExtent[2]) { + var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth); + translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]]; + } + } + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + if (layer.getSource()) { + result = layerRenderer.forEachFeatureAtCoordinate( + layer.getSource().getWrapX() ? translatedCoordinate : coordinate, + frameState, hitTolerance, forEachFeatureAtCoordinate, thisArg); + } + if (result) { + return result; + } + } + } + return undefined; +}; + + +/** + * @abstract + * @param {ol.Pixel} pixel Pixel. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer + * callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result. + * @template S,T,U + */ +ol.renderer.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg, + layerFilter, thisArg2) {}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState FrameState. + * @param {number} hitTolerance Hit tolerance in pixels. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg Value to use as `this` when executing `layerFilter`. + * @return {boolean} Is there a feature at the given coordinate? + * @template U + */ +ol.renderer.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) { + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, hitTolerance, ol.functions.TRUE, this, layerFilter, thisArg); + + return hasFeature !== undefined; +}; + + +/** + * @param {ol.layer.Layer} layer Layer. + * @protected + * @return {ol.renderer.Layer} Layer renderer. + */ +ol.renderer.Map.prototype.getLayerRenderer = function(layer) { + var layerKey = ol.getUid(layer).toString(); + if (layerKey in this.layerRenderers_) { + return this.layerRenderers_[layerKey]; + } else { + var layerRenderer = this.createLayerRenderer(layer); + this.layerRenderers_[layerKey] = layerRenderer; + this.layerRendererListeners_[layerKey] = ol.events.listen(layerRenderer, + ol.events.EventType.CHANGE, this.handleLayerRendererChange_, this); + + return layerRenderer; + } +}; + + +/** + * @param {string} layerKey Layer key. + * @protected + * @return {ol.renderer.Layer} Layer renderer. + */ +ol.renderer.Map.prototype.getLayerRendererByKey = function(layerKey) { + ol.DEBUG && console.assert(layerKey in this.layerRenderers_, + 'given layerKey (%s) exists in layerRenderers', layerKey); + return this.layerRenderers_[layerKey]; +}; + + +/** + * @protected + * @return {Object.<string, ol.renderer.Layer>} Layer renderers. + */ +ol.renderer.Map.prototype.getLayerRenderers = function() { + return this.layerRenderers_; +}; + + +/** + * @return {ol.Map} Map. + */ +ol.renderer.Map.prototype.getMap = function() { + return this.map_; +}; + + +/** + * @abstract + * @return {string} Type + */ +ol.renderer.Map.prototype.getType = function() {}; + + +/** + * Handle changes in a layer renderer. + * @private + */ +ol.renderer.Map.prototype.handleLayerRendererChange_ = function() { + this.map_.render(); +}; + + +/** + * @param {string} layerKey Layer key. + * @return {ol.renderer.Layer} Layer renderer. + * @private + */ +ol.renderer.Map.prototype.removeLayerRendererByKey_ = function(layerKey) { + ol.DEBUG && console.assert(layerKey in this.layerRenderers_, + 'given layerKey (%s) exists in layerRenderers', layerKey); + var layerRenderer = this.layerRenderers_[layerKey]; + delete this.layerRenderers_[layerKey]; + + ol.DEBUG && console.assert(layerKey in this.layerRendererListeners_, + 'given layerKey (%s) exists in layerRendererListeners', layerKey); + ol.events.unlistenByKey(this.layerRendererListeners_[layerKey]); + delete this.layerRendererListeners_[layerKey]; + + return layerRenderer; +}; + + +/** + * Render. + * @param {?olx.FrameState} frameState Frame state. + */ +ol.renderer.Map.prototype.renderFrame = ol.nullFunction; + + +/** + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.Map.prototype.removeUnusedLayerRenderers_ = function(map, frameState) { + var layerKey; + for (layerKey in this.layerRenderers_) { + if (!frameState || !(layerKey in frameState.layerStates)) { + this.removeLayerRendererByKey_(layerKey).dispose(); + } + } +}; + + +/** + * @param {olx.FrameState} frameState Frame state. + * @protected + */ +ol.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) { + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (ol.renderer.Map.expireIconCache_) + ); +}; + + +/** + * @param {!olx.FrameState} frameState Frame state. + * @protected + */ +ol.renderer.Map.prototype.scheduleRemoveUnusedLayerRenderers = function(frameState) { + var layerKey; + for (layerKey in this.layerRenderers_) { + if (!(layerKey in frameState.layerStates)) { + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (this.removeUnusedLayerRenderers_.bind(this)) + ); + return; + } + } +}; + + +/** + * @param {ol.LayerState} state1 First layer state. + * @param {ol.LayerState} state2 Second layer state. + * @return {number} The zIndex difference. + */ +ol.renderer.Map.sortByZIndex = function(state1, state2) { + return state1.zIndex - state2.zIndex; +}; + +goog.provide('ol.layer.Image'); + +goog.require('ol'); +goog.require('ol.layer.Layer'); + + +/** + * @classdesc + * Server-rendered images that are available for arbitrary extents and + * resolutions. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Layer} + * @fires ol.render.Event + * @param {olx.layer.ImageOptions=} opt_options Layer options. + * @api stable + */ +ol.layer.Image = function(opt_options) { + var options = opt_options ? opt_options : {}; + ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (options)); +}; +ol.inherits(ol.layer.Image, ol.layer.Layer); + + +/** + * Return the associated {@link ol.source.Image source} of the image layer. + * @function + * @return {ol.source.Image} Source. + * @api stable + */ +ol.layer.Image.prototype.getSource; + +goog.provide('ol.layer.Tile'); + +goog.require('ol'); +goog.require('ol.layer.Layer'); +goog.require('ol.obj'); + + +/** + * @classdesc + * For layer sources that provide pre-rendered, tiled images in grids that are + * organized by zoom levels for specific resolutions. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Layer} + * @fires ol.render.Event + * @param {olx.layer.TileOptions=} opt_options Tile layer options. + * @api stable + */ +ol.layer.Tile = function(opt_options) { + var options = opt_options ? opt_options : {}; + + var baseOptions = ol.obj.assign({}, options); + + delete baseOptions.preload; + delete baseOptions.useInterimTilesOnError; + ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (baseOptions)); + + this.setPreload(options.preload !== undefined ? options.preload : 0); + this.setUseInterimTilesOnError(options.useInterimTilesOnError !== undefined ? + options.useInterimTilesOnError : true); +}; +ol.inherits(ol.layer.Tile, ol.layer.Layer); + + +/** + * Return the level as number to which we will preload tiles up to. + * @return {number} The level to preload tiles up to. + * @observable + * @api + */ +ol.layer.Tile.prototype.getPreload = function() { + return /** @type {number} */ (this.get(ol.layer.Tile.Property.PRELOAD)); +}; + + +/** + * Return the associated {@link ol.source.Tile tilesource} of the layer. + * @function + * @return {ol.source.Tile} Source. + * @api stable + */ +ol.layer.Tile.prototype.getSource; + + +/** + * Set the level as number to which we will preload tiles up to. + * @param {number} preload The level to preload tiles up to. + * @observable + * @api + */ +ol.layer.Tile.prototype.setPreload = function(preload) { + this.set(ol.layer.Tile.Property.PRELOAD, preload); +}; + + +/** + * Whether we use interim tiles on error. + * @return {boolean} Use interim tiles on error. + * @observable + * @api + */ +ol.layer.Tile.prototype.getUseInterimTilesOnError = function() { + return /** @type {boolean} */ ( + this.get(ol.layer.Tile.Property.USE_INTERIM_TILES_ON_ERROR)); +}; + + +/** + * Set whether we use interim tiles on error. + * @param {boolean} useInterimTilesOnError Use interim tiles on error. + * @observable + * @api + */ +ol.layer.Tile.prototype.setUseInterimTilesOnError = function(useInterimTilesOnError) { + this.set( + ol.layer.Tile.Property.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError); +}; + + +/** + * @enum {string} + */ +ol.layer.Tile.Property = { + PRELOAD: 'preload', + USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError' +}; + +goog.provide('ol.ImageBase'); + +goog.require('ol'); +goog.require('ol.events.EventTarget'); +goog.require('ol.events.EventType'); + + +/** + * @constructor + * @extends {ol.events.EventTarget} + * @param {ol.Extent} extent Extent. + * @param {number|undefined} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Image.State} state State. + * @param {Array.<ol.Attribution>} attributions Attributions. + */ +ol.ImageBase = function(extent, resolution, pixelRatio, state, attributions) { + + ol.events.EventTarget.call(this); + + /** + * @private + * @type {Array.<ol.Attribution>} + */ + this.attributions_ = attributions; + + /** + * @protected + * @type {ol.Extent} + */ + this.extent = extent; + + /** + * @private + * @type {number} + */ + this.pixelRatio_ = pixelRatio; + + /** + * @protected + * @type {number|undefined} + */ + this.resolution = resolution; + + /** + * @protected + * @type {ol.Image.State} + */ + this.state = state; + +}; +ol.inherits(ol.ImageBase, ol.events.EventTarget); + + +/** + * @protected + */ +ol.ImageBase.prototype.changed = function() { + this.dispatchEvent(ol.events.EventType.CHANGE); +}; + + +/** + * @return {Array.<ol.Attribution>} Attributions. + */ +ol.ImageBase.prototype.getAttributions = function() { + return this.attributions_; +}; + + +/** + * @return {ol.Extent} Extent. + */ +ol.ImageBase.prototype.getExtent = function() { + return this.extent; +}; + + +/** + * @abstract + * @param {Object=} opt_context Object. + * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image. + */ +ol.ImageBase.prototype.getImage = function(opt_context) {}; + + +/** + * @return {number} PixelRatio. + */ +ol.ImageBase.prototype.getPixelRatio = function() { + return this.pixelRatio_; +}; + + +/** + * @return {number} Resolution. + */ +ol.ImageBase.prototype.getResolution = function() { + ol.DEBUG && console.assert(this.resolution !== undefined, 'resolution not yet set'); + return /** @type {number} */ (this.resolution); +}; + + +/** + * @return {ol.Image.State} State. + */ +ol.ImageBase.prototype.getState = function() { + return this.state; +}; + + +/** + * Load not yet loaded URI. + * @abstract + */ +ol.ImageBase.prototype.load = function() {}; + +goog.provide('ol.Image'); + +goog.require('ol'); +goog.require('ol.ImageBase'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.obj'); + + +/** + * @constructor + * @extends {ol.ImageBase} + * @param {ol.Extent} extent Extent. + * @param {number|undefined} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {Array.<ol.Attribution>} attributions Attributions. + * @param {string} src Image source URI. + * @param {?string} crossOrigin Cross origin. + * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function. + */ +ol.Image = function(extent, resolution, pixelRatio, attributions, src, + crossOrigin, imageLoadFunction) { + + ol.ImageBase.call(this, extent, resolution, pixelRatio, ol.Image.State.IDLE, + attributions); + + /** + * @private + * @type {string} + */ + this.src_ = src; + + /** + * @private + * @type {HTMLCanvasElement|Image|HTMLVideoElement} + */ + this.image_ = new Image(); + if (crossOrigin !== null) { + this.image_.crossOrigin = crossOrigin; + } + + /** + * @private + * @type {Object.<number, (HTMLCanvasElement|Image|HTMLVideoElement)>} + */ + this.imageByContext_ = {}; + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.imageListenerKeys_ = null; + + /** + * @protected + * @type {ol.Image.State} + */ + this.state = ol.Image.State.IDLE; + + /** + * @private + * @type {ol.ImageLoadFunctionType} + */ + this.imageLoadFunction_ = imageLoadFunction; + +}; +ol.inherits(ol.Image, ol.ImageBase); + + +/** + * Get the HTML image element (may be a Canvas, Image, or Video). + * @param {Object=} opt_context Object. + * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image. + * @api + */ +ol.Image.prototype.getImage = function(opt_context) { + if (opt_context !== undefined) { + var image; + var key = ol.getUid(opt_context); + if (key in this.imageByContext_) { + return this.imageByContext_[key]; + } else if (ol.obj.isEmpty(this.imageByContext_)) { + image = this.image_; + } else { + image = /** @type {Image} */ (this.image_.cloneNode(false)); + } + this.imageByContext_[key] = image; + return image; + } else { + return this.image_; + } +}; + + +/** + * Tracks loading or read errors. + * + * @private + */ +ol.Image.prototype.handleImageError_ = function() { + this.state = ol.Image.State.ERROR; + this.unlistenImage_(); + this.changed(); +}; + + +/** + * Tracks successful image load. + * + * @private + */ +ol.Image.prototype.handleImageLoad_ = function() { + if (this.resolution === undefined) { + this.resolution = ol.extent.getHeight(this.extent) / this.image_.height; + } + this.state = ol.Image.State.LOADED; + this.unlistenImage_(); + this.changed(); +}; + + +/** + * Load the image or retry if loading previously failed. + * Loading is taken care of by the tile queue, and calling this method is + * only needed for preloading or for reloading in case of an error. + * @api + */ +ol.Image.prototype.load = function() { + if (this.state == ol.Image.State.IDLE || this.state == ol.Image.State.ERROR) { + this.state = ol.Image.State.LOADING; + this.changed(); + ol.DEBUG && console.assert(!this.imageListenerKeys_, + 'this.imageListenerKeys_ should be null'); + this.imageListenerKeys_ = [ + ol.events.listenOnce(this.image_, ol.events.EventType.ERROR, + this.handleImageError_, this), + ol.events.listenOnce(this.image_, ol.events.EventType.LOAD, + this.handleImageLoad_, this) + ]; + this.imageLoadFunction_(this, this.src_); + } +}; + + +/** + * @param {HTMLCanvasElement|Image|HTMLVideoElement} image Image. + */ +ol.Image.prototype.setImage = function(image) { + this.image_ = image; +}; + + +/** + * Discards event handlers which listen for load completion or errors. + * + * @private + */ +ol.Image.prototype.unlistenImage_ = function() { + this.imageListenerKeys_.forEach(ol.events.unlistenByKey); + this.imageListenerKeys_ = null; +}; + + +/** + * @enum {number} + */ +ol.Image.State = { + IDLE: 0, + LOADING: 1, + LOADED: 2, + ERROR: 3 +}; + +goog.provide('ol.render.canvas'); + + +/** + * @const + * @type {string} + */ +ol.render.canvas.defaultFont = '10px sans-serif'; + + +/** + * @const + * @type {ol.Color} + */ +ol.render.canvas.defaultFillStyle = [0, 0, 0, 1]; + + +/** + * @const + * @type {string} + */ +ol.render.canvas.defaultLineCap = 'round'; + + +/** + * @const + * @type {Array.<number>} + */ +ol.render.canvas.defaultLineDash = []; + + +/** + * @const + * @type {string} + */ +ol.render.canvas.defaultLineJoin = 'round'; + + +/** + * @const + * @type {number} + */ +ol.render.canvas.defaultMiterLimit = 10; + + +/** + * @const + * @type {ol.Color} + */ +ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1]; + + +/** + * @const + * @type {string} + */ +ol.render.canvas.defaultTextAlign = 'center'; + + +/** + * @const + * @type {string} + */ +ol.render.canvas.defaultTextBaseline = 'middle'; + + +/** + * @const + * @type {number} + */ +ol.render.canvas.defaultLineWidth = 1; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {number} rotation Rotation. + * @param {number} offsetX X offset. + * @param {number} offsetY Y offset. + */ +ol.render.canvas.rotateAtOffset = function(context, rotation, offsetX, offsetY) { + if (rotation !== 0) { + context.translate(offsetX, offsetY); + context.rotate(rotation); + context.translate(-offsetX, -offsetY); + } +}; + +goog.provide('ol.style.Image'); + + +/** + * @classdesc + * A base class used for creating subclasses and not instantiated in + * apps. Base class for {@link ol.style.Icon}, {@link ol.style.Circle} and + * {@link ol.style.RegularShape}. + * + * @constructor + * @param {ol.StyleImageOptions} options Options. + * @api + */ +ol.style.Image = function(options) { + + /** + * @private + * @type {number} + */ + this.opacity_ = options.opacity; + + /** + * @private + * @type {boolean} + */ + this.rotateWithView_ = options.rotateWithView; + + /** + * @private + * @type {number} + */ + this.rotation_ = options.rotation; + + /** + * @private + * @type {number} + */ + this.scale_ = options.scale; + + /** + * @private + * @type {boolean} + */ + this.snapToPixel_ = options.snapToPixel; + +}; + + +/** + * Get the symbolizer opacity. + * @return {number} Opacity. + * @api + */ +ol.style.Image.prototype.getOpacity = function() { + return this.opacity_; +}; + + +/** + * Determine whether the symbolizer rotates with the map. + * @return {boolean} Rotate with map. + * @api + */ +ol.style.Image.prototype.getRotateWithView = function() { + return this.rotateWithView_; +}; + + +/** + * Get the symoblizer rotation. + * @return {number} Rotation. + * @api + */ +ol.style.Image.prototype.getRotation = function() { + return this.rotation_; +}; + + +/** + * Get the symbolizer scale. + * @return {number} Scale. + * @api + */ +ol.style.Image.prototype.getScale = function() { + return this.scale_; +}; + + +/** + * Determine whether the symbolizer should be snapped to a pixel. + * @return {boolean} The symbolizer should snap to a pixel. + * @api + */ +ol.style.Image.prototype.getSnapToPixel = function() { + return this.snapToPixel_; +}; + + +/** + * Get the anchor point in pixels. The anchor determines the center point for the + * symbolizer. + * @abstract + * @return {Array.<number>} Anchor. + */ +ol.style.Image.prototype.getAnchor = function() {}; + + +/** + * Get the image element for the symbolizer. + * @abstract + * @param {number} pixelRatio Pixel ratio. + * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element. + */ +ol.style.Image.prototype.getImage = function(pixelRatio) {}; + + +/** + * @abstract + * @param {number} pixelRatio Pixel ratio. + * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element. + */ +ol.style.Image.prototype.getHitDetectionImage = function(pixelRatio) {}; + + +/** + * @abstract + * @return {ol.Image.State} Image state. + */ +ol.style.Image.prototype.getImageState = function() {}; + + +/** + * @abstract + * @return {ol.Size} Image size. + */ +ol.style.Image.prototype.getImageSize = function() {}; + + +/** + * @abstract + * @return {ol.Size} Size of the hit-detection image. + */ +ol.style.Image.prototype.getHitDetectionImageSize = function() {}; + + +/** + * Get the origin of the symbolizer. + * @abstract + * @return {Array.<number>} Origin. + */ +ol.style.Image.prototype.getOrigin = function() {}; + + +/** + * Get the size of the symbolizer (in pixels). + * @abstract + * @return {ol.Size} Size. + */ +ol.style.Image.prototype.getSize = function() {}; + + +/** + * Set the opacity. + * + * @param {number} opacity Opacity. + * @api + */ +ol.style.Image.prototype.setOpacity = function(opacity) { + this.opacity_ = opacity; +}; + + +/** + * Set whether to rotate the style with the view. + * + * @param {boolean} rotateWithView Rotate with map. + */ +ol.style.Image.prototype.setRotateWithView = function(rotateWithView) { + this.rotateWithView_ = rotateWithView; +}; + + +/** + * Set the rotation. + * + * @param {number} rotation Rotation. + * @api + */ +ol.style.Image.prototype.setRotation = function(rotation) { + this.rotation_ = rotation; +}; + + +/** + * Set the scale. + * + * @param {number} scale Scale. + * @api + */ +ol.style.Image.prototype.setScale = function(scale) { + this.scale_ = scale; +}; + + +/** + * Set whether to snap the image to the closest pixel. + * + * @param {boolean} snapToPixel Snap to pixel? + */ +ol.style.Image.prototype.setSnapToPixel = function(snapToPixel) { + this.snapToPixel_ = snapToPixel; +}; + + +/** + * @abstract + * @param {function(this: T, ol.events.Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @return {ol.EventsKey|undefined} Listener key. + * @template T + */ +ol.style.Image.prototype.listenImageChange = function(listener, thisArg) {}; + + +/** + * Load not yet loaded URI. + * @abstract + */ +ol.style.Image.prototype.load = function() {}; + + +/** + * @abstract + * @param {function(this: T, ol.events.Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @template T + */ +ol.style.Image.prototype.unlistenImageChange = function(listener, thisArg) {}; + +goog.provide('ol.style.RegularShape'); + +goog.require('ol'); +goog.require('ol.colorlike'); +goog.require('ol.dom'); +goog.require('ol.has'); +goog.require('ol.Image'); +goog.require('ol.render.canvas'); +goog.require('ol.style.Image'); + + +/** + * @classdesc + * Set regular shape style for vector features. The resulting shape will be + * a regular polygon when `radius` is provided, or a star when `radius1` and + * `radius2` are provided. + * + * @constructor + * @param {olx.style.RegularShapeOptions} options Options. + * @extends {ol.style.Image} + * @api + */ +ol.style.RegularShape = function(options) { + + ol.DEBUG && console.assert( + options.radius !== undefined || options.radius1 !== undefined, + 'must provide either "radius" or "radius1"'); + + /** + * @private + * @type {Array.<string>} + */ + this.checksums_ = null; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = null; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.hitDetectionCanvas_ = null; + + /** + * @private + * @type {ol.style.Fill} + */ + this.fill_ = options.fill !== undefined ? options.fill : null; + + /** + * @private + * @type {Array.<number>} + */ + this.origin_ = [0, 0]; + + /** + * @private + * @type {number} + */ + this.points_ = options.points; + + /** + * @protected + * @type {number} + */ + this.radius_ = /** @type {number} */ (options.radius !== undefined ? + options.radius : options.radius1); + + /** + * @private + * @type {number} + */ + this.radius2_ = + options.radius2 !== undefined ? options.radius2 : this.radius_; + + /** + * @private + * @type {number} + */ + this.angle_ = options.angle !== undefined ? options.angle : 0; + + /** + * @private + * @type {ol.style.Stroke} + */ + this.stroke_ = options.stroke !== undefined ? options.stroke : null; + + /** + * @private + * @type {Array.<number>} + */ + this.anchor_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.size_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.imageSize_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.hitDetectionImageSize_ = null; + + /** + * @protected + * @type {ol.style.AtlasManager|undefined} + */ + this.atlasManager_ = options.atlasManager; + + this.render_(this.atlasManager_); + + /** + * @type {boolean} + */ + var snapToPixel = options.snapToPixel !== undefined ? + options.snapToPixel : true; + + /** + * @type {boolean} + */ + var rotateWithView = options.rotateWithView !== undefined ? + options.rotateWithView : false; + + ol.style.Image.call(this, { + opacity: 1, + rotateWithView: rotateWithView, + rotation: options.rotation !== undefined ? options.rotation : 0, + scale: 1, + snapToPixel: snapToPixel + }); + +}; +ol.inherits(ol.style.RegularShape, ol.style.Image); + + +/** + * Clones the style. If an atlasmanager was provided to the original style it will be used in the cloned style, too. + * @return {ol.style.RegularShape} The cloned style. + * @api + */ +ol.style.RegularShape.prototype.clone = function() { + var style = new ol.style.RegularShape({ + fill: this.getFill() ? this.getFill().clone() : undefined, + points: this.getRadius2() !== this.getRadius() ? this.getPoints() / 2 : this.getPoints(), + radius: this.getRadius(), + radius2: this.getRadius2(), + angle: this.getAngle(), + snapToPixel: this.getSnapToPixel(), + stroke: this.getStroke() ? this.getStroke().clone() : undefined, + rotation: this.getRotation(), + rotateWithView: this.getRotateWithView(), + atlasManager: this.atlasManager_ + }); + style.setOpacity(this.getOpacity()); + style.setScale(this.getScale()); + return style; +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.RegularShape.prototype.getAnchor = function() { + return this.anchor_; +}; + + +/** + * Get the angle used in generating the shape. + * @return {number} Shape's rotation in radians. + * @api + */ +ol.style.RegularShape.prototype.getAngle = function() { + return this.angle_; +}; + + +/** + * Get the fill style for the shape. + * @return {ol.style.Fill} Fill style. + * @api + */ +ol.style.RegularShape.prototype.getFill = function() { + return this.fill_; +}; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.getHitDetectionImage = function(pixelRatio) { + return this.hitDetectionCanvas_; +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.RegularShape.prototype.getImage = function(pixelRatio) { + return this.canvas_; +}; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.getImageSize = function() { + return this.imageSize_; +}; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.getHitDetectionImageSize = function() { + return this.hitDetectionImageSize_; +}; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.getImageState = function() { + return ol.Image.State.LOADED; +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.RegularShape.prototype.getOrigin = function() { + return this.origin_; +}; + + +/** + * Get the number of points for generating the shape. + * @return {number} Number of points for stars and regular polygons. + * @api + */ +ol.style.RegularShape.prototype.getPoints = function() { + return this.points_; +}; + + +/** + * Get the (primary) radius for the shape. + * @return {number} Radius. + * @api + */ +ol.style.RegularShape.prototype.getRadius = function() { + return this.radius_; +}; + + +/** + * Get the secondary radius for the shape. + * @return {number} Radius2. + * @api + */ +ol.style.RegularShape.prototype.getRadius2 = function() { + return this.radius2_; +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.RegularShape.prototype.getSize = function() { + return this.size_; +}; + + +/** + * Get the stroke style for the shape. + * @return {ol.style.Stroke} Stroke style. + * @api + */ +ol.style.RegularShape.prototype.getStroke = function() { + return this.stroke_; +}; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.listenImageChange = ol.nullFunction; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.load = ol.nullFunction; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.unlistenImageChange = ol.nullFunction; + + +/** + * @protected + * @param {ol.style.AtlasManager|undefined} atlasManager An atlas manager. + */ +ol.style.RegularShape.prototype.render_ = function(atlasManager) { + var imageSize; + var lineCap = ''; + var lineJoin = ''; + var miterLimit = 0; + var lineDash = null; + var strokeStyle; + var strokeWidth = 0; + + if (this.stroke_) { + strokeStyle = ol.colorlike.asColorLike(this.stroke_.getColor()); + strokeWidth = this.stroke_.getWidth(); + if (strokeWidth === undefined) { + strokeWidth = ol.render.canvas.defaultLineWidth; + } + lineDash = this.stroke_.getLineDash(); + if (!ol.has.CANVAS_LINE_DASH) { + lineDash = null; + } + lineJoin = this.stroke_.getLineJoin(); + if (lineJoin === undefined) { + lineJoin = ol.render.canvas.defaultLineJoin; + } + lineCap = this.stroke_.getLineCap(); + if (lineCap === undefined) { + lineCap = ol.render.canvas.defaultLineCap; + } + miterLimit = this.stroke_.getMiterLimit(); + if (miterLimit === undefined) { + miterLimit = ol.render.canvas.defaultMiterLimit; + } + } + + var size = 2 * (this.radius_ + strokeWidth) + 1; + + /** @type {ol.RegularShapeRenderOptions} */ + var renderOptions = { + strokeStyle: strokeStyle, + strokeWidth: strokeWidth, + size: size, + lineCap: lineCap, + lineDash: lineDash, + lineJoin: lineJoin, + miterLimit: miterLimit + }; + + if (atlasManager === undefined) { + // no atlas manager is used, create a new canvas + var context = ol.dom.createCanvasContext2D(size, size); + this.canvas_ = context.canvas; + + // canvas.width and height are rounded to the closest integer + size = this.canvas_.width; + imageSize = size; + + this.draw_(renderOptions, context, 0, 0); + + this.createHitDetectionCanvas_(renderOptions); + } else { + // an atlas manager is used, add the symbol to an atlas + size = Math.round(size); + + var hasCustomHitDetectionImage = !this.fill_; + var renderHitDetectionCallback; + if (hasCustomHitDetectionImage) { + // render the hit-detection image into a separate atlas image + renderHitDetectionCallback = + this.drawHitDetectionCanvas_.bind(this, renderOptions); + } + + var id = this.getChecksum(); + var info = atlasManager.add( + id, size, size, this.draw_.bind(this, renderOptions), + renderHitDetectionCallback); + ol.DEBUG && console.assert(info, 'shape size is too large'); + + this.canvas_ = info.image; + this.origin_ = [info.offsetX, info.offsetY]; + imageSize = info.image.width; + + if (hasCustomHitDetectionImage) { + this.hitDetectionCanvas_ = info.hitImage; + this.hitDetectionImageSize_ = + [info.hitImage.width, info.hitImage.height]; + } else { + this.hitDetectionCanvas_ = this.canvas_; + this.hitDetectionImageSize_ = [imageSize, imageSize]; + } + } + + this.anchor_ = [size / 2, size / 2]; + this.size_ = [size, size]; + this.imageSize_ = [imageSize, imageSize]; +}; + + +/** + * @private + * @param {ol.RegularShapeRenderOptions} renderOptions Render options. + * @param {CanvasRenderingContext2D} context The rendering context. + * @param {number} x The origin for the symbol (x). + * @param {number} y The origin for the symbol (y). + */ +ol.style.RegularShape.prototype.draw_ = function(renderOptions, context, x, y) { + var i, angle0, radiusC; + // reset transform + context.setTransform(1, 0, 0, 1, 0, 0); + + // then move to (x, y) + context.translate(x, y); + + context.beginPath(); + + if (this.points_ === Infinity) { + context.arc( + renderOptions.size / 2, renderOptions.size / 2, + this.radius_, 0, 2 * Math.PI, true); + } else { + if (this.radius2_ !== this.radius_) { + this.points_ = 2 * this.points_; + } + for (i = 0; i <= this.points_; i++) { + angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_; + radiusC = i % 2 === 0 ? this.radius_ : this.radius2_; + context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0), + renderOptions.size / 2 + radiusC * Math.sin(angle0)); + } + } + + + if (this.fill_) { + context.fillStyle = ol.colorlike.asColorLike(this.fill_.getColor()); + context.fill(); + } + if (this.stroke_) { + context.strokeStyle = renderOptions.strokeStyle; + context.lineWidth = renderOptions.strokeWidth; + if (renderOptions.lineDash) { + context.setLineDash(renderOptions.lineDash); + } + context.lineCap = renderOptions.lineCap; + context.lineJoin = renderOptions.lineJoin; + context.miterLimit = renderOptions.miterLimit; + context.stroke(); + } + context.closePath(); +}; + + +/** + * @private + * @param {ol.RegularShapeRenderOptions} renderOptions Render options. + */ +ol.style.RegularShape.prototype.createHitDetectionCanvas_ = function(renderOptions) { + this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size]; + if (this.fill_) { + this.hitDetectionCanvas_ = this.canvas_; + return; + } + + // if no fill style is set, create an extra hit-detection image with a + // default fill style + var context = ol.dom.createCanvasContext2D(renderOptions.size, renderOptions.size); + this.hitDetectionCanvas_ = context.canvas; + + this.drawHitDetectionCanvas_(renderOptions, context, 0, 0); +}; + + +/** + * @private + * @param {ol.RegularShapeRenderOptions} renderOptions Render options. + * @param {CanvasRenderingContext2D} context The context. + * @param {number} x The origin for the symbol (x). + * @param {number} y The origin for the symbol (y). + */ +ol.style.RegularShape.prototype.drawHitDetectionCanvas_ = function(renderOptions, context, x, y) { + // reset transform + context.setTransform(1, 0, 0, 1, 0, 0); + + // then move to (x, y) + context.translate(x, y); + + context.beginPath(); + + if (this.points_ === Infinity) { + context.arc( + renderOptions.size / 2, renderOptions.size / 2, + this.radius_, 0, 2 * Math.PI, true); + } else { + if (this.radius2_ !== this.radius_) { + this.points_ = 2 * this.points_; + } + var i, radiusC, angle0; + for (i = 0; i <= this.points_; i++) { + angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_; + radiusC = i % 2 === 0 ? this.radius_ : this.radius2_; + context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0), + renderOptions.size / 2 + radiusC * Math.sin(angle0)); + } + } + + context.fillStyle = ol.render.canvas.defaultFillStyle; + context.fill(); + if (this.stroke_) { + context.strokeStyle = renderOptions.strokeStyle; + context.lineWidth = renderOptions.strokeWidth; + if (renderOptions.lineDash) { + context.setLineDash(renderOptions.lineDash); + } + context.stroke(); + } + context.closePath(); +}; + + +/** + * @return {string} The checksum. + */ +ol.style.RegularShape.prototype.getChecksum = function() { + var strokeChecksum = this.stroke_ ? + this.stroke_.getChecksum() : '-'; + var fillChecksum = this.fill_ ? + this.fill_.getChecksum() : '-'; + + var recalculate = !this.checksums_ || + (strokeChecksum != this.checksums_[1] || + fillChecksum != this.checksums_[2] || + this.radius_ != this.checksums_[3] || + this.radius2_ != this.checksums_[4] || + this.angle_ != this.checksums_[5] || + this.points_ != this.checksums_[6]); + + if (recalculate) { + var checksum = 'r' + strokeChecksum + fillChecksum + + (this.radius_ !== undefined ? this.radius_.toString() : '-') + + (this.radius2_ !== undefined ? this.radius2_.toString() : '-') + + (this.angle_ !== undefined ? this.angle_.toString() : '-') + + (this.points_ !== undefined ? this.points_.toString() : '-'); + this.checksums_ = [checksum, strokeChecksum, fillChecksum, + this.radius_, this.radius2_, this.angle_, this.points_]; + } + + return this.checksums_[0]; +}; + +goog.provide('ol.style.Circle'); + +goog.require('ol'); +goog.require('ol.style.RegularShape'); + + +/** + * @classdesc + * Set circle style for vector features. + * + * @constructor + * @param {olx.style.CircleOptions=} opt_options Options. + * @extends {ol.style.RegularShape} + * @api + */ +ol.style.Circle = function(opt_options) { + + var options = opt_options || {}; + + ol.style.RegularShape.call(this, { + points: Infinity, + fill: options.fill, + radius: options.radius, + snapToPixel: options.snapToPixel, + stroke: options.stroke, + atlasManager: options.atlasManager + }); + +}; +ol.inherits(ol.style.Circle, ol.style.RegularShape); + + +/** + * Clones the style. If an atlasmanager was provided to the original style it will be used in the cloned style, too. + * @return {ol.style.Circle} The cloned style. + * @api + */ +ol.style.Circle.prototype.clone = function() { + var style = new ol.style.Circle({ + fill: this.getFill() ? this.getFill().clone() : undefined, + stroke: this.getStroke() ? this.getStroke().clone() : undefined, + radius: this.getRadius(), + snapToPixel: this.getSnapToPixel(), + atlasManager: this.atlasManager_ + }); + style.setOpacity(this.getOpacity()); + style.setScale(this.getScale()); + return style; +}; + + +/** + * Set the circle radius. + * + * @param {number} radius Circle radius. + * @api + */ +ol.style.Circle.prototype.setRadius = function(radius) { + this.radius_ = radius; + this.render_(this.atlasManager_); +}; + +goog.provide('ol.style.Fill'); + +goog.require('ol'); +goog.require('ol.color'); + + +/** + * @classdesc + * Set fill style for vector features. + * + * @constructor + * @param {olx.style.FillOptions=} opt_options Options. + * @api + */ +ol.style.Fill = function(opt_options) { + + var options = opt_options || {}; + + /** + * @private + * @type {ol.Color|ol.ColorLike} + */ + this.color_ = options.color !== undefined ? options.color : null; + + /** + * @private + * @type {string|undefined} + */ + this.checksum_ = undefined; +}; + + +/** + * Clones the style. The color is not cloned if it is an {@link ol.ColorLike}. + * @return {ol.style.Fill} The cloned style. + * @api + */ +ol.style.Fill.prototype.clone = function() { + var color = this.getColor(); + return new ol.style.Fill({ + color: (color && color.slice) ? color.slice() : color || undefined + }); +}; + + +/** + * Get the fill color. + * @return {ol.Color|ol.ColorLike} Color. + * @api + */ +ol.style.Fill.prototype.getColor = function() { + return this.color_; +}; + + +/** + * Set the color. + * + * @param {ol.Color|ol.ColorLike} color Color. + * @api + */ +ol.style.Fill.prototype.setColor = function(color) { + this.color_ = color; + this.checksum_ = undefined; +}; + + +/** + * @return {string} The checksum. + */ +ol.style.Fill.prototype.getChecksum = function() { + if (this.checksum_ === undefined) { + if ( + this.color_ instanceof CanvasPattern || + this.color_ instanceof CanvasGradient + ) { + this.checksum_ = ol.getUid(this.color_).toString(); + } else { + this.checksum_ = 'f' + (this.color_ ? + ol.color.asString(this.color_) : '-'); + } + } + + return this.checksum_; +}; + +goog.provide('ol.style.Stroke'); + +goog.require('ol'); + + +/** + * @classdesc + * Set stroke style for vector features. + * Note that the defaults given are the Canvas defaults, which will be used if + * option is not defined. The `get` functions return whatever was entered in + * the options; they will not return the default. + * + * @constructor + * @param {olx.style.StrokeOptions=} opt_options Options. + * @api + */ +ol.style.Stroke = function(opt_options) { + + var options = opt_options || {}; + + /** + * @private + * @type {ol.Color|ol.ColorLike} + */ + this.color_ = options.color !== undefined ? options.color : null; + + /** + * @private + * @type {string|undefined} + */ + this.lineCap_ = options.lineCap; + + /** + * @private + * @type {Array.<number>} + */ + this.lineDash_ = options.lineDash !== undefined ? options.lineDash : null; + + /** + * @private + * @type {string|undefined} + */ + this.lineJoin_ = options.lineJoin; + + /** + * @private + * @type {number|undefined} + */ + this.miterLimit_ = options.miterLimit; + + /** + * @private + * @type {number|undefined} + */ + this.width_ = options.width; + + /** + * @private + * @type {string|undefined} + */ + this.checksum_ = undefined; +}; + + +/** + * Clones the style. + * @return {ol.style.Stroke} The cloned style. + * @api + */ +ol.style.Stroke.prototype.clone = function() { + var color = this.getColor(); + return new ol.style.Stroke({ + color: (color && color.slice) ? color.slice() : color || undefined, + lineCap: this.getLineCap(), + lineDash: this.getLineDash() ? this.getLineDash().slice() : undefined, + lineJoin: this.getLineJoin(), + miterLimit: this.getMiterLimit(), + width: this.getWidth() + }); +}; + + +/** + * Get the stroke color. + * @return {ol.Color|ol.ColorLike} Color. + * @api + */ +ol.style.Stroke.prototype.getColor = function() { + return this.color_; +}; + + +/** + * Get the line cap type for the stroke. + * @return {string|undefined} Line cap. + * @api + */ +ol.style.Stroke.prototype.getLineCap = function() { + return this.lineCap_; +}; + + +/** + * Get the line dash style for the stroke. + * @return {Array.<number>} Line dash. + * @api + */ +ol.style.Stroke.prototype.getLineDash = function() { + return this.lineDash_; +}; + + +/** + * Get the line join type for the stroke. + * @return {string|undefined} Line join. + * @api + */ +ol.style.Stroke.prototype.getLineJoin = function() { + return this.lineJoin_; +}; + + +/** + * Get the miter limit for the stroke. + * @return {number|undefined} Miter limit. + * @api + */ +ol.style.Stroke.prototype.getMiterLimit = function() { + return this.miterLimit_; +}; + + +/** + * Get the stroke width. + * @return {number|undefined} Width. + * @api + */ +ol.style.Stroke.prototype.getWidth = function() { + return this.width_; +}; + + +/** + * Set the color. + * + * @param {ol.Color|ol.ColorLike} color Color. + * @api + */ +ol.style.Stroke.prototype.setColor = function(color) { + this.color_ = color; + this.checksum_ = undefined; +}; + + +/** + * Set the line cap. + * + * @param {string|undefined} lineCap Line cap. + * @api + */ +ol.style.Stroke.prototype.setLineCap = function(lineCap) { + this.lineCap_ = lineCap; + this.checksum_ = undefined; +}; + + +/** + * Set the line dash. + * + * Please note that Internet Explorer 10 and lower [do not support][mdn] the + * `setLineDash` method on the `CanvasRenderingContext2D` and therefore this + * property will have no visual effect in these browsers. + * + * [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility + * + * @param {Array.<number>} lineDash Line dash. + * @api + */ +ol.style.Stroke.prototype.setLineDash = function(lineDash) { + this.lineDash_ = lineDash; + this.checksum_ = undefined; +}; + + +/** + * Set the line join. + * + * @param {string|undefined} lineJoin Line join. + * @api + */ +ol.style.Stroke.prototype.setLineJoin = function(lineJoin) { + this.lineJoin_ = lineJoin; + this.checksum_ = undefined; +}; + + +/** + * Set the miter limit. + * + * @param {number|undefined} miterLimit Miter limit. + * @api + */ +ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) { + this.miterLimit_ = miterLimit; + this.checksum_ = undefined; +}; + + +/** + * Set the width. + * + * @param {number|undefined} width Width. + * @api + */ +ol.style.Stroke.prototype.setWidth = function(width) { + this.width_ = width; + this.checksum_ = undefined; +}; + + +/** + * @return {string} The checksum. + */ +ol.style.Stroke.prototype.getChecksum = function() { + if (this.checksum_ === undefined) { + this.checksum_ = 's'; + if (this.color_) { + if (typeof this.color_ === 'string') { + this.checksum_ += this.color_; + } else { + this.checksum_ += ol.getUid(this.color_).toString(); + } + } else { + this.checksum_ += '-'; + } + this.checksum_ += ',' + + (this.lineCap_ !== undefined ? + this.lineCap_.toString() : '-') + ',' + + (this.lineDash_ ? + this.lineDash_.toString() : '-') + ',' + + (this.lineJoin_ !== undefined ? + this.lineJoin_ : '-') + ',' + + (this.miterLimit_ !== undefined ? + this.miterLimit_.toString() : '-') + ',' + + (this.width_ !== undefined ? + this.width_.toString() : '-'); + } + + return this.checksum_; +}; + +goog.provide('ol.style.Style'); + +goog.require('ol.asserts'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.style.Circle'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Stroke'); + + +/** + * @classdesc + * Container for vector feature rendering styles. Any changes made to the style + * or its children through `set*()` methods will not take effect until the + * feature or layer that uses the style is re-rendered. + * + * @constructor + * @struct + * @param {olx.style.StyleOptions=} opt_options Style options. + * @api + */ +ol.style.Style = function(opt_options) { + + var options = opt_options || {}; + + /** + * @private + * @type {string|ol.geom.Geometry|ol.StyleGeometryFunction} + */ + this.geometry_ = null; + + /** + * @private + * @type {!ol.StyleGeometryFunction} + */ + this.geometryFunction_ = ol.style.Style.defaultGeometryFunction; + + if (options.geometry !== undefined) { + this.setGeometry(options.geometry); + } + + /** + * @private + * @type {ol.style.Fill} + */ + this.fill_ = options.fill !== undefined ? options.fill : null; + + /** + * @private + * @type {ol.style.Image} + */ + this.image_ = options.image !== undefined ? options.image : null; + + /** + * @private + * @type {ol.style.Stroke} + */ + this.stroke_ = options.stroke !== undefined ? options.stroke : null; + + /** + * @private + * @type {ol.style.Text} + */ + this.text_ = options.text !== undefined ? options.text : null; + + /** + * @private + * @type {number|undefined} + */ + this.zIndex_ = options.zIndex; + +}; + + +/** + * Clones the style. + * @return {ol.style.Style} The cloned style. + * @api + */ +ol.style.Style.prototype.clone = function() { + var geometry = this.getGeometry(); + if (geometry && geometry.clone) { + geometry = geometry.clone(); + } + return new ol.style.Style({ + geometry: geometry, + fill: this.getFill() ? this.getFill().clone() : undefined, + image: this.getImage() ? this.getImage().clone() : undefined, + stroke: this.getStroke() ? this.getStroke().clone() : undefined, + text: this.getText() ? this.getText().clone() : undefined, + zIndex: this.getZIndex() + }); +}; + + +/** + * Get the geometry to be rendered. + * @return {string|ol.geom.Geometry|ol.StyleGeometryFunction} + * Feature property or geometry or function that returns the geometry that will + * be rendered with this style. + * @api + */ +ol.style.Style.prototype.getGeometry = function() { + return this.geometry_; +}; + + +/** + * Get the function used to generate a geometry for rendering. + * @return {!ol.StyleGeometryFunction} Function that is called with a feature + * and returns the geometry to render instead of the feature's geometry. + * @api + */ +ol.style.Style.prototype.getGeometryFunction = function() { + return this.geometryFunction_; +}; + + +/** + * Get the fill style. + * @return {ol.style.Fill} Fill style. + * @api + */ +ol.style.Style.prototype.getFill = function() { + return this.fill_; +}; + + +/** + * Set the fill style. + * @param {ol.style.Fill} fill Fill style. + * @api + */ +ol.style.Style.prototype.setFill = function(fill) { + this.fill_ = fill; +}; + + +/** + * Get the image style. + * @return {ol.style.Image} Image style. + * @api + */ +ol.style.Style.prototype.getImage = function() { + return this.image_; +}; + + +/** + * Set the image style. + * @param {ol.style.Image} image Image style. + * @api + */ +ol.style.Style.prototype.setImage = function(image) { + this.image_ = image; +}; + + +/** + * Get the stroke style. + * @return {ol.style.Stroke} Stroke style. + * @api + */ +ol.style.Style.prototype.getStroke = function() { + return this.stroke_; +}; + + +/** + * Set the stroke style. + * @param {ol.style.Stroke} stroke Stroke style. + * @api + */ +ol.style.Style.prototype.setStroke = function(stroke) { + this.stroke_ = stroke; +}; + + +/** + * Get the text style. + * @return {ol.style.Text} Text style. + * @api + */ +ol.style.Style.prototype.getText = function() { + return this.text_; +}; + + +/** + * Set the text style. + * @param {ol.style.Text} text Text style. + * @api + */ +ol.style.Style.prototype.setText = function(text) { + this.text_ = text; +}; + + +/** + * Get the z-index for the style. + * @return {number|undefined} ZIndex. + * @api + */ +ol.style.Style.prototype.getZIndex = function() { + return this.zIndex_; +}; + + +/** + * Set a geometry that is rendered instead of the feature's geometry. + * + * @param {string|ol.geom.Geometry|ol.StyleGeometryFunction} geometry + * Feature property or geometry or function returning a geometry to render + * for this style. + * @api + */ +ol.style.Style.prototype.setGeometry = function(geometry) { + if (typeof geometry === 'function') { + this.geometryFunction_ = geometry; + } else if (typeof geometry === 'string') { + this.geometryFunction_ = function(feature) { + return /** @type {ol.geom.Geometry} */ (feature.get(geometry)); + }; + } else if (!geometry) { + this.geometryFunction_ = ol.style.Style.defaultGeometryFunction; + } else if (geometry !== undefined) { + this.geometryFunction_ = function() { + return /** @type {ol.geom.Geometry} */ (geometry); + }; + } + this.geometry_ = geometry; +}; + + +/** + * Set the z-index. + * + * @param {number|undefined} zIndex ZIndex. + * @api + */ +ol.style.Style.prototype.setZIndex = function(zIndex) { + this.zIndex_ = zIndex; +}; + + +/** + * Convert the provided object into a style function. Functions passed through + * unchanged. Arrays of ol.style.Style or single style objects wrapped in a + * new style function. + * @param {ol.StyleFunction|Array.<ol.style.Style>|ol.style.Style} obj + * A style function, a single style, or an array of styles. + * @return {ol.StyleFunction} A style function. + */ +ol.style.Style.createFunction = function(obj) { + var styleFunction; + + if (typeof obj === 'function') { + styleFunction = obj; + } else { + /** + * @type {Array.<ol.style.Style>} + */ + var styles; + if (Array.isArray(obj)) { + styles = obj; + } else { + ol.asserts.assert(obj instanceof ol.style.Style, + 41); // Expected an `ol.style.Style` or an array of `ol.style.Style` + styles = [obj]; + } + styleFunction = function() { + return styles; + }; + } + return styleFunction; +}; + + +/** + * @type {Array.<ol.style.Style>} + * @private + */ +ol.style.Style.default_ = null; + + +/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {number} resolution Resolution. + * @return {Array.<ol.style.Style>} Style. + */ +ol.style.Style.defaultFunction = function(feature, resolution) { + // We don't use an immediately-invoked function + // and a closure so we don't get an error at script evaluation time in + // browsers that do not support Canvas. (ol.style.Circle does + // canvas.getContext('2d') at construction time, which will cause an.error + // in such browsers.) + if (!ol.style.Style.default_) { + var fill = new ol.style.Fill({ + color: 'rgba(255,255,255,0.4)' + }); + var stroke = new ol.style.Stroke({ + color: '#3399CC', + width: 1.25 + }); + ol.style.Style.default_ = [ + new ol.style.Style({ + image: new ol.style.Circle({ + fill: fill, + stroke: stroke, + radius: 5 + }), + fill: fill, + stroke: stroke + }) + ]; + } + return ol.style.Style.default_; +}; + + +/** + * Default styles for editing features. + * @return {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} Styles + */ +ol.style.Style.createDefaultEditing = function() { + /** @type {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} */ + var styles = {}; + var white = [255, 255, 255, 1]; + var blue = [0, 153, 255, 1]; + var width = 3; + styles[ol.geom.GeometryType.POLYGON] = [ + new ol.style.Style({ + fill: new ol.style.Fill({ + color: [255, 255, 255, 0.5] + }) + }) + ]; + styles[ol.geom.GeometryType.MULTI_POLYGON] = + styles[ol.geom.GeometryType.POLYGON]; + + styles[ol.geom.GeometryType.LINE_STRING] = [ + new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: white, + width: width + 2 + }) + }), + new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: blue, + width: width + }) + }) + ]; + styles[ol.geom.GeometryType.MULTI_LINE_STRING] = + styles[ol.geom.GeometryType.LINE_STRING]; + + styles[ol.geom.GeometryType.CIRCLE] = + styles[ol.geom.GeometryType.POLYGON].concat( + styles[ol.geom.GeometryType.LINE_STRING] + ); + + + styles[ol.geom.GeometryType.POINT] = [ + new ol.style.Style({ + image: new ol.style.Circle({ + radius: width * 2, + fill: new ol.style.Fill({ + color: blue + }), + stroke: new ol.style.Stroke({ + color: white, + width: width / 2 + }) + }), + zIndex: Infinity + }) + ]; + styles[ol.geom.GeometryType.MULTI_POINT] = + styles[ol.geom.GeometryType.POINT]; + + styles[ol.geom.GeometryType.GEOMETRY_COLLECTION] = + styles[ol.geom.GeometryType.POLYGON].concat( + styles[ol.geom.GeometryType.LINE_STRING], + styles[ol.geom.GeometryType.POINT] + ); + + return styles; +}; + + +/** + * Function that is called with a feature and returns its default geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature to get the geometry + * for. + * @return {ol.geom.Geometry|ol.render.Feature|undefined} Geometry to render. + */ +ol.style.Style.defaultGeometryFunction = function(feature) { + return feature.getGeometry(); +}; + +goog.provide('ol.layer.Vector'); + +goog.require('ol'); +goog.require('ol.layer.Layer'); +goog.require('ol.obj'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * Vector data that is rendered client-side. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Layer} + * @fires ol.render.Event + * @param {olx.layer.VectorOptions=} opt_options Options. + * @api stable + */ +ol.layer.Vector = function(opt_options) { + + var options = opt_options ? + opt_options : /** @type {olx.layer.VectorOptions} */ ({}); + + ol.DEBUG && console.assert( + options.renderOrder === undefined || !options.renderOrder || + typeof options.renderOrder === 'function', + 'renderOrder must be a comparator function'); + + var baseOptions = ol.obj.assign({}, options); + + delete baseOptions.style; + delete baseOptions.renderBuffer; + delete baseOptions.updateWhileAnimating; + delete baseOptions.updateWhileInteracting; + ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (baseOptions)); + + /** + * @type {number} + * @private + */ + this.renderBuffer_ = options.renderBuffer !== undefined ? + options.renderBuffer : 100; + + /** + * User provided style. + * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction} + * @private + */ + this.style_ = null; + + /** + * Style function for use within the library. + * @type {ol.StyleFunction|undefined} + * @private + */ + this.styleFunction_ = undefined; + + this.setStyle(options.style); + + /** + * @type {boolean} + * @private + */ + this.updateWhileAnimating_ = options.updateWhileAnimating !== undefined ? + options.updateWhileAnimating : false; + + /** + * @type {boolean} + * @private + */ + this.updateWhileInteracting_ = options.updateWhileInteracting !== undefined ? + options.updateWhileInteracting : false; + +}; +ol.inherits(ol.layer.Vector, ol.layer.Layer); + + +/** + * @return {number|undefined} Render buffer. + */ +ol.layer.Vector.prototype.getRenderBuffer = function() { + return this.renderBuffer_; +}; + + +/** + * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render + * order. + */ +ol.layer.Vector.prototype.getRenderOrder = function() { + return /** @type {function(ol.Feature, ol.Feature):number|null|undefined} */ ( + this.get(ol.layer.Vector.Property.RENDER_ORDER)); +}; + + +/** + * Return the associated {@link ol.source.Vector vectorsource} of the layer. + * @function + * @return {ol.source.Vector} Source. + * @api stable + */ +ol.layer.Vector.prototype.getSource; + + +/** + * Get the style for features. This returns whatever was passed to the `style` + * option at construction or to the `setStyle` method. + * @return {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction} + * Layer style. + * @api stable + */ +ol.layer.Vector.prototype.getStyle = function() { + return this.style_; +}; + + +/** + * Get the style function. + * @return {ol.StyleFunction|undefined} Layer style function. + * @api stable + */ +ol.layer.Vector.prototype.getStyleFunction = function() { + return this.styleFunction_; +}; + + +/** + * @return {boolean} Whether the rendered layer should be updated while + * animating. + */ +ol.layer.Vector.prototype.getUpdateWhileAnimating = function() { + return this.updateWhileAnimating_; +}; + + +/** + * @return {boolean} Whether the rendered layer should be updated while + * interacting. + */ +ol.layer.Vector.prototype.getUpdateWhileInteracting = function() { + return this.updateWhileInteracting_; +}; + + +/** + * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder + * Render order. + */ +ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) { + ol.DEBUG && console.assert( + renderOrder === undefined || !renderOrder || + typeof renderOrder === 'function', + 'renderOrder must be a comparator function'); + this.set(ol.layer.Vector.Property.RENDER_ORDER, renderOrder); +}; + + +/** + * Set the style for features. This can be a single style object, an array + * of styles, or a function that takes a feature and resolution and returns + * an array of styles. If it is `undefined` the default style is used. If + * it is `null` the layer has no style (a `null` style), so only features + * that have their own styles will be rendered in the layer. See + * {@link ol.style} for information on the default style. + * @param {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction|null|undefined} + * style Layer style. + * @api stable + */ +ol.layer.Vector.prototype.setStyle = function(style) { + this.style_ = style !== undefined ? style : ol.style.Style.defaultFunction; + this.styleFunction_ = style === null ? + undefined : ol.style.Style.createFunction(this.style_); + this.changed(); +}; + + +/** + * @enum {string} + */ +ol.layer.Vector.Property = { + RENDER_ORDER: 'renderOrder' +}; + +goog.provide('ol.layer.VectorTile'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); +goog.require('ol.obj'); + + +/** + * @classdesc + * Layer for vector tile data that is rendered client-side. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Vector} + * @param {olx.layer.VectorTileOptions=} opt_options Options. + * @api + */ +ol.layer.VectorTile = function(opt_options) { + var options = opt_options ? opt_options : {}; + + var baseOptions = ol.obj.assign({}, options); + + delete baseOptions.preload; + delete baseOptions.useInterimTilesOnError; + ol.layer.Vector.call(this, /** @type {olx.layer.VectorOptions} */ (baseOptions)); + + this.setPreload(options.preload ? options.preload : 0); + this.setUseInterimTilesOnError(options.useInterimTilesOnError ? + options.useInterimTilesOnError : true); + + ol.asserts.assert(options.renderMode == undefined || + options.renderMode == ol.layer.VectorTile.RenderType.IMAGE || + options.renderMode == ol.layer.VectorTile.RenderType.HYBRID || + options.renderMode == ol.layer.VectorTile.RenderType.VECTOR, + 28); // `renderMode` must be `'image'`, `'hybrid'` or `'vector'` + + /** + * @private + * @type {ol.layer.VectorTile.RenderType|string} + */ + this.renderMode_ = options.renderMode || ol.layer.VectorTile.RenderType.HYBRID; + +}; +ol.inherits(ol.layer.VectorTile, ol.layer.Vector); + + +/** + * Return the level as number to which we will preload tiles up to. + * @return {number} The level to preload tiles up to. + * @observable + * @api + */ +ol.layer.VectorTile.prototype.getPreload = function() { + return /** @type {number} */ (this.get(ol.layer.VectorTile.Property.PRELOAD)); +}; + + +/** + * @return {ol.layer.VectorTile.RenderType|string} The render mode. + */ +ol.layer.VectorTile.prototype.getRenderMode = function() { + return this.renderMode_; +}; + + +/** + * Whether we use interim tiles on error. + * @return {boolean} Use interim tiles on error. + * @observable + * @api + */ +ol.layer.VectorTile.prototype.getUseInterimTilesOnError = function() { + return /** @type {boolean} */ ( + this.get(ol.layer.VectorTile.Property.USE_INTERIM_TILES_ON_ERROR)); +}; + + +/** + * Set the level as number to which we will preload tiles up to. + * @param {number} preload The level to preload tiles up to. + * @observable + * @api + */ +ol.layer.VectorTile.prototype.setPreload = function(preload) { + this.set(ol.layer.Tile.Property.PRELOAD, preload); +}; + + +/** + * Set whether we use interim tiles on error. + * @param {boolean} useInterimTilesOnError Use interim tiles on error. + * @observable + * @api + */ +ol.layer.VectorTile.prototype.setUseInterimTilesOnError = function(useInterimTilesOnError) { + this.set( + ol.layer.Tile.Property.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError); +}; + + +/** + * @enum {string} + */ +ol.layer.VectorTile.Property = { + PRELOAD: 'preload', + USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError' +}; + + +/** + * @enum {string} + * Render mode for vector tiles: + * * `'image'`: Vector tiles are rendered as images. Great performance, but + * point symbols and texts are always rotated with the view and pixels are + * scaled during zoom animations. + * * `'hybrid'`: Polygon and line elements are rendered as images, so pixels + * are scaled during zoom animations. Point symbols and texts are accurately + * rendered as vectors and can stay upright on rotated views. + * * `'vector'`: Vector tiles are rendered as vectors. Most accurate rendering + * even during animations, but slower performance than the other options. + * @api + */ +ol.layer.VectorTile.RenderType = { + IMAGE: 'image', + HYBRID: 'hybrid', + VECTOR: 'vector' +}; + +goog.provide('ol.render.VectorContext'); + + +/** + * Context for drawing geometries. A vector context is available on render + * events and does not need to be constructed directly. + * @constructor + * @struct + * @api + */ +ol.render.VectorContext = function() { +}; + + +/** + * Render a geometry. + * + * @abstract + * @param {ol.geom.Geometry} geometry The geometry to render. + */ +ol.render.VectorContext.prototype.drawGeometry = function(geometry) {}; + + +/** + * Set the rendering style. + * + * @abstract + * @param {ol.style.Style} style The rendering style. + */ +ol.render.VectorContext.prototype.setStyle = function(style) {}; + + +/** + * @abstract + * @param {ol.geom.Circle} circleGeometry Circle geometry. + * @param {ol.Feature} feature Feature, + */ +ol.render.VectorContext.prototype.drawCircle = function(circleGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.Feature} feature Feature. + * @param {ol.style.Style} style Style. + */ +ol.render.VectorContext.prototype.drawFeature = function(feature, style) {}; + + +/** + * @abstract + * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry + * collection. + * @param {ol.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawGeometryCollection = function(geometryCollectionGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.geom.LineString|ol.render.Feature} lineStringGeometry Line + * string geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawLineString = function(lineStringGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.geom.MultiLineString|ol.render.Feature} multiLineStringGeometry + * MultiLineString geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.geom.MultiPoint|ol.render.Feature} multiPointGeometry MultiPoint + * geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawMultiPoint = function(multiPointGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.geom.Point|ol.render.Feature} pointGeometry Point geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawPoint = function(pointGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.geom.Polygon|ol.render.Feature} polygonGeometry Polygon + * geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawPolygon = function(polygonGeometry, feature) {}; + + +/** + * @abstract + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) {}; + + +/** + * @abstract + * @param {ol.style.Fill} fillStyle Fill style. + * @param {ol.style.Stroke} strokeStyle Stroke style. + */ +ol.render.VectorContext.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {}; + + +/** + * @abstract + * @param {ol.style.Image} imageStyle Image style. + */ +ol.render.VectorContext.prototype.setImageStyle = function(imageStyle) {}; + + +/** + * @abstract + * @param {ol.style.Text} textStyle Text style. + */ +ol.render.VectorContext.prototype.setTextStyle = function(textStyle) {}; + +// FIXME test, especially polygons with holes and multipolygons +// FIXME need to handle large thick features (where pixel size matters) +// FIXME add offset and end to ol.geom.flat.transform.transform2D? + +goog.provide('ol.render.canvas.Immediate'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.colorlike'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.has'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.canvas'); +goog.require('ol.transform'); + + +/** + * @classdesc + * A concrete subclass of {@link ol.render.VectorContext} that implements + * direct rendering of features and geometries to an HTML5 Canvas context. + * Instances of this class are created internally by the library and + * provided to application code as vectorContext member of the + * {@link ol.render.Event} object associated with postcompose, precompose and + * render events emitted by layers and maps. + * + * @constructor + * @extends {ol.render.VectorContext} + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Extent} extent Extent. + * @param {ol.Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @struct + */ +ol.render.canvas.Immediate = function(context, pixelRatio, extent, transform, viewRotation) { + ol.render.VectorContext.call(this); + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = context; + + /** + * @private + * @type {number} + */ + this.pixelRatio_ = pixelRatio; + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = extent; + + /** + * @private + * @type {ol.Transform} + */ + this.transform_ = transform; + + /** + * @private + * @type {number} + */ + this.viewRotation_ = viewRotation; + + /** + * @private + * @type {?ol.CanvasFillState} + */ + this.contextFillState_ = null; + + /** + * @private + * @type {?ol.CanvasStrokeState} + */ + this.contextStrokeState_ = null; + + /** + * @private + * @type {?ol.CanvasTextState} + */ + this.contextTextState_ = null; + + /** + * @private + * @type {?ol.CanvasFillState} + */ + this.fillState_ = null; + + /** + * @private + * @type {?ol.CanvasStrokeState} + */ + this.strokeState_ = null; + + /** + * @private + * @type {HTMLCanvasElement|HTMLVideoElement|Image} + */ + this.image_ = null; + + /** + * @private + * @type {number} + */ + this.imageAnchorX_ = 0; + + /** + * @private + * @type {number} + */ + this.imageAnchorY_ = 0; + + /** + * @private + * @type {number} + */ + this.imageHeight_ = 0; + + /** + * @private + * @type {number} + */ + this.imageOpacity_ = 0; + + /** + * @private + * @type {number} + */ + this.imageOriginX_ = 0; + + /** + * @private + * @type {number} + */ + this.imageOriginY_ = 0; + + /** + * @private + * @type {boolean} + */ + this.imageRotateWithView_ = false; + + /** + * @private + * @type {number} + */ + this.imageRotation_ = 0; + + /** + * @private + * @type {number} + */ + this.imageScale_ = 0; + + /** + * @private + * @type {boolean} + */ + this.imageSnapToPixel_ = false; + + /** + * @private + * @type {number} + */ + this.imageWidth_ = 0; + + /** + * @private + * @type {string} + */ + this.text_ = ''; + + /** + * @private + * @type {number} + */ + this.textOffsetX_ = 0; + + /** + * @private + * @type {number} + */ + this.textOffsetY_ = 0; + + /** + * @private + * @type {boolean} + */ + this.textRotateWithView_ = false; + + /** + * @private + * @type {number} + */ + this.textRotation_ = 0; + + /** + * @private + * @type {number} + */ + this.textScale_ = 0; + + /** + * @private + * @type {?ol.CanvasFillState} + */ + this.textFillState_ = null; + + /** + * @private + * @type {?ol.CanvasStrokeState} + */ + this.textStrokeState_ = null; + + /** + * @private + * @type {?ol.CanvasTextState} + */ + this.textState_ = null; + + /** + * @private + * @type {Array.<number>} + */ + this.pixelCoordinates_ = []; + + /** + * @private + * @type {ol.Transform} + */ + this.tmpLocalTransform_ = ol.transform.create(); + +}; +ol.inherits(ol.render.canvas.Immediate, ol.render.VectorContext); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @private + */ +ol.render.canvas.Immediate.prototype.drawImages_ = function(flatCoordinates, offset, end, stride) { + if (!this.image_) { + return; + } + ol.DEBUG && console.assert(offset === 0, 'offset should be 0'); + ol.DEBUG && console.assert(end == flatCoordinates.length, + 'end should be equal to the length of flatCoordinates'); + var pixelCoordinates = ol.geom.flat.transform.transform2D( + flatCoordinates, offset, end, 2, this.transform_, + this.pixelCoordinates_); + var context = this.context_; + var localTransform = this.tmpLocalTransform_; + var alpha = context.globalAlpha; + if (this.imageOpacity_ != 1) { + context.globalAlpha = alpha * this.imageOpacity_; + } + var rotation = this.imageRotation_; + if (this.imageRotateWithView_) { + rotation += this.viewRotation_; + } + var i, ii; + for (i = 0, ii = pixelCoordinates.length; i < ii; i += 2) { + var x = pixelCoordinates[i] - this.imageAnchorX_; + var y = pixelCoordinates[i + 1] - this.imageAnchorY_; + if (this.imageSnapToPixel_) { + x = Math.round(x); + y = Math.round(y); + } + if (rotation !== 0 || this.imageScale_ != 1) { + var centerX = x + this.imageAnchorX_; + var centerY = y + this.imageAnchorY_; + ol.transform.compose(localTransform, + centerX, centerY, + this.imageScale_, this.imageScale_, + rotation, + -centerX, -centerY); + context.setTransform.apply(context, localTransform); + } + context.drawImage(this.image_, this.imageOriginX_, this.imageOriginY_, + this.imageWidth_, this.imageHeight_, x, y, + this.imageWidth_, this.imageHeight_); + } + if (rotation !== 0 || this.imageScale_ != 1) { + context.setTransform(1, 0, 0, 1, 0, 0); + } + if (this.imageOpacity_ != 1) { + context.globalAlpha = alpha; + } +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @private + */ +ol.render.canvas.Immediate.prototype.drawText_ = function(flatCoordinates, offset, end, stride) { + if (!this.textState_ || this.text_ === '') { + return; + } + if (this.textFillState_) { + this.setContextFillState_(this.textFillState_); + } + if (this.textStrokeState_) { + this.setContextStrokeState_(this.textStrokeState_); + } + this.setContextTextState_(this.textState_); + ol.DEBUG && console.assert(offset === 0, 'offset should be 0'); + ol.DEBUG && console.assert(end == flatCoordinates.length, + 'end should be equal to the length of flatCoordinates'); + var pixelCoordinates = ol.geom.flat.transform.transform2D( + flatCoordinates, offset, end, stride, this.transform_, + this.pixelCoordinates_); + var context = this.context_; + var rotation = this.textRotation_; + if (this.textRotateWithView_) { + rotation += this.viewRotation_; + } + for (; offset < end; offset += stride) { + var x = pixelCoordinates[offset] + this.textOffsetX_; + var y = pixelCoordinates[offset + 1] + this.textOffsetY_; + if (rotation !== 0 || this.textScale_ != 1) { + var localTransform = ol.transform.compose(this.tmpLocalTransform_, + x, y, + this.textScale_, this.textScale_, + rotation, + -x, -y); + context.setTransform.apply(context, localTransform); + } + if (this.textStrokeState_) { + context.strokeText(this.text_, x, y); + } + if (this.textFillState_) { + context.fillText(this.text_, x, y); + } + } + if (rotation !== 0 || this.textScale_ != 1) { + context.setTransform(1, 0, 0, 1, 0, 0); + } +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {boolean} close Close. + * @private + * @return {number} end End. + */ +ol.render.canvas.Immediate.prototype.moveToLineTo_ = function(flatCoordinates, offset, end, stride, close) { + var context = this.context_; + var pixelCoordinates = ol.geom.flat.transform.transform2D( + flatCoordinates, offset, end, stride, this.transform_, + this.pixelCoordinates_); + context.moveTo(pixelCoordinates[0], pixelCoordinates[1]); + var length = pixelCoordinates.length; + if (close) { + length -= 2; + } + for (var i = 2; i < length; i += 2) { + context.lineTo(pixelCoordinates[i], pixelCoordinates[i + 1]); + } + if (close) { + context.closePath(); + } + return end; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @private + * @return {number} End. + */ +ol.render.canvas.Immediate.prototype.drawRings_ = function(flatCoordinates, offset, ends, stride) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + offset = this.moveToLineTo_( + flatCoordinates, offset, ends[i], stride, true); + } + return offset; +}; + + +/** + * Render a circle geometry into the canvas. Rendering is immediate and uses + * the current fill and stroke styles. + * + * @param {ol.geom.Circle} geometry Circle geometry. + * @api + */ +ol.render.canvas.Immediate.prototype.drawCircle = function(geometry) { + if (!ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + if (this.fillState_ || this.strokeState_) { + if (this.fillState_) { + this.setContextFillState_(this.fillState_); + } + if (this.strokeState_) { + this.setContextStrokeState_(this.strokeState_); + } + var pixelCoordinates = ol.geom.SimpleGeometry.transform2D( + geometry, this.transform_, this.pixelCoordinates_); + var dx = pixelCoordinates[2] - pixelCoordinates[0]; + var dy = pixelCoordinates[3] - pixelCoordinates[1]; + var radius = Math.sqrt(dx * dx + dy * dy); + var context = this.context_; + context.beginPath(); + context.arc( + pixelCoordinates[0], pixelCoordinates[1], radius, 0, 2 * Math.PI); + if (this.fillState_) { + context.fill(); + } + if (this.strokeState_) { + context.stroke(); + } + } + if (this.text_ !== '') { + this.drawText_(geometry.getCenter(), 0, 2, 2); + } +}; + + +/** + * Set the rendering style. Note that since this is an immediate rendering API, + * any `zIndex` on the provided style will be ignored. + * + * @param {ol.style.Style} style The rendering style. + * @api + */ +ol.render.canvas.Immediate.prototype.setStyle = function(style) { + this.setFillStrokeStyle(style.getFill(), style.getStroke()); + this.setImageStyle(style.getImage()); + this.setTextStyle(style.getText()); +}; + + +/** + * Render a geometry into the canvas. Call + * {@link ol.render.canvas.Immediate#setStyle} first to set the rendering style. + * + * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render. + * @api + */ +ol.render.canvas.Immediate.prototype.drawGeometry = function(geometry) { + var type = geometry.getType(); + switch (type) { + case ol.geom.GeometryType.POINT: + this.drawPoint(/** @type {ol.geom.Point} */ (geometry)); + break; + case ol.geom.GeometryType.LINE_STRING: + this.drawLineString(/** @type {ol.geom.LineString} */ (geometry)); + break; + case ol.geom.GeometryType.POLYGON: + this.drawPolygon(/** @type {ol.geom.Polygon} */ (geometry)); + break; + case ol.geom.GeometryType.MULTI_POINT: + this.drawMultiPoint(/** @type {ol.geom.MultiPoint} */ (geometry)); + break; + case ol.geom.GeometryType.MULTI_LINE_STRING: + this.drawMultiLineString(/** @type {ol.geom.MultiLineString} */ (geometry)); + break; + case ol.geom.GeometryType.MULTI_POLYGON: + this.drawMultiPolygon(/** @type {ol.geom.MultiPolygon} */ (geometry)); + break; + case ol.geom.GeometryType.GEOMETRY_COLLECTION: + this.drawGeometryCollection(/** @type {ol.geom.GeometryCollection} */ (geometry)); + break; + case ol.geom.GeometryType.CIRCLE: + this.drawCircle(/** @type {ol.geom.Circle} */ (geometry)); + break; + default: + ol.DEBUG && console.assert(false, 'Unsupported geometry type: ' + type); + } +}; + + +/** + * Render a feature into the canvas. Note that any `zIndex` on the provided + * style will be ignored - features are rendered immediately in the order that + * this method is called. If you need `zIndex` support, you should be using an + * {@link ol.layer.Vector} instead. + * + * @param {ol.Feature} feature Feature. + * @param {ol.style.Style} style Style. + * @api + */ +ol.render.canvas.Immediate.prototype.drawFeature = function(feature, style) { + var geometry = style.getGeometryFunction()(feature); + if (!geometry || + !ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + this.setStyle(style); + this.drawGeometry(geometry); +}; + + +/** + * Render a GeometryCollection to the canvas. Rendering is immediate and + * uses the current styles appropriate for each geometry in the collection. + * + * @param {ol.geom.GeometryCollection} geometry Geometry collection. + */ +ol.render.canvas.Immediate.prototype.drawGeometryCollection = function(geometry) { + var geometries = geometry.getGeometriesArray(); + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + this.drawGeometry(geometries[i]); + } +}; + + +/** + * Render a Point geometry into the canvas. Rendering is immediate and uses + * the current style. + * + * @param {ol.geom.Point|ol.render.Feature} geometry Point geometry. + */ +ol.render.canvas.Immediate.prototype.drawPoint = function(geometry) { + var flatCoordinates = geometry.getFlatCoordinates(); + var stride = geometry.getStride(); + if (this.image_) { + this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride); + } + if (this.text_ !== '') { + this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride); + } +}; + + +/** + * Render a MultiPoint geometry into the canvas. Rendering is immediate and + * uses the current style. + * + * @param {ol.geom.MultiPoint|ol.render.Feature} geometry MultiPoint geometry. + */ +ol.render.canvas.Immediate.prototype.drawMultiPoint = function(geometry) { + var flatCoordinates = geometry.getFlatCoordinates(); + var stride = geometry.getStride(); + if (this.image_) { + this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride); + } + if (this.text_ !== '') { + this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride); + } +}; + + +/** + * Render a LineString into the canvas. Rendering is immediate and uses + * the current style. + * + * @param {ol.geom.LineString|ol.render.Feature} geometry LineString geometry. + */ +ol.render.canvas.Immediate.prototype.drawLineString = function(geometry) { + if (!ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + if (this.strokeState_) { + this.setContextStrokeState_(this.strokeState_); + var context = this.context_; + var flatCoordinates = geometry.getFlatCoordinates(); + context.beginPath(); + this.moveToLineTo_(flatCoordinates, 0, flatCoordinates.length, + geometry.getStride(), false); + context.stroke(); + } + if (this.text_ !== '') { + var flatMidpoint = geometry.getFlatMidpoint(); + this.drawText_(flatMidpoint, 0, 2, 2); + } +}; + + +/** + * Render a MultiLineString geometry into the canvas. Rendering is immediate + * and uses the current style. + * + * @param {ol.geom.MultiLineString|ol.render.Feature} geometry MultiLineString + * geometry. + */ +ol.render.canvas.Immediate.prototype.drawMultiLineString = function(geometry) { + var geometryExtent = geometry.getExtent(); + if (!ol.extent.intersects(this.extent_, geometryExtent)) { + return; + } + if (this.strokeState_) { + this.setContextStrokeState_(this.strokeState_); + var context = this.context_; + var flatCoordinates = geometry.getFlatCoordinates(); + var offset = 0; + var ends = geometry.getEnds(); + var stride = geometry.getStride(); + context.beginPath(); + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + offset = this.moveToLineTo_( + flatCoordinates, offset, ends[i], stride, false); + } + context.stroke(); + } + if (this.text_ !== '') { + var flatMidpoints = geometry.getFlatMidpoints(); + this.drawText_(flatMidpoints, 0, flatMidpoints.length, 2); + } +}; + + +/** + * Render a Polygon geometry into the canvas. Rendering is immediate and uses + * the current style. + * + * @param {ol.geom.Polygon|ol.render.Feature} geometry Polygon geometry. + */ +ol.render.canvas.Immediate.prototype.drawPolygon = function(geometry) { + if (!ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + if (this.strokeState_ || this.fillState_) { + if (this.fillState_) { + this.setContextFillState_(this.fillState_); + } + if (this.strokeState_) { + this.setContextStrokeState_(this.strokeState_); + } + var context = this.context_; + context.beginPath(); + this.drawRings_(geometry.getOrientedFlatCoordinates(), + 0, geometry.getEnds(), geometry.getStride()); + if (this.fillState_) { + context.fill(); + } + if (this.strokeState_) { + context.stroke(); + } + } + if (this.text_ !== '') { + var flatInteriorPoint = geometry.getFlatInteriorPoint(); + this.drawText_(flatInteriorPoint, 0, 2, 2); + } +}; + + +/** + * Render MultiPolygon geometry into the canvas. Rendering is immediate and + * uses the current style. + * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry. + */ +ol.render.canvas.Immediate.prototype.drawMultiPolygon = function(geometry) { + if (!ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + if (this.strokeState_ || this.fillState_) { + if (this.fillState_) { + this.setContextFillState_(this.fillState_); + } + if (this.strokeState_) { + this.setContextStrokeState_(this.strokeState_); + } + var context = this.context_; + var flatCoordinates = geometry.getOrientedFlatCoordinates(); + var offset = 0; + var endss = geometry.getEndss(); + var stride = geometry.getStride(); + var i, ii; + context.beginPath(); + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + offset = this.drawRings_(flatCoordinates, offset, ends, stride); + } + if (this.fillState_) { + context.fill(); + } + if (this.strokeState_) { + context.stroke(); + } + } + if (this.text_ !== '') { + var flatInteriorPoints = geometry.getFlatInteriorPoints(); + this.drawText_(flatInteriorPoints, 0, flatInteriorPoints.length, 2); + } +}; + + +/** + * @param {ol.CanvasFillState} fillState Fill state. + * @private + */ +ol.render.canvas.Immediate.prototype.setContextFillState_ = function(fillState) { + var context = this.context_; + var contextFillState = this.contextFillState_; + if (!contextFillState) { + context.fillStyle = fillState.fillStyle; + this.contextFillState_ = { + fillStyle: fillState.fillStyle + }; + } else { + if (contextFillState.fillStyle != fillState.fillStyle) { + contextFillState.fillStyle = context.fillStyle = fillState.fillStyle; + } + } +}; + + +/** + * @param {ol.CanvasStrokeState} strokeState Stroke state. + * @private + */ +ol.render.canvas.Immediate.prototype.setContextStrokeState_ = function(strokeState) { + var context = this.context_; + var contextStrokeState = this.contextStrokeState_; + if (!contextStrokeState) { + context.lineCap = strokeState.lineCap; + if (ol.has.CANVAS_LINE_DASH) { + context.setLineDash(strokeState.lineDash); + } + context.lineJoin = strokeState.lineJoin; + context.lineWidth = strokeState.lineWidth; + context.miterLimit = strokeState.miterLimit; + context.strokeStyle = strokeState.strokeStyle; + this.contextStrokeState_ = { + lineCap: strokeState.lineCap, + lineDash: strokeState.lineDash, + lineJoin: strokeState.lineJoin, + lineWidth: strokeState.lineWidth, + miterLimit: strokeState.miterLimit, + strokeStyle: strokeState.strokeStyle + }; + } else { + if (contextStrokeState.lineCap != strokeState.lineCap) { + contextStrokeState.lineCap = context.lineCap = strokeState.lineCap; + } + if (ol.has.CANVAS_LINE_DASH) { + if (!ol.array.equals( + contextStrokeState.lineDash, strokeState.lineDash)) { + context.setLineDash(contextStrokeState.lineDash = strokeState.lineDash); + } + } + if (contextStrokeState.lineJoin != strokeState.lineJoin) { + contextStrokeState.lineJoin = context.lineJoin = strokeState.lineJoin; + } + if (contextStrokeState.lineWidth != strokeState.lineWidth) { + contextStrokeState.lineWidth = context.lineWidth = strokeState.lineWidth; + } + if (contextStrokeState.miterLimit != strokeState.miterLimit) { + contextStrokeState.miterLimit = context.miterLimit = + strokeState.miterLimit; + } + if (contextStrokeState.strokeStyle != strokeState.strokeStyle) { + contextStrokeState.strokeStyle = context.strokeStyle = + strokeState.strokeStyle; + } + } +}; + + +/** + * @param {ol.CanvasTextState} textState Text state. + * @private + */ +ol.render.canvas.Immediate.prototype.setContextTextState_ = function(textState) { + var context = this.context_; + var contextTextState = this.contextTextState_; + if (!contextTextState) { + context.font = textState.font; + context.textAlign = textState.textAlign; + context.textBaseline = textState.textBaseline; + this.contextTextState_ = { + font: textState.font, + textAlign: textState.textAlign, + textBaseline: textState.textBaseline + }; + } else { + if (contextTextState.font != textState.font) { + contextTextState.font = context.font = textState.font; + } + if (contextTextState.textAlign != textState.textAlign) { + contextTextState.textAlign = context.textAlign = textState.textAlign; + } + if (contextTextState.textBaseline != textState.textBaseline) { + contextTextState.textBaseline = context.textBaseline = + textState.textBaseline; + } + } +}; + + +/** + * Set the fill and stroke style for subsequent draw operations. To clear + * either fill or stroke styles, pass null for the appropriate parameter. + * + * @param {ol.style.Fill} fillStyle Fill style. + * @param {ol.style.Stroke} strokeStyle Stroke style. + */ +ol.render.canvas.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + if (!fillStyle) { + this.fillState_ = null; + } else { + var fillStyleColor = fillStyle.getColor(); + this.fillState_ = { + fillStyle: ol.colorlike.asColorLike(fillStyleColor ? + fillStyleColor : ol.render.canvas.defaultFillStyle) + }; + } + if (!strokeStyle) { + this.strokeState_ = null; + } else { + var strokeStyleColor = strokeStyle.getColor(); + var strokeStyleLineCap = strokeStyle.getLineCap(); + var strokeStyleLineDash = strokeStyle.getLineDash(); + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + var strokeStyleWidth = strokeStyle.getWidth(); + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + this.strokeState_ = { + lineCap: strokeStyleLineCap !== undefined ? + strokeStyleLineCap : ol.render.canvas.defaultLineCap, + lineDash: strokeStyleLineDash ? + strokeStyleLineDash : ol.render.canvas.defaultLineDash, + lineJoin: strokeStyleLineJoin !== undefined ? + strokeStyleLineJoin : ol.render.canvas.defaultLineJoin, + lineWidth: this.pixelRatio_ * (strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.canvas.defaultLineWidth), + miterLimit: strokeStyleMiterLimit !== undefined ? + strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit, + strokeStyle: ol.colorlike.asColorLike(strokeStyleColor ? + strokeStyleColor : ol.render.canvas.defaultStrokeStyle) + }; + } +}; + + +/** + * Set the image style for subsequent draw operations. Pass null to remove + * the image style. + * + * @param {ol.style.Image} imageStyle Image style. + */ +ol.render.canvas.Immediate.prototype.setImageStyle = function(imageStyle) { + if (!imageStyle) { + this.image_ = null; + } else { + var imageAnchor = imageStyle.getAnchor(); + // FIXME pixel ratio + var imageImage = imageStyle.getImage(1); + var imageOrigin = imageStyle.getOrigin(); + var imageSize = imageStyle.getSize(); + ol.DEBUG && console.assert(imageImage, 'imageImage must be truthy'); + this.imageAnchorX_ = imageAnchor[0]; + this.imageAnchorY_ = imageAnchor[1]; + this.imageHeight_ = imageSize[1]; + this.image_ = imageImage; + this.imageOpacity_ = imageStyle.getOpacity(); + this.imageOriginX_ = imageOrigin[0]; + this.imageOriginY_ = imageOrigin[1]; + this.imageRotateWithView_ = imageStyle.getRotateWithView(); + this.imageRotation_ = imageStyle.getRotation(); + this.imageScale_ = imageStyle.getScale(); + this.imageSnapToPixel_ = imageStyle.getSnapToPixel(); + this.imageWidth_ = imageSize[0]; + } +}; + + +/** + * Set the text style for subsequent draw operations. Pass null to + * remove the text style. + * + * @param {ol.style.Text} textStyle Text style. + */ +ol.render.canvas.Immediate.prototype.setTextStyle = function(textStyle) { + if (!textStyle) { + this.text_ = ''; + } else { + var textFillStyle = textStyle.getFill(); + if (!textFillStyle) { + this.textFillState_ = null; + } else { + var textFillStyleColor = textFillStyle.getColor(); + this.textFillState_ = { + fillStyle: ol.colorlike.asColorLike(textFillStyleColor ? + textFillStyleColor : ol.render.canvas.defaultFillStyle) + }; + } + var textStrokeStyle = textStyle.getStroke(); + if (!textStrokeStyle) { + this.textStrokeState_ = null; + } else { + var textStrokeStyleColor = textStrokeStyle.getColor(); + var textStrokeStyleLineCap = textStrokeStyle.getLineCap(); + var textStrokeStyleLineDash = textStrokeStyle.getLineDash(); + var textStrokeStyleLineJoin = textStrokeStyle.getLineJoin(); + var textStrokeStyleWidth = textStrokeStyle.getWidth(); + var textStrokeStyleMiterLimit = textStrokeStyle.getMiterLimit(); + this.textStrokeState_ = { + lineCap: textStrokeStyleLineCap !== undefined ? + textStrokeStyleLineCap : ol.render.canvas.defaultLineCap, + lineDash: textStrokeStyleLineDash ? + textStrokeStyleLineDash : ol.render.canvas.defaultLineDash, + lineJoin: textStrokeStyleLineJoin !== undefined ? + textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin, + lineWidth: textStrokeStyleWidth !== undefined ? + textStrokeStyleWidth : ol.render.canvas.defaultLineWidth, + miterLimit: textStrokeStyleMiterLimit !== undefined ? + textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit, + strokeStyle: ol.colorlike.asColorLike(textStrokeStyleColor ? + textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle) + }; + } + var textFont = textStyle.getFont(); + var textOffsetX = textStyle.getOffsetX(); + var textOffsetY = textStyle.getOffsetY(); + var textRotateWithView = textStyle.getRotateWithView(); + var textRotation = textStyle.getRotation(); + var textScale = textStyle.getScale(); + var textText = textStyle.getText(); + var textTextAlign = textStyle.getTextAlign(); + var textTextBaseline = textStyle.getTextBaseline(); + this.textState_ = { + font: textFont !== undefined ? + textFont : ol.render.canvas.defaultFont, + textAlign: textTextAlign !== undefined ? + textTextAlign : ol.render.canvas.defaultTextAlign, + textBaseline: textTextBaseline !== undefined ? + textTextBaseline : ol.render.canvas.defaultTextBaseline + }; + this.text_ = textText !== undefined ? textText : ''; + this.textOffsetX_ = + textOffsetX !== undefined ? (this.pixelRatio_ * textOffsetX) : 0; + this.textOffsetY_ = + textOffsetY !== undefined ? (this.pixelRatio_ * textOffsetY) : 0; + this.textRotateWithView_ = textRotateWithView !== undefined ? textRotateWithView : false; + this.textRotation_ = textRotation !== undefined ? textRotation : 0; + this.textScale_ = this.pixelRatio_ * (textScale !== undefined ? + textScale : 1); + } +}; + +goog.provide('ol.renderer.Layer'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.Observable'); +goog.require('ol.Tile'); +goog.require('ol.asserts'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.functions'); +goog.require('ol.source.State'); + + +/** + * @constructor + * @extends {ol.Observable} + * @param {ol.layer.Layer} layer Layer. + * @struct + */ +ol.renderer.Layer = function(layer) { + + ol.Observable.call(this); + + /** + * @private + * @type {ol.layer.Layer} + */ + this.layer_ = layer; + + +}; +ol.inherits(ol.renderer.Layer, ol.Observable); + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState Frame state. + * @param {number} hitTolerance Hit tolerance in pixels. + * @param {function(this: S, (ol.Feature|ol.render.Feature), ol.layer.Layer): T} + * callback Feature callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @return {T|undefined} Callback result. + * @template S,T + */ +ol.renderer.Layer.prototype.forEachFeatureAtCoordinate = ol.nullFunction; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState Frame state. + * @return {boolean} Is there a feature at the given coordinate? + */ +ol.renderer.Layer.prototype.hasFeatureAtCoordinate = ol.functions.FALSE; + + +/** + * Create a function that adds loaded tiles to the tile lookup. + * @param {ol.source.Tile} source Tile source. + * @param {ol.proj.Projection} projection Projection of the tiles. + * @param {Object.<number, Object.<string, ol.Tile>>} tiles Lookup of loaded + * tiles by zoom level. + * @return {function(number, ol.TileRange):boolean} A function that can be + * called with a zoom level and a tile range to add loaded tiles to the + * lookup. + * @protected + */ +ol.renderer.Layer.prototype.createLoadedTileFinder = function(source, projection, tiles) { + return ( + /** + * @param {number} zoom Zoom level. + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} The tile range is fully loaded. + */ + function(zoom, tileRange) { + function callback(tile) { + if (!tiles[zoom]) { + tiles[zoom] = {}; + } + tiles[zoom][tile.tileCoord.toString()] = tile; + } + return source.forEachLoadedTile(projection, zoom, tileRange, callback); + }); +}; + + +/** + * @return {ol.layer.Layer} Layer. + */ +ol.renderer.Layer.prototype.getLayer = function() { + return this.layer_; +}; + + +/** + * Handle changes in image state. + * @param {ol.events.Event} event Image change event. + * @private + */ +ol.renderer.Layer.prototype.handleImageChange_ = function(event) { + var image = /** @type {ol.Image} */ (event.target); + if (image.getState() === ol.Image.State.LOADED) { + this.renderIfReadyAndVisible(); + } +}; + + +/** + * Load the image if not already loaded, and register the image change + * listener if needed. + * @param {ol.ImageBase} image Image. + * @return {boolean} `true` if the image is already loaded, `false` + * otherwise. + * @protected + */ +ol.renderer.Layer.prototype.loadImage = function(image) { + var imageState = image.getState(); + if (imageState != ol.Image.State.LOADED && + imageState != ol.Image.State.ERROR) { + // the image is either "idle" or "loading", register the change + // listener (a noop if the listener was already registered) + ol.DEBUG && console.assert(imageState == ol.Image.State.IDLE || + imageState == ol.Image.State.LOADING, + 'imageState is "idle" or "loading"'); + ol.events.listen(image, ol.events.EventType.CHANGE, + this.handleImageChange_, this); + } + if (imageState == ol.Image.State.IDLE) { + image.load(); + imageState = image.getState(); + ol.DEBUG && console.assert(imageState == ol.Image.State.LOADING || + imageState == ol.Image.State.LOADED, + 'imageState is "loading" or "loaded"'); + } + return imageState == ol.Image.State.LOADED; +}; + + +/** + * @protected + */ +ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() { + var layer = this.getLayer(); + if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) { + this.changed(); + } +}; + + +/** + * @param {olx.FrameState} frameState Frame state. + * @param {ol.source.Tile} tileSource Tile source. + * @protected + */ +ol.renderer.Layer.prototype.scheduleExpireCache = function(frameState, tileSource) { + if (tileSource.canExpireCache()) { + /** + * @param {ol.source.Tile} tileSource Tile source. + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + */ + var postRenderFunction = function(tileSource, map, frameState) { + var tileSourceKey = ol.getUid(tileSource).toString(); + tileSource.expireCache(frameState.viewState.projection, + frameState.usedTiles[tileSourceKey]); + }.bind(null, tileSource); + + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (postRenderFunction) + ); + } +}; + + +/** + * @param {Object.<string, ol.Attribution>} attributionsSet Attributions + * set (target). + * @param {Array.<ol.Attribution>} attributions Attributions (source). + * @protected + */ +ol.renderer.Layer.prototype.updateAttributions = function(attributionsSet, attributions) { + if (attributions) { + var attribution, i, ii; + for (i = 0, ii = attributions.length; i < ii; ++i) { + attribution = attributions[i]; + attributionsSet[ol.getUid(attribution).toString()] = attribution; + } + } +}; + + +/** + * @param {olx.FrameState} frameState Frame state. + * @param {ol.source.Source} source Source. + * @protected + */ +ol.renderer.Layer.prototype.updateLogos = function(frameState, source) { + var logo = source.getLogo(); + if (logo !== undefined) { + if (typeof logo === 'string') { + frameState.logos[logo] = ''; + } else if (logo) { + ol.asserts.assert(typeof logo.href == 'string', 44); // `logo.href` should be a string. + ol.asserts.assert(typeof logo.src == 'string', 45); // `logo.src` should be a string. + frameState.logos[logo.src] = logo.href; + } + } +}; + + +/** + * @param {Object.<string, Object.<string, ol.TileRange>>} usedTiles Used tiles. + * @param {ol.source.Tile} tileSource Tile source. + * @param {number} z Z. + * @param {ol.TileRange} tileRange Tile range. + * @protected + */ +ol.renderer.Layer.prototype.updateUsedTiles = function(usedTiles, tileSource, z, tileRange) { + // FIXME should we use tilesToDrawByZ instead? + var tileSourceKey = ol.getUid(tileSource).toString(); + var zKey = z.toString(); + if (tileSourceKey in usedTiles) { + if (zKey in usedTiles[tileSourceKey]) { + usedTiles[tileSourceKey][zKey].extend(tileRange); + } else { + usedTiles[tileSourceKey][zKey] = tileRange; + } + } else { + usedTiles[tileSourceKey] = {}; + usedTiles[tileSourceKey][zKey] = tileRange; + } +}; + + +/** + * Manage tile pyramid. + * This function performs a number of functions related to the tiles at the + * current zoom and lower zoom levels: + * - registers idle tiles in frameState.wantedTiles so that they are not + * discarded by the tile queue + * - enqueues missing tiles + * @param {olx.FrameState} frameState Frame state. + * @param {ol.source.Tile} tileSource Tile source. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {ol.Extent} extent Extent. + * @param {number} currentZ Current Z. + * @param {number} preload Load low resolution tiles up to 'preload' levels. + * @param {function(this: T, ol.Tile)=} opt_tileCallback Tile callback. + * @param {T=} opt_this Object to use as `this` in `opt_tileCallback`. + * @protected + * @template T + */ +ol.renderer.Layer.prototype.manageTilePyramid = function( + frameState, tileSource, tileGrid, pixelRatio, projection, extent, + currentZ, preload, opt_tileCallback, opt_this) { + var tileSourceKey = ol.getUid(tileSource).toString(); + if (!(tileSourceKey in frameState.wantedTiles)) { + frameState.wantedTiles[tileSourceKey] = {}; + } + var wantedTiles = frameState.wantedTiles[tileSourceKey]; + var tileQueue = frameState.tileQueue; + var minZoom = tileGrid.getMinZoom(); + var tile, tileRange, tileResolution, x, y, z; + for (z = currentZ; z >= minZoom; --z) { + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange); + tileResolution = tileGrid.getResolution(z); + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + if (currentZ - z <= preload) { + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + if (tile.getState() == ol.Tile.State.IDLE) { + wantedTiles[tile.getKey()] = true; + if (!tileQueue.isKeyQueued(tile.getKey())) { + tileQueue.enqueue([tile, tileSourceKey, + tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]); + } + } + if (opt_tileCallback !== undefined) { + opt_tileCallback.call(opt_this, tile); + } + } else { + tileSource.useTile(z, x, y, projection); + } + } + } + } +}; + +goog.provide('ol.renderer.canvas.Layer'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.functions'); +goog.require('ol.render.Event'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.Immediate'); +goog.require('ol.renderer.Layer'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.renderer.Layer} + * @param {ol.layer.Layer} layer Layer. + */ +ol.renderer.canvas.Layer = function(layer) { + + ol.renderer.Layer.call(this, layer); + + /** + * @protected + * @type {number} + */ + this.renderedResolution; + + /** + * @private + * @type {ol.Transform} + */ + this.transform_ = ol.transform.create(); + +}; +ol.inherits(ol.renderer.canvas.Layer, ol.renderer.Layer); + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.Extent} extent Clip extent. + * @protected + */ +ol.renderer.canvas.Layer.prototype.clip = function(context, frameState, extent) { + var pixelRatio = frameState.pixelRatio; + var width = frameState.size[0] * pixelRatio; + var height = frameState.size[1] * pixelRatio; + var rotation = frameState.viewState.rotation; + var topLeft = ol.extent.getTopLeft(/** @type {ol.Extent} */ (extent)); + var topRight = ol.extent.getTopRight(/** @type {ol.Extent} */ (extent)); + var bottomRight = ol.extent.getBottomRight(/** @type {ol.Extent} */ (extent)); + var bottomLeft = ol.extent.getBottomLeft(/** @type {ol.Extent} */ (extent)); + + ol.transform.apply(frameState.coordinateToPixelTransform, topLeft); + ol.transform.apply(frameState.coordinateToPixelTransform, topRight); + ol.transform.apply(frameState.coordinateToPixelTransform, bottomRight); + ol.transform.apply(frameState.coordinateToPixelTransform, bottomLeft); + + context.save(); + ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2); + context.beginPath(); + context.moveTo(topLeft[0] * pixelRatio, topLeft[1] * pixelRatio); + context.lineTo(topRight[0] * pixelRatio, topRight[1] * pixelRatio); + context.lineTo(bottomRight[0] * pixelRatio, bottomRight[1] * pixelRatio); + context.lineTo(bottomLeft[0] * pixelRatio, bottomLeft[1] * pixelRatio); + context.clip(); + ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2); +}; + + +/** + * @param {ol.render.Event.Type} type Event type. + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.Transform=} opt_transform Transform. + * @private + */ +ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState, opt_transform) { + var layer = this.getLayer(); + if (layer.hasListener(type)) { + var width = frameState.size[0] * frameState.pixelRatio; + var height = frameState.size[1] * frameState.pixelRatio; + var rotation = frameState.viewState.rotation; + ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2); + var transform = opt_transform !== undefined ? + opt_transform : this.getTransform(frameState, 0); + var render = new ol.render.canvas.Immediate( + context, frameState.pixelRatio, frameState.extent, transform, + frameState.viewState.rotation); + var composeEvent = new ol.render.Event(type, render, frameState, + context, null); + layer.dispatchEvent(composeEvent); + ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2); + } +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer + * callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @return {T|undefined} Callback result. + * @template S,T,U + */ +ol.renderer.canvas.Layer.prototype.forEachLayerAtCoordinate = function(coordinate, frameState, callback, thisArg) { + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, 0, ol.functions.TRUE, this); + + if (hasFeature) { + return callback.call(thisArg, this.getLayer(), null); + } else { + return undefined; + } +}; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @param {ol.Transform=} opt_transform Transform. + * @protected + */ +ol.renderer.canvas.Layer.prototype.postCompose = function(context, frameState, layerState, opt_transform) { + this.dispatchComposeEvent_(ol.render.Event.Type.POSTCOMPOSE, context, + frameState, opt_transform); +}; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.Transform=} opt_transform Transform. + * @protected + */ +ol.renderer.canvas.Layer.prototype.preCompose = function(context, frameState, opt_transform) { + this.dispatchComposeEvent_(ol.render.Event.Type.PRECOMPOSE, context, + frameState, opt_transform); +}; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.Transform=} opt_transform Transform. + * @protected + */ +ol.renderer.canvas.Layer.prototype.dispatchRenderEvent = function(context, frameState, opt_transform) { + this.dispatchComposeEvent_(ol.render.Event.Type.RENDER, context, + frameState, opt_transform); +}; + + +/** + * @param {olx.FrameState} frameState Frame state. + * @param {number} offsetX Offset on the x-axis in view coordinates. + * @protected + * @return {!ol.Transform} Transform. + */ +ol.renderer.canvas.Layer.prototype.getTransform = function(frameState, offsetX) { + var viewState = frameState.viewState; + var pixelRatio = frameState.pixelRatio; + var dx1 = pixelRatio * frameState.size[0] / 2; + var dy1 = pixelRatio * frameState.size[1] / 2; + var sx = pixelRatio / viewState.resolution; + var sy = -sx; + var angle = -viewState.rotation; + var dx2 = -viewState.center[0] + offsetX; + var dy2 = -viewState.center[1]; + return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2); +}; + + +/** + * @abstract + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @param {CanvasRenderingContext2D} context Context. + */ +ol.renderer.canvas.Layer.prototype.composeFrame = function(frameState, layerState, context) {}; + +/** + * @abstract + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @return {boolean} whether composeFrame should be called. + */ +ol.renderer.canvas.Layer.prototype.prepareFrame = function(frameState, layerState) {}; + +goog.provide('ol.renderer.canvas.IntermediateCanvas'); + +goog.require('ol'); +goog.require('ol.coordinate'); +goog.require('ol.dom'); +goog.require('ol.renderer.canvas.Layer'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.renderer.canvas.Layer} + * @param {ol.layer.Layer} layer Layer. + */ +ol.renderer.canvas.IntermediateCanvas = function(layer) { + + ol.renderer.canvas.Layer.call(this, layer); + + /** + * @protected + * @type {ol.Transform} + */ + this.coordinateToCanvasPixelTransform = ol.transform.create(); + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitCanvasContext_ = null; + + /** + * @protected + * @type {number} + */ + this.renderedResolution; + +}; +ol.inherits(ol.renderer.canvas.IntermediateCanvas, ol.renderer.canvas.Layer); + + +/** + * @inheritDoc + */ +ol.renderer.canvas.IntermediateCanvas.prototype.composeFrame = function(frameState, layerState, context) { + + this.preCompose(context, frameState); + + var image = this.getImage(); + if (image) { + + // clipped rendering if layer extent is set + var extent = layerState.extent; + var clipped = extent !== undefined; + if (clipped) { + this.clip(context, frameState, /** @type {ol.Extent} */ (extent)); + } + + var imageTransform = this.getImageTransform(); + // for performance reasons, context.save / context.restore is not used + // to save and restore the transformation matrix and the opacity. + // see http://jsperf.com/context-save-restore-versus-variable + var alpha = context.globalAlpha; + context.globalAlpha = layerState.opacity; + + // for performance reasons, context.setTransform is only used + // when the view is rotated. see http://jsperf.com/canvas-transform + var dx = imageTransform[4]; + var dy = imageTransform[5]; + var dw = image.width * imageTransform[0]; + var dh = image.height * imageTransform[3]; + context.drawImage(image, 0, 0, +image.width, +image.height, + Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh)); + context.globalAlpha = alpha; + + if (clipped) { + context.restore(); + } + } + + this.postCompose(context, frameState, layerState); +}; + + +/** + * @abstract + * @return {HTMLCanvasElement|HTMLVideoElement|Image} Canvas. + */ +ol.renderer.canvas.IntermediateCanvas.prototype.getImage = function() {}; + + +/** + * @abstract + * @return {!ol.Transform} Image transform. + */ +ol.renderer.canvas.IntermediateCanvas.prototype.getImageTransform = function() {}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.IntermediateCanvas.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { + var layer = this.getLayer(); + var source = layer.getSource(); + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var skippedFeatureUids = frameState.skippedFeatureUids; + return source.forEachFeatureAtCoordinate( + coordinate, resolution, rotation, hitTolerance, skippedFeatureUids, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + return callback.call(thisArg, feature, layer); + }); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.IntermediateCanvas.prototype.forEachLayerAtCoordinate = function(coordinate, frameState, callback, thisArg) { + if (!this.getImage()) { + return undefined; + } + + if (this.getLayer().getSource().forEachFeatureAtCoordinate !== ol.nullFunction) { + // for ImageVector sources use the original hit-detection logic, + // so that for example also transparent polygons are detected + return ol.renderer.canvas.Layer.prototype.forEachLayerAtCoordinate.apply(this, arguments); + } else { + var pixel = ol.transform.apply(this.coordinateToCanvasPixelTransform, coordinate.slice()); + ol.coordinate.scale(pixel, frameState.viewState.resolution / this.renderedResolution); + + if (!this.hitCanvasContext_) { + this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1); + } + + this.hitCanvasContext_.clearRect(0, 0, 1, 1); + this.hitCanvasContext_.drawImage(this.getImage(), pixel[0], pixel[1], 1, 1, 0, 0, 1, 1); + + var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data; + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer(), imageData); + } else { + return undefined; + } + } +}; + +goog.provide('ol.renderer.canvas.ImageLayer'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.extent'); +goog.require('ol.proj'); +goog.require('ol.renderer.canvas.IntermediateCanvas'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.renderer.canvas.IntermediateCanvas} + * @param {ol.layer.Image} imageLayer Single image layer. + */ +ol.renderer.canvas.ImageLayer = function(imageLayer) { + + ol.renderer.canvas.IntermediateCanvas.call(this, imageLayer); + + /** + * @private + * @type {?ol.ImageBase} + */ + this.image_ = null; + + /** + * @private + * @type {ol.Transform} + */ + this.imageTransform_ = ol.transform.create(); + +}; +ol.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.IntermediateCanvas); + + +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.getImage = function() { + return !this.image_ ? null : this.image_.getImage(); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.getImageTransform = function() { + return this.imageTransform_; +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.prepareFrame = function(frameState, layerState) { + + var pixelRatio = frameState.pixelRatio; + var size = frameState.size; + var viewState = frameState.viewState; + var viewCenter = viewState.center; + var viewResolution = viewState.resolution; + + var image; + var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer()); + var imageSource = imageLayer.getSource(); + + var hints = frameState.viewHints; + + var renderedExtent = frameState.extent; + if (layerState.extent !== undefined) { + renderedExtent = ol.extent.getIntersection( + renderedExtent, layerState.extent); + } + + if (!hints[ol.View.Hint.ANIMATING] && !hints[ol.View.Hint.INTERACTING] && + !ol.extent.isEmpty(renderedExtent)) { + var projection = viewState.projection; + if (!ol.ENABLE_RASTER_REPROJECTION) { + var sourceProjection = imageSource.getProjection(); + if (sourceProjection) { + ol.DEBUG && console.assert(ol.proj.equivalent(projection, sourceProjection), + 'projection and sourceProjection are equivalent'); + projection = sourceProjection; + } + } + image = imageSource.getImage( + renderedExtent, viewResolution, pixelRatio, projection); + if (image) { + var loaded = this.loadImage(image); + if (loaded) { + this.image_ = image; + this.renderedResolution = viewResolution; + } + } + } + + if (this.image_) { + image = this.image_; + var imageExtent = image.getExtent(); + var imageResolution = image.getResolution(); + var imagePixelRatio = image.getPixelRatio(); + var scale = pixelRatio * imageResolution / + (viewResolution * imagePixelRatio); + var transform = ol.transform.compose(this.imageTransform_, + pixelRatio * size[0] / 2, pixelRatio * size[1] / 2, + scale, scale, + 0, + imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution, + imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution); + ol.transform.compose(this.coordinateToCanvasPixelTransform, + pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], + pixelRatio / viewResolution, -pixelRatio / viewResolution, + 0, + -viewCenter[0], -viewCenter[1]); + + this.updateAttributions(frameState.attributions, image.getAttributions()); + this.updateLogos(frameState, imageSource); + } + + return !!this.image_; +}; + +// FIXME find correct globalCompositeOperation + +goog.provide('ol.renderer.canvas.TileLayer'); + +goog.require('ol'); +goog.require('ol.transform'); +goog.require('ol.TileRange'); +goog.require('ol.Tile'); +goog.require('ol.array'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.renderer.canvas.IntermediateCanvas'); + + +/** + * @constructor + * @extends {ol.renderer.canvas.IntermediateCanvas} + * @param {ol.layer.Tile|ol.layer.VectorTile} tileLayer Tile layer. + */ +ol.renderer.canvas.TileLayer = function(tileLayer) { + + ol.renderer.canvas.IntermediateCanvas.call(this, tileLayer); + + /** + * @protected + * @type {CanvasRenderingContext2D} + */ + this.context = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {ol.Extent} + */ + this.renderedExtent_ = null; + + /** + * @private + * @type {number} + */ + this.renderedRevision_; + + /** + * @protected + * @type {!Array.<ol.Tile>} + */ + this.renderedTiles = []; + + /** + * @protected + * @type {ol.Extent} + */ + this.tmpExtent = ol.extent.createEmpty(); + + /** + * @private + * @type {ol.TileCoord} + */ + this.tmpTileCoord_ = [0, 0, 0]; + + /** + * @private + * @type {ol.TileRange} + */ + this.tmpTileRange_ = new ol.TileRange(0, 0, 0, 0); + + /** + * @private + * @type {ol.Transform} + */ + this.imageTransform_ = ol.transform.create(); + + /** + * @protected + * @type {number} + */ + this.zDirection = 0; + +}; +ol.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.IntermediateCanvas); + + +/** + * @inheritDoc + */ +ol.renderer.canvas.TileLayer.prototype.prepareFrame = function(frameState, layerState) { + + var pixelRatio = frameState.pixelRatio; + var size = frameState.size; + var viewState = frameState.viewState; + var projection = viewState.projection; + var viewResolution = viewState.resolution; + var viewCenter = viewState.center; + + var tileLayer = this.getLayer(); + var tileSource = /** @type {ol.source.Tile} */ (tileLayer.getSource()); + var sourceRevision = tileSource.getRevision(); + var tileGrid = tileSource.getTileGridForProjection(projection); + var z = tileGrid.getZForResolution(viewResolution, this.zDirection); + var tileResolution = tileGrid.getResolution(z); + var extent = frameState.extent; + + if (layerState.extent !== undefined) { + extent = ol.extent.getIntersection(extent, layerState.extent); + } + if (ol.extent.isEmpty(extent)) { + // Return false to prevent the rendering of the layer. + return false; + } + + var tileRange = tileGrid.getTileRangeForExtentAndResolution( + extent, tileResolution); + var imageExtent = tileGrid.getTileRangeExtent(z, tileRange); + + var tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio); + + /** + * @type {Object.<number, Object.<string, ol.Tile>>} + */ + var tilesToDrawByZ = {}; + tilesToDrawByZ[z] = {}; + + var findLoadedTiles = this.createLoadedTileFinder( + tileSource, projection, tilesToDrawByZ); + + var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError(); + var tmpExtent = this.tmpExtent; + var tmpTileRange = this.tmpTileRange_; + var newTiles = false; + var tile, x, y; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + var tileState = tile.getState(); + var drawable = tileState == ol.Tile.State.LOADED || + tileState == ol.Tile.State.EMPTY || + tileState == ol.Tile.State.ERROR && !useInterimTilesOnError; + if (!drawable) { + tile = tile.getInterimTile(); + } else { + if (tileState == ol.Tile.State.LOADED) { + tilesToDrawByZ[z][tile.tileCoord.toString()] = tile; + if (!newTiles && this.renderedTiles.indexOf(tile) == -1) { + newTiles = true; + } + } + continue; + } + + var fullyLoaded = tileGrid.forEachTileCoordParentTileRange( + tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); + if (!fullyLoaded) { + var childTileRange = tileGrid.getTileCoordChildTileRange( + tile.tileCoord, tmpTileRange, tmpExtent); + if (childTileRange) { + findLoadedTiles(z + 1, childTileRange); + } + } + + } + } + + var hints = frameState.viewHints; + if (!(this.renderedResolution && Date.now() - frameState.time > 16 && + (hints[ol.View.Hint.ANIMATING] || hints[ol.View.Hint.INTERACTING])) && + (newTiles || !(this.renderedExtent_ && + ol.extent.equals(this.renderedExtent_, imageExtent)) || + this.renderedRevision_ != sourceRevision)) { + + var tilePixelSize = tileSource.getTilePixelSize(z, pixelRatio, projection); + var width = tileRange.getWidth() * tilePixelSize[0]; + var height = tileRange.getHeight() * tilePixelSize[0]; + var context = this.context; + var canvas = context.canvas; + var opaque = tileSource.getOpaque(projection); + if (canvas.width != width || canvas.height != height) { + canvas.width = width; + canvas.height = height; + } else { + context.clearRect(0, 0, width, height); + } + + this.renderedTiles.length = 0; + /** @type {Array.<number>} */ + var zs = Object.keys(tilesToDrawByZ).map(Number); + zs.sort(ol.array.numberSafeCompareFunction); + var currentResolution, currentScale, currentTilePixelSize, currentZ, i, ii; + var tileExtent, tileGutter, tilesToDraw, w, h; + for (i = 0, ii = zs.length; i < ii; ++i) { + currentZ = zs[i]; + currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection); + currentResolution = tileGrid.getResolution(currentZ); + currentScale = currentResolution / tileResolution; + tileGutter = tilePixelRatio * tileSource.getGutter(projection); + tilesToDraw = tilesToDrawByZ[currentZ]; + for (var tileCoordKey in tilesToDraw) { + tile = tilesToDraw[tileCoordKey]; + tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent); + x = (tileExtent[0] - imageExtent[0]) / tileResolution * tilePixelRatio; + y = (imageExtent[3] - tileExtent[3]) / tileResolution * tilePixelRatio; + w = currentTilePixelSize[0] * currentScale; + h = currentTilePixelSize[1] * currentScale; + if (!opaque) { + context.clearRect(x, y, w, h); + } + this.drawTileImage(tile, frameState, layerState, x, y, w, h, tileGutter); + this.renderedTiles.push(tile); + } + } + + this.renderedRevision_ = sourceRevision; + this.renderedResolution = tileResolution; + this.renderedExtent_ = imageExtent; + } + + var scale = pixelRatio / tilePixelRatio * this.renderedResolution / viewResolution; + var transform = ol.transform.compose(this.imageTransform_, + pixelRatio * size[0] / 2, pixelRatio * size[1] / 2, + scale, scale, + 0, + tilePixelRatio * (this.renderedExtent_[0] - viewCenter[0]) / this.renderedResolution, + tilePixelRatio * (viewCenter[1] - this.renderedExtent_[3]) / this.renderedResolution); + ol.transform.compose(this.coordinateToCanvasPixelTransform, + pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], + pixelRatio / viewResolution, -pixelRatio / viewResolution, + 0, + -viewCenter[0], -viewCenter[1]); + + + this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); + this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio, + projection, extent, z, tileLayer.getPreload()); + this.scheduleExpireCache(frameState, tileSource); + this.updateLogos(frameState, tileSource); + + return this.renderedTiles.length > 0; +}; + + +/** + * @param {ol.Tile} tile Tile. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @param {number} x Left of the tile. + * @param {number} y Top of the tile. + * @param {number} w Width of the tile. + * @param {number} h Height of the tile. + * @param {number} gutter Tile gutter. + */ +ol.renderer.canvas.TileLayer.prototype.drawTileImage = function(tile, frameState, layerState, x, y, w, h, gutter) { + var image = tile.getImage(); + if (image) { + this.context.drawImage(image, gutter, gutter, + image.width - 2 * gutter, image.height - 2 * gutter, x, y, w, h); + } +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.TileLayer.prototype.getImage = function() { + return this.context.canvas; +}; + + +/** + * @function + * @return {ol.layer.Tile|ol.layer.VectorTile} + */ +ol.renderer.canvas.TileLayer.prototype.getLayer; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.TileLayer.prototype.getImageTransform = function() { + return this.imageTransform_; +}; + +goog.provide('ol.render.ReplayGroup'); + + +/** + * Base class for replay groups. + * @constructor + */ +ol.render.ReplayGroup = function() {}; + + +/** + * @abstract + * @param {number|undefined} zIndex Z index. + * @param {ol.render.ReplayType} replayType Replay type. + * @return {ol.render.VectorContext} Replay. + */ +ol.render.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {}; + + +/** + * @abstract + * @return {boolean} Is empty. + */ +ol.render.ReplayGroup.prototype.isEmpty = function() {}; + +goog.provide('ol.render.canvas.Instruction'); + +/** + * @enum {number} + */ +ol.render.canvas.Instruction = { + BEGIN_GEOMETRY: 0, + BEGIN_PATH: 1, + CIRCLE: 2, + CLOSE_PATH: 3, + DRAW_IMAGE: 4, + DRAW_TEXT: 5, + END_GEOMETRY: 6, + FILL: 7, + MOVE_TO_LINE_TO: 8, + SET_FILL_STYLE: 9, + SET_STROKE_STYLE: 10, + SET_TEXT_STYLE: 11, + STROKE: 12 +}; + +goog.provide('ol.render.canvas.Replay'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.colorlike'); +goog.require('ol.extent'); +goog.require('ol.extent.Relationship'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.has'); +goog.require('ol.obj'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.Instruction'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.render.VectorContext} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. + * @struct + */ +ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, overlaps) { + ol.render.VectorContext.call(this); + + /** + * @protected + * @type {number} + */ + this.tolerance = tolerance; + + /** + * @protected + * @const + * @type {ol.Extent} + */ + this.maxExtent = maxExtent; + + /** + * @protected + * @type {boolean} + */ + this.overlaps = overlaps; + + /** + * @protected + * @type {number} + */ + this.maxLineWidth = 0; + + /** + * @protected + * @const + * @type {number} + */ + this.resolution = resolution; + + /** + * @private + * @type {ol.Coordinate} + */ + this.fillOrigin_; + + /** + * @private + * @type {Array.<*>} + */ + this.beginGeometryInstruction1_ = null; + + /** + * @private + * @type {Array.<*>} + */ + this.beginGeometryInstruction2_ = null; + + /** + * @protected + * @type {Array.<*>} + */ + this.instructions = []; + + /** + * @protected + * @type {Array.<number>} + */ + this.coordinates = []; + + /** + * @private + * @type {ol.Transform} + */ + this.renderedTransform_ = ol.transform.create(); + + /** + * @protected + * @type {Array.<*>} + */ + this.hitDetectionInstructions = []; + + /** + * @private + * @type {Array.<number>} + */ + this.pixelCoordinates_ = []; + + /** + * @private + * @type {ol.Transform} + */ + this.tmpLocalTransform_ = ol.transform.create(); + + /** + * @private + * @type {ol.Transform} + */ + this.resetTransform_ = ol.transform.create(); +}; +ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {boolean} closed Last input coordinate equals first. + * @param {boolean} skipFirst Skip first coordinate. + * @protected + * @return {number} My end. + */ +ol.render.canvas.Replay.prototype.appendFlatCoordinates = function(flatCoordinates, offset, end, stride, closed, skipFirst) { + + var myEnd = this.coordinates.length; + var extent = this.getBufferedMaxExtent(); + if (skipFirst) { + offset += stride; + } + var lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]]; + var nextCoord = [NaN, NaN]; + var skipped = true; + + var i, lastRel, nextRel; + for (i = offset + stride; i < end; i += stride) { + nextCoord[0] = flatCoordinates[i]; + nextCoord[1] = flatCoordinates[i + 1]; + nextRel = ol.extent.coordinateRelationship(extent, nextCoord); + if (nextRel !== lastRel) { + if (skipped) { + this.coordinates[myEnd++] = lastCoord[0]; + this.coordinates[myEnd++] = lastCoord[1]; + } + this.coordinates[myEnd++] = nextCoord[0]; + this.coordinates[myEnd++] = nextCoord[1]; + skipped = false; + } else if (nextRel === ol.extent.Relationship.INTERSECTING) { + this.coordinates[myEnd++] = nextCoord[0]; + this.coordinates[myEnd++] = nextCoord[1]; + skipped = false; + } else { + skipped = true; + } + lastCoord[0] = nextCoord[0]; + lastCoord[1] = nextCoord[1]; + lastRel = nextRel; + } + + // Last coordinate equals first or only one point to append: + if ((closed && skipped) || i === offset + stride) { + this.coordinates[myEnd++] = lastCoord[0]; + this.coordinates[myEnd++] = lastCoord[1]; + } + return myEnd; +}; + + +/** + * @protected + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) { + this.beginGeometryInstruction1_ = + [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0]; + this.instructions.push(this.beginGeometryInstruction1_); + this.beginGeometryInstruction2_ = + [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0]; + this.hitDetectionInstructions.push(this.beginGeometryInstruction2_); +}; + + +/** + * @private + * @param {CanvasRenderingContext2D} context Context. + * @param {number} rotation Rotation. + */ +ol.render.canvas.Replay.prototype.fill_ = function(context, rotation) { + if (this.fillOrigin_) { + var origin = ol.transform.apply(this.renderedTransform_, this.fillOrigin_.slice()); + context.translate(origin[0], origin[1]); + context.rotate(rotation); + } + context.fill(); + if (this.fillOrigin_) { + context.setTransform.apply(context, this.resetTransform_); + } +}; + + +/** + * @private + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {Array.<*>} instructions Instructions array. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} + * featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Only check features that intersect this + * extent. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.canvas.Replay.prototype.replay_ = function( + context, pixelRatio, transform, viewRotation, skippedFeaturesHash, + instructions, featureCallback, opt_hitExtent) { + /** @type {Array.<number>} */ + var pixelCoordinates; + if (ol.array.equals(transform, this.renderedTransform_)) { + pixelCoordinates = this.pixelCoordinates_; + } else { + pixelCoordinates = ol.geom.flat.transform.transform2D( + this.coordinates, 0, this.coordinates.length, 2, + transform, this.pixelCoordinates_); + ol.transform.setFromArray(this.renderedTransform_, transform); + ol.DEBUG && console.assert(pixelCoordinates === this.pixelCoordinates_, + 'pixelCoordinates should be the same as this.pixelCoordinates_'); + } + var skipFeatures = !ol.obj.isEmpty(skippedFeaturesHash); + var i = 0; // instruction index + var ii = instructions.length; // end of instructions + var d = 0; // data index + var dd; // end of per-instruction data + var localTransform = this.tmpLocalTransform_; + var resetTransform = this.resetTransform_; + var prevX, prevY, roundX, roundY; + var pendingFill = 0; + var pendingStroke = 0; + // When the batch size gets too big, performance decreases. 200 is a good + // balance between batch size and number of fill/stroke instructions. + var batchSize = + this.instructions != instructions || this.overlaps ? 0 : 200; + while (i < ii) { + var instruction = instructions[i]; + var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]); + var feature, fill, stroke, text, x, y; + switch (type) { + case ol.render.canvas.Instruction.BEGIN_GEOMETRY: + feature = /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]); + if ((skipFeatures && + skippedFeaturesHash[ol.getUid(feature).toString()]) || + !feature.getGeometry()) { + i = /** @type {number} */ (instruction[2]); + } else if (opt_hitExtent !== undefined && !ol.extent.intersects( + opt_hitExtent, feature.getGeometry().getExtent())) { + i = /** @type {number} */ (instruction[2]) + 1; + } else { + ++i; + } + break; + case ol.render.canvas.Instruction.BEGIN_PATH: + if (pendingFill > batchSize) { + this.fill_(context, viewRotation); + pendingFill = 0; + } + if (pendingStroke > batchSize) { + context.stroke(); + pendingStroke = 0; + } + if (!pendingFill && !pendingStroke) { + context.beginPath(); + } + ++i; + break; + case ol.render.canvas.Instruction.CIRCLE: + ol.DEBUG && console.assert(typeof instruction[1] === 'number', + 'second instruction should be a number'); + d = /** @type {number} */ (instruction[1]); + var x1 = pixelCoordinates[d]; + var y1 = pixelCoordinates[d + 1]; + var x2 = pixelCoordinates[d + 2]; + var y2 = pixelCoordinates[d + 3]; + var dx = x2 - x1; + var dy = y2 - y1; + var r = Math.sqrt(dx * dx + dy * dy); + context.moveTo(x1 + r, y1); + context.arc(x1, y1, r, 0, 2 * Math.PI, true); + ++i; + break; + case ol.render.canvas.Instruction.CLOSE_PATH: + context.closePath(); + ++i; + break; + case ol.render.canvas.Instruction.DRAW_IMAGE: + ol.DEBUG && console.assert(typeof instruction[1] === 'number', + 'second instruction should be a number'); + d = /** @type {number} */ (instruction[1]); + ol.DEBUG && console.assert(typeof instruction[2] === 'number', + 'third instruction should be a number'); + dd = /** @type {number} */ (instruction[2]); + var image = /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */ + (instruction[3]); + // Remaining arguments in DRAW_IMAGE are in alphabetical order + var anchorX = /** @type {number} */ (instruction[4]) * pixelRatio; + var anchorY = /** @type {number} */ (instruction[5]) * pixelRatio; + var height = /** @type {number} */ (instruction[6]); + var opacity = /** @type {number} */ (instruction[7]); + var originX = /** @type {number} */ (instruction[8]); + var originY = /** @type {number} */ (instruction[9]); + var rotateWithView = /** @type {boolean} */ (instruction[10]); + var rotation = /** @type {number} */ (instruction[11]); + var scale = /** @type {number} */ (instruction[12]); + var snapToPixel = /** @type {boolean} */ (instruction[13]); + var width = /** @type {number} */ (instruction[14]); + if (rotateWithView) { + rotation += viewRotation; + } + for (; d < dd; d += 2) { + x = pixelCoordinates[d] - anchorX; + y = pixelCoordinates[d + 1] - anchorY; + if (snapToPixel) { + x = Math.round(x); + y = Math.round(y); + } + if (scale != 1 || rotation !== 0) { + var centerX = x + anchorX; + var centerY = y + anchorY; + ol.transform.compose(localTransform, + centerX, centerY, scale, scale, rotation, -centerX, -centerY); + context.setTransform.apply(context, localTransform); + } + var alpha = context.globalAlpha; + if (opacity != 1) { + context.globalAlpha = alpha * opacity; + } + + var w = (width + originX > image.width) ? image.width - originX : width; + var h = (height + originY > image.height) ? image.height - originY : height; + + context.drawImage(image, originX, originY, w, h, + x, y, w * pixelRatio, h * pixelRatio); + + if (opacity != 1) { + context.globalAlpha = alpha; + } + if (scale != 1 || rotation !== 0) { + context.setTransform.apply(context, resetTransform); + } + } + ++i; + break; + case ol.render.canvas.Instruction.DRAW_TEXT: + ol.DEBUG && console.assert(typeof instruction[1] === 'number', + '2nd instruction should be a number'); + d = /** @type {number} */ (instruction[1]); + ol.DEBUG && console.assert(typeof instruction[2] === 'number', + '3rd instruction should be a number'); + dd = /** @type {number} */ (instruction[2]); + ol.DEBUG && console.assert(typeof instruction[3] === 'string', + '4th instruction should be a string'); + text = /** @type {string} */ (instruction[3]); + ol.DEBUG && console.assert(typeof instruction[4] === 'number', + '5th instruction should be a number'); + var offsetX = /** @type {number} */ (instruction[4]) * pixelRatio; + ol.DEBUG && console.assert(typeof instruction[5] === 'number', + '6th instruction should be a number'); + var offsetY = /** @type {number} */ (instruction[5]) * pixelRatio; + ol.DEBUG && console.assert(typeof instruction[6] === 'number', + '7th instruction should be a number'); + rotation = /** @type {number} */ (instruction[6]); + ol.DEBUG && console.assert(typeof instruction[7] === 'number', + '8th instruction should be a number'); + scale = /** @type {number} */ (instruction[7]) * pixelRatio; + ol.DEBUG && console.assert(typeof instruction[8] === 'boolean', + '9th instruction should be a boolean'); + fill = /** @type {boolean} */ (instruction[8]); + ol.DEBUG && console.assert(typeof instruction[9] === 'boolean', + '10th instruction should be a boolean'); + stroke = /** @type {boolean} */ (instruction[9]); + rotateWithView = /** @type {boolean} */ (instruction[10]); + if (rotateWithView) { + rotation += viewRotation; + } + for (; d < dd; d += 2) { + x = pixelCoordinates[d] + offsetX; + y = pixelCoordinates[d + 1] + offsetY; + if (scale != 1 || rotation !== 0) { + ol.transform.compose(localTransform, x, y, scale, scale, rotation, -x, -y); + context.setTransform.apply(context, localTransform); + } + + // Support multiple lines separated by \n + var lines = text.split('\n'); + var numLines = lines.length; + var fontSize, lineY; + if (numLines > 1) { + // Estimate line height using width of capital M, and add padding + fontSize = Math.round(context.measureText('M').width * 1.5); + lineY = y - (((numLines - 1) / 2) * fontSize); + } else { + // No need to calculate line height/offset for a single line + fontSize = 0; + lineY = y; + } + + for (var lineIndex = 0; lineIndex < numLines; lineIndex++) { + var line = lines[lineIndex]; + if (stroke) { + context.strokeText(line, x, lineY); + } + if (fill) { + context.fillText(line, x, lineY); + } + + // Move next line down by fontSize px + lineY = lineY + fontSize; + } + + if (scale != 1 || rotation !== 0) { + context.setTransform.apply(context, resetTransform); + } + } + ++i; + break; + case ol.render.canvas.Instruction.END_GEOMETRY: + if (featureCallback !== undefined) { + feature = + /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]); + var result = featureCallback(feature); + if (result) { + return result; + } + } + ++i; + break; + case ol.render.canvas.Instruction.FILL: + if (batchSize) { + pendingFill++; + } else { + this.fill_(context, viewRotation); + } + ++i; + break; + case ol.render.canvas.Instruction.MOVE_TO_LINE_TO: + ol.DEBUG && console.assert(typeof instruction[1] === 'number', + '2nd instruction should be a number'); + d = /** @type {number} */ (instruction[1]); + ol.DEBUG && console.assert(typeof instruction[2] === 'number', + '3rd instruction should be a number'); + dd = /** @type {number} */ (instruction[2]); + x = pixelCoordinates[d]; + y = pixelCoordinates[d + 1]; + roundX = (x + 0.5) | 0; + roundY = (y + 0.5) | 0; + if (roundX !== prevX || roundY !== prevY) { + context.moveTo(x, y); + prevX = roundX; + prevY = roundY; + } + for (d += 2; d < dd; d += 2) { + x = pixelCoordinates[d]; + y = pixelCoordinates[d + 1]; + roundX = (x + 0.5) | 0; + roundY = (y + 0.5) | 0; + if (d == dd - 2 || roundX !== prevX || roundY !== prevY) { + context.lineTo(x, y); + prevX = roundX; + prevY = roundY; + } + } + ++i; + break; + case ol.render.canvas.Instruction.SET_FILL_STYLE: + ol.DEBUG && console.assert( + ol.colorlike.isColorLike(instruction[1]), + '2nd instruction should be a string, ' + + 'CanvasPattern, or CanvasGradient'); + this.fillOrigin_ = instruction[2]; + + if (pendingFill) { + this.fill_(context, viewRotation); + pendingFill = 0; + } + + context.fillStyle = /** @type {ol.ColorLike} */ (instruction[1]); + ++i; + break; + case ol.render.canvas.Instruction.SET_STROKE_STYLE: + ol.DEBUG && console.assert(ol.colorlike.isColorLike(instruction[1]), + '2nd instruction should be a string, CanvasPattern, or CanvasGradient'); + ol.DEBUG && console.assert(typeof instruction[2] === 'number', + '3rd instruction should be a number'); + ol.DEBUG && console.assert(typeof instruction[3] === 'string', + '4rd instruction should be a string'); + ol.DEBUG && console.assert(typeof instruction[4] === 'string', + '5th instruction should be a string'); + ol.DEBUG && console.assert(typeof instruction[5] === 'number', + '6th instruction should be a number'); + ol.DEBUG && console.assert(instruction[6], + '7th instruction should not be null'); + ol.DEBUG && console.assert(typeof instruction[8] === 'number', + '9th instruction should be a number'); + var usePixelRatio = instruction[7] !== undefined ? + instruction[7] : true; + var renderedPixelRatio = instruction[8]; + + var lineWidth = /** @type {number} */ (instruction[2]); + if (pendingStroke) { + context.stroke(); + pendingStroke = 0; + } + context.strokeStyle = /** @type {ol.ColorLike} */ (instruction[1]); + context.lineWidth = usePixelRatio ? lineWidth * pixelRatio : lineWidth; + context.lineCap = /** @type {string} */ (instruction[3]); + context.lineJoin = /** @type {string} */ (instruction[4]); + context.miterLimit = /** @type {number} */ (instruction[5]); + if (ol.has.CANVAS_LINE_DASH) { + var lineDash = /** @type {Array.<number>} */ (instruction[6]); + if (usePixelRatio && pixelRatio !== renderedPixelRatio) { + lineDash = lineDash.map(function(dash) { + return dash * pixelRatio / renderedPixelRatio; + }); + instruction[6] = lineDash; + instruction[8] = pixelRatio; + } + context.setLineDash(lineDash); + } + prevX = NaN; + prevY = NaN; + ++i; + break; + case ol.render.canvas.Instruction.SET_TEXT_STYLE: + ol.DEBUG && console.assert(typeof instruction[1] === 'string', + '2nd instruction should be a string'); + ol.DEBUG && console.assert(typeof instruction[2] === 'string', + '3rd instruction should be a string'); + ol.DEBUG && console.assert(typeof instruction[3] === 'string', + '4th instruction should be a string'); + context.font = /** @type {string} */ (instruction[1]); + context.textAlign = /** @type {string} */ (instruction[2]); + context.textBaseline = /** @type {string} */ (instruction[3]); + ++i; + break; + case ol.render.canvas.Instruction.STROKE: + if (batchSize) { + pendingStroke++; + } else { + context.stroke(); + } + ++i; + break; + default: + ol.DEBUG && console.assert(false, 'Unknown canvas render instruction'); + ++i; // consume the instruction anyway, to avoid an infinite loop + break; + } + } + if (pendingFill) { + this.fill_(context, viewRotation); + } + if (pendingStroke) { + context.stroke(); + } + // assert that all instructions were consumed + ol.DEBUG && console.assert(i == instructions.length, + 'all instructions should be consumed'); + return undefined; +}; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + */ +ol.render.canvas.Replay.prototype.replay = function( + context, pixelRatio, transform, viewRotation, skippedFeaturesHash) { + var instructions = this.instructions; + this.replay_(context, pixelRatio, transform, viewRotation, + skippedFeaturesHash, instructions, undefined, undefined); +}; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {ol.Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T=} opt_featureCallback + * Feature callback. + * @param {ol.Extent=} opt_hitExtent Only check features that intersect this + * extent. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.canvas.Replay.prototype.replayHitDetection = function( + context, transform, viewRotation, skippedFeaturesHash, + opt_featureCallback, opt_hitExtent) { + var instructions = this.hitDetectionInstructions; + return this.replay_(context, 1, transform, viewRotation, + skippedFeaturesHash, instructions, opt_featureCallback, opt_hitExtent); +}; + + +/** + * Reverse the hit detection instructions. + */ +ol.render.canvas.Replay.prototype.reverseHitDetectionInstructions = function() { + var hitDetectionInstructions = this.hitDetectionInstructions; + // step 1 - reverse array + hitDetectionInstructions.reverse(); + // step 2 - reverse instructions within geometry blocks + var i; + var n = hitDetectionInstructions.length; + var instruction; + var type; + var begin = -1; + for (i = 0; i < n; ++i) { + instruction = hitDetectionInstructions[i]; + type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]); + if (type == ol.render.canvas.Instruction.END_GEOMETRY) { + ol.DEBUG && console.assert(begin == -1, 'begin should be -1'); + begin = i; + } else if (type == ol.render.canvas.Instruction.BEGIN_GEOMETRY) { + instruction[2] = i; + ol.DEBUG && console.assert(begin >= 0, + 'begin should be larger than or equal to 0'); + ol.array.reverseSubArray(this.hitDetectionInstructions, begin, i); + begin = -1; + } + } +}; + + +/** + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.canvas.Replay.prototype.endGeometry = function(geometry, feature) { + ol.DEBUG && console.assert(this.beginGeometryInstruction1_, + 'this.beginGeometryInstruction1_ should not be null'); + this.beginGeometryInstruction1_[2] = this.instructions.length; + this.beginGeometryInstruction1_ = null; + ol.DEBUG && console.assert(this.beginGeometryInstruction2_, + 'this.beginGeometryInstruction2_ should not be null'); + this.beginGeometryInstruction2_[2] = this.hitDetectionInstructions.length; + this.beginGeometryInstruction2_ = null; + var endGeometryInstruction = + [ol.render.canvas.Instruction.END_GEOMETRY, feature]; + this.instructions.push(endGeometryInstruction); + this.hitDetectionInstructions.push(endGeometryInstruction); +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.render.canvas.Replay.prototype.finish = ol.nullFunction; + + +/** + * Get the buffered rendering extent. Rendering will be clipped to the extent + * provided to the constructor. To account for symbolizers that may intersect + * this extent, we calculate a buffered extent (e.g. based on stroke width). + * @return {ol.Extent} The buffered rendering extent. + * @protected + */ +ol.render.canvas.Replay.prototype.getBufferedMaxExtent = function() { + return this.maxExtent; +}; + +goog.provide('ol.render.canvas.ImageReplay'); + +goog.require('ol'); +goog.require('ol.render.canvas.Instruction'); +goog.require('ol.render.canvas.Replay'); + + +/** + * @constructor + * @extends {ol.render.canvas.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. + * @struct + */ +ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution, overlaps) { + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); + + /** + * @private + * @type {HTMLCanvasElement|HTMLVideoElement|Image} + */ + this.hitDetectionImage_ = null; + + /** + * @private + * @type {HTMLCanvasElement|HTMLVideoElement|Image} + */ + this.image_ = null; + + /** + * @private + * @type {number|undefined} + */ + this.anchorX_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.anchorY_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.height_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.opacity_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.originX_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.originY_ = undefined; + + /** + * @private + * @type {boolean|undefined} + */ + this.rotateWithView_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.rotation_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.scale_ = undefined; + + /** + * @private + * @type {boolean|undefined} + */ + this.snapToPixel_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.width_ = undefined; + +}; +ol.inherits(ol.render.canvas.ImageReplay, ol.render.canvas.Replay); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @private + * @return {number} My end. + */ +ol.render.canvas.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { + return this.appendFlatCoordinates( + flatCoordinates, offset, end, stride, false, false); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) { + if (!this.image_) { + return; + } + ol.DEBUG && console.assert(this.anchorX_ !== undefined, + 'this.anchorX_ should be defined'); + ol.DEBUG && console.assert(this.anchorY_ !== undefined, + 'this.anchorY_ should be defined'); + ol.DEBUG && console.assert(this.height_ !== undefined, + 'this.height_ should be defined'); + ol.DEBUG && console.assert(this.opacity_ !== undefined, + 'this.opacity_ should be defined'); + ol.DEBUG && console.assert(this.originX_ !== undefined, + 'this.originX_ should be defined'); + ol.DEBUG && console.assert(this.originY_ !== undefined, + 'this.originY_ should be defined'); + ol.DEBUG && console.assert(this.rotateWithView_ !== undefined, + 'this.rotateWithView_ should be defined'); + ol.DEBUG && console.assert(this.rotation_ !== undefined, + 'this.rotation_ should be defined'); + ol.DEBUG && console.assert(this.scale_ !== undefined, + 'this.scale_ should be defined'); + ol.DEBUG && console.assert(this.width_ !== undefined, + 'this.width_ should be defined'); + this.beginGeometry(pointGeometry, feature); + var flatCoordinates = pointGeometry.getFlatCoordinates(); + var stride = pointGeometry.getStride(); + var myBegin = this.coordinates.length; + var myEnd = this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + this.instructions.push([ + ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_, + // Remaining arguments to DRAW_IMAGE are in alphabetical order + this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.originX_, this.originY_, this.rotateWithView_, this.rotation_, + this.scale_, this.snapToPixel_, this.width_ + ]); + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, + this.hitDetectionImage_, + // Remaining arguments to DRAW_IMAGE are in alphabetical order + this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.originX_, this.originY_, this.rotateWithView_, this.rotation_, + this.scale_, this.snapToPixel_, this.width_ + ]); + this.endGeometry(pointGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) { + if (!this.image_) { + return; + } + ol.DEBUG && console.assert(this.anchorX_ !== undefined, + 'this.anchorX_ should be defined'); + ol.DEBUG && console.assert(this.anchorY_ !== undefined, + 'this.anchorY_ should be defined'); + ol.DEBUG && console.assert(this.height_ !== undefined, + 'this.height_ should be defined'); + ol.DEBUG && console.assert(this.opacity_ !== undefined, + 'this.opacity_ should be defined'); + ol.DEBUG && console.assert(this.originX_ !== undefined, + 'this.originX_ should be defined'); + ol.DEBUG && console.assert(this.originY_ !== undefined, + 'this.originY_ should be defined'); + ol.DEBUG && console.assert(this.rotateWithView_ !== undefined, + 'this.rotateWithView_ should be defined'); + ol.DEBUG && console.assert(this.rotation_ !== undefined, + 'this.rotation_ should be defined'); + ol.DEBUG && console.assert(this.scale_ !== undefined, + 'this.scale_ should be defined'); + ol.DEBUG && console.assert(this.width_ !== undefined, + 'this.width_ should be defined'); + this.beginGeometry(multiPointGeometry, feature); + var flatCoordinates = multiPointGeometry.getFlatCoordinates(); + var stride = multiPointGeometry.getStride(); + var myBegin = this.coordinates.length; + var myEnd = this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + this.instructions.push([ + ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_, + // Remaining arguments to DRAW_IMAGE are in alphabetical order + this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.originX_, this.originY_, this.rotateWithView_, this.rotation_, + this.scale_, this.snapToPixel_, this.width_ + ]); + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, + this.hitDetectionImage_, + // Remaining arguments to DRAW_IMAGE are in alphabetical order + this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.originX_, this.originY_, this.rotateWithView_, this.rotation_, + this.scale_, this.snapToPixel_, this.width_ + ]); + this.endGeometry(multiPointGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.ImageReplay.prototype.finish = function() { + this.reverseHitDetectionInstructions(); + // FIXME this doesn't really protect us against further calls to draw*Geometry + this.anchorX_ = undefined; + this.anchorY_ = undefined; + this.hitDetectionImage_ = null; + this.image_ = null; + this.height_ = undefined; + this.scale_ = undefined; + this.opacity_ = undefined; + this.originX_ = undefined; + this.originY_ = undefined; + this.rotateWithView_ = undefined; + this.rotation_ = undefined; + this.snapToPixel_ = undefined; + this.width_ = undefined; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) { + ol.DEBUG && console.assert(imageStyle, 'imageStyle should not be null'); + var anchor = imageStyle.getAnchor(); + ol.DEBUG && console.assert(anchor, 'anchor should not be null'); + var size = imageStyle.getSize(); + ol.DEBUG && console.assert(size, 'size should not be null'); + var hitDetectionImage = imageStyle.getHitDetectionImage(1); + ol.DEBUG && console.assert(hitDetectionImage, + 'hitDetectionImage should not be null'); + var image = imageStyle.getImage(1); + ol.DEBUG && console.assert(image, 'image should not be null'); + var origin = imageStyle.getOrigin(); + ol.DEBUG && console.assert(origin, 'origin should not be null'); + this.anchorX_ = anchor[0]; + this.anchorY_ = anchor[1]; + this.hitDetectionImage_ = hitDetectionImage; + this.image_ = image; + this.height_ = size[1]; + this.opacity_ = imageStyle.getOpacity(); + this.originX_ = origin[0]; + this.originY_ = origin[1]; + this.rotateWithView_ = imageStyle.getRotateWithView(); + this.rotation_ = imageStyle.getRotation(); + this.scale_ = imageStyle.getScale(); + this.snapToPixel_ = imageStyle.getSnapToPixel(); + this.width_ = size[0]; +}; + +goog.provide('ol.render.canvas.LineStringReplay'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.colorlike'); +goog.require('ol.extent'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.Instruction'); +goog.require('ol.render.canvas.Replay'); + + +/** + * @constructor + * @extends {ol.render.canvas.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. + * @struct + */ +ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution, overlaps) { + + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); + + /** + * @private + * @type {ol.Extent} + */ + this.bufferedMaxExtent_ = null; + + /** + * @private + * @type {{currentStrokeStyle: (ol.ColorLike|undefined), + * currentLineCap: (string|undefined), + * currentLineDash: Array.<number>, + * currentLineJoin: (string|undefined), + * currentLineWidth: (number|undefined), + * currentMiterLimit: (number|undefined), + * lastStroke: number, + * strokeStyle: (ol.ColorLike|undefined), + * lineCap: (string|undefined), + * lineDash: Array.<number>, + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined)}|null} + */ + this.state_ = { + currentStrokeStyle: undefined, + currentLineCap: undefined, + currentLineDash: null, + currentLineJoin: undefined, + currentLineWidth: undefined, + currentMiterLimit: undefined, + lastStroke: 0, + strokeStyle: undefined, + lineCap: undefined, + lineDash: null, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined + }; + +}; +ol.inherits(ol.render.canvas.LineStringReplay, ol.render.canvas.Replay); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @private + * @return {number} end. + */ +ol.render.canvas.LineStringReplay.prototype.drawFlatCoordinates_ = function(flatCoordinates, offset, end, stride) { + var myBegin = this.coordinates.length; + var myEnd = this.appendFlatCoordinates( + flatCoordinates, offset, end, stride, false, false); + var moveToLineToInstruction = + [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd]; + this.instructions.push(moveToLineToInstruction); + this.hitDetectionInstructions.push(moveToLineToInstruction); + return end; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.LineStringReplay.prototype.getBufferedMaxExtent = function() { + if (!this.bufferedMaxExtent_) { + this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent); + if (this.maxLineWidth > 0) { + var width = this.resolution * (this.maxLineWidth + 1) / 2; + ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_); + } + } + return this.bufferedMaxExtent_; +}; + + +/** + * @private + */ +ol.render.canvas.LineStringReplay.prototype.setStrokeStyle_ = function() { + var state = this.state_; + var strokeStyle = state.strokeStyle; + var lineCap = state.lineCap; + var lineDash = state.lineDash; + var lineJoin = state.lineJoin; + var lineWidth = state.lineWidth; + var miterLimit = state.miterLimit; + ol.DEBUG && console.assert(strokeStyle !== undefined, + 'strokeStyle should be defined'); + ol.DEBUG && console.assert(lineCap !== undefined, 'lineCap should be defined'); + ol.DEBUG && console.assert(lineDash, 'lineDash should not be null'); + ol.DEBUG && console.assert(lineJoin !== undefined, 'lineJoin should be defined'); + ol.DEBUG && console.assert(lineWidth !== undefined, 'lineWidth should be defined'); + ol.DEBUG && console.assert(miterLimit !== undefined, 'miterLimit should be defined'); + if (state.currentStrokeStyle != strokeStyle || + state.currentLineCap != lineCap || + !ol.array.equals(state.currentLineDash, lineDash) || + state.currentLineJoin != lineJoin || + state.currentLineWidth != lineWidth || + state.currentMiterLimit != miterLimit) { + if (state.lastStroke != this.coordinates.length) { + this.instructions.push([ol.render.canvas.Instruction.STROKE]); + state.lastStroke = this.coordinates.length; + } + this.instructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash, true, 1 + ], [ + ol.render.canvas.Instruction.BEGIN_PATH + ]); + state.currentStrokeStyle = strokeStyle; + state.currentLineCap = lineCap; + state.currentLineDash = lineDash; + state.currentLineJoin = lineJoin; + state.currentLineWidth = lineWidth; + state.currentMiterLimit = miterLimit; + } +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) { + var state = this.state_; + ol.DEBUG && console.assert(state, 'state should not be null'); + var strokeStyle = state.strokeStyle; + var lineWidth = state.lineWidth; + if (strokeStyle === undefined || lineWidth === undefined) { + return; + } + this.setStrokeStyle_(); + this.beginGeometry(lineStringGeometry, feature); + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash, true, 1 + ], [ + ol.render.canvas.Instruction.BEGIN_PATH + ]); + var flatCoordinates = lineStringGeometry.getFlatCoordinates(); + var stride = lineStringGeometry.getStride(); + this.drawFlatCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); + this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]); + this.endGeometry(lineStringGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) { + var state = this.state_; + ol.DEBUG && console.assert(state, 'state should not be null'); + var strokeStyle = state.strokeStyle; + var lineWidth = state.lineWidth; + if (strokeStyle === undefined || lineWidth === undefined) { + return; + } + this.setStrokeStyle_(); + this.beginGeometry(multiLineStringGeometry, feature); + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash, true, 1 + ], [ + ol.render.canvas.Instruction.BEGIN_PATH + ]); + var ends = multiLineStringGeometry.getEnds(); + var flatCoordinates = multiLineStringGeometry.getFlatCoordinates(); + var stride = multiLineStringGeometry.getStride(); + var offset = 0; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + offset = this.drawFlatCoordinates_( + flatCoordinates, offset, ends[i], stride); + } + this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]); + this.endGeometry(multiLineStringGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.LineStringReplay.prototype.finish = function() { + var state = this.state_; + ol.DEBUG && console.assert(state, 'state should not be null'); + if (state.lastStroke != this.coordinates.length) { + this.instructions.push([ol.render.canvas.Instruction.STROKE]); + } + this.reverseHitDetectionInstructions(); + this.state_ = null; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + ol.DEBUG && console.assert(!fillStyle, 'fillStyle should be null'); + ol.DEBUG && console.assert(strokeStyle, 'strokeStyle should not be null'); + var strokeStyleColor = strokeStyle.getColor(); + this.state_.strokeStyle = ol.colorlike.asColorLike(strokeStyleColor ? + strokeStyleColor : ol.render.canvas.defaultStrokeStyle); + var strokeStyleLineCap = strokeStyle.getLineCap(); + this.state_.lineCap = strokeStyleLineCap !== undefined ? + strokeStyleLineCap : ol.render.canvas.defaultLineCap; + var strokeStyleLineDash = strokeStyle.getLineDash(); + this.state_.lineDash = strokeStyleLineDash ? + strokeStyleLineDash : ol.render.canvas.defaultLineDash; + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + this.state_.lineJoin = strokeStyleLineJoin !== undefined ? + strokeStyleLineJoin : ol.render.canvas.defaultLineJoin; + var strokeStyleWidth = strokeStyle.getWidth(); + this.state_.lineWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.canvas.defaultLineWidth; + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + this.state_.miterLimit = strokeStyleMiterLimit !== undefined ? + strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; + + if (this.state_.lineWidth > this.maxLineWidth) { + this.maxLineWidth = this.state_.lineWidth; + // invalidate the buffered max extent cache + this.bufferedMaxExtent_ = null; + } +}; + +goog.provide('ol.render.canvas.PolygonReplay'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.color'); +goog.require('ol.colorlike'); +goog.require('ol.extent'); +goog.require('ol.geom.flat.simplify'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.Instruction'); +goog.require('ol.render.canvas.Replay'); + + +/** + * @constructor + * @extends {ol.render.canvas.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. + * @struct + */ +ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution, overlaps) { + + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); + + /** + * @private + * @type {ol.Extent} + */ + this.bufferedMaxExtent_ = null; + + /** + * @private + * @type {{currentFillStyle: (ol.ColorLike|undefined), + * currentStrokeStyle: (ol.ColorLike|undefined), + * currentLineCap: (string|undefined), + * currentLineDash: Array.<number>, + * currentLineJoin: (string|undefined), + * currentLineWidth: (number|undefined), + * currentMiterLimit: (number|undefined), + * fillStyle: (ol.ColorLike|undefined), + * strokeStyle: (ol.ColorLike|undefined), + * lineCap: (string|undefined), + * lineDash: Array.<number>, + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined)}|null} + */ + this.state_ = { + currentFillStyle: undefined, + currentStrokeStyle: undefined, + currentLineCap: undefined, + currentLineDash: null, + currentLineJoin: undefined, + currentLineWidth: undefined, + currentMiterLimit: undefined, + fillStyle: undefined, + strokeStyle: undefined, + lineCap: undefined, + lineDash: null, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined + }; + +}; +ol.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @private + * @return {number} End. + */ +ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ = function(flatCoordinates, offset, ends, stride) { + var state = this.state_; + var fill = state.fillStyle !== undefined; + var stroke = state.strokeStyle != undefined; + var numEnds = ends.length; + var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH]; + this.instructions.push(beginPathInstruction); + this.hitDetectionInstructions.push(beginPathInstruction); + for (var i = 0; i < numEnds; ++i) { + var end = ends[i]; + var myBegin = this.coordinates.length; + var myEnd = this.appendFlatCoordinates( + flatCoordinates, offset, end, stride, true, !stroke); + var moveToLineToInstruction = + [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd]; + this.instructions.push(moveToLineToInstruction); + this.hitDetectionInstructions.push(moveToLineToInstruction); + if (stroke) { + // Performance optimization: only call closePath() when we have a stroke. + // Otherwise the ring is closed already (see appendFlatCoordinates above). + var closePathInstruction = [ol.render.canvas.Instruction.CLOSE_PATH]; + this.instructions.push(closePathInstruction); + this.hitDetectionInstructions.push(closePathInstruction); + } + offset = end; + } + var fillInstruction = [ol.render.canvas.Instruction.FILL]; + this.hitDetectionInstructions.push(fillInstruction); + if (fill) { + this.instructions.push(fillInstruction); + } + if (stroke) { + ol.DEBUG && console.assert(state.lineWidth !== undefined, + 'state.lineWidth should be defined'); + var strokeInstruction = [ol.render.canvas.Instruction.STROKE]; + this.instructions.push(strokeInstruction); + this.hitDetectionInstructions.push(strokeInstruction); + } + return offset; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.drawCircle = function(circleGeometry, feature) { + var state = this.state_; + ol.DEBUG && console.assert(state, 'state should not be null'); + var fillStyle = state.fillStyle; + var strokeStyle = state.strokeStyle; + if (fillStyle === undefined && strokeStyle === undefined) { + return; + } + if (strokeStyle !== undefined) { + ol.DEBUG && console.assert(state.lineWidth !== undefined, + 'state.lineWidth should be defined'); + } + this.setFillStrokeStyles_(circleGeometry); + this.beginGeometry(circleGeometry, feature); + // always fill the circle for hit detection + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_FILL_STYLE, + ol.color.asString(ol.render.canvas.defaultFillStyle) + ]); + if (state.strokeStyle !== undefined) { + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash, true, 1 + ]); + } + var flatCoordinates = circleGeometry.getFlatCoordinates(); + var stride = circleGeometry.getStride(); + var myBegin = this.coordinates.length; + this.appendFlatCoordinates( + flatCoordinates, 0, flatCoordinates.length, stride, false, false); + var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH]; + var circleInstruction = [ol.render.canvas.Instruction.CIRCLE, myBegin]; + this.instructions.push(beginPathInstruction, circleInstruction); + this.hitDetectionInstructions.push(beginPathInstruction, circleInstruction); + var fillInstruction = [ol.render.canvas.Instruction.FILL]; + this.hitDetectionInstructions.push(fillInstruction); + if (state.fillStyle !== undefined) { + this.instructions.push(fillInstruction); + } + if (state.strokeStyle !== undefined) { + ol.DEBUG && console.assert(state.lineWidth !== undefined, + 'state.lineWidth should be defined'); + var strokeInstruction = [ol.render.canvas.Instruction.STROKE]; + this.instructions.push(strokeInstruction); + this.hitDetectionInstructions.push(strokeInstruction); + } + this.endGeometry(circleGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { + var state = this.state_; + ol.DEBUG && console.assert(state, 'state should not be null'); + var strokeStyle = state.strokeStyle; + ol.DEBUG && console.assert(state.fillStyle !== undefined || strokeStyle !== undefined, + 'fillStyle or strokeStyle should be defined'); + if (strokeStyle !== undefined) { + ol.DEBUG && console.assert(state.lineWidth !== undefined, + 'state.lineWidth should be defined'); + } + this.setFillStrokeStyles_(polygonGeometry); + this.beginGeometry(polygonGeometry, feature); + // always fill the polygon for hit detection + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_FILL_STYLE, + ol.color.asString(ol.render.canvas.defaultFillStyle)] + ); + if (state.strokeStyle !== undefined) { + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash, true, 1 + ]); + } + var ends = polygonGeometry.getEnds(); + var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates(); + var stride = polygonGeometry.getStride(); + this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride); + this.endGeometry(polygonGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) { + var state = this.state_; + ol.DEBUG && console.assert(state, 'state should not be null'); + var fillStyle = state.fillStyle; + var strokeStyle = state.strokeStyle; + if (fillStyle === undefined && strokeStyle === undefined) { + return; + } + if (strokeStyle !== undefined) { + ol.DEBUG && console.assert(state.lineWidth !== undefined, + 'state.lineWidth should be defined'); + } + this.setFillStrokeStyles_(multiPolygonGeometry); + this.beginGeometry(multiPolygonGeometry, feature); + // always fill the multi-polygon for hit detection + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_FILL_STYLE, + ol.color.asString(ol.render.canvas.defaultFillStyle) + ]); + if (state.strokeStyle !== undefined) { + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash, true, 1 + ]); + } + var endss = multiPolygonGeometry.getEndss(); + var flatCoordinates = multiPolygonGeometry.getOrientedFlatCoordinates(); + var stride = multiPolygonGeometry.getStride(); + var offset = 0; + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + offset = this.drawFlatCoordinatess_( + flatCoordinates, offset, endss[i], stride); + } + this.endGeometry(multiPolygonGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.finish = function() { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + this.reverseHitDetectionInstructions(); + this.state_ = null; + // We want to preserve topology when drawing polygons. Polygons are + // simplified using quantization and point elimination. However, we might + // have received a mix of quantized and non-quantized geometries, so ensure + // that all are quantized by quantizing all coordinates in the batch. + var tolerance = this.tolerance; + if (tolerance !== 0) { + var coordinates = this.coordinates; + var i, ii; + for (i = 0, ii = coordinates.length; i < ii; ++i) { + coordinates[i] = ol.geom.flat.simplify.snap(coordinates[i], tolerance); + } + } +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.getBufferedMaxExtent = function() { + if (!this.bufferedMaxExtent_) { + this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent); + if (this.maxLineWidth > 0) { + var width = this.resolution * (this.maxLineWidth + 1) / 2; + ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_); + } + } + return this.bufferedMaxExtent_; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + ol.DEBUG && console.assert(fillStyle || strokeStyle, + 'fillStyle or strokeStyle should not be null'); + var state = this.state_; + if (fillStyle) { + var fillStyleColor = fillStyle.getColor(); + state.fillStyle = ol.colorlike.asColorLike(fillStyleColor ? + fillStyleColor : ol.render.canvas.defaultFillStyle); + } else { + state.fillStyle = undefined; + } + if (strokeStyle) { + var strokeStyleColor = strokeStyle.getColor(); + state.strokeStyle = ol.colorlike.asColorLike(strokeStyleColor ? + strokeStyleColor : ol.render.canvas.defaultStrokeStyle); + var strokeStyleLineCap = strokeStyle.getLineCap(); + state.lineCap = strokeStyleLineCap !== undefined ? + strokeStyleLineCap : ol.render.canvas.defaultLineCap; + var strokeStyleLineDash = strokeStyle.getLineDash(); + state.lineDash = strokeStyleLineDash ? + strokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash; + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + state.lineJoin = strokeStyleLineJoin !== undefined ? + strokeStyleLineJoin : ol.render.canvas.defaultLineJoin; + var strokeStyleWidth = strokeStyle.getWidth(); + state.lineWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.canvas.defaultLineWidth; + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + state.miterLimit = strokeStyleMiterLimit !== undefined ? + strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; + + if (state.lineWidth > this.maxLineWidth) { + this.maxLineWidth = state.lineWidth; + // invalidate the buffered max extent cache + this.bufferedMaxExtent_ = null; + } + } else { + state.strokeStyle = undefined; + state.lineCap = undefined; + state.lineDash = null; + state.lineJoin = undefined; + state.lineWidth = undefined; + state.miterLimit = undefined; + } +}; + + +/** + * @private + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + */ +ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function(geometry) { + var state = this.state_; + var fillStyle = state.fillStyle; + var strokeStyle = state.strokeStyle; + var lineCap = state.lineCap; + var lineDash = state.lineDash; + var lineJoin = state.lineJoin; + var lineWidth = state.lineWidth; + var miterLimit = state.miterLimit; + if (fillStyle !== undefined && (typeof fillStyle !== 'string' || state.currentFillStyle != fillStyle)) { + var fillInstruction = [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle]; + if (typeof fillStyle !== 'string') { + var fillExtent = geometry.getExtent(); + fillInstruction.push([fillExtent[0], fillExtent[3]]); + } + this.instructions.push(fillInstruction); + state.currentFillStyle = state.fillStyle; + } + if (strokeStyle !== undefined) { + ol.DEBUG && console.assert(lineCap !== undefined, 'lineCap should be defined'); + ol.DEBUG && console.assert(lineDash, 'lineDash should not be null'); + ol.DEBUG && console.assert(lineJoin !== undefined, 'lineJoin should be defined'); + ol.DEBUG && console.assert(lineWidth !== undefined, 'lineWidth should be defined'); + ol.DEBUG && console.assert(miterLimit !== undefined, + 'miterLimit should be defined'); + if (state.currentStrokeStyle != strokeStyle || + state.currentLineCap != lineCap || + !ol.array.equals(state.currentLineDash, lineDash) || + state.currentLineJoin != lineJoin || + state.currentLineWidth != lineWidth || + state.currentMiterLimit != miterLimit) { + this.instructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash, true, 1 + ]); + state.currentStrokeStyle = strokeStyle; + state.currentLineCap = lineCap; + state.currentLineDash = lineDash; + state.currentLineJoin = lineJoin; + state.currentLineWidth = lineWidth; + state.currentMiterLimit = miterLimit; + } + } +}; + +goog.provide('ol.render.canvas.TextReplay'); + +goog.require('ol'); +goog.require('ol.colorlike'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.Instruction'); +goog.require('ol.render.canvas.Replay'); + + +/** + * @constructor + * @extends {ol.render.canvas.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. + * @struct + */ +ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution, overlaps) { + + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); + + /** + * @private + * @type {?ol.CanvasFillState} + */ + this.replayFillState_ = null; + + /** + * @private + * @type {?ol.CanvasStrokeState} + */ + this.replayStrokeState_ = null; + + /** + * @private + * @type {?ol.CanvasTextState} + */ + this.replayTextState_ = null; + + /** + * @private + * @type {string} + */ + this.text_ = ''; + + /** + * @private + * @type {number} + */ + this.textOffsetX_ = 0; + + /** + * @private + * @type {number} + */ + this.textOffsetY_ = 0; + + /** + * @private + * @type {boolean|undefined} + */ + this.textRotateWithView_ = undefined; + + /** + * @private + * @type {number} + */ + this.textRotation_ = 0; + + /** + * @private + * @type {number} + */ + this.textScale_ = 0; + + /** + * @private + * @type {?ol.CanvasFillState} + */ + this.textFillState_ = null; + + /** + * @private + * @type {?ol.CanvasStrokeState} + */ + this.textStrokeState_ = null; + + /** + * @private + * @type {?ol.CanvasTextState} + */ + this.textState_ = null; + +}; +ol.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay); + + +/** + * @inheritDoc + */ +ol.render.canvas.TextReplay.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) { + if (this.text_ === '' || !this.textState_ || + (!this.textFillState_ && !this.textStrokeState_)) { + return; + } + if (this.textFillState_) { + this.setReplayFillState_(this.textFillState_); + } + if (this.textStrokeState_) { + this.setReplayStrokeState_(this.textStrokeState_); + } + this.setReplayTextState_(this.textState_); + this.beginGeometry(geometry, feature); + var myBegin = this.coordinates.length; + var myEnd = + this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false, false); + var fill = !!this.textFillState_; + var stroke = !!this.textStrokeState_; + var drawTextInstruction = [ + ol.render.canvas.Instruction.DRAW_TEXT, myBegin, myEnd, this.text_, + this.textOffsetX_, this.textOffsetY_, this.textRotation_, this.textScale_, + fill, stroke, this.textRotateWithView_]; + this.instructions.push(drawTextInstruction); + this.hitDetectionInstructions.push(drawTextInstruction); + this.endGeometry(geometry, feature); +}; + + +/** + * @param {ol.CanvasFillState} fillState Fill state. + * @private + */ +ol.render.canvas.TextReplay.prototype.setReplayFillState_ = function(fillState) { + var replayFillState = this.replayFillState_; + if (replayFillState && + replayFillState.fillStyle == fillState.fillStyle) { + return; + } + var setFillStyleInstruction = + [ol.render.canvas.Instruction.SET_FILL_STYLE, fillState.fillStyle]; + this.instructions.push(setFillStyleInstruction); + this.hitDetectionInstructions.push(setFillStyleInstruction); + if (!replayFillState) { + this.replayFillState_ = { + fillStyle: fillState.fillStyle + }; + } else { + replayFillState.fillStyle = fillState.fillStyle; + } +}; + + +/** + * @param {ol.CanvasStrokeState} strokeState Stroke state. + * @private + */ +ol.render.canvas.TextReplay.prototype.setReplayStrokeState_ = function(strokeState) { + var replayStrokeState = this.replayStrokeState_; + if (replayStrokeState && + replayStrokeState.lineCap == strokeState.lineCap && + replayStrokeState.lineDash == strokeState.lineDash && + replayStrokeState.lineJoin == strokeState.lineJoin && + replayStrokeState.lineWidth == strokeState.lineWidth && + replayStrokeState.miterLimit == strokeState.miterLimit && + replayStrokeState.strokeStyle == strokeState.strokeStyle) { + return; + } + var setStrokeStyleInstruction = [ + ol.render.canvas.Instruction.SET_STROKE_STYLE, strokeState.strokeStyle, + strokeState.lineWidth, strokeState.lineCap, strokeState.lineJoin, + strokeState.miterLimit, strokeState.lineDash, false, 1 + ]; + this.instructions.push(setStrokeStyleInstruction); + this.hitDetectionInstructions.push(setStrokeStyleInstruction); + if (!replayStrokeState) { + this.replayStrokeState_ = { + lineCap: strokeState.lineCap, + lineDash: strokeState.lineDash, + lineJoin: strokeState.lineJoin, + lineWidth: strokeState.lineWidth, + miterLimit: strokeState.miterLimit, + strokeStyle: strokeState.strokeStyle + }; + } else { + replayStrokeState.lineCap = strokeState.lineCap; + replayStrokeState.lineDash = strokeState.lineDash; + replayStrokeState.lineJoin = strokeState.lineJoin; + replayStrokeState.lineWidth = strokeState.lineWidth; + replayStrokeState.miterLimit = strokeState.miterLimit; + replayStrokeState.strokeStyle = strokeState.strokeStyle; + } +}; + + +/** + * @param {ol.CanvasTextState} textState Text state. + * @private + */ +ol.render.canvas.TextReplay.prototype.setReplayTextState_ = function(textState) { + var replayTextState = this.replayTextState_; + if (replayTextState && + replayTextState.font == textState.font && + replayTextState.textAlign == textState.textAlign && + replayTextState.textBaseline == textState.textBaseline) { + return; + } + var setTextStyleInstruction = [ol.render.canvas.Instruction.SET_TEXT_STYLE, + textState.font, textState.textAlign, textState.textBaseline]; + this.instructions.push(setTextStyleInstruction); + this.hitDetectionInstructions.push(setTextStyleInstruction); + if (!replayTextState) { + this.replayTextState_ = { + font: textState.font, + textAlign: textState.textAlign, + textBaseline: textState.textBaseline + }; + } else { + replayTextState.font = textState.font; + replayTextState.textAlign = textState.textAlign; + replayTextState.textBaseline = textState.textBaseline; + } +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) { + if (!textStyle) { + this.text_ = ''; + } else { + var textFillStyle = textStyle.getFill(); + if (!textFillStyle) { + this.textFillState_ = null; + } else { + var textFillStyleColor = textFillStyle.getColor(); + var fillStyle = ol.colorlike.asColorLike(textFillStyleColor ? + textFillStyleColor : ol.render.canvas.defaultFillStyle); + if (!this.textFillState_) { + this.textFillState_ = { + fillStyle: fillStyle + }; + } else { + var textFillState = this.textFillState_; + textFillState.fillStyle = fillStyle; + } + } + var textStrokeStyle = textStyle.getStroke(); + if (!textStrokeStyle) { + this.textStrokeState_ = null; + } else { + var textStrokeStyleColor = textStrokeStyle.getColor(); + var textStrokeStyleLineCap = textStrokeStyle.getLineCap(); + var textStrokeStyleLineDash = textStrokeStyle.getLineDash(); + var textStrokeStyleLineJoin = textStrokeStyle.getLineJoin(); + var textStrokeStyleWidth = textStrokeStyle.getWidth(); + var textStrokeStyleMiterLimit = textStrokeStyle.getMiterLimit(); + var lineCap = textStrokeStyleLineCap !== undefined ? + textStrokeStyleLineCap : ol.render.canvas.defaultLineCap; + var lineDash = textStrokeStyleLineDash ? + textStrokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash; + var lineJoin = textStrokeStyleLineJoin !== undefined ? + textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin; + var lineWidth = textStrokeStyleWidth !== undefined ? + textStrokeStyleWidth : ol.render.canvas.defaultLineWidth; + var miterLimit = textStrokeStyleMiterLimit !== undefined ? + textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; + var strokeStyle = ol.colorlike.asColorLike(textStrokeStyleColor ? + textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle); + if (!this.textStrokeState_) { + this.textStrokeState_ = { + lineCap: lineCap, + lineDash: lineDash, + lineJoin: lineJoin, + lineWidth: lineWidth, + miterLimit: miterLimit, + strokeStyle: strokeStyle + }; + } else { + var textStrokeState = this.textStrokeState_; + textStrokeState.lineCap = lineCap; + textStrokeState.lineDash = lineDash; + textStrokeState.lineJoin = lineJoin; + textStrokeState.lineWidth = lineWidth; + textStrokeState.miterLimit = miterLimit; + textStrokeState.strokeStyle = strokeStyle; + } + } + var textFont = textStyle.getFont(); + var textOffsetX = textStyle.getOffsetX(); + var textOffsetY = textStyle.getOffsetY(); + var textRotateWithView = textStyle.getRotateWithView(); + var textRotation = textStyle.getRotation(); + var textScale = textStyle.getScale(); + var textText = textStyle.getText(); + var textTextAlign = textStyle.getTextAlign(); + var textTextBaseline = textStyle.getTextBaseline(); + var font = textFont !== undefined ? + textFont : ol.render.canvas.defaultFont; + var textAlign = textTextAlign !== undefined ? + textTextAlign : ol.render.canvas.defaultTextAlign; + var textBaseline = textTextBaseline !== undefined ? + textTextBaseline : ol.render.canvas.defaultTextBaseline; + if (!this.textState_) { + this.textState_ = { + font: font, + textAlign: textAlign, + textBaseline: textBaseline + }; + } else { + var textState = this.textState_; + textState.font = font; + textState.textAlign = textAlign; + textState.textBaseline = textBaseline; + } + this.text_ = textText !== undefined ? textText : ''; + this.textOffsetX_ = textOffsetX !== undefined ? textOffsetX : 0; + this.textOffsetY_ = textOffsetY !== undefined ? textOffsetY : 0; + this.textRotateWithView_ = textRotateWithView !== undefined ? textRotateWithView : false; + this.textRotation_ = textRotation !== undefined ? textRotation : 0; + this.textScale_ = textScale !== undefined ? textScale : 1; + } +}; + +goog.provide('ol.render.ReplayType'); + + +/** + * @enum {string} + */ +ol.render.ReplayType = { + CIRCLE: 'Circle', + IMAGE: 'Image', + LINE_STRING: 'LineString', + POLYGON: 'Polygon', + TEXT: 'Text' +}; + +goog.provide('ol.render.replay'); + +goog.require('ol.render.ReplayType'); + + +/** + * @const + * @type {Array.<ol.render.ReplayType>} + */ +ol.render.replay.ORDER = [ + ol.render.ReplayType.POLYGON, + ol.render.ReplayType.CIRCLE, + ol.render.ReplayType.LINE_STRING, + ol.render.ReplayType.IMAGE, + ol.render.ReplayType.TEXT +]; + +goog.provide('ol.render.canvas.ReplayGroup'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.obj'); +goog.require('ol.render.ReplayGroup'); +goog.require('ol.render.canvas.ImageReplay'); +goog.require('ol.render.canvas.LineStringReplay'); +goog.require('ol.render.canvas.PolygonReplay'); +goog.require('ol.render.canvas.TextReplay'); +goog.require('ol.render.replay'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.render.ReplayGroup} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay group can have overlapping geometries. + * @param {number=} opt_renderBuffer Optional rendering buffer. + * @struct + */ +ol.render.canvas.ReplayGroup = function( + tolerance, maxExtent, resolution, overlaps, opt_renderBuffer) { + ol.render.ReplayGroup.call(this); + + /** + * @private + * @type {number} + */ + this.tolerance_ = tolerance; + + /** + * @private + * @type {ol.Extent} + */ + this.maxExtent_ = maxExtent; + + /** + * @private + * @type {boolean} + */ + this.overlaps_ = overlaps; + + /** + * @private + * @type {number} + */ + this.resolution_ = resolution; + + /** + * @private + * @type {number|undefined} + */ + this.renderBuffer_ = opt_renderBuffer; + + /** + * @private + * @type {!Object.<string, + * Object.<ol.render.ReplayType, ol.render.canvas.Replay>>} + */ + this.replaysByZIndex_ = {}; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitDetectionContext_ = ol.dom.createCanvasContext2D(1, 1); + + /** + * @private + * @type {ol.Transform} + */ + this.hitDetectionTransform_ = ol.transform.create(); +}; +ol.inherits(ol.render.canvas.ReplayGroup, ol.render.ReplayGroup); + + +/** + * This cache is used for storing calculated pixel circles for increasing performance. + * It is a static property to allow each Replaygroup to access it. + * @type {Object.<number, Array.<Array.<(boolean|undefined)>>>} + * @private + */ +ol.render.canvas.ReplayGroup.circleArrayCache_ = { + 0: [[true]] +}; + + +/** + * This method fills a row in the array from the given coordinate to the + * middle with `true`. + * @param {Array.<Array.<(boolean|undefined)>>} array The array that will be altered. + * @param {number} x X coordinate. + * @param {number} y Y coordinate. + * @private + */ +ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_ = function(array, x, y) { + var i; + var radius = Math.floor(array.length / 2); + if (x >= radius) { + for (i = radius; i < x; i++) { + array[i][y] = true; + } + } else if (x < radius) { + for (i = x + 1; i < radius; i++) { + array[i][y] = true; + } + } +}; + + +/** + * This methods creates a circle inside a fitting array. Points inside the + * circle are marked by true, points on the outside are undefined. + * It uses the midpoint circle algorithm. + * A cache is used to increase performance. + * @param {number} radius Radius. + * @returns {Array.<Array.<(boolean|undefined)>>} An array with marked circle points. + * @private + */ +ol.render.canvas.ReplayGroup.getCircleArray_ = function(radius) { + if (ol.render.canvas.ReplayGroup.circleArrayCache_[radius] !== undefined) { + return ol.render.canvas.ReplayGroup.circleArrayCache_[radius]; + } + + var arraySize = radius * 2 + 1; + var arr = new Array(arraySize); + for (var i = 0; i < arraySize; i++) { + arr[i] = new Array(arraySize); + } + + var x = radius; + var y = 0; + var error = 0; + + while (x >= y) { + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + x, radius + y); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + y, radius + x); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - y, radius + x); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - x, radius + y); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - x, radius - y); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - y, radius - x); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + y, radius - x); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + x, radius - y); + + y++; + error += 1 + 2 * y; + if (2 * (error - x) + 1 > 0) { + x -= 1; + error += 1 - 2 * x; + } + } + + ol.render.canvas.ReplayGroup.circleArrayCache_[radius] = arr; + return arr; +}; + +/** + * FIXME empty description for jsdoc + */ +ol.render.canvas.ReplayGroup.prototype.finish = function() { + var zKey; + for (zKey in this.replaysByZIndex_) { + var replays = this.replaysByZIndex_[zKey]; + var replayKey; + for (replayKey in replays) { + replays[replayKey].finish(); + } + } +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {number} hitTolerance Hit tolerance in pixels. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature + * callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( + coordinate, resolution, rotation, hitTolerance, skippedFeaturesHash, callback) { + + hitTolerance = Math.round(hitTolerance); + var contextSize = hitTolerance * 2 + 1; + var transform = ol.transform.compose(this.hitDetectionTransform_, + hitTolerance + 0.5, hitTolerance + 0.5, + 1 / resolution, -1 / resolution, + -rotation, + -coordinate[0], -coordinate[1]); + var context = this.hitDetectionContext_; + + if (context.canvas.width !== contextSize || context.canvas.height !== contextSize) { + context.canvas.width = contextSize; + context.canvas.height = contextSize; + } else { + context.clearRect(0, 0, contextSize, contextSize); + } + + /** + * @type {ol.Extent} + */ + var hitExtent; + if (this.renderBuffer_ !== undefined) { + hitExtent = ol.extent.createEmpty(); + ol.extent.extendCoordinate(hitExtent, coordinate); + ol.extent.buffer(hitExtent, resolution * (this.renderBuffer_ + hitTolerance), hitExtent); + } + + var mask = ol.render.canvas.ReplayGroup.getCircleArray_(hitTolerance); + + return this.replayHitDetection_(context, transform, rotation, + skippedFeaturesHash, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var imageData = context.getImageData(0, 0, contextSize, contextSize).data; + for (var i = 0; i < contextSize; i++) { + for (var j = 0; j < contextSize; j++) { + if (mask[i][j]) { + if (imageData[(j * contextSize + i) * 4 + 3] > 0) { + var result = callback(feature); + if (result) { + return result; + } else { + context.clearRect(0, 0, contextSize, contextSize); + return undefined; + } + } + } + } + } + }, hitExtent); +}; + + +/** + * @param {ol.Transform} transform Transform. + * @return {Array.<number>} Clip coordinates. + */ +ol.render.canvas.ReplayGroup.prototype.getClipCoords = function(transform) { + var maxExtent = this.maxExtent_; + var minX = maxExtent[0]; + var minY = maxExtent[1]; + var maxX = maxExtent[2]; + var maxY = maxExtent[3]; + var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY]; + ol.geom.flat.transform.transform2D( + flatClipCoords, 0, 8, 2, transform, flatClipCoords); + return flatClipCoords; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { + var zIndexKey = zIndex !== undefined ? zIndex.toString() : '0'; + var replays = this.replaysByZIndex_[zIndexKey]; + if (replays === undefined) { + replays = {}; + this.replaysByZIndex_[zIndexKey] = replays; + } + var replay = replays[replayType]; + if (replay === undefined) { + var Constructor = ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_[replayType]; + ol.DEBUG && console.assert(Constructor !== undefined, + replayType + + ' constructor missing from ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_'); + replay = new Constructor(this.tolerance_, this.maxExtent_, + this.resolution_, this.overlaps_); + replays[replayType] = replay; + } + return replay; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.ReplayGroup.prototype.isEmpty = function() { + return ol.obj.isEmpty(this.replaysByZIndex_); +}; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {Array.<ol.render.ReplayType>=} opt_replayTypes Ordered replay types + * to replay. Default is {@link ol.render.replay.ORDER} + */ +ol.render.canvas.ReplayGroup.prototype.replay = function(context, pixelRatio, + transform, viewRotation, skippedFeaturesHash, opt_replayTypes) { + + /** @type {Array.<number>} */ + var zs = Object.keys(this.replaysByZIndex_).map(Number); + zs.sort(ol.array.numberSafeCompareFunction); + + // setup clipping so that the parts of over-simplified geometries are not + // visible outside the current extent when panning + var flatClipCoords = this.getClipCoords(transform); + context.save(); + context.beginPath(); + context.moveTo(flatClipCoords[0], flatClipCoords[1]); + context.lineTo(flatClipCoords[2], flatClipCoords[3]); + context.lineTo(flatClipCoords[4], flatClipCoords[5]); + context.lineTo(flatClipCoords[6], flatClipCoords[7]); + context.clip(); + + var replayTypes = opt_replayTypes ? opt_replayTypes : ol.render.replay.ORDER; + var i, ii, j, jj, replays, replay; + for (i = 0, ii = zs.length; i < ii; ++i) { + replays = this.replaysByZIndex_[zs[i].toString()]; + for (j = 0, jj = replayTypes.length; j < jj; ++j) { + replay = replays[replayTypes[j]]; + if (replay !== undefined) { + replay.replay(context, pixelRatio, transform, viewRotation, + skippedFeaturesHash); + } + } + } + + context.restore(); +}; + + +/** + * @private + * @param {CanvasRenderingContext2D} context Context. + * @param {ol.Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T} featureCallback + * Feature callback. + * @param {ol.Extent=} opt_hitExtent Only check features that intersect this + * extent. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function( + context, transform, viewRotation, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + /** @type {Array.<number>} */ + var zs = Object.keys(this.replaysByZIndex_).map(Number); + zs.sort(function(a, b) { + return b - a; + }); + + var i, ii, j, replays, replay, result; + for (i = 0, ii = zs.length; i < ii; ++i) { + replays = this.replaysByZIndex_[zs[i].toString()]; + for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) { + replay = replays[ol.render.replay.ORDER[j]]; + if (replay !== undefined) { + result = replay.replayHitDetection(context, transform, viewRotation, + skippedFeaturesHash, featureCallback, opt_hitExtent); + if (result) { + return result; + } + } + } + } + return undefined; +}; + + +/** + * @const + * @private + * @type {Object.<ol.render.ReplayType, + * function(new: ol.render.canvas.Replay, number, ol.Extent, + * number, boolean)>} + */ +ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_ = { + 'Circle': ol.render.canvas.PolygonReplay, + 'Image': ol.render.canvas.ImageReplay, + 'LineString': ol.render.canvas.LineStringReplay, + 'Polygon': ol.render.canvas.PolygonReplay, + 'Text': ol.render.canvas.TextReplay +}; + +goog.provide('ol.renderer.vector'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.render.ReplayType'); + + +/** + * @param {ol.Feature|ol.render.Feature} feature1 Feature 1. + * @param {ol.Feature|ol.render.Feature} feature2 Feature 2. + * @return {number} Order. + */ +ol.renderer.vector.defaultOrder = function(feature1, feature2) { + return ol.getUid(feature1) - ol.getUid(feature2); +}; + + +/** + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @return {number} Squared pixel tolerance. + */ +ol.renderer.vector.getSquaredTolerance = function(resolution, pixelRatio) { + var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio); + return tolerance * tolerance; +}; + + +/** + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @return {number} Pixel tolerance. + */ +ol.renderer.vector.getTolerance = function(resolution, pixelRatio) { + return ol.SIMPLIFY_TOLERANCE * resolution / pixelRatio; +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.Circle} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderCircleGeometry_ = function(replayGroup, geometry, style, feature) { + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + if (fillStyle || strokeStyle) { + var circleReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.CIRCLE); + circleReplay.setFillStrokeStyle(fillStyle, strokeStyle); + circleReplay.drawCircle(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + textReplay.drawText(geometry.getCenter(), 0, 2, 2, geometry, feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.style.Style} style Style. + * @param {number} squaredTolerance Squared tolerance. + * @param {function(this: T, ol.events.Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @return {boolean} `true` if style is loading. + * @template T + */ +ol.renderer.vector.renderFeature = function( + replayGroup, feature, style, squaredTolerance, listener, thisArg) { + var loading = false; + var imageStyle, imageState; + imageStyle = style.getImage(); + if (imageStyle) { + imageState = imageStyle.getImageState(); + if (imageState == ol.Image.State.LOADED || + imageState == ol.Image.State.ERROR) { + imageStyle.unlistenImageChange(listener, thisArg); + } else { + if (imageState == ol.Image.State.IDLE) { + imageStyle.load(); + } + imageState = imageStyle.getImageState(); + ol.DEBUG && console.assert(imageState == ol.Image.State.LOADING, + 'imageState should be LOADING'); + imageStyle.listenImageChange(listener, thisArg); + loading = true; + } + } + ol.renderer.vector.renderFeature_(replayGroup, feature, style, + squaredTolerance); + return loading; +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.style.Style} style Style. + * @param {number} squaredTolerance Squared tolerance. + * @private + */ +ol.renderer.vector.renderFeature_ = function( + replayGroup, feature, style, squaredTolerance) { + var geometry = style.getGeometryFunction()(feature); + if (!geometry) { + return; + } + var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance); + var geometryRenderer = + ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()]; + geometryRenderer(replayGroup, simplifiedGeometry, style, feature); +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.GeometryCollection} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderGeometryCollectionGeometry_ = function(replayGroup, geometry, style, feature) { + var geometries = geometry.getGeometriesArray(); + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + var geometryRenderer = + ol.renderer.vector.GEOMETRY_RENDERERS_[geometries[i].getType()]; + geometryRenderer(replayGroup, geometries[i], style, feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.LineString|ol.render.Feature} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderLineStringGeometry_ = function(replayGroup, geometry, style, feature) { + var strokeStyle = style.getStroke(); + if (strokeStyle) { + var lineStringReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.LINE_STRING); + lineStringReplay.setFillStrokeStyle(null, strokeStyle); + lineStringReplay.drawLineString(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + textReplay.drawText(geometry.getFlatMidpoint(), 0, 2, 2, geometry, feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.MultiLineString|ol.render.Feature} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderMultiLineStringGeometry_ = function(replayGroup, geometry, style, feature) { + var strokeStyle = style.getStroke(); + if (strokeStyle) { + var lineStringReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.LINE_STRING); + lineStringReplay.setFillStrokeStyle(null, strokeStyle); + lineStringReplay.drawMultiLineString(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + var flatMidpointCoordinates = geometry.getFlatMidpoints(); + textReplay.drawText(flatMidpointCoordinates, 0, + flatMidpointCoordinates.length, 2, geometry, feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.MultiPolygon} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderMultiPolygonGeometry_ = function(replayGroup, geometry, style, feature) { + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + if (strokeStyle || fillStyle) { + var polygonReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.POLYGON); + polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); + polygonReplay.drawMultiPolygon(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + var flatInteriorPointCoordinates = geometry.getFlatInteriorPoints(); + textReplay.drawText(flatInteriorPointCoordinates, 0, + flatInteriorPointCoordinates.length, 2, geometry, feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.Point|ol.render.Feature} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderPointGeometry_ = function(replayGroup, geometry, style, feature) { + var imageStyle = style.getImage(); + if (imageStyle) { + if (imageStyle.getImageState() != ol.Image.State.LOADED) { + return; + } + var imageReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.IMAGE); + imageReplay.setImageStyle(imageStyle); + imageReplay.drawPoint(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + textReplay.drawText(geometry.getFlatCoordinates(), 0, 2, 2, geometry, + feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.MultiPoint|ol.render.Feature} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderMultiPointGeometry_ = function(replayGroup, geometry, style, feature) { + var imageStyle = style.getImage(); + if (imageStyle) { + if (imageStyle.getImageState() != ol.Image.State.LOADED) { + return; + } + var imageReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.IMAGE); + imageReplay.setImageStyle(imageStyle); + imageReplay.drawMultiPoint(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + var flatCoordinates = geometry.getFlatCoordinates(); + textReplay.drawText(flatCoordinates, 0, flatCoordinates.length, + geometry.getStride(), geometry, feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.Polygon|ol.render.Feature} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderPolygonGeometry_ = function(replayGroup, geometry, style, feature) { + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + if (fillStyle || strokeStyle) { + var polygonReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.POLYGON); + polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); + polygonReplay.drawPolygon(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + textReplay.drawText( + geometry.getFlatInteriorPoint(), 0, 2, 2, geometry, feature); + } +}; + + +/** + * @const + * @private + * @type {Object.<ol.geom.GeometryType, + * function(ol.render.ReplayGroup, ol.geom.Geometry, + * ol.style.Style, Object)>} + */ +ol.renderer.vector.GEOMETRY_RENDERERS_ = { + 'Point': ol.renderer.vector.renderPointGeometry_, + 'LineString': ol.renderer.vector.renderLineStringGeometry_, + 'Polygon': ol.renderer.vector.renderPolygonGeometry_, + 'MultiPoint': ol.renderer.vector.renderMultiPointGeometry_, + 'MultiLineString': ol.renderer.vector.renderMultiLineStringGeometry_, + 'MultiPolygon': ol.renderer.vector.renderMultiPolygonGeometry_, + 'GeometryCollection': ol.renderer.vector.renderGeometryCollectionGeometry_, + 'Circle': ol.renderer.vector.renderCircleGeometry_ +}; + +goog.provide('ol.renderer.canvas.VectorLayer'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.render.Event'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.ReplayGroup'); +goog.require('ol.renderer.canvas.Layer'); +goog.require('ol.renderer.vector'); + + +/** + * @constructor + * @extends {ol.renderer.canvas.Layer} + * @param {ol.layer.Vector} vectorLayer Vector layer. + */ +ol.renderer.canvas.VectorLayer = function(vectorLayer) { + + ol.renderer.canvas.Layer.call(this, vectorLayer); + + /** + * @private + * @type {boolean} + */ + this.dirty_ = false; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = -1; + + /** + * @private + * @type {number} + */ + this.renderedResolution_ = NaN; + + /** + * @private + * @type {ol.Extent} + */ + this.renderedExtent_ = ol.extent.createEmpty(); + + /** + * @private + * @type {function(ol.Feature, ol.Feature): number|null} + */ + this.renderedRenderOrder_ = null; + + /** + * @private + * @type {ol.render.canvas.ReplayGroup} + */ + this.replayGroup_ = null; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = ol.dom.createCanvasContext2D(); + +}; +ol.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer); + + +/** + * @inheritDoc + */ +ol.renderer.canvas.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) { + + var extent = frameState.extent; + var pixelRatio = frameState.pixelRatio; + var skippedFeatureUids = layerState.managed ? + frameState.skippedFeatureUids : {}; + var viewState = frameState.viewState; + var projection = viewState.projection; + var rotation = viewState.rotation; + var projectionExtent = projection.getExtent(); + var vectorSource = /** @type {ol.source.Vector} */ (this.getLayer().getSource()); + + var transform = this.getTransform(frameState, 0); + + this.preCompose(context, frameState, transform); + + // clipped rendering if layer extent is set + var clipExtent = layerState.extent; + var clipped = clipExtent !== undefined; + if (clipped) { + this.clip(context, frameState, /** @type {ol.Extent} */ (clipExtent)); + } + var replayGroup = this.replayGroup_; + if (replayGroup && !replayGroup.isEmpty()) { + var layer = this.getLayer(); + var drawOffsetX = 0; + var drawOffsetY = 0; + var replayContext; + if (layer.hasListener(ol.render.Event.Type.RENDER)) { + var drawWidth = context.canvas.width; + var drawHeight = context.canvas.height; + if (rotation) { + var drawSize = Math.round(Math.sqrt(drawWidth * drawWidth + drawHeight * drawHeight)); + drawOffsetX = (drawSize - drawWidth) / 2; + drawOffsetY = (drawSize - drawHeight) / 2; + drawWidth = drawHeight = drawSize; + } + // resize and clear + this.context_.canvas.width = drawWidth; + this.context_.canvas.height = drawHeight; + replayContext = this.context_; + } else { + replayContext = context; + } + // for performance reasons, context.save / context.restore is not used + // to save and restore the transformation matrix and the opacity. + // see http://jsperf.com/context-save-restore-versus-variable + var alpha = replayContext.globalAlpha; + replayContext.globalAlpha = layerState.opacity; + if (replayContext != context) { + replayContext.translate(drawOffsetX, drawOffsetY); + } + + var width = frameState.size[0] * pixelRatio; + var height = frameState.size[1] * pixelRatio; + ol.render.canvas.rotateAtOffset(replayContext, -rotation, + width / 2, height / 2); + replayGroup.replay(replayContext, pixelRatio, transform, rotation, + skippedFeatureUids); + if (vectorSource.getWrapX() && projection.canWrapX() && + !ol.extent.containsExtent(projectionExtent, extent)) { + var startX = extent[0]; + var worldWidth = ol.extent.getWidth(projectionExtent); + var world = 0; + var offsetX; + while (startX < projectionExtent[0]) { + --world; + offsetX = worldWidth * world; + transform = this.getTransform(frameState, offsetX); + replayGroup.replay(replayContext, pixelRatio, transform, rotation, + skippedFeatureUids); + startX += worldWidth; + } + world = 0; + startX = extent[2]; + while (startX > projectionExtent[2]) { + ++world; + offsetX = worldWidth * world; + transform = this.getTransform(frameState, offsetX); + replayGroup.replay(replayContext, pixelRatio, transform, rotation, + skippedFeatureUids); + startX -= worldWidth; + } + // restore original transform for render and compose events + transform = this.getTransform(frameState, 0); + } + ol.render.canvas.rotateAtOffset(replayContext, rotation, + width / 2, height / 2); + + if (replayContext != context) { + this.dispatchRenderEvent(replayContext, frameState, transform); + context.drawImage(replayContext.canvas, -drawOffsetX, -drawOffsetY); + replayContext.translate(-drawOffsetX, -drawOffsetY); + } + replayContext.globalAlpha = alpha; + } + + if (clipped) { + context.restore(); + } + this.postCompose(context, frameState, layerState, transform); + +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { + if (!this.replayGroup_) { + return undefined; + } else { + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var layer = this.getLayer(); + /** @type {Object.<string, boolean>} */ + var features = {}; + return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution, + rotation, hitTolerance, {}, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var key = ol.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); + } +}; + + +/** + * Handle changes in image style state. + * @param {ol.events.Event} event Image style change event. + * @private + */ +ol.renderer.canvas.VectorLayer.prototype.handleStyleImageChange_ = function(event) { + this.renderIfReadyAndVisible(); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.VectorLayer.prototype.prepareFrame = function(frameState, layerState) { + + var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); + var vectorSource = vectorLayer.getSource(); + + this.updateAttributions( + frameState.attributions, vectorSource.getAttributions()); + this.updateLogos(frameState, vectorSource); + + var animating = frameState.viewHints[ol.View.Hint.ANIMATING]; + var interacting = frameState.viewHints[ol.View.Hint.INTERACTING]; + var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating(); + var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting(); + + if (!this.dirty_ && (!updateWhileAnimating && animating) || + (!updateWhileInteracting && interacting)) { + return true; + } + + var frameStateExtent = frameState.extent; + var viewState = frameState.viewState; + var projection = viewState.projection; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var vectorLayerRevision = vectorLayer.getRevision(); + var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer(); + var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); + + if (vectorLayerRenderOrder === undefined) { + vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; + } + + var extent = ol.extent.buffer(frameStateExtent, + vectorLayerRenderBuffer * resolution); + var projectionExtent = viewState.projection.getExtent(); + + if (vectorSource.getWrapX() && viewState.projection.canWrapX() && + !ol.extent.containsExtent(projectionExtent, frameState.extent)) { + // For the replay group, we need an extent that intersects the real world + // (-180° to +180°). To support geometries in a coordinate range from -540° + // to +540°, we add at least 1 world width on each side of the projection + // extent. If the viewport is wider than the world, we need to add half of + // the viewport width to make sure we cover the whole viewport. + var worldWidth = ol.extent.getWidth(projectionExtent); + var buffer = Math.max(ol.extent.getWidth(extent) / 2, worldWidth); + extent[0] = projectionExtent[0] - buffer; + extent[2] = projectionExtent[2] + buffer; + } + + if (!this.dirty_ && + this.renderedResolution_ == resolution && + this.renderedRevision_ == vectorLayerRevision && + this.renderedRenderOrder_ == vectorLayerRenderOrder && + ol.extent.containsExtent(this.renderedExtent_, extent)) { + return true; + } + + this.replayGroup_ = null; + + this.dirty_ = false; + + var replayGroup = + new ol.render.canvas.ReplayGroup( + ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, + resolution, vectorSource.getOverlaps(), vectorLayer.getRenderBuffer()); + vectorSource.loadFeatures(extent, resolution, projection); + /** + * @param {ol.Feature} feature Feature. + * @this {ol.renderer.canvas.VectorLayer} + */ + var renderFeature = function(feature) { + var styles; + var styleFunction = feature.getStyleFunction(); + if (styleFunction) { + styles = styleFunction.call(feature, resolution); + } else { + styleFunction = vectorLayer.getStyleFunction(); + if (styleFunction) { + styles = styleFunction(feature, resolution); + } + } + if (styles) { + var dirty = this.renderFeature( + feature, resolution, pixelRatio, styles, replayGroup); + this.dirty_ = this.dirty_ || dirty; + } + }; + if (vectorLayerRenderOrder) { + /** @type {Array.<ol.Feature>} */ + var features = []; + vectorSource.forEachFeatureInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + features.push(feature); + }, this); + features.sort(vectorLayerRenderOrder); + features.forEach(renderFeature, this); + } else { + vectorSource.forEachFeatureInExtent(extent, renderFeature, this); + } + replayGroup.finish(); + + this.renderedResolution_ = resolution; + this.renderedRevision_ = vectorLayerRevision; + this.renderedRenderOrder_ = vectorLayerRenderOrder; + this.renderedExtent_ = extent; + this.replayGroup_ = replayGroup; + + return true; +}; + + +/** + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of + * styles. + * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + */ +ol.renderer.canvas.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) { + if (!styles) { + return false; + } + var loading = false; + if (Array.isArray(styles)) { + for (var i = 0, ii = styles.length; i < ii; ++i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleStyleImageChange_, this) || loading; + } + } else { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles, + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleStyleImageChange_, this) || loading; + } + return loading; +}; + +goog.provide('ol.renderer.canvas.VectorTileLayer'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.proj'); +goog.require('ol.proj.Units'); +goog.require('ol.layer.VectorTile'); +goog.require('ol.render.ReplayType'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.ReplayGroup'); +goog.require('ol.render.replay'); +goog.require('ol.renderer.canvas.TileLayer'); +goog.require('ol.renderer.vector'); +goog.require('ol.size'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.renderer.canvas.TileLayer} + * @param {ol.layer.VectorTile} layer VectorTile layer. + */ +ol.renderer.canvas.VectorTileLayer = function(layer) { + + ol.renderer.canvas.TileLayer.call(this, layer); + + /** + * @private + * @type {boolean} + */ + this.dirty_ = false; + + /** + * @private + * @type {ol.Transform} + */ + this.tmpTransform_ = ol.transform.create(); + + // Use lower resolution for pure vector rendering. Closest resolution otherwise. + this.zDirection = + layer.getRenderMode() == ol.layer.VectorTile.RenderType.VECTOR ? 1 : 0; + +}; +ol.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer); + + +/** + * @const + * @type {!Object.<string, Array.<ol.render.ReplayType>>} + */ +ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS = { + 'image': ol.render.replay.ORDER, + 'hybrid': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.LINE_STRING] +}; + + +/** + * @const + * @type {!Object.<string, Array.<ol.render.ReplayType>>} + */ +ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS = { + 'hybrid': [ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT], + 'vector': ol.render.replay.ORDER +}; + + +/** + * @param {ol.VectorTile} tile Tile. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(tile, + frameState) { + var layer = this.getLayer(); + var pixelRatio = frameState.pixelRatio; + var projection = frameState.viewState.projection; + var revision = layer.getRevision(); + var renderOrder = layer.getRenderOrder() || null; + + var replayState = tile.getReplayState(); + if (!replayState.dirty && replayState.renderedRevision == revision && + replayState.renderedRenderOrder == renderOrder) { + return; + } + + replayState.replayGroup = null; + replayState.dirty = false; + + var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); + var tileGrid = source.getTileGrid(); + var tileCoord = tile.tileCoord; + var tileProjection = tile.getProjection(); + var resolution = tileGrid.getResolution(tileCoord[0]); + var extent, reproject, tileResolution; + if (tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS) { + var tilePixelRatio = tileResolution = source.getTilePixelRatio(); + var tileSize = ol.size.toSize(tileGrid.getTileSize(tileCoord[0])); + extent = [0, 0, tileSize[0] * tilePixelRatio, tileSize[1] * tilePixelRatio]; + } else { + tileResolution = resolution; + extent = tileGrid.getTileCoordExtent(tileCoord); + if (!ol.proj.equivalent(projection, tileProjection)) { + reproject = true; + tile.setProjection(projection); + } + } + replayState.dirty = false; + var replayGroup = new ol.render.canvas.ReplayGroup(0, extent, + tileResolution, source.getOverlaps(), layer.getRenderBuffer()); + var squaredTolerance = ol.renderer.vector.getSquaredTolerance( + tileResolution, pixelRatio); + + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @this {ol.renderer.canvas.VectorTileLayer} + */ + function renderFeature(feature) { + var styles; + var styleFunction = feature.getStyleFunction(); + if (styleFunction) { + styles = styleFunction.call(/** @type {ol.Feature} */ (feature), resolution); + } else { + styleFunction = layer.getStyleFunction(); + if (styleFunction) { + styles = styleFunction(feature, resolution); + } + } + if (styles) { + if (!Array.isArray(styles)) { + styles = [styles]; + } + var dirty = this.renderFeature(feature, squaredTolerance, styles, + replayGroup); + this.dirty_ = this.dirty_ || dirty; + replayState.dirty = replayState.dirty || dirty; + } + } + + var features = tile.getFeatures(); + if (renderOrder && renderOrder !== replayState.renderedRenderOrder) { + features.sort(renderOrder); + } + var feature; + for (var i = 0, ii = features.length; i < ii; ++i) { + feature = features[i]; + if (reproject) { + feature.getGeometry().transform(tileProjection, projection); + } + renderFeature.call(this, feature); + } + + replayGroup.finish(); + + replayState.renderedRevision = revision; + replayState.renderedRenderOrder = renderOrder; + replayState.replayGroup = replayGroup; + replayState.resolution = NaN; +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.VectorTileLayer.prototype.drawTileImage = function( + tile, frameState, layerState, x, y, w, h, gutter) { + var vectorTile = /** @type {ol.VectorTile} */ (tile); + this.createReplayGroup_(vectorTile, frameState); + var layer = this.getLayer(); + if (layer.getRenderMode() != ol.layer.VectorTile.RenderType.VECTOR) { + this.renderTileImage_(vectorTile, frameState, layerState); + } + ol.renderer.canvas.TileLayer.prototype.drawTileImage.apply(this, arguments); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + hitTolerance = hitTolerance == undefined ? 0 : hitTolerance; + var layer = this.getLayer(); + /** @type {Object.<string, boolean>} */ + var features = {}; + + /** @type {Array.<ol.VectorTile>} */ + var replayables = this.renderedTiles; + + var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); + var tileGrid = source.getTileGrid(); + var found, tileSpaceCoordinate; + var i, ii, origin, replayGroup; + var tile, tileCoord, tileExtent, tilePixelRatio, tileResolution; + for (i = 0, ii = replayables.length; i < ii; ++i) { + tile = replayables[i]; + tileCoord = tile.tileCoord; + tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord, this.tmpExtent); + if (!ol.extent.containsCoordinate(ol.extent.buffer(tileExtent, hitTolerance * resolution), coordinate)) { + continue; + } + if (tile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) { + origin = ol.extent.getTopLeft(tileExtent); + tilePixelRatio = source.getTilePixelRatio(); + tileResolution = tileGrid.getResolution(tileCoord[0]) / tilePixelRatio; + tileSpaceCoordinate = [ + (coordinate[0] - origin[0]) / tileResolution, + (origin[1] - coordinate[1]) / tileResolution + ]; + resolution = tilePixelRatio; + } else { + tileSpaceCoordinate = coordinate; + } + replayGroup = tile.getReplayState().replayGroup; + found = found || replayGroup.forEachFeatureAtCoordinate( + tileSpaceCoordinate, resolution, rotation, hitTolerance, {}, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var key = ol.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); + } + return found; +}; + + +/** + * @param {ol.Tile} tile Tile. + * @param {olx.FrameState} frameState Frame state. + * @return {ol.Transform} transform Transform. + * @private + */ +ol.renderer.canvas.VectorTileLayer.prototype.getReplayTransform_ = function(tile, frameState) { + if (tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) { + var layer = this.getLayer(); + var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); + var tileGrid = source.getTileGrid(); + var tileCoord = tile.tileCoord; + var tileResolution = + tileGrid.getResolution(tileCoord[0]) / source.getTilePixelRatio(); + var viewState = frameState.viewState; + var pixelRatio = frameState.pixelRatio; + var renderResolution = viewState.resolution / pixelRatio; + var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent); + var center = viewState.center; + var origin = ol.extent.getTopLeft(tileExtent); + var size = frameState.size; + var offsetX = Math.round(pixelRatio * size[0] / 2); + var offsetY = Math.round(pixelRatio * size[1] / 2); + return ol.transform.compose(this.tmpTransform_, + offsetX, offsetY, + tileResolution / renderResolution, tileResolution / renderResolution, + viewState.rotation, + (origin[0] - center[0]) / tileResolution, + (center[1] - origin[1]) / tileResolution); + } else { + return this.getTransform(frameState, 0); + } +}; + + +/** + * Handle changes in image style state. + * @param {ol.events.Event} event Image style change event. + * @private + */ +ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function(event) { + this.renderIfReadyAndVisible(); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, frameState, layerState) { + var renderMode = this.getLayer().getRenderMode(); + var replays = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode]; + if (replays) { + var pixelRatio = frameState.pixelRatio; + var rotation = frameState.viewState.rotation; + var size = frameState.size; + var offsetX = Math.round(pixelRatio * size[0] / 2); + var offsetY = Math.round(pixelRatio * size[1] / 2); + var tiles = this.renderedTiles; + var clips = []; + var zs = []; + for (var i = tiles.length - 1; i >= 0; --i) { + var tile = /** @type {ol.VectorTile} */ (tiles[i]); + // Create a clip mask for regions in this low resolution tile that are + // already filled by a higher resolution tile + var transform = this.getReplayTransform_(tile, frameState); + var currentClip = tile.getReplayState().replayGroup.getClipCoords(transform); + var currentZ = tile.tileCoord[0]; + context.save(); + context.globalAlpha = layerState.opacity; + ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY); + for (var j = 0, jj = clips.length; j < jj; ++j) { + var clip = clips[j]; + if (currentZ < zs[j]) { + context.beginPath(); + // counter-clockwise (outer ring) for current tile + context.moveTo(currentClip[0], currentClip[1]); + context.lineTo(currentClip[2], currentClip[3]); + context.lineTo(currentClip[4], currentClip[5]); + context.lineTo(currentClip[6], currentClip[7]); + // clockwise (inner ring) for higher resolution tile + context.moveTo(clip[6], clip[7]); + context.lineTo(clip[4], clip[5]); + context.lineTo(clip[2], clip[3]); + context.lineTo(clip[0], clip[1]); + context.clip(); + } + } + var replayGroup = tile.getReplayState().replayGroup; + replayGroup.replay(context, pixelRatio, transform, rotation, {}, replays); + context.restore(); + clips.push(currentClip); + zs.push(currentZ); + } + } + ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments); +}; + + +/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {number} squaredTolerance Squared tolerance. + * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of + * styles. + * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + */ +ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, squaredTolerance, styles, replayGroup) { + if (!styles) { + return false; + } + var loading = false; + if (Array.isArray(styles)) { + for (var i = 0, ii = styles.length; i < ii; ++i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], squaredTolerance, + this.handleStyleImageChange_, this) || loading; + } + } else { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles, squaredTolerance, + this.handleStyleImageChange_, this) || loading; + } + return loading; +}; + + +/** + * @param {ol.VectorTile} tile Tile. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @private + */ +ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function( + tile, frameState, layerState) { + var layer = this.getLayer(); + var replayState = tile.getReplayState(); + var revision = layer.getRevision(); + var replays = ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS[layer.getRenderMode()]; + if (replays && replayState.renderedTileRevision !== revision) { + replayState.renderedTileRevision = revision; + var tileCoord = tile.tileCoord; + var z = tile.tileCoord[0]; + var pixelRatio = frameState.pixelRatio; + var source = layer.getSource(); + var tileGrid = source.getTileGrid(); + var tilePixelRatio = source.getTilePixelRatio(); + var transform = ol.transform.reset(this.tmpTransform_); + if (tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) { + var renderPixelRatio = pixelRatio / tilePixelRatio; + ol.transform.scale(transform, renderPixelRatio, renderPixelRatio); + } else { + var resolution = tileGrid.getResolution(z); + var pixelScale = pixelRatio / resolution; + var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent); + ol.transform.scale(transform, pixelScale, -pixelScale); + ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]); + } + + var context = tile.getContext(); + var size = source.getTilePixelSize(z, pixelRatio, frameState.viewState.projection); + context.canvas.width = size[0]; + context.canvas.height = size[1]; + replayState.replayGroup.replay(context, pixelRatio, transform, 0, {}, replays); + } +}; + +// FIXME offset panning + +goog.provide('ol.renderer.canvas.Map'); + +goog.require('ol.transform'); +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.css'); +goog.require('ol.dom'); +goog.require('ol.layer.Image'); +goog.require('ol.layer.Layer'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); +goog.require('ol.layer.VectorTile'); +goog.require('ol.render.Event'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.Immediate'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.Type'); +goog.require('ol.renderer.canvas.ImageLayer'); +goog.require('ol.renderer.canvas.TileLayer'); +goog.require('ol.renderer.canvas.VectorLayer'); +goog.require('ol.renderer.canvas.VectorTileLayer'); +goog.require('ol.source.State'); + + +/** + * @constructor + * @extends {ol.renderer.Map} + * @param {Element} container Container. + * @param {ol.Map} map Map. + */ +ol.renderer.canvas.Map = function(container, map) { + + ol.renderer.Map.call(this, container, map); + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = this.context_.canvas; + + this.canvas_.style.width = '100%'; + this.canvas_.style.height = '100%'; + this.canvas_.className = ol.css.CLASS_UNSELECTABLE; + container.insertBefore(this.canvas_, container.childNodes[0] || null); + + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = true; + + /** + * @private + * @type {ol.Transform} + */ + this.transform_ = ol.transform.create(); + +}; +ol.inherits(ol.renderer.canvas.Map, ol.renderer.Map); + + +/** + * @inheritDoc + */ +ol.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) { + if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) { + return new ol.renderer.canvas.ImageLayer(layer); + } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) { + return new ol.renderer.canvas.TileLayer(layer); + } else if (ol.ENABLE_VECTOR_TILE && layer instanceof ol.layer.VectorTile) { + return new ol.renderer.canvas.VectorTileLayer(layer); + } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) { + return new ol.renderer.canvas.VectorLayer(layer); + } else { + ol.DEBUG && console.assert(false, 'unexpected layer configuration'); + return null; + } +}; + + +/** + * @param {ol.render.Event.Type} type Event type. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ = function(type, frameState) { + var map = this.getMap(); + var context = this.context_; + if (map.hasListener(type)) { + var extent = frameState.extent; + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var rotation = viewState.rotation; + + var transform = this.getTransform(frameState); + + var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio, + extent, transform, rotation); + var composeEvent = new ol.render.Event(type, vectorContext, + frameState, context, null); + map.dispatchEvent(composeEvent); + } +}; + + +/** + * @param {olx.FrameState} frameState Frame state. + * @protected + * @return {!ol.Transform} Transform. + */ +ol.renderer.canvas.Map.prototype.getTransform = function(frameState) { + var viewState = frameState.viewState; + var dx1 = this.canvas_.width / 2; + var dy1 = this.canvas_.height / 2; + var sx = frameState.pixelRatio / viewState.resolution; + var sy = -sx; + var angle = -viewState.rotation; + var dx2 = -viewState.center[0]; + var dy2 = -viewState.center[1]; + return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.Map.prototype.getType = function() { + return ol.renderer.Type.CANVAS; +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) { + + if (!frameState) { + if (this.renderedVisible_) { + this.canvas_.style.display = 'none'; + this.renderedVisible_ = false; + } + return; + } + + var context = this.context_; + var pixelRatio = frameState.pixelRatio; + var width = Math.round(frameState.size[0] * pixelRatio); + var height = Math.round(frameState.size[1] * pixelRatio); + if (this.canvas_.width != width || this.canvas_.height != height) { + this.canvas_.width = width; + this.canvas_.height = height; + } else { + context.clearRect(0, 0, width, height); + } + + var rotation = frameState.viewState.rotation; + + this.calculateMatrices2D(frameState); + + this.dispatchComposeEvent_(ol.render.Event.Type.PRECOMPOSE, frameState); + + var layerStatesArray = frameState.layerStatesArray; + ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex); + + ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2); + + var viewResolution = frameState.viewState.resolution; + var i, ii, layer, layerRenderer, layerState; + for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerState = layerStatesArray[i]; + layer = layerState.layer; + layerRenderer = /** @type {ol.renderer.canvas.Layer} */ (this.getLayerRenderer(layer)); + if (!ol.layer.Layer.visibleAtResolution(layerState, viewResolution) || + layerState.sourceState != ol.source.State.READY) { + continue; + } + if (layerRenderer.prepareFrame(frameState, layerState)) { + layerRenderer.composeFrame(frameState, layerState, context); + } + } + + ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2); + + this.dispatchComposeEvent_( + ol.render.Event.Type.POSTCOMPOSE, frameState); + + if (!this.renderedVisible_) { + this.canvas_.style.display = ''; + this.renderedVisible_ = true; + } + + this.scheduleRemoveUnusedLayerRenderers(frameState); + this.scheduleExpireIconCache(frameState); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg, + layerFilter, thisArg2) { + var result; + var viewState = frameState.viewState; + var viewResolution = viewState.resolution; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + + var coordinate = ol.transform.apply( + frameState.pixelToCoordinateTransform, pixel.slice()); + + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = /** @type {ol.renderer.canvas.Layer} */ (this.getLayerRenderer(layer)); + result = layerRenderer.forEachLayerAtCoordinate( + coordinate, frameState, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; +}; + +goog.provide('ol.render.webgl'); + +/** + * @const + * @type {ol.Color} + */ +ol.render.webgl.defaultFillStyle = [0.0, 0.0, 0.0, 1.0]; + +/** + * @const + * @type {string} + */ +ol.render.webgl.defaultLineCap = 'round'; + + +/** + * @const + * @type {Array.<number>} + */ +ol.render.webgl.defaultLineDash = []; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.defaultLineJoin = 'round'; + + +/** + * @const + * @type {number} + */ +ol.render.webgl.defaultMiterLimit = 10; + +/** + * @const + * @type {ol.Color} + */ +ol.render.webgl.defaultStrokeStyle = [0.0, 0.0, 0.0, 1.0]; + +/** + * @const + * @type {number} + */ +ol.render.webgl.defaultLineWidth = 1; + +/** + * @enum {number} + */ +ol.render.webgl.lineStringInstruction = { + ROUND: 2, + BEGIN_LINE: 3, + END_LINE: 5, + BEGIN_LINE_CAP: 7, + END_LINE_CAP : 11, + BEVEL_FIRST: 13, + BEVEL_SECOND: 17, + MITER_BOTTOM: 19, + MITER_TOP: 23 +}; + +/** + * Calculates the orientation of a triangle based on the determinant method. + * @param {number} x1 First X coordinate. + * @param {number} y1 First Y coordinate. + * @param {number} x2 Second X coordinate. + * @param {number} y2 Second Y coordinate. + * @param {number} x3 Third X coordinate. + * @param {number} y3 Third Y coordinate. + * @return {boolean|undefined} Triangle is clockwise. + */ +ol.render.webgl.triangleIsCounterClockwise = function(x1, y1, x2, y2, x3, y3) { + var area = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1); + return (area <= ol.render.webgl.EPSILON && area >= -ol.render.webgl.EPSILON) ? + undefined : area > 0; +}; + +/** + * @const + * @type {number} + */ +ol.render.webgl.EPSILON = Number.EPSILON || 2.220446049250313e-16; + +goog.provide('ol.webgl.Shader'); + +goog.require('ol.functions'); +goog.require('ol.webgl'); + + +/** + * @constructor + * @param {string} source Source. + * @struct + */ +ol.webgl.Shader = function(source) { + + /** + * @private + * @type {string} + */ + this.source_ = source; + +}; + + +/** + * @abstract + * @return {number} Type. + */ +ol.webgl.Shader.prototype.getType = function() {}; + + +/** + * @return {string} Source. + */ +ol.webgl.Shader.prototype.getSource = function() { + return this.source_; +}; + + +/** + * @return {boolean} Is animated? + */ +ol.webgl.Shader.prototype.isAnimated = ol.functions.FALSE; + +goog.provide('ol.webgl.Fragment'); + +goog.require('ol'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Shader'); + + +/** + * @constructor + * @extends {ol.webgl.Shader} + * @param {string} source Source. + * @struct + */ +ol.webgl.Fragment = function(source) { + ol.webgl.Shader.call(this, source); +}; +ol.inherits(ol.webgl.Fragment, ol.webgl.Shader); + + +/** + * @inheritDoc + */ +ol.webgl.Fragment.prototype.getType = function() { + return ol.webgl.FRAGMENT_SHADER; +}; + +goog.provide('ol.webgl.Vertex'); + +goog.require('ol'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Shader'); + + +/** + * @constructor + * @extends {ol.webgl.Shader} + * @param {string} source Source. + * @struct + */ +ol.webgl.Vertex = function(source) { + ol.webgl.Shader.call(this, source); +}; +ol.inherits(ol.webgl.Vertex, ol.webgl.Shader); + + +/** + * @inheritDoc + */ +ol.webgl.Vertex.prototype.getType = function() { + return ol.webgl.VERTEX_SHADER; +}; + +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.circlereplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.circlereplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.circlereplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.circlereplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_center;\nvarying vec2 v_offset;\nvarying float v_halfWidth;\nvarying float v_pixelRatio;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_fillColor;\nuniform vec4 u_strokeColor;\nuniform vec2 u_size;\n\nvoid main(void) {\n vec2 windowCenter = vec2((v_center.x + 1.0) / 2.0 * u_size.x * v_pixelRatio,\n (v_center.y + 1.0) / 2.0 * u_size.y * v_pixelRatio);\n vec2 windowOffset = vec2((v_offset.x + 1.0) / 2.0 * u_size.x * v_pixelRatio,\n (v_offset.y + 1.0) / 2.0 * u_size.y * v_pixelRatio);\n float radius = length(windowCenter - windowOffset);\n float dist = length(windowCenter - gl_FragCoord.xy);\n if (dist > radius + v_halfWidth) {\n if (u_strokeColor.a == 0.0) {\n gl_FragColor = u_fillColor;\n } else {\n gl_FragColor = u_strokeColor;\n }\n gl_FragColor.a = gl_FragColor.a - (dist - (radius + v_halfWidth));\n } else if (u_fillColor.a == 0.0) {\n // Hooray, no fill, just stroke. We can use real antialiasing.\n gl_FragColor = u_strokeColor;\n if (dist < radius - v_halfWidth) {\n gl_FragColor.a = gl_FragColor.a - (radius - v_halfWidth - dist);\n }\n } else {\n gl_FragColor = u_fillColor;\n float strokeDist = radius - v_halfWidth;\n float antialias = 2.0 * v_pixelRatio;\n if (dist > strokeDist) {\n gl_FragColor = u_strokeColor;\n } else if (dist >= strokeDist - antialias) {\n float step = smoothstep(strokeDist - antialias, strokeDist, dist);\n gl_FragColor = mix(u_fillColor, u_strokeColor, step);\n }\n }\n gl_FragColor.a = gl_FragColor.a * u_opacity;\n if (gl_FragColor.a <= 0.0) {\n discard;\n }\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying vec2 b;varying float c;varying float d;uniform float m;uniform vec4 n;uniform vec4 o;uniform vec2 p;void main(void){vec2 windowCenter=vec2((a.x+1.0)/2.0*p.x*d,(a.y+1.0)/2.0*p.y*d);vec2 windowOffset=vec2((b.x+1.0)/2.0*p.x*d,(b.y+1.0)/2.0*p.y*d);float radius=length(windowCenter-windowOffset);float dist=length(windowCenter-gl_FragCoord.xy);if(dist>radius+c){if(o.a==0.0){gl_FragColor=n;}else{gl_FragColor=o;}gl_FragColor.a=gl_FragColor.a-(dist-(radius+c));}else if(n.a==0.0){gl_FragColor=o;if(dist<radius-c){gl_FragColor.a=gl_FragColor.a-(radius-c-dist);}} else{gl_FragColor=n;float strokeDist=radius-c;float antialias=2.0*d;if(dist>strokeDist){gl_FragColor=o;}else if(dist>=strokeDist-antialias){float step=smoothstep(strokeDist-antialias,strokeDist,dist);gl_FragColor=mix(n,o,step);}} gl_FragColor.a=gl_FragColor.a*m;if(gl_FragColor.a<=0.0){discard;}}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + ol.render.webgl.circlereplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.circlereplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.circlereplay.defaultshader.fragment = new ol.render.webgl.circlereplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.circlereplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.circlereplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.circlereplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_center;\nvarying vec2 v_offset;\nvarying float v_halfWidth;\nvarying float v_pixelRatio;\n\n\nattribute vec2 a_position;\nattribute float a_instruction;\nattribute float a_radius;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\nuniform float u_lineWidth;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n v_center = vec4(u_projectionMatrix * vec4(a_position, 0.0, 1.0)).xy;\n v_pixelRatio = u_pixelRatio;\n float lineWidth = u_lineWidth * u_pixelRatio;\n v_halfWidth = lineWidth / 2.0;\n if (lineWidth == 0.0) {\n lineWidth = 2.0 * u_pixelRatio;\n }\n vec2 offset;\n // Radius with anitaliasing (roughly).\n float radius = a_radius + 3.0 * u_pixelRatio;\n // Until we get gl_VertexID in WebGL, we store an instruction.\n if (a_instruction == 0.0) {\n // Offsetting the edges of the triangle by lineWidth / 2 is necessary, however\n // we should also leave some space for the antialiasing, thus we offset by lineWidth.\n offset = vec2(-1.0, 1.0);\n } else if (a_instruction == 1.0) {\n offset = vec2(-1.0, -1.0);\n } else if (a_instruction == 2.0) {\n offset = vec2(1.0, -1.0);\n } else {\n offset = vec2(1.0, 1.0);\n }\n\n gl_Position = u_projectionMatrix * vec4(a_position + offset * radius, 0.0, 1.0) +\n offsetMatrix * vec4(offset * lineWidth, 0.0, 0.0);\n v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y,\n 0.0, 1.0)).xy;\n\n if (distance(v_center, v_offset) > 20000.0) {\n gl_Position = vec4(v_center, 0.0, 1.0);\n }\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying vec2 b;varying float c;varying float d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;void main(void){mat4 offsetMatrix=i*j;a=vec4(h*vec4(e,0.0,1.0)).xy;d=l;float lineWidth=k*l;c=lineWidth/2.0;if(lineWidth==0.0){lineWidth=2.0*l;}vec2 offset;float radius=g+3.0*l;if(f==0.0){offset=vec2(-1.0,1.0);}else if(f==1.0){offset=vec2(-1.0,-1.0);}else if(f==2.0){offset=vec2(1.0,-1.0);}else{offset=vec2(1.0,1.0);}gl_Position=h*vec4(e+offset*radius,0.0,1.0)+offsetMatrix*vec4(offset*lineWidth,0.0,0.0);b=vec4(h*vec4(e.x+g,e.y,0.0,1.0)).xy;if(distance(a,b)>20000.0){gl_Position=vec4(a,0.0,1.0);}}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + ol.render.webgl.circlereplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.circlereplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.circlereplay.defaultshader.vertex = new ol.render.webgl.circlereplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.circlereplay.defaultshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_fillColor = gl.getUniformLocation( + program, ol.DEBUG ? 'u_fillColor' : 'n'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_lineWidth = gl.getUniformLocation( + program, ol.DEBUG ? 'u_lineWidth' : 'k'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'j'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'i'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'm'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_pixelRatio = gl.getUniformLocation( + program, ol.DEBUG ? 'u_pixelRatio' : 'l'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'h'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_size = gl.getUniformLocation( + program, ol.DEBUG ? 'u_size' : 'p'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_strokeColor = gl.getUniformLocation( + program, ol.DEBUG ? 'u_strokeColor' : 'o'); + + /** + * @type {number} + */ + this.a_instruction = gl.getAttribLocation( + program, ol.DEBUG ? 'a_instruction' : 'f'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'e'); + + /** + * @type {number} + */ + this.a_radius = gl.getAttribLocation( + program, ol.DEBUG ? 'a_radius' : 'g'); +}; + +goog.provide('ol.vec.Mat4'); + + +/** + * @return {Array.<number>} 4x4 matrix representing a 3D identity transform. + */ +ol.vec.Mat4.create = function() { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; +}; + + +/** + * @param {Array.<number>} mat4 Flattened 4x4 matrix receiving the result. + * @param {ol.Transform} transform Transformation matrix. + * @return {Array.<number>} 2D transformation matrix as flattened 4x4 matrix. + */ +ol.vec.Mat4.fromTransform = function(mat4, transform) { + mat4[0] = transform[0]; + mat4[1] = transform[1]; + mat4[4] = transform[2]; + mat4[5] = transform[3]; + mat4[12] = transform[4]; + mat4[13] = transform[5]; + return mat4; +}; + +goog.provide('ol.render.webgl.Replay'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.render.VectorContext'); +goog.require('ol.transform'); +goog.require('ol.vec.Mat4'); +goog.require('ol.webgl'); + +/** + * @constructor + * @extends {ol.render.VectorContext} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.Replay = function(tolerance, maxExtent) { + ol.render.VectorContext.call(this); + + /** + * @protected + * @type {number} + */ + this.tolerance = tolerance; + + /** + * @protected + * @const + * @type {ol.Extent} + */ + this.maxExtent = maxExtent; + + /** + * The origin of the coordinate system for the point coordinates sent to + * the GPU. To eliminate jitter caused by precision problems in the GPU + * we use the "Rendering Relative to Eye" technique described in the "3D + * Engine Design for Virtual Globes" book. + * @protected + * @type {ol.Coordinate} + */ + this.origin = ol.extent.getCenter(maxExtent); + + /** + * @private + * @type {ol.Transform} + */ + this.projectionMatrix_ = ol.transform.create(); + + /** + * @private + * @type {ol.Transform} + */ + this.offsetRotateMatrix_ = ol.transform.create(); + + /** + * @private + * @type {ol.Transform} + */ + this.offsetScaleMatrix_ = ol.transform.create(); + + /** + * @private + * @type {Array.<number>} + */ + this.tmpMat4_ = ol.vec.Mat4.create(); + + /** + * @protected + * @type {Array.<number>} + */ + this.indices = []; + + /** + * @protected + * @type {?ol.webgl.Buffer} + */ + this.indicesBuffer = null; + + /** + * Start index per feature (the index). + * @protected + * @type {Array.<number>} + */ + this.startIndices = []; + + /** + * Start index per feature (the feature). + * @protected + * @type {Array.<ol.Feature|ol.render.Feature>} + */ + this.startIndicesFeature = []; + + /** + * @protected + * @type {Array.<number>} + */ + this.vertices = []; + + /** + * @protected + * @type {?ol.webgl.Buffer} + */ + this.verticesBuffer = null; + + /** + * Optional parameter for PolygonReplay instances. + * @protected + * @type {ol.render.webgl.LineStringReplay|undefined} + */ + this.lineStringReplay = undefined; + +}; +ol.inherits(ol.render.webgl.Replay, ol.render.VectorContext); + + +/** + * @abstract + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = function(context) {}; + + +/** + * @abstract + * @param {ol.webgl.Context} context Context. + */ +ol.render.webgl.Replay.prototype.finish = function(context) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @return {ol.render.webgl.circlereplay.defaultshader.Locations| + ol.render.webgl.imagereplay.defaultshader.Locations| + ol.render.webgl.linestringreplay.defaultshader.Locations| + ol.render.webgl.polygonreplay.defaultshader.Locations} Locations. + */ +ol.render.webgl.Replay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.render.webgl.circlereplay.defaultshader.Locations| + ol.render.webgl.imagereplay.defaultshader.Locations| + ol.render.webgl.linestringreplay.defaultshader.Locations| + ol.render.webgl.polygonreplay.defaultshader.Locations} locations Locations. + */ +ol.render.webgl.Replay.prototype.shutDownProgram = function(gl, locations) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {boolean} hitDetection Hit detection mode. + */ +ol.render.webgl.Replay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, featureCallback, opt_hitExtent) {}; + + +/** + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplay = function(gl, context, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + if (!oneByOne) { + // draw all hit-detection features in "once" (by texture group) + return this.drawHitDetectionReplayAll(gl, context, + skippedFeaturesHash, featureCallback); + } else { + // draw hit-detection features one by one + return this.drawHitDetectionReplayOneByOne(gl, context, + skippedFeaturesHash, featureCallback, opt_hitExtent); + } +}; + + +/** + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplayAll = function(gl, context, skippedFeaturesHash, + featureCallback) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawReplay(gl, context, skippedFeaturesHash, true); + + var result = featureCallback(null); + if (result) { + return result; + } else { + return undefined; + } +}; + + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + var gl = context.getGL(); + var tmpStencil, tmpStencilFunc, tmpStencilMaskVal, tmpStencilRef, tmpStencilMask, + tmpStencilOpFail, tmpStencilOpPass, tmpStencilOpZFail; + + if (this.lineStringReplay) { + tmpStencil = gl.isEnabled(gl.STENCIL_TEST); + tmpStencilFunc = gl.getParameter(gl.STENCIL_FUNC); + tmpStencilMaskVal = gl.getParameter(gl.STENCIL_VALUE_MASK); + tmpStencilRef = gl.getParameter(gl.STENCIL_REF); + tmpStencilMask = gl.getParameter(gl.STENCIL_WRITEMASK); + tmpStencilOpFail = gl.getParameter(gl.STENCIL_FAIL); + tmpStencilOpPass = gl.getParameter(gl.STENCIL_PASS_DEPTH_PASS); + tmpStencilOpZFail = gl.getParameter(gl.STENCIL_PASS_DEPTH_FAIL); + + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilMask(255); + gl.stencilFunc(gl.ALWAYS, 1, 255); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); + + this.lineStringReplay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + + gl.stencilMask(0); + gl.stencilFunc(gl.NOTEQUAL, 1, 255); + } + + // bind the vertices buffer + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer); + + // bind the indices buffer + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer); + + var locations = this.setUpProgram(gl, context, size, pixelRatio); + + // set the "uniform" values + var projectionMatrix = ol.transform.reset(this.projectionMatrix_); + ol.transform.scale(projectionMatrix, 2 / (resolution * size[0]), 2 / (resolution * size[1])); + ol.transform.rotate(projectionMatrix, -rotation); + ol.transform.translate(projectionMatrix, -(center[0] - this.origin[0]), -(center[1] - this.origin[1])); + + var offsetScaleMatrix = ol.transform.reset(this.offsetScaleMatrix_); + ol.transform.scale(offsetScaleMatrix, 2 / size[0], 2 / size[1]); + + var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_); + if (rotation !== 0) { + ol.transform.rotate(offsetRotateMatrix, -rotation); + } + + gl.uniformMatrix4fv(locations.u_projectionMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, projectionMatrix)); + gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetScaleMatrix)); + gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetRotateMatrix)); + gl.uniform1f(locations.u_opacity, opacity); + + // draw! + var result; + if (featureCallback === undefined) { + this.drawReplay(gl, context, skippedFeaturesHash, false); + } else { + // draw feature by feature for the hit-detection + result = this.drawHitDetectionReplay(gl, context, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + } + + // disable the vertex attrib arrays + this.shutDownProgram(gl, locations); + + if (this.lineStringReplay) { + if (!tmpStencil) { + gl.disable(gl.STENCIL_TEST); + } + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(/** @type {number} */ (tmpStencilFunc), + /** @type {number} */ (tmpStencilRef), /** @type {number} */ (tmpStencilMaskVal)); + gl.stencilMask(/** @type {number} */ (tmpStencilMask)); + gl.stencilOp(/** @type {number} */ (tmpStencilOpFail), + /** @type {number} */ (tmpStencilOpZFail), /** @type {number} */ (tmpStencilOpPass)); + } + + return result; +}; + +/** + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {number} start Start index. + * @param {number} end End index. + */ +ol.render.webgl.Replay.prototype.drawElements = function( + gl, context, start, end) { + var elementType = context.hasOESElementIndexUint ? + ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT; + var elementSize = context.hasOESElementIndexUint ? 4 : 2; + + var numItems = end - start; + var offsetInBytes = start * elementSize; + gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, offsetInBytes); +}; + +goog.provide('ol.webgl.Buffer'); + +goog.require('ol'); +goog.require('ol.webgl'); + + +/** + * @constructor + * @param {Array.<number>=} opt_arr Array. + * @param {number=} opt_usage Usage. + * @struct + */ +ol.webgl.Buffer = function(opt_arr, opt_usage) { + + /** + * @private + * @type {Array.<number>} + */ + this.arr_ = opt_arr !== undefined ? opt_arr : []; + + /** + * @private + * @type {number} + */ + this.usage_ = opt_usage !== undefined ? + opt_usage : ol.webgl.Buffer.Usage.STATIC_DRAW; + +}; + + +/** + * @return {Array.<number>} Array. + */ +ol.webgl.Buffer.prototype.getArray = function() { + return this.arr_; +}; + + +/** + * @return {number} Usage. + */ +ol.webgl.Buffer.prototype.getUsage = function() { + return this.usage_; +}; + + +/** + * @enum {number} + */ +ol.webgl.Buffer.Usage = { + STATIC_DRAW: ol.webgl.STATIC_DRAW, + STREAM_DRAW: ol.webgl.STREAM_DRAW, + DYNAMIC_DRAW: ol.webgl.DYNAMIC_DRAW +}; + +goog.provide('ol.render.webgl.CircleReplay'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.color'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.render.webgl.circlereplay.defaultshader'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.CircleReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + /** + * @private + * @type {ol.render.webgl.circlereplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.<Array.<Array.<number>|number>>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.<number>} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {number} + */ + this.radius_ = 0; + + /** + * @private + * @type {{fillColor: (Array.<number>|null), + * strokeColor: (Array.<number>|null), + * lineDash: Array.<number>, + * lineWidth: (number|undefined), + * changed: boolean}|null} + */ + this.state_ = { + fillColor: null, + strokeColor: null, + lineDash: null, + lineWidth: undefined, + changed: false + }; + +}; +ol.inherits(ol.render.webgl.CircleReplay, ol.render.webgl.Replay); + + +/** + * @private + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + */ +ol.render.webgl.CircleReplay.prototype.drawCoordinates_ = function( + flatCoordinates, offset, end, stride) { + var numVertices = this.vertices.length; + var numIndices = this.indices.length; + var n = numVertices / 4; + var i, ii; + for (i = offset, ii = end; i < ii; i += stride) { + this.vertices[numVertices++] = flatCoordinates[i]; + this.vertices[numVertices++] = flatCoordinates[i + 1]; + this.vertices[numVertices++] = 0; + this.vertices[numVertices++] = this.radius_; + + this.vertices[numVertices++] = flatCoordinates[i]; + this.vertices[numVertices++] = flatCoordinates[i + 1]; + this.vertices[numVertices++] = 1; + this.vertices[numVertices++] = this.radius_; + + this.vertices[numVertices++] = flatCoordinates[i]; + this.vertices[numVertices++] = flatCoordinates[i + 1]; + this.vertices[numVertices++] = 2; + this.vertices[numVertices++] = this.radius_; + + this.vertices[numVertices++] = flatCoordinates[i]; + this.vertices[numVertices++] = flatCoordinates[i + 1]; + this.vertices[numVertices++] = 3; + this.vertices[numVertices++] = this.radius_; + + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 2; + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n; + + n += 4; + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.drawCircle = function(circleGeometry, feature) { + var radius = circleGeometry.getRadius(); + var stride = circleGeometry.getStride(); + if (radius) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(this.indices.length); + this.state_.changed = false; + } + + this.radius_ = radius; + var flatCoordinates = circleGeometry.getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, 2, + stride, -this.origin[0], -this.origin[1]); + this.drawCoordinates_(flatCoordinates, 0, 2, stride); + } else { + if (this.state_.changed) { + this.styles_.pop(); + if (this.styles_.length) { + var lastState = this.styles_[this.styles_.length - 1]; + this.state_.fillColor = /** @type {Array.<number>} */ (lastState[0]); + this.state_.strokeColor = /** @type {Array.<number>} */ (lastState[1]); + this.state_.lineWidth = /** @type {number} */ (lastState[2]); + this.state_.changed = false; + } + } + } +}; + + +/** + * @inheritDoc + **/ +ol.render.webgl.CircleReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); + + this.startIndices.push(this.indices.length); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } + + this.vertices = null; + this.indices = null; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other CircleReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.circlereplay.defaultshader.fragment; + vertexShader = ol.render.webgl.circlereplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.circlereplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 16, 0); + + gl.enableVertexAttribArray(locations.a_instruction); + gl.vertexAttribPointer(locations.a_instruction, 1, ol.webgl.FLOAT, + false, 16, 8); + + gl.enableVertexAttribArray(locations.a_radius); + gl.vertexAttribPointer(locations.a_radius, 1, ol.webgl.FLOAT, + false, 16, 12); + + // Enable renderer specific uniforms. + gl.uniform2fv(locations.u_size, size); + gl.uniform1f(locations.u_pixelRatio, pixelRatio); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_instruction); + gl.disableVertexAttribArray(locations.a_radius); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); + } else { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, start, end, nextStyle; + end = this.startIndices[this.startIndices.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, /** @type {Array.<number>} */ (nextStyle[0])); + this.setStrokeStyle_(gl, /** @type {Array.<number>} */ (nextStyle[1]), + /** @type {number} */ (nextStyle[2])); + this.drawElements(gl, context, start, end); + end = start; + } + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices.length - 2; + end = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, /** @type {Array.<number>} */ (nextStyle[0])); + this.setStrokeStyle_(gl, /** @type {Array.<number>} */ (nextStyle[1]), + /** @type {number} */ (nextStyle[2])); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array<number>} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; + } + } + return undefined; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.CircleReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices.length - 2; + end = start = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, /** @type {Array.<number>} */ (nextStyle[0])); + this.setStrokeStyle_(gl, /** @type {Array.<number>} */ (nextStyle[1]), + /** @type {number} */ (nextStyle[2])); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + featureStart = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements(gl, context, start, end); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements(gl, context, start, end); + } + start = end = groupStart; + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.<number>} color Color. + */ +ol.render.webgl.CircleReplay.prototype.setFillStyle_ = function(gl, color) { + gl.uniform4fv(this.defaultLocations_.u_fillColor, color); +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.<number>} color Color. + * @param {number} lineWidth Line width. + */ +ol.render.webgl.CircleReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth) { + gl.uniform4fv(this.defaultLocations_.u_strokeColor, color); + gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + var strokeStyleColor, strokeStyleWidth; + if (strokeStyle) { + var strokeStyleLineDash = strokeStyle.getLineDash(); + this.state_.lineDash = strokeStyleLineDash ? + strokeStyleLineDash : ol.render.webgl.defaultLineDash; + strokeStyleColor = strokeStyle.getColor(); + if (!(strokeStyleColor instanceof CanvasGradient) && + !(strokeStyleColor instanceof CanvasPattern)) { + strokeStyleColor = ol.color.asArray(strokeStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultStrokeStyle; + } else { + strokeStyleColor = ol.render.webgl.defaultStrokeStyle; + } + strokeStyleWidth = strokeStyle.getWidth(); + strokeStyleWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.webgl.defaultLineWidth; + } else { + strokeStyleColor = [0, 0, 0, 0]; + strokeStyleWidth = 0; + } + var fillStyleColor = fillStyle ? fillStyle.getColor() : [0, 0, 0, 0]; + if (!(fillStyleColor instanceof CanvasGradient) && + !(fillStyleColor instanceof CanvasPattern)) { + fillStyleColor = ol.color.asArray(fillStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultFillStyle; + } else { + fillStyleColor = ol.render.webgl.defaultFillStyle; + } + if (!this.state_.strokeColor || !ol.array.equals(this.state_.strokeColor, strokeStyleColor) || + !this.state_.fillColor || !ol.array.equals(this.state_.fillColor, fillStyleColor) || + this.state_.lineWidth !== strokeStyleWidth) { + this.state_.changed = true; + this.state_.fillColor = fillStyleColor; + this.state_.strokeColor = strokeStyleColor; + this.state_.lineWidth = strokeStyleWidth; + this.styles_.push([fillStyleColor, strokeStyleColor, strokeStyleWidth]); + } +}; + +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.imagereplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.imagereplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.imagereplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.imagereplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + ol.render.webgl.imagereplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.imagereplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.imagereplay.defaultshader.fragment = new ol.render.webgl.imagereplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.imagereplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.imagereplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.imagereplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);\n gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.0,0.0);gl_Position=h*vec4(c,0.0,1.0)+offsets;a=d;b=f;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + ol.render.webgl.imagereplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.imagereplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.imagereplay.defaultshader.vertex = new ol.render.webgl.imagereplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.imagereplay.defaultshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_image = gl.getUniformLocation( + program, ol.DEBUG ? 'u_image' : 'l'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'j'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'i'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'k'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'h'); + + /** + * @type {number} + */ + this.a_offsets = gl.getAttribLocation( + program, ol.DEBUG ? 'a_offsets' : 'e'); + + /** + * @type {number} + */ + this.a_opacity = gl.getAttribLocation( + program, ol.DEBUG ? 'a_opacity' : 'f'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'c'); + + /** + * @type {number} + */ + this.a_rotateWithView = gl.getAttribLocation( + program, ol.DEBUG ? 'a_rotateWithView' : 'g'); + + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, ol.DEBUG ? 'a_texCoord' : 'd'); +}; + +goog.provide('ol.webgl.ContextEventType'); + + +/** + * @enum {string} + */ +ol.webgl.ContextEventType = { + LOST: 'webglcontextlost', + RESTORED: 'webglcontextrestored' +}; + +goog.provide('ol.webgl.Context'); + +goog.require('ol'); +goog.require('ol.Disposable'); +goog.require('ol.array'); +goog.require('ol.events'); +goog.require('ol.obj'); +goog.require('ol.webgl'); +goog.require('ol.webgl.ContextEventType'); + + +/** + * @classdesc + * A WebGL context for accessing low-level WebGL capabilities. + * + * @constructor + * @extends {ol.Disposable} + * @param {HTMLCanvasElement} canvas Canvas. + * @param {WebGLRenderingContext} gl GL. + */ +ol.webgl.Context = function(canvas, gl) { + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = canvas; + + /** + * @private + * @type {WebGLRenderingContext} + */ + this.gl_ = gl; + + /** + * @private + * @type {Object.<string, ol.WebglBufferCacheEntry>} + */ + this.bufferCache_ = {}; + + /** + * @private + * @type {Object.<string, WebGLShader>} + */ + this.shaderCache_ = {}; + + /** + * @private + * @type {Object.<string, WebGLProgram>} + */ + this.programCache_ = {}; + + /** + * @private + * @type {WebGLProgram} + */ + this.currentProgram_ = null; + + /** + * @private + * @type {WebGLFramebuffer} + */ + this.hitDetectionFramebuffer_ = null; + + /** + * @private + * @type {WebGLTexture} + */ + this.hitDetectionTexture_ = null; + + /** + * @private + * @type {WebGLRenderbuffer} + */ + this.hitDetectionRenderbuffer_ = null; + + /** + * @type {boolean} + */ + this.hasOESElementIndexUint = ol.array.includes( + ol.WEBGL_EXTENSIONS, 'OES_element_index_uint'); + + // use the OES_element_index_uint extension if available + if (this.hasOESElementIndexUint) { + var ext = gl.getExtension('OES_element_index_uint'); + ol.DEBUG && console.assert(ext, + 'Failed to get extension "OES_element_index_uint"'); + } + + ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST, + this.handleWebGLContextLost, this); + ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED, + this.handleWebGLContextRestored, this); + +}; +ol.inherits(ol.webgl.Context, ol.Disposable); + + +/** + * Just bind the buffer if it's in the cache. Otherwise create + * the WebGL buffer, bind it, populate it, and add an entry to + * the cache. + * @param {number} target Target. + * @param {ol.webgl.Buffer} buf Buffer. + */ +ol.webgl.Context.prototype.bindBuffer = function(target, buf) { + var gl = this.getGL(); + var arr = buf.getArray(); + var bufferKey = String(ol.getUid(buf)); + if (bufferKey in this.bufferCache_) { + var bufferCacheEntry = this.bufferCache_[bufferKey]; + gl.bindBuffer(target, bufferCacheEntry.buffer); + } else { + var buffer = gl.createBuffer(); + gl.bindBuffer(target, buffer); + ol.DEBUG && console.assert(target == ol.webgl.ARRAY_BUFFER || + target == ol.webgl.ELEMENT_ARRAY_BUFFER, + 'target is supposed to be an ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER'); + var /** @type {ArrayBufferView} */ arrayBuffer; + if (target == ol.webgl.ARRAY_BUFFER) { + arrayBuffer = new Float32Array(arr); + } else if (target == ol.webgl.ELEMENT_ARRAY_BUFFER) { + arrayBuffer = this.hasOESElementIndexUint ? + new Uint32Array(arr) : new Uint16Array(arr); + } + gl.bufferData(target, arrayBuffer, buf.getUsage()); + this.bufferCache_[bufferKey] = { + buf: buf, + buffer: buffer + }; + } +}; + + +/** + * @param {ol.webgl.Buffer} buf Buffer. + */ +ol.webgl.Context.prototype.deleteBuffer = function(buf) { + var gl = this.getGL(); + var bufferKey = String(ol.getUid(buf)); + ol.DEBUG && console.assert(bufferKey in this.bufferCache_, + 'attempted to delete uncached buffer'); + var bufferCacheEntry = this.bufferCache_[bufferKey]; + if (!gl.isContextLost()) { + gl.deleteBuffer(bufferCacheEntry.buffer); + } + delete this.bufferCache_[bufferKey]; +}; + + +/** + * @inheritDoc + */ +ol.webgl.Context.prototype.disposeInternal = function() { + ol.events.unlistenAll(this.canvas_); + var gl = this.getGL(); + if (!gl.isContextLost()) { + var key; + for (key in this.bufferCache_) { + gl.deleteBuffer(this.bufferCache_[key].buffer); + } + for (key in this.programCache_) { + gl.deleteProgram(this.programCache_[key]); + } + for (key in this.shaderCache_) { + gl.deleteShader(this.shaderCache_[key]); + } + // delete objects for hit-detection + gl.deleteFramebuffer(this.hitDetectionFramebuffer_); + gl.deleteRenderbuffer(this.hitDetectionRenderbuffer_); + gl.deleteTexture(this.hitDetectionTexture_); + } +}; + + +/** + * @return {HTMLCanvasElement} Canvas. + */ +ol.webgl.Context.prototype.getCanvas = function() { + return this.canvas_; +}; + + +/** + * Get the WebGL rendering context + * @return {WebGLRenderingContext} The rendering context. + * @api + */ +ol.webgl.Context.prototype.getGL = function() { + return this.gl_; +}; + + +/** + * Get the frame buffer for hit detection. + * @return {WebGLFramebuffer} The hit detection frame buffer. + */ +ol.webgl.Context.prototype.getHitDetectionFramebuffer = function() { + if (!this.hitDetectionFramebuffer_) { + this.initHitDetectionFramebuffer_(); + } + return this.hitDetectionFramebuffer_; +}; + + +/** + * Get shader from the cache if it's in the cache. Otherwise, create + * the WebGL shader, compile it, and add entry to cache. + * @param {ol.webgl.Shader} shaderObject Shader object. + * @return {WebGLShader} Shader. + */ +ol.webgl.Context.prototype.getShader = function(shaderObject) { + var shaderKey = String(ol.getUid(shaderObject)); + if (shaderKey in this.shaderCache_) { + return this.shaderCache_[shaderKey]; + } else { + var gl = this.getGL(); + var shader = gl.createShader(shaderObject.getType()); + gl.shaderSource(shader, shaderObject.getSource()); + gl.compileShader(shader); + ol.DEBUG && console.assert( + gl.getShaderParameter(shader, ol.webgl.COMPILE_STATUS) || + gl.isContextLost(), + gl.getShaderInfoLog(shader) || 'illegal state, shader not compiled or context lost'); + this.shaderCache_[shaderKey] = shader; + return shader; + } +}; + + +/** + * Get the program from the cache if it's in the cache. Otherwise create + * the WebGL program, attach the shaders to it, and add an entry to the + * cache. + * @param {ol.webgl.Fragment} fragmentShaderObject Fragment shader. + * @param {ol.webgl.Vertex} vertexShaderObject Vertex shader. + * @return {WebGLProgram} Program. + */ +ol.webgl.Context.prototype.getProgram = function( + fragmentShaderObject, vertexShaderObject) { + var programKey = + ol.getUid(fragmentShaderObject) + '/' + ol.getUid(vertexShaderObject); + if (programKey in this.programCache_) { + return this.programCache_[programKey]; + } else { + var gl = this.getGL(); + var program = gl.createProgram(); + gl.attachShader(program, this.getShader(fragmentShaderObject)); + gl.attachShader(program, this.getShader(vertexShaderObject)); + gl.linkProgram(program); + ol.DEBUG && console.assert( + gl.getProgramParameter(program, ol.webgl.LINK_STATUS) || + gl.isContextLost(), + gl.getProgramInfoLog(program) || 'illegal state, shader not linked or context lost'); + this.programCache_[programKey] = program; + return program; + } +}; + + +/** + * FIXME empy description for jsdoc + */ +ol.webgl.Context.prototype.handleWebGLContextLost = function() { + ol.obj.clear(this.bufferCache_); + ol.obj.clear(this.shaderCache_); + ol.obj.clear(this.programCache_); + this.currentProgram_ = null; + this.hitDetectionFramebuffer_ = null; + this.hitDetectionTexture_ = null; + this.hitDetectionRenderbuffer_ = null; +}; + + +/** + * FIXME empy description for jsdoc + */ +ol.webgl.Context.prototype.handleWebGLContextRestored = function() { +}; + + +/** + * Creates a 1x1 pixel framebuffer for the hit-detection. + * @private + */ +ol.webgl.Context.prototype.initHitDetectionFramebuffer_ = function() { + var gl = this.gl_; + var framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + + var texture = ol.webgl.Context.createEmptyTexture(gl, 1, 1); + var renderbuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1, 1); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, + gl.RENDERBUFFER, renderbuffer); + + gl.bindTexture(gl.TEXTURE_2D, null); + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + this.hitDetectionFramebuffer_ = framebuffer; + this.hitDetectionTexture_ = texture; + this.hitDetectionRenderbuffer_ = renderbuffer; +}; + + +/** + * Use a program. If the program is already in use, this will return `false`. + * @param {WebGLProgram} program Program. + * @return {boolean} Changed. + * @api + */ +ol.webgl.Context.prototype.useProgram = function(program) { + if (program == this.currentProgram_) { + return false; + } else { + var gl = this.getGL(); + gl.useProgram(program); + this.currentProgram_ = program; + return true; + } +}; + + +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} The texture. + * @private + */ +ol.webgl.Context.createTexture_ = function(gl, opt_wrapS, opt_wrapT) { + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + if (opt_wrapS !== undefined) { + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S, opt_wrapS); + } + if (opt_wrapT !== undefined) { + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T, opt_wrapT); + } + + return texture; +}; + + +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {number} width Width. + * @param {number} height Height. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} The texture. + */ +ol.webgl.Context.createEmptyTexture = function( + gl, width, height, opt_wrapS, opt_wrapT) { + var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, + null); + + return texture; +}; + + +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} The texture. + */ +ol.webgl.Context.createTexture = function(gl, image, opt_wrapS, opt_wrapT) { + var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); + + return texture; +}; + +goog.provide('ol.render.webgl.ImageReplay'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.render.webgl.imagereplay.defaultshader'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); +goog.require('ol.webgl.Context'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + /** + * @type {number|undefined} + * @private + */ + this.anchorX_ = undefined; + + /** + * @type {number|undefined} + * @private + */ + this.anchorY_ = undefined; + + /** + * @type {Array.<number>} + * @private + */ + this.groupIndices_ = []; + + /** + * @type {Array.<number>} + * @private + */ + this.hitDetectionGroupIndices_ = []; + + /** + * @type {number|undefined} + * @private + */ + this.height_ = undefined; + + /** + * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} + * @private + */ + this.images_ = []; + + /** + * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} + * @private + */ + this.hitDetectionImages_ = []; + + /** + * @type {number|undefined} + * @private + */ + this.imageHeight_ = undefined; + + /** + * @type {number|undefined} + * @private + */ + this.imageWidth_ = undefined; + + /** + * @private + * @type {ol.render.webgl.imagereplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {number|undefined} + */ + this.opacity_ = undefined; + + /** + * @type {number|undefined} + * @private + */ + this.originX_ = undefined; + + /** + * @type {number|undefined} + * @private + */ + this.originY_ = undefined; + + /** + * @private + * @type {boolean|undefined} + */ + this.rotateWithView_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.rotation_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.scale_ = undefined; + + /** + * @type {Array.<WebGLTexture>} + * @private + */ + this.textures_ = []; + + /** + * @type {Array.<WebGLTexture>} + * @private + */ + this.hitDetectionTextures_ = []; + + /** + * @type {number|undefined} + * @private + */ + this.width_ = undefined; +}; +ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.Replay); + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other ImageReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + var textures = this.textures_; + var hitDetectionTextures = this.hitDetectionTextures_; + var gl = context.getGL(); + return function() { + if (!gl.isContextLost()) { + var i, ii; + for (i = 0, ii = textures.length; i < ii; ++i) { + gl.deleteTexture(textures[i]); + } + for (i = 0, ii = hitDetectionTextures.length; i < ii; ++i) { + gl.deleteTexture(hitDetectionTextures[i]); + } + } + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} My end. + * @private + */ +ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { + ol.DEBUG && console.assert(this.anchorX_ !== undefined, 'anchorX is defined'); + ol.DEBUG && console.assert(this.anchorY_ !== undefined, 'anchorY is defined'); + ol.DEBUG && console.assert(this.height_ !== undefined, 'height is defined'); + ol.DEBUG && console.assert(this.imageHeight_ !== undefined, + 'imageHeight is defined'); + ol.DEBUG && console.assert(this.imageWidth_ !== undefined, 'imageWidth is defined'); + ol.DEBUG && console.assert(this.opacity_ !== undefined, 'opacity is defined'); + ol.DEBUG && console.assert(this.originX_ !== undefined, 'originX is defined'); + ol.DEBUG && console.assert(this.originY_ !== undefined, 'originY is defined'); + ol.DEBUG && console.assert(this.rotateWithView_ !== undefined, + 'rotateWithView is defined'); + ol.DEBUG && console.assert(this.rotation_ !== undefined, 'rotation is defined'); + ol.DEBUG && console.assert(this.scale_ !== undefined, 'scale is defined'); + ol.DEBUG && console.assert(this.width_ !== undefined, 'width is defined'); + var anchorX = /** @type {number} */ (this.anchorX_); + var anchorY = /** @type {number} */ (this.anchorY_); + var height = /** @type {number} */ (this.height_); + var imageHeight = /** @type {number} */ (this.imageHeight_); + var imageWidth = /** @type {number} */ (this.imageWidth_); + var opacity = /** @type {number} */ (this.opacity_); + var originX = /** @type {number} */ (this.originX_); + var originY = /** @type {number} */ (this.originY_); + var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0; + // this.rotation_ is anti-clockwise, but rotation is clockwise + var rotation = /** @type {number} */ (-this.rotation_); + var scale = /** @type {number} */ (this.scale_); + var width = /** @type {number} */ (this.width_); + var cos = Math.cos(rotation); + var sin = Math.sin(rotation); + var numIndices = this.indices.length; + var numVertices = this.vertices.length; + var i, n, offsetX, offsetY, x, y; + for (i = offset; i < end; i += stride) { + x = flatCoordinates[i] - this.origin[0]; + y = flatCoordinates[i + 1] - this.origin[1]; + + // There are 4 vertices per [x, y] point, one for each corner of the + // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if + // WebGL supported Geometry Shaders (which can emit new vertices), but that + // is not currently the case. + // + // And each vertex includes 8 values: the x and y coordinates, the x and + // y offsets used to calculate the position of the corner, the u and + // v texture coordinates for the corner, the opacity, and whether the + // the image should be rotated with the view (rotateWithView). + + n = numVertices / 8; + + // bottom-left corner + offsetX = -scale * anchorX; + offsetY = -scale * (height - anchorY); + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = originX / imageWidth; + this.vertices[numVertices++] = (originY + height) / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; + + // bottom-right corner + offsetX = scale * (width - anchorX); + offsetY = -scale * (height - anchorY); + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = (originX + width) / imageWidth; + this.vertices[numVertices++] = (originY + height) / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; + + // top-right corner + offsetX = scale * (width - anchorX); + offsetY = scale * anchorY; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = (originX + width) / imageWidth; + this.vertices[numVertices++] = originY / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; + + // top-left corner + offsetX = -scale * anchorX; + offsetY = scale * anchorY; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = originX / imageWidth; + this.vertices[numVertices++] = originY / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; + + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 3; + } + + return numVertices; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + var flatCoordinates = multiPointGeometry.getFlatCoordinates(); + var stride = multiPointGeometry.getStride(); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + var flatCoordinates = pointGeometry.getFlatCoordinates(); + var stride = pointGeometry.getStride(); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.finish = function(context) { + var gl = context.getGL(); + + this.groupIndices_.push(this.indices.length); + ol.DEBUG && console.assert(this.images_.length === this.groupIndices_.length, + 'number of images and groupIndices match'); + this.hitDetectionGroupIndices_.push(this.indices.length); + ol.DEBUG && console.assert(this.hitDetectionImages_.length === + this.hitDetectionGroupIndices_.length, + 'number of hitDetectionImages and hitDetectionGroupIndices match'); + + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + var indices = this.indices; + var bits = context.hasOESElementIndexUint ? 32 : 16; + ol.DEBUG && console.assert(indices[indices.length - 1] < Math.pow(2, bits), + 'Too large element index detected [%s] (OES_element_index_uint "%s")', + indices[indices.length - 1], context.hasOESElementIndexUint); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(indices); + + // create textures + /** @type {Object.<string, WebGLTexture>} */ + var texturePerImage = {}; + + this.createTextures_(this.textures_, this.images_, texturePerImage, gl); + ol.DEBUG && console.assert(this.textures_.length === this.groupIndices_.length, + 'number of textures and groupIndices match'); + + this.createTextures_(this.hitDetectionTextures_, this.hitDetectionImages_, + texturePerImage, gl); + ol.DEBUG && console.assert(this.hitDetectionTextures_.length === + this.hitDetectionGroupIndices_.length, + 'number of hitDetectionTextures and hitDetectionGroupIndices match'); + + this.anchorX_ = undefined; + this.anchorY_ = undefined; + this.height_ = undefined; + this.images_ = null; + this.hitDetectionImages_ = null; + this.imageHeight_ = undefined; + this.imageWidth_ = undefined; + this.indices = null; + this.opacity_ = undefined; + this.originX_ = undefined; + this.originY_ = undefined; + this.rotateWithView_ = undefined; + this.rotation_ = undefined; + this.scale_ = undefined; + this.vertices = null; + this.width_ = undefined; +}; + + +/** + * @private + * @param {Array.<WebGLTexture>} textures Textures. + * @param {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} images + * Images. + * @param {Object.<string, WebGLTexture>} texturePerImage Texture cache. + * @param {WebGLRenderingContext} gl Gl. + */ +ol.render.webgl.ImageReplay.prototype.createTextures_ = function(textures, images, texturePerImage, gl) { + ol.DEBUG && console.assert(textures.length === 0, + 'upon creation, textures is empty'); + + var texture, image, uid, i; + var ii = images.length; + for (i = 0; i < ii; ++i) { + image = images[i]; + + uid = ol.getUid(image).toString(); + if (uid in texturePerImage) { + texture = texturePerImage[uid]; + } else { + texture = ol.webgl.Context.createTexture( + gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE); + texturePerImage[uid] = texture; + } + textures[i] = texture; + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader = ol.render.webgl.imagereplay.defaultshader.fragment; + var vertexShader = ol.render.webgl.imagereplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.imagereplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + // use the program (FIXME: use the return value) + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 32, 0); + + gl.enableVertexAttribArray(locations.a_offsets); + gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT, + false, 32, 8); + + gl.enableVertexAttribArray(locations.a_texCoord); + gl.vertexAttribPointer(locations.a_texCoord, 2, ol.webgl.FLOAT, + false, 32, 16); + + gl.enableVertexAttribArray(locations.a_opacity); + gl.vertexAttribPointer(locations.a_opacity, 1, ol.webgl.FLOAT, + false, 32, 24); + + gl.enableVertexAttribArray(locations.a_rotateWithView); + gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT, + false, 32, 28); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_offsets); + gl.disableVertexAttribArray(locations.a_texCoord); + gl.disableVertexAttribArray(locations.a_opacity); + gl.disableVertexAttribArray(locations.a_rotateWithView); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + var textures = hitDetection ? this.hitDetectionTextures_ : this.textures_; + var groupIndices = hitDetection ? this.hitDetectionGroupIndices_ : this.groupIndices_; + ol.DEBUG && console.assert(textures.length === groupIndices.length, + 'number of textures and groupIndeces match'); + + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_( + gl, context, skippedFeaturesHash, textures, groupIndices); + } else { + var i, ii, start; + for (i = 0, ii = textures.length, start = 0; i < ii; ++i) { + gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); + var end = groupIndices[i]; + this.drawElements(gl, context, start, end); + start = end; + } + } +}; + + +/** + * Draw the replay while paying attention to skipped features. + * + * This functions creates groups of features that can be drawn to together, + * so that the number of `drawElements` calls is minimized. + * + * For example given the following texture groups: + * + * Group 1: A B C + * Group 2: D [E] F G + * + * If feature E should be skipped, the following `drawElements` calls will be + * made: + * + * drawElements with feature A, B and C + * drawElements with feature D + * drawElements with feature F and G + * + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {Array.<WebGLTexture>} textures Textures. + * @param {Array.<number>} groupIndices Texture group indices. + */ +ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash, textures, + groupIndices) { + var featureIndex = 0; + + var i, ii; + for (i = 0, ii = textures.length; i < ii; ++i) { + gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); + var groupStart = (i > 0) ? groupIndices[i - 1] : 0; + var groupEnd = groupIndices[i]; + + var start = groupStart; + var end = groupStart; + while (featureIndex < this.startIndices.length && + this.startIndices[featureIndex] <= groupEnd) { + var feature = this.startIndicesFeature[featureIndex]; + + var featureUid = ol.getUid(feature).toString(); + if (skippedFeaturesHash[featureUid] !== undefined) { + // feature should be skipped + if (start !== end) { + // draw the features so far + this.drawElements(gl, context, start, end); + } + // continue with the next feature + start = (featureIndex === this.startIndices.length - 1) ? + groupEnd : this.startIndices[featureIndex + 1]; + end = start; + } else { + // the feature is not skipped, augment the end index + end = (featureIndex === this.startIndices.length - 1) ? + groupEnd : this.startIndices[featureIndex + 1]; + } + featureIndex++; + } + + if (start !== end) { + // draw the remaining features (in case there was no skipped feature + // in this texture group, all features of a group are drawn together) + this.drawElements(gl, context, start, end); + } + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + ol.DEBUG && console.assert(this.hitDetectionTextures_.length === + this.hitDetectionGroupIndices_.length, + 'number of hitDetectionTextures and hitDetectionGroupIndices match'); + + var i, groupStart, start, end, feature, featureUid; + var featureIndex = this.startIndices.length - 1; + for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) { + gl.bindTexture(ol.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]); + groupStart = (i > 0) ? this.hitDetectionGroupIndices_[i - 1] : 0; + end = this.hitDetectionGroupIndices_[i]; + + // draw all features for this texture group + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array<number>} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + if (result) { + return result; + } + } + + end = start; + featureIndex--; + } + } + return undefined; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { + var anchor = imageStyle.getAnchor(); + var image = imageStyle.getImage(1); + var imageSize = imageStyle.getImageSize(); + var hitDetectionImage = imageStyle.getHitDetectionImage(1); + var hitDetectionImageSize = imageStyle.getHitDetectionImageSize(); + var opacity = imageStyle.getOpacity(); + var origin = imageStyle.getOrigin(); + var rotateWithView = imageStyle.getRotateWithView(); + var rotation = imageStyle.getRotation(); + var size = imageStyle.getSize(); + var scale = imageStyle.getScale(); + ol.DEBUG && console.assert(anchor, 'imageStyle anchor is not null'); + ol.DEBUG && console.assert(image, 'imageStyle image is not null'); + ol.DEBUG && console.assert(imageSize, + 'imageStyle imageSize is not null'); + ol.DEBUG && console.assert(hitDetectionImage, + 'imageStyle hitDetectionImage is not null'); + ol.DEBUG && console.assert(hitDetectionImageSize, + 'imageStyle hitDetectionImageSize is not null'); + ol.DEBUG && console.assert(opacity !== undefined, 'imageStyle opacity is defined'); + ol.DEBUG && console.assert(origin, 'imageStyle origin is not null'); + ol.DEBUG && console.assert(rotateWithView !== undefined, + 'imageStyle rotateWithView is defined'); + ol.DEBUG && console.assert(rotation !== undefined, 'imageStyle rotation is defined'); + ol.DEBUG && console.assert(size, 'imageStyle size is not null'); + ol.DEBUG && console.assert(scale !== undefined, 'imageStyle scale is defined'); + + var currentImage; + if (this.images_.length === 0) { + this.images_.push(image); + } else { + currentImage = this.images_[this.images_.length - 1]; + if (ol.getUid(currentImage) != ol.getUid(image)) { + this.groupIndices_.push(this.indices.length); + ol.DEBUG && console.assert(this.groupIndices_.length === this.images_.length, + 'number of groupIndices and images match'); + this.images_.push(image); + } + } + + if (this.hitDetectionImages_.length === 0) { + this.hitDetectionImages_.push(hitDetectionImage); + } else { + currentImage = + this.hitDetectionImages_[this.hitDetectionImages_.length - 1]; + if (ol.getUid(currentImage) != ol.getUid(hitDetectionImage)) { + this.hitDetectionGroupIndices_.push(this.indices.length); + ol.DEBUG && console.assert(this.hitDetectionGroupIndices_.length === + this.hitDetectionImages_.length, + 'number of hitDetectionGroupIndices and hitDetectionImages match'); + this.hitDetectionImages_.push(hitDetectionImage); + } + } + + this.anchorX_ = anchor[0]; + this.anchorY_ = anchor[1]; + this.height_ = size[1]; + this.imageHeight_ = imageSize[1]; + this.imageWidth_ = imageSize[0]; + this.opacity_ = opacity; + this.originX_ = origin[0]; + this.originY_ = origin[1]; + this.rotation_ = rotation; + this.rotateWithView_ = rotateWithView; + this.scale_ = scale; + this.width_ = size[0]; +}; + +goog.provide('ol.geom.flat.topology'); + +goog.require('ol.geom.flat.area'); + +/** + * Check if the linestring is a boundary. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {boolean} The linestring is a boundary. + */ +ol.geom.flat.topology.lineStringIsClosed = function(flatCoordinates, offset, end, stride) { + var lastCoord = end - stride; + if (flatCoordinates[offset] === flatCoordinates[lastCoord] && + flatCoordinates[offset + 1] === flatCoordinates[lastCoord + 1] && (end - offset) / stride > 3) { + return !!ol.geom.flat.area.linearRing(flatCoordinates, offset, end, stride); + } + return false; +}; + +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.linestringreplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.linestringreplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\nuniform vec2 u_size;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n if (v_round > 0.0) {\n vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_roundVertex.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth * u_pixelRatio) {\n discard;\n }\n }\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec2 b;varying float c;uniform float m;uniform vec4 n;uniform vec2 o;uniform float p;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*o.x*p,(b.y+1.0)/2.0*o.y*p);if(length(windowCoords-gl_FragCoord.xy)>c*p){discard;}} gl_FragColor=n;float alpha=n.a*m;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.linestringreplay.defaultshader.fragment = new ol.render.webgl.linestringreplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.linestringreplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nbool nearlyEquals(in float value, in float ref) {\n float epsilon = 0.000000000001;\n return value >= ref - epsilon && value <= ref + epsilon;\n}\n\nvoid alongNormal(out vec2 offset, in vec2 nextP, in float turnDir, in float direction) {\n vec2 dirVect = nextP - a_position;\n vec2 normal = normalize(vec2(-turnDir * dirVect.y, turnDir * dirVect.x));\n offset = u_lineWidth / 2.0 * normal * direction;\n}\n\nvoid miterUp(out vec2 offset, out float round, in bool isRound, in float direction) {\n float halfWidth = u_lineWidth / 2.0;\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n float miterLength = abs(halfWidth / dot(normal, tmpNormal));\n offset = normal * direction * miterLength;\n round = 0.0;\n if (isRound) {\n round = 1.0;\n } else if (miterLength > u_miterLimit + u_lineWidth) {\n offset = halfWidth * tmpNormal * direction;\n }\n}\n\nbool miterDown(out vec2 offset, in vec4 projPos, in mat4 offsetMatrix, in float direction) {\n bool degenerate = false;\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n vec2 dirVect = a_lastPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n float halfWidth = u_lineWidth / 2.0;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0.0, 1.0);\n } else {\n shortOffset = tmpNormal * direction * halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0.0, 1.0);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0.0, 1.0) + offsetMatrix * vec4(longOffset, 0.0, 0.0);\n vec4 p2 = projPos + offsetMatrix * vec4(longOffset, 0.0, 0.0);\n vec4 p3 = shortProjVertex + offsetMatrix * vec4(-shortOffset, 0.0, 0.0);\n vec4 p4 = shortProjVertex + offsetMatrix * vec4(shortOffset, 0.0, 0.0);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n float epsilon = 0.000000000001;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n shortProjVertex.x = p1.x + firstU * (p2.x - p1.x);\n shortProjVertex.y = p1.y + firstU * (p2.y - p1.y);\n offset = shortProjVertex.xy;\n degenerate = true;\n } else {\n float miterLength = abs(halfWidth / dot(normal, tmpNormal));\n offset = normal * direction * miterLength;\n }\n return degenerate;\n}\n\nvoid squareCap(out vec2 offset, out float round, in bool isRound, in vec2 nextP,\n in float turnDir, in float direction) {\n round = 0.0;\n vec2 dirVect = a_position - nextP;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(turnDir * firstNormal.y * direction, -turnDir * firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n vec2 normal = vec2(turnDir * hypotenuse.y * direction, -turnDir * hypotenuse.x * direction);\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (isRound) {\n round = 1.0;\n }\n}\n\nvoid main(void) {\n bool degenerate = false;\n float direction = float(sign(a_direction));\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n vec2 offset;\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0.0, 1.0);\n bool round = nearlyEquals(mod(a_direction, 2.0), 0.0);\n\n v_round = 0.0;\n v_halfWidth = u_lineWidth / 2.0;\n v_roundVertex = projPos.xy;\n\n if (nearlyEquals(mod(a_direction, 3.0), 0.0) || nearlyEquals(mod(a_direction, 17.0), 0.0)) {\n alongNormal(offset, a_nextPos, 1.0, direction);\n } else if (nearlyEquals(mod(a_direction, 5.0), 0.0) || nearlyEquals(mod(a_direction, 13.0), 0.0)) {\n alongNormal(offset, a_lastPos, -1.0, direction);\n } else if (nearlyEquals(mod(a_direction, 23.0), 0.0)) {\n miterUp(offset, v_round, round, direction);\n } else if (nearlyEquals(mod(a_direction, 19.0), 0.0)) {\n degenerate = miterDown(offset, projPos, offsetMatrix, direction);\n } else if (nearlyEquals(mod(a_direction, 7.0), 0.0)) {\n squareCap(offset, v_round, round, a_nextPos, 1.0, direction);\n } else if (nearlyEquals(mod(a_direction, 11.0), 0.0)) {\n squareCap(offset, v_round, round, a_lastPos, -1.0, direction);\n }\n if (!degenerate) {\n vec4 offsets = offsetMatrix * vec4(offset, 0.0, 0.0);\n gl_Position = projPos + offsets;\n } else {\n gl_Position = vec4(offset, 0.0, 1.0);\n }\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;bool nearlyEquals(in float value,in float ref){float epsilon=0.000000000001;return value>=ref-epsilon&&value<=ref+epsilon;}void alongNormal(out vec2 offset,in vec2 nextP,in float turnDir,in float direction){vec2 dirVect=nextP-e;vec2 normal=normalize(vec2(-turnDir*dirVect.y,turnDir*dirVect.x));offset=k/2.0*normal*direction;}void miterUp(out vec2 offset,out float round,in bool isRound,in float direction){float halfWidth=k/2.0;vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));float miterLength=abs(halfWidth/dot(normal,tmpNormal));offset=normal*direction*miterLength;round=0.0;if(isRound){round=1.0;}else if(miterLength>l+k){offset=halfWidth*tmpNormal*direction;}} bool miterDown(out vec2 offset,in vec4 projPos,in mat4 offsetMatrix,in float direction){bool degenerate=false;vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);vec2 dirVect=d-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;float halfWidth=k/2.0;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*halfWidth;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*halfWidth;longVertex=f;shortProjVertex=h*vec4(d,0.0,1.0);}else{shortOffset=tmpNormal*direction*halfWidth;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*halfWidth;longVertex=d;shortProjVertex=h*vec4(f,0.0,1.0);}vec4 p1=h*vec4(longVertex,0.0,1.0)+offsetMatrix*vec4(longOffset,0.0,0.0);vec4 p2=projPos+offsetMatrix*vec4(longOffset,0.0,0.0);vec4 p3=shortProjVertex+offsetMatrix*vec4(-shortOffset,0.0,0.0);vec4 p4=shortProjVertex+offsetMatrix*vec4(shortOffset,0.0,0.0);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;float epsilon=0.000000000001;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){shortProjVertex.x=p1.x+firstU*(p2.x-p1.x);shortProjVertex.y=p1.y+firstU*(p2.y-p1.y);offset=shortProjVertex.xy;degenerate=true;}else{float miterLength=abs(halfWidth/dot(normal,tmpNormal));offset=normal*direction*miterLength;}return degenerate;}void squareCap(out vec2 offset,out float round,in bool isRound,in vec2 nextP,in float turnDir,in float direction){round=0.0;vec2 dirVect=e-nextP;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(turnDir*firstNormal.y*direction,-turnDir*firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);vec2 normal=vec2(turnDir*hypotenuse.y*direction,-turnDir*hypotenuse.x*direction);float length=sqrt(c*c*2.0);offset=normal*length;if(isRound){round=1.0;}} void main(void){bool degenerate=false;float direction=float(sign(g));mat4 offsetMatrix=i*j;vec2 offset;vec4 projPos=h*vec4(e,0.0,1.0);bool round=nearlyEquals(mod(g,2.0),0.0);a=0.0;c=k/2.0;b=projPos.xy;if(nearlyEquals(mod(g,3.0),0.0)||nearlyEquals(mod(g,17.0),0.0)){alongNormal(offset,f,1.0,direction);}else if(nearlyEquals(mod(g,5.0),0.0)||nearlyEquals(mod(g,13.0),0.0)){alongNormal(offset,d,-1.0,direction);}else if(nearlyEquals(mod(g,23.0),0.0)){miterUp(offset,a,round,direction);}else if(nearlyEquals(mod(g,19.0),0.0)){degenerate=miterDown(offset,projPos,offsetMatrix,direction);}else if(nearlyEquals(mod(g,7.0),0.0)){squareCap(offset,a,round,f,1.0,direction);}else if(nearlyEquals(mod(g,11.0),0.0)){squareCap(offset,a,round,d,-1.0,direction);}if(!degenerate){vec4 offsets=offsetMatrix*vec4(offset,0.0,0.0);gl_Position=projPos+offsets;}else{gl_Position=vec4(offset,0.0,1.0);}}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.linestringreplay.defaultshader.vertex = new ol.render.webgl.linestringreplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.linestringreplay.defaultshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_color = gl.getUniformLocation( + program, ol.DEBUG ? 'u_color' : 'n'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_lineWidth = gl.getUniformLocation( + program, ol.DEBUG ? 'u_lineWidth' : 'k'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_miterLimit = gl.getUniformLocation( + program, ol.DEBUG ? 'u_miterLimit' : 'l'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'j'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'i'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'm'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_pixelRatio = gl.getUniformLocation( + program, ol.DEBUG ? 'u_pixelRatio' : 'p'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'h'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_size = gl.getUniformLocation( + program, ol.DEBUG ? 'u_size' : 'o'); + + /** + * @type {number} + */ + this.a_direction = gl.getAttribLocation( + program, ol.DEBUG ? 'a_direction' : 'g'); + + /** + * @type {number} + */ + this.a_lastPos = gl.getAttribLocation( + program, ol.DEBUG ? 'a_lastPos' : 'd'); + + /** + * @type {number} + */ + this.a_nextPos = gl.getAttribLocation( + program, ol.DEBUG ? 'a_nextPos' : 'f'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'e'); +}; + +goog.provide('ol.render.webgl.LineStringReplay'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.color'); +goog.require('ol.extent'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.geom.flat.topology'); +goog.require('ol.obj'); +goog.require('ol.render.webgl'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl.linestringreplay.defaultshader'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + /** + * @private + * @type {ol.render.webgl.linestringreplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.<Array.<?>>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.<number>} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {{strokeColor: (Array.<number>|null), + * lineCap: (string|undefined), + * lineDash: Array.<number>, + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined), + * changed: boolean}|null} + */ + this.state_ = { + strokeColor: null, + lineCap: undefined, + lineDash: null, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined, + changed: false + }; + +}; +ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); + + +/** + * Draw one segment. + * @private + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + */ +ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { + + var i, ii; + var numVertices = this.vertices.length; + var numIndices = this.indices.length; + //To save a vertex, the direction of a point is a product of the sign (1 or -1), a prime from + //ol.render.webgl.lineStringInstruction, and a rounding factor (1 or 2). If the product is even, + //we round it. If it is odd, we don't. + var lineJoin = this.state_.lineJoin === 'bevel' ? 0 : + this.state_.lineJoin === 'miter' ? 1 : 2; + var lineCap = this.state_.lineCap === 'butt' ? 0 : + this.state_.lineCap === 'square' ? 1 : 2; + var closed = ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, offset, end, stride); + var startCoords, sign, n; + var lastIndex = numIndices; + var lastSign = 1; + //We need the adjacent vertices to define normals in joins. p0 = last, p1 = current, p2 = next. + var p0, p1, p2; + + for (i = offset, ii = end; i < ii; i += stride) { + + n = numVertices / 7; + + p0 = p1; + p1 = p2 || [flatCoordinates[i], flatCoordinates[i + 1]]; + //First vertex. + if (i === offset) { + p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; + if (end - offset === stride * 2 && ol.array.equals(p1, p2)) { + break; + } + if (closed) { + //A closed line! Complete the circle. + p0 = [flatCoordinates[end - stride * 2], + flatCoordinates[end - stride * 2 + 1]]; + + startCoords = p2; + } else { + //Add the first two/four vertices. + + if (lineCap) { + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n + 2; + + } + + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + + lastIndex = numVertices / 7 - 1; + + continue; + } + } else if (i === end - stride) { + //Last vertex. + if (closed) { + //Same as the first vertex. + p2 = startCoords; + break; + } else { + //For the compiler not to complain. This will never be [0, 0]. + ol.DEBUG && console.assert(p0, 'p0 should be defined'); + p0 = p0 || [0, 0]; + + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); + + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastIndex - 1; + this.indices[numIndices++] = lastIndex; + + this.indices[numIndices++] = lastIndex; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n; + + if (lineCap) { + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n + 2; + + } + + break; + } + } else { + p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; + } + + // We group CW and straight lines, thus the not so inituitive CCW checking function. + sign = ol.render.webgl.triangleIsCounterClockwise(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]) + ? -1 : 1; + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.BEVEL_SECOND * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + + if (i > offset) { + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastIndex - 1; + this.indices[numIndices++] = lastIndex; + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; + } + + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 1; + + lastIndex = n + 2; + lastSign = sign; + + //Add miter + if (lineJoin) { + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.MITER_TOP * lineJoin, numVertices); + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n; + } + } + + if (closed) { + //Link the last triangle/rhombus to the first one. + //n will never be numVertices / 7 here. However, the compiler complains otherwise. + ol.DEBUG && console.assert(n, 'n should be defined'); + n = n || numVertices / 7; + sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) + ? 1 : -1; + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastIndex - 1; + this.indices[numIndices++] = lastIndex; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; + } +}; + +/** + * @param {Array.<number>} p0 Last coordinates. + * @param {Array.<number>} p1 Current coordinates. + * @param {Array.<number>} p2 Next coordinates. + * @param {number} product Sign, instruction, and rounding product. + * @param {number} numVertices Vertex counter. + * @return {number} Vertex counter. + * @private + */ +ol.render.webgl.LineStringReplay.prototype.addVertices_ = function(p0, p1, p2, product, numVertices) { + this.vertices[numVertices++] = p0[0]; + this.vertices[numVertices++] = p0[1]; + this.vertices[numVertices++] = p1[0]; + this.vertices[numVertices++] = p1[1]; + this.vertices[numVertices++] = p2[0]; + this.vertices[numVertices++] = p2[1]; + this.vertices[numVertices++] = product; + + return numVertices; +}; + +/** + * Check if the linestring can be drawn (i. e. valid). + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {boolean} The linestring can be drawn. + * @private + */ +ol.render.webgl.LineStringReplay.prototype.isValid_ = function(flatCoordinates, offset, end, stride) { + var range = end - offset; + if (range < stride * 2) { + return false; + } else if (range === stride * 2) { + var firstP = [flatCoordinates[offset], flatCoordinates[offset + 1]]; + var lastP = [flatCoordinates[offset + stride], flatCoordinates[offset + stride + 1]]; + return !ol.array.equals(firstP, lastP); + } + + return true; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) { + var flatCoordinates = lineStringGeometry.getFlatCoordinates(); + var stride = lineStringGeometry.getStride(); + if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + if (this.state_.changed) { + this.styleIndices_.push(this.indices.length); + this.state_.changed = false; + } + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) { + var indexCount = this.indices.length; + var lineStringGeometries = multiLineStringGeometry.getLineStrings(); + var i, ii; + for (i = 0, ii = lineStringGeometries.length; i < ii; ++i) { + var flatCoordinates = lineStringGeometries[i].getFlatCoordinates(); + var stride = lineStringGeometries[i].getStride(); + if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + } + } + if (this.indices.length > indexCount) { + this.startIndices.push(indexCount); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(indexCount); + this.state_.changed = false; + } + } +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<Array.<number>>} holeFlatCoordinates Hole flat coordinates. + * @param {number} stride Stride. + */ +ol.render.webgl.LineStringReplay.prototype.drawPolygonCoordinates = function( + flatCoordinates, holeFlatCoordinates, stride) { + if (!ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, 0, + flatCoordinates.length, stride)) { + flatCoordinates.push(flatCoordinates[0]); + flatCoordinates.push(flatCoordinates[1]); + } + this.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); + if (holeFlatCoordinates.length) { + var i, ii; + for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { + if (!ol.geom.flat.topology.lineStringIsClosed(holeFlatCoordinates[i], 0, + holeFlatCoordinates[i].length, stride)) { + holeFlatCoordinates[i].push(holeFlatCoordinates[i][0]); + holeFlatCoordinates[i].push(holeFlatCoordinates[i][1]); + } + this.drawCoordinates_(holeFlatCoordinates[i], 0, + holeFlatCoordinates[i].length, stride); + } + } +}; + + +/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {number=} opt_index Index count. + */ +ol.render.webgl.LineStringReplay.prototype.setPolygonStyle = function(feature, opt_index) { + var index = opt_index === undefined ? this.indices.length : opt_index; + this.startIndices.push(index); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(index); + this.state_.changed = false; + } +}; + + +/** + * @return {number} Current index. + */ +ol.render.webgl.LineStringReplay.prototype.getCurrentIndex = function() { + return this.indices.length; +}; + + +/** + * @inheritDoc + **/ +ol.render.webgl.LineStringReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); + + this.startIndices.push(this.indices.length); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } + + this.vertices = null; + this.indices = null; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other LineStringReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, 'verticesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.linestringreplay.defaultshader.fragment; + vertexShader = ol.render.webgl.linestringreplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.linestringreplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_lastPos); + gl.vertexAttribPointer(locations.a_lastPos, 2, ol.webgl.FLOAT, + false, 28, 0); + + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 28, 8); + + gl.enableVertexAttribArray(locations.a_nextPos); + gl.vertexAttribPointer(locations.a_nextPos, 2, ol.webgl.FLOAT, + false, 28, 16); + + gl.enableVertexAttribArray(locations.a_direction); + gl.vertexAttribPointer(locations.a_direction, 1, ol.webgl.FLOAT, + false, 28, 24); + + // Enable renderer specific uniforms. + gl.uniform2fv(locations.u_size, size); + gl.uniform1f(locations.u_pixelRatio, pixelRatio); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_lastPos); + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_nextPos); + gl.disableVertexAttribArray(locations.a_direction); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + //Save GL parameters. + var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); + var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); + + if (!hitDetection) { + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.depthFunc(gl.NOTEQUAL); + } + + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); + } else { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, start, end, nextStyle; + end = this.startIndices[this.startIndices.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); + end = start; + } + } + if (!hitDetection) { + gl.disable(gl.DEPTH_TEST); + gl.clear(gl.DEPTH_BUFFER_BIT); + //Restore GL parameters. + gl.depthMask(tmpDepthMask); + gl.depthFunc(tmpDepthFunc); + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.LineStringReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices.length - 2; + end = start = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + featureStart = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); + } + start = end = groupStart; + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices.length - 2; + end = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array<number>} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; + } + } + return undefined; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.<number>} color Color. + * @param {number} lineWidth Line width. + * @param {number} miterLimit Miter limit. + */ +ol.render.webgl.LineStringReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth, miterLimit) { + gl.uniform4fv(this.defaultLocations_.u_color, color); + gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth); + gl.uniform1f(this.defaultLocations_.u_miterLimit, miterLimit); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + var strokeStyleLineCap = strokeStyle.getLineCap(); + this.state_.lineCap = strokeStyleLineCap !== undefined ? + strokeStyleLineCap : ol.render.webgl.defaultLineCap; + var strokeStyleLineDash = strokeStyle.getLineDash(); + this.state_.lineDash = strokeStyleLineDash ? + strokeStyleLineDash : ol.render.webgl.defaultLineDash; + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + this.state_.lineJoin = strokeStyleLineJoin !== undefined ? + strokeStyleLineJoin : ol.render.webgl.defaultLineJoin; + var strokeStyleColor = strokeStyle.getColor(); + if (!(strokeStyleColor instanceof CanvasGradient) && + !(strokeStyleColor instanceof CanvasPattern)) { + strokeStyleColor = ol.color.asArray(strokeStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultStrokeStyle; + } else { + strokeStyleColor = ol.render.webgl.defaultStrokeStyle; + } + var strokeStyleWidth = strokeStyle.getWidth(); + strokeStyleWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.webgl.defaultLineWidth; + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + strokeStyleMiterLimit = strokeStyleMiterLimit !== undefined ? + strokeStyleMiterLimit : ol.render.webgl.defaultMiterLimit; + if (!this.state_.strokeColor || !ol.array.equals(this.state_.strokeColor, strokeStyleColor) || + this.state_.lineWidth !== strokeStyleWidth || this.state_.miterLimit !== strokeStyleMiterLimit) { + this.state_.changed = true; + this.state_.strokeColor = strokeStyleColor; + this.state_.lineWidth = strokeStyleWidth; + this.state_.miterLimit = strokeStyleMiterLimit; + this.styles_.push([strokeStyleColor, strokeStyleWidth, strokeStyleMiterLimit]); + } +}; + +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.polygonreplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nuniform vec4 u_color;\nuniform float u_opacity;\n\nvoid main(void) {\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform vec4 e;uniform float f;void main(void){gl_FragColor=e;float alpha=e.a*f;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.polygonreplay.defaultshader.fragment = new ol.render.webgl.polygonreplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0);\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){gl_Position=b*vec4(a,0.0,1.0);}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.polygonreplay.defaultshader.vertex = new ol.render.webgl.polygonreplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_color = gl.getUniformLocation( + program, ol.DEBUG ? 'u_color' : 'e'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'd'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'c'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'f'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'b'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'a'); +}; + +goog.provide('ol.structs.LinkedList'); + +/** + * Creates an empty linked list structure. + * + * @constructor + * @struct + * @param {boolean=} opt_circular The last item is connected to the first one, + * and the first item to the last one. Default is true. + */ +ol.structs.LinkedList = function(opt_circular) { + + /** + * @private + * @type {ol.LinkedListItem|undefined} + */ + this.first_ = undefined; + + /** + * @private + * @type {ol.LinkedListItem|undefined} + */ + this.last_ = undefined; + + /** + * @private + * @type {ol.LinkedListItem|undefined} + */ + this.head_ = undefined; + + /** + * @private + * @type {boolean} + */ + this.circular_ = opt_circular === undefined ? true : opt_circular; + + /** + * @private + * @type {number} + */ + this.length_ = 0; +}; + +/** + * Inserts an item into the linked list right after the current one. + * + * @param {?} data Item data. + */ +ol.structs.LinkedList.prototype.insertItem = function(data) { + + /** @type {ol.LinkedListItem} */ + var item = { + prev: undefined, + next: undefined, + data: data + }; + + var head = this.head_; + + //Initialize the list. + if (!head) { + this.first_ = item; + this.last_ = item; + if (this.circular_) { + item.next = item; + item.prev = item; + } + } else { + //Link the new item to the adjacent ones. + var next = head.next; + item.prev = head; + item.next = next; + head.next = item; + if (next) { + next.prev = item; + } + + if (head === this.last_) { + this.last_ = item; + } + } + this.head_ = item; + this.length_++; +}; + +/** + * Removes the current item from the list. Sets the cursor to the next item, + * if possible. + */ +ol.structs.LinkedList.prototype.removeItem = function() { + var head = this.head_; + if (head) { + var next = head.next; + var prev = head.prev; + if (next) { + next.prev = prev; + } + if (prev) { + prev.next = next; + } + this.head_ = next || prev; + + if (this.first_ === this.last_) { + this.head_ = undefined; + this.first_ = undefined; + this.last_ = undefined; + } else if (this.first_ === head) { + this.first_ = this.head_; + } else if (this.last_ === head) { + this.last_ = prev ? this.head_.prev : this.head_; + } + this.length_--; + } +}; + +/** + * Sets the cursor to the first item, and returns the associated data. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.firstItem = function() { + this.head_ = this.first_; + if (this.head_) { + return this.head_.data; + } + return undefined; +}; + +/** +* Sets the cursor to the last item, and returns the associated data. +* +* @return {?} Item data. +*/ +ol.structs.LinkedList.prototype.lastItem = function() { + this.head_ = this.last_; + if (this.head_) { + return this.head_.data; + } + return undefined; +}; + +/** + * Sets the cursor to the next item, and returns the associated data. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.nextItem = function() { + if (this.head_ && this.head_.next) { + this.head_ = this.head_.next; + return this.head_.data; + } + return undefined; +}; + +/** + * Returns the next item's data without moving the cursor. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.getNextItem = function() { + if (this.head_ && this.head_.next) { + return this.head_.next.data; + } + return undefined; +}; + +/** + * Sets the cursor to the previous item, and returns the associated data. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.prevItem = function() { + if (this.head_ && this.head_.prev) { + this.head_ = this.head_.prev; + return this.head_.data; + } + return undefined; +}; + +/** + * Returns the previous item's data without moving the cursor. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.getPrevItem = function() { + if (this.head_ && this.head_.prev) { + return this.head_.prev.data; + } + return undefined; +}; + +/** + * Returns the current item's data. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.getCurrItem = function() { + if (this.head_) { + return this.head_.data; + } + return undefined; +}; + +/** + * Sets the first item of the list. This only works for circular lists, and sets + * the last item accordingly. + */ +ol.structs.LinkedList.prototype.setFirstItem = function() { + if (this.circular_ && this.head_) { + this.first_ = this.head_; + this.last_ = this.head_.prev; + } +}; + +/** + * Concatenates two lists. + * @param {ol.structs.LinkedList} list List to merge into the current list. + */ +ol.structs.LinkedList.prototype.concat = function(list) { + if (list.head_) { + if (this.head_) { + var end = this.head_.next; + this.head_.next = list.first_; + list.first_.prev = this.head_; + end.prev = list.last_; + list.last_.next = end; + this.length_ += list.length_; + } else { + this.head_ = list.head_; + this.first_ = list.first_; + this.last_ = list.last_; + this.length_ = list.length_; + } + list.head_ = undefined; + list.first_ = undefined; + list.last_ = undefined; + list.length_ = 0; + } +}; + +/** + * Returns the current length of the list. + * + * @return {number} Length. + */ +ol.structs.LinkedList.prototype.getLength = function() { + return this.length_; +}; + +goog.provide('ol.ext.rbush'); +/** @typedef {function(*)} */ +ol.ext.rbush; +(function() { +var exports = {}; +var module = {exports: exports}; +var define; +/** + * @fileoverview + * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility} + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.rbush = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ +'use strict'; + +module.exports = partialSort; + +// Floyd-Rivest selection algorithm: +// Rearrange items so that all items in the [left, k] range are smaller than all items in (k, right]; +// The k-th element will have the (k - left + 1)th smallest value in [left, right] + +function partialSort(arr, k, left, right, compare) { + left = left || 0; + right = right || (arr.length - 1); + compare = compare || defaultCompare; + + while (right > left) { + if (right - left > 600) { + var n = right - left + 1; + var m = k - left + 1; + var z = Math.log(n); + var s = 0.5 * Math.exp(2 * z / 3); + var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); + var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); + var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); + partialSort(arr, k, newLeft, newRight, compare); + } + + var t = arr[k]; + var i = left; + var j = right; + + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); + + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } + + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); + } + + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } +} + +function swap(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; +} + +function defaultCompare(a, b) { + return a < b ? -1 : a > b ? 1 : 0; +} + +},{}],2:[function(_dereq_,module,exports){ +'use strict'; + +module.exports = rbush; + +var quickselect = _dereq_('quickselect'); + +function rbush(maxEntries, format) { + if (!(this instanceof rbush)) return new rbush(maxEntries, format); + + // max entries in a node is 9 by default; min node fill is 40% for best performance + this._maxEntries = Math.max(4, maxEntries || 9); + this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); + + if (format) { + this._initFormat(format); + } + + this.clear(); +} + +rbush.prototype = { + + all: function () { + return this._all(this.data, []); + }, + + search: function (bbox) { + + var node = this.data, + result = [], + toBBox = this.toBBox; + + if (!intersects(bbox, node)) return result; + + var nodesToSearch = [], + i, len, child, childBBox; + + while (node) { + for (i = 0, len = node.children.length; i < len; i++) { + + child = node.children[i]; + childBBox = node.leaf ? toBBox(child) : child; + + if (intersects(bbox, childBBox)) { + if (node.leaf) result.push(child); + else if (contains(bbox, childBBox)) this._all(child, result); + else nodesToSearch.push(child); + } + } + node = nodesToSearch.pop(); + } + + return result; + }, + + collides: function (bbox) { + + var node = this.data, + toBBox = this.toBBox; + + if (!intersects(bbox, node)) return false; + + var nodesToSearch = [], + i, len, child, childBBox; + + while (node) { + for (i = 0, len = node.children.length; i < len; i++) { + + child = node.children[i]; + childBBox = node.leaf ? toBBox(child) : child; + + if (intersects(bbox, childBBox)) { + if (node.leaf || contains(bbox, childBBox)) return true; + nodesToSearch.push(child); + } + } + node = nodesToSearch.pop(); + } + + return false; + }, + + load: function (data) { + if (!(data && data.length)) return this; + + if (data.length < this._minEntries) { + for (var i = 0, len = data.length; i < len; i++) { + this.insert(data[i]); + } + return this; + } + + // recursively build the tree with the given data from stratch using OMT algorithm + var node = this._build(data.slice(), 0, data.length - 1, 0); + + if (!this.data.children.length) { + // save as is if tree is empty + this.data = node; + + } else if (this.data.height === node.height) { + // split root if trees have the same height + this._splitRoot(this.data, node); + + } else { + if (this.data.height < node.height) { + // swap trees if inserted one is bigger + var tmpNode = this.data; + this.data = node; + node = tmpNode; + } + + // insert the small tree into the large tree at appropriate level + this._insert(node, this.data.height - node.height - 1, true); + } + + return this; + }, + + insert: function (item) { + if (item) this._insert(item, this.data.height - 1); + return this; + }, + + clear: function () { + this.data = createNode([]); + return this; + }, + + remove: function (item, equalsFn) { + if (!item) return this; + + var node = this.data, + bbox = this.toBBox(item), + path = [], + indexes = [], + i, parent, index, goingUp; + + // depth-first iterative tree traversal + while (node || path.length) { + + if (!node) { // go up + node = path.pop(); + parent = path[path.length - 1]; + i = indexes.pop(); + goingUp = true; + } + + if (node.leaf) { // check current node + index = findItem(item, node.children, equalsFn); + + if (index !== -1) { + // item found, remove the item and condense tree upwards + node.children.splice(index, 1); + path.push(node); + this._condense(path); + return this; + } + } + + if (!goingUp && !node.leaf && contains(node, bbox)) { // go down + path.push(node); + indexes.push(i); + i = 0; + parent = node; + node = node.children[0]; + + } else if (parent) { // go right + i++; + node = parent.children[i]; + goingUp = false; + + } else node = null; // nothing found + } + + return this; + }, + + toBBox: function (item) { return item; }, + + compareMinX: compareNodeMinX, + compareMinY: compareNodeMinY, + + toJSON: function () { return this.data; }, + + fromJSON: function (data) { + this.data = data; + return this; + }, + + _all: function (node, result) { + var nodesToSearch = []; + while (node) { + if (node.leaf) result.push.apply(result, node.children); + else nodesToSearch.push.apply(nodesToSearch, node.children); + + node = nodesToSearch.pop(); + } + return result; + }, + + _build: function (items, left, right, height) { + + var N = right - left + 1, + M = this._maxEntries, + node; + + if (N <= M) { + // reached leaf level; return leaf + node = createNode(items.slice(left, right + 1)); + calcBBox(node, this.toBBox); + return node; + } + + if (!height) { + // target height of the bulk-loaded tree + height = Math.ceil(Math.log(N) / Math.log(M)); + + // target number of root entries to maximize storage utilization + M = Math.ceil(N / Math.pow(M, height - 1)); + } + + node = createNode([]); + node.leaf = false; + node.height = height; + + // split the items into M mostly square tiles + + var N2 = Math.ceil(N / M), + N1 = N2 * Math.ceil(Math.sqrt(M)), + i, j, right2, right3; + + multiSelect(items, left, right, N1, this.compareMinX); + + for (i = left; i <= right; i += N1) { + + right2 = Math.min(i + N1 - 1, right); + + multiSelect(items, i, right2, N2, this.compareMinY); + + for (j = i; j <= right2; j += N2) { + + right3 = Math.min(j + N2 - 1, right2); + + // pack each entry recursively + node.children.push(this._build(items, j, right3, height - 1)); + } + } + + calcBBox(node, this.toBBox); + + return node; + }, + + _chooseSubtree: function (bbox, node, level, path) { + + var i, len, child, targetNode, area, enlargement, minArea, minEnlargement; + + while (true) { + path.push(node); + + if (node.leaf || path.length - 1 === level) break; + + minArea = minEnlargement = Infinity; + + for (i = 0, len = node.children.length; i < len; i++) { + child = node.children[i]; + area = bboxArea(child); + enlargement = enlargedArea(bbox, child) - area; + + // choose entry with the least area enlargement + if (enlargement < minEnlargement) { + minEnlargement = enlargement; + minArea = area < minArea ? area : minArea; + targetNode = child; + + } else if (enlargement === minEnlargement) { + // otherwise choose one with the smallest area + if (area < minArea) { + minArea = area; + targetNode = child; + } + } + } + + node = targetNode || node.children[0]; + } + + return node; + }, + + _insert: function (item, level, isNode) { + + var toBBox = this.toBBox, + bbox = isNode ? item : toBBox(item), + insertPath = []; + + // find the best node for accommodating the item, saving all nodes along the path too + var node = this._chooseSubtree(bbox, this.data, level, insertPath); + + // put the item into the node + node.children.push(item); + extend(node, bbox); + + // split on node overflow; propagate upwards if necessary + while (level >= 0) { + if (insertPath[level].children.length > this._maxEntries) { + this._split(insertPath, level); + level--; + } else break; + } + + // adjust bboxes along the insertion path + this._adjustParentBBoxes(bbox, insertPath, level); + }, + + // split overflowed node into two + _split: function (insertPath, level) { + + var node = insertPath[level], + M = node.children.length, + m = this._minEntries; + + this._chooseSplitAxis(node, m, M); + + var splitIndex = this._chooseSplitIndex(node, m, M); + + var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); + newNode.height = node.height; + newNode.leaf = node.leaf; + + calcBBox(node, this.toBBox); + calcBBox(newNode, this.toBBox); + + if (level) insertPath[level - 1].children.push(newNode); + else this._splitRoot(node, newNode); + }, + + _splitRoot: function (node, newNode) { + // split root node + this.data = createNode([node, newNode]); + this.data.height = node.height + 1; + this.data.leaf = false; + calcBBox(this.data, this.toBBox); + }, + + _chooseSplitIndex: function (node, m, M) { + + var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index; + + minOverlap = minArea = Infinity; + + for (i = m; i <= M - m; i++) { + bbox1 = distBBox(node, 0, i, this.toBBox); + bbox2 = distBBox(node, i, M, this.toBBox); + + overlap = intersectionArea(bbox1, bbox2); + area = bboxArea(bbox1) + bboxArea(bbox2); + + // choose distribution with minimum overlap + if (overlap < minOverlap) { + minOverlap = overlap; + index = i; + + minArea = area < minArea ? area : minArea; + + } else if (overlap === minOverlap) { + // otherwise choose distribution with minimum area + if (area < minArea) { + minArea = area; + index = i; + } + } + } + + return index; + }, + + // sorts node children by the best axis for split + _chooseSplitAxis: function (node, m, M) { + + var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX, + compareMinY = node.leaf ? this.compareMinY : compareNodeMinY, + xMargin = this._allDistMargin(node, m, M, compareMinX), + yMargin = this._allDistMargin(node, m, M, compareMinY); + + // if total distributions margin value is minimal for x, sort by minX, + // otherwise it's already sorted by minY + if (xMargin < yMargin) node.children.sort(compareMinX); + }, + + // total margin of all possible split distributions where each node is at least m full + _allDistMargin: function (node, m, M, compare) { + + node.children.sort(compare); + + var toBBox = this.toBBox, + leftBBox = distBBox(node, 0, m, toBBox), + rightBBox = distBBox(node, M - m, M, toBBox), + margin = bboxMargin(leftBBox) + bboxMargin(rightBBox), + i, child; + + for (i = m; i < M - m; i++) { + child = node.children[i]; + extend(leftBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(leftBBox); + } + + for (i = M - m - 1; i >= m; i--) { + child = node.children[i]; + extend(rightBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(rightBBox); + } + + return margin; + }, + + _adjustParentBBoxes: function (bbox, path, level) { + // adjust bboxes along the given tree path + for (var i = level; i >= 0; i--) { + extend(path[i], bbox); + } + }, + + _condense: function (path) { + // go through the path, removing empty nodes and updating bboxes + for (var i = path.length - 1, siblings; i >= 0; i--) { + if (path[i].children.length === 0) { + if (i > 0) { + siblings = path[i - 1].children; + siblings.splice(siblings.indexOf(path[i]), 1); + + } else this.clear(); + + } else calcBBox(path[i], this.toBBox); + } + }, + + _initFormat: function (format) { + // data format (minX, minY, maxX, maxY accessors) + + // uses eval-type function compilation instead of just accepting a toBBox function + // because the algorithms are very sensitive to sorting functions performance, + // so they should be dead simple and without inner calls + + var compareArr = ['return a', ' - b', ';']; + + this.compareMinX = new Function('a', 'b', compareArr.join(format[0])); + this.compareMinY = new Function('a', 'b', compareArr.join(format[1])); + + this.toBBox = new Function('a', + 'return {minX: a' + format[0] + + ', minY: a' + format[1] + + ', maxX: a' + format[2] + + ', maxY: a' + format[3] + '};'); + } +}; + +function findItem(item, items, equalsFn) { + if (!equalsFn) return items.indexOf(item); + + for (var i = 0; i < items.length; i++) { + if (equalsFn(item, items[i])) return i; + } + return -1; +} + +// calculate node's bbox from bboxes of its children +function calcBBox(node, toBBox) { + distBBox(node, 0, node.children.length, toBBox, node); +} + +// min bounding rectangle of node children from k to p-1 +function distBBox(node, k, p, toBBox, destNode) { + if (!destNode) destNode = createNode(null); + destNode.minX = Infinity; + destNode.minY = Infinity; + destNode.maxX = -Infinity; + destNode.maxY = -Infinity; + + for (var i = k, child; i < p; i++) { + child = node.children[i]; + extend(destNode, node.leaf ? toBBox(child) : child); + } + + return destNode; +} + +function extend(a, b) { + a.minX = Math.min(a.minX, b.minX); + a.minY = Math.min(a.minY, b.minY); + a.maxX = Math.max(a.maxX, b.maxX); + a.maxY = Math.max(a.maxY, b.maxY); + return a; +} + +function compareNodeMinX(a, b) { return a.minX - b.minX; } +function compareNodeMinY(a, b) { return a.minY - b.minY; } + +function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); } +function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); } + +function enlargedArea(a, b) { + return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * + (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)); +} + +function intersectionArea(a, b) { + var minX = Math.max(a.minX, b.minX), + minY = Math.max(a.minY, b.minY), + maxX = Math.min(a.maxX, b.maxX), + maxY = Math.min(a.maxY, b.maxY); + + return Math.max(0, maxX - minX) * + Math.max(0, maxY - minY); +} + +function contains(a, b) { + return a.minX <= b.minX && + a.minY <= b.minY && + b.maxX <= a.maxX && + b.maxY <= a.maxY; +} + +function intersects(a, b) { + return b.minX <= a.maxX && + b.minY <= a.maxY && + b.maxX >= a.minX && + b.maxY >= a.minY; +} + +function createNode(children) { + return { + children: children, + height: 1, + leaf: true, + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity + }; +} + +// sort an array so that items come in groups of n unsorted items, with groups sorted between each other; +// combines selection algorithm with binary divide & conquer approach + +function multiSelect(arr, left, right, n, compare) { + var stack = [left, right], + mid; + + while (stack.length) { + right = stack.pop(); + left = stack.pop(); + + if (right - left <= n) continue; + + mid = left + Math.ceil((right - left) / n / 2) * n; + quickselect(arr, mid, left, right, compare); + + stack.push(left, mid, mid, right); + } +} + +},{"quickselect":1}]},{},[2])(2) +}); +ol.ext.rbush = module.exports; +})(); + +goog.provide('ol.structs.RBush'); + +goog.require('ol'); +goog.require('ol.ext.rbush'); +goog.require('ol.extent'); +goog.require('ol.obj'); + + +/** + * Wrapper around the RBush by Vladimir Agafonkin. + * + * @constructor + * @param {number=} opt_maxEntries Max entries. + * @see https://github.com/mourner/rbush + * @struct + * @template T + */ +ol.structs.RBush = function(opt_maxEntries) { + + /** + * @private + */ + this.rbush_ = ol.ext.rbush(opt_maxEntries); + + /** + * A mapping between the objects added to this rbush wrapper + * and the objects that are actually added to the internal rbush. + * @private + * @type {Object.<number, ol.RBushEntry>} + */ + this.items_ = {}; + + if (ol.DEBUG) { + /** + * @private + * @type {number} + */ + this.readers_ = 0; + } +}; + + +/** + * Insert a value into the RBush. + * @param {ol.Extent} extent Extent. + * @param {T} value Value. + */ +ol.structs.RBush.prototype.insert = function(extent, value) { + if (ol.DEBUG && this.readers_) { + throw new Error('Can not insert value while reading'); + } + /** @type {ol.RBushEntry} */ + var item = { + minX: extent[0], + minY: extent[1], + maxX: extent[2], + maxY: extent[3], + value: value + }; + + this.rbush_.insert(item); + // remember the object that was added to the internal rbush + ol.DEBUG && console.assert(!(ol.getUid(value) in this.items_), + 'uid (%s) of value (%s) already exists', ol.getUid(value), value); + this.items_[ol.getUid(value)] = item; +}; + + +/** + * Bulk-insert values into the RBush. + * @param {Array.<ol.Extent>} extents Extents. + * @param {Array.<T>} values Values. + */ +ol.structs.RBush.prototype.load = function(extents, values) { + if (ol.DEBUG && this.readers_) { + throw new Error('Can not insert values while reading'); + } + ol.DEBUG && console.assert(extents.length === values.length, + 'extens and values must have same length (%s === %s)', + extents.length, values.length); + + var items = new Array(values.length); + for (var i = 0, l = values.length; i < l; i++) { + var extent = extents[i]; + var value = values[i]; + + /** @type {ol.RBushEntry} */ + var item = { + minX: extent[0], + minY: extent[1], + maxX: extent[2], + maxY: extent[3], + value: value + }; + items[i] = item; + ol.DEBUG && console.assert(!(ol.getUid(value) in this.items_), + 'uid (%s) of value (%s) already exists', ol.getUid(value), value); + this.items_[ol.getUid(value)] = item; + } + this.rbush_.load(items); +}; + + +/** + * Remove a value from the RBush. + * @param {T} value Value. + * @return {boolean} Removed. + */ +ol.structs.RBush.prototype.remove = function(value) { + if (ol.DEBUG && this.readers_) { + throw new Error('Can not remove value while reading'); + } + var uid = ol.getUid(value); + ol.DEBUG && console.assert(uid in this.items_, + 'uid (%s) of value (%s) does not exist', uid, value); + + // get the object in which the value was wrapped when adding to the + // internal rbush. then use that object to do the removal. + var item = this.items_[uid]; + delete this.items_[uid]; + return this.rbush_.remove(item) !== null; +}; + + +/** + * Update the extent of a value in the RBush. + * @param {ol.Extent} extent Extent. + * @param {T} value Value. + */ +ol.structs.RBush.prototype.update = function(extent, value) { + ol.DEBUG && console.assert(ol.getUid(value) in this.items_, + 'uid (%s) of value (%s) does not exist', ol.getUid(value), value); + + var item = this.items_[ol.getUid(value)]; + var bbox = [item.minX, item.minY, item.maxX, item.maxY]; + if (!ol.extent.equals(bbox, extent)) { + if (ol.DEBUG && this.readers_) { + throw new Error('Can not update extent while reading'); + } + this.remove(value); + this.insert(extent, value); + } +}; + + +/** + * Return all values in the RBush. + * @return {Array.<T>} All. + */ +ol.structs.RBush.prototype.getAll = function() { + var items = this.rbush_.all(); + return items.map(function(item) { + return item.value; + }); +}; + + +/** + * Return all values in the given extent. + * @param {ol.Extent} extent Extent. + * @return {Array.<T>} All in extent. + */ +ol.structs.RBush.prototype.getInExtent = function(extent) { + /** @type {ol.RBushEntry} */ + var bbox = { + minX: extent[0], + minY: extent[1], + maxX: extent[2], + maxY: extent[3] + }; + var items = this.rbush_.search(bbox); + return items.map(function(item) { + return item.value; + }); +}; + + +/** + * Calls a callback function with each value in the tree. + * If the callback returns a truthy value, this value is returned without + * checking the rest of the tree. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_this The object to use as `this` in `callback`. + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEach = function(callback, opt_this) { + if (ol.DEBUG) { + ++this.readers_; + try { + return this.forEach_(this.getAll(), callback, opt_this); + } finally { + --this.readers_; + } + } else { + return this.forEach_(this.getAll(), callback, opt_this); + } +}; + + +/** + * Calls a callback function with each value in the provided extent. + * @param {ol.Extent} extent Extent. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_this The object to use as `this` in `callback`. + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEachInExtent = function(extent, callback, opt_this) { + if (ol.DEBUG) { + ++this.readers_; + try { + return this.forEach_(this.getInExtent(extent), callback, opt_this); + } finally { + --this.readers_; + } + } else { + return this.forEach_(this.getInExtent(extent), callback, opt_this); + } +}; + + +/** + * @param {Array.<T>} values Values. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_this The object to use as `this` in `callback`. + * @private + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEach_ = function(values, callback, opt_this) { + var result; + for (var i = 0, l = values.length; i < l; i++) { + result = callback.call(opt_this, values[i]); + if (result) { + return result; + } + } + return result; +}; + + +/** + * @return {boolean} Is empty. + */ +ol.structs.RBush.prototype.isEmpty = function() { + return ol.obj.isEmpty(this.items_); +}; + + +/** + * Remove all values from the RBush. + */ +ol.structs.RBush.prototype.clear = function() { + this.rbush_.clear(); + this.items_ = {}; +}; + + +/** + * @param {ol.Extent=} opt_extent Extent. + * @return {!ol.Extent} Extent. + */ +ol.structs.RBush.prototype.getExtent = function(opt_extent) { + // FIXME add getExtent() to rbush + var data = this.rbush_.data; + return [data.minX, data.minY, data.maxX, data.maxY]; +}; + +goog.provide('ol.render.webgl.PolygonReplay'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.color'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.geom.flat.contains'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.render.webgl.polygonreplay.defaultshader'); +goog.require('ol.render.webgl.LineStringReplay'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl'); +goog.require('ol.style.Stroke'); +goog.require('ol.structs.LinkedList'); +goog.require('ol.structs.RBush'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + this.lineStringReplay = new ol.render.webgl.LineStringReplay( + tolerance, maxExtent); + + /** + * @private + * @type {ol.render.webgl.polygonreplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.<Array.<number>>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.<number>} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {{fillColor: (Array.<number>|null), + * changed: boolean}|null} + */ + this.state_ = { + fillColor: null, + changed: false + }; + +}; +ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); + + +/** + * Draw one polygon. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<Array.<number>>} holeFlatCoordinates Hole flat coordinates. + * @param {number} stride Stride. + * @private + */ +ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function( + flatCoordinates, holeFlatCoordinates, stride) { + // Triangulate the polygon + var outerRing = new ol.structs.LinkedList(); + var rtree = new ol.structs.RBush(); + // Initialize the outer ring + var maxX = this.processFlatCoordinates_(flatCoordinates, stride, outerRing, rtree, true); + + // Eliminate holes, if there are any + if (holeFlatCoordinates.length) { + var i, ii; + var holeLists = []; + for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { + var holeList = { + list: new ol.structs.LinkedList(), + maxX: undefined + }; + holeLists.push(holeList); + holeList.maxX = this.processFlatCoordinates_(holeFlatCoordinates[i], + stride, holeList.list, rtree, false); + } + holeLists.sort(function(a, b) { + return b.maxX - a.maxX; + }); + for (i = 0; i < holeLists.length; ++i) { + this.bridgeHole_(holeLists[i].list, holeLists[i].maxX, outerRing, maxX, rtree); + } + } + this.classifyPoints_(outerRing, rtree, false); + this.triangulate_(outerRing, rtree); +}; + + +/** + * Inserts flat coordinates in a linked list and adds them to the vertex buffer. + * @private + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} stride Stride. + * @param {ol.structs.LinkedList} list Linked list. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} clockwise Coordinate order should be clockwise. + * @return {number} Maximum X value. + */ +ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( + flatCoordinates, stride, list, rtree, clockwise) { + var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, + 0, flatCoordinates.length, stride); + var i, ii, maxX; + var n = this.vertices.length / 2; + /** @type {ol.WebglPolygonVertex} */ + var start; + /** @type {ol.WebglPolygonVertex} */ + var p0; + /** @type {ol.WebglPolygonVertex} */ + var p1; + var extents = []; + var segments = []; + if (clockwise === isClockwise) { + start = this.createPoint_(flatCoordinates[0], flatCoordinates[1], n++); + p0 = start; + maxX = flatCoordinates[0]; + for (i = stride, ii = flatCoordinates.length; i < ii; i += stride) { + p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); + segments.push(this.insertItem_(p0, p1, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; + p0 = p1; + } + segments.push(this.insertItem_(p1, start, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + } else { + var end = flatCoordinates.length - stride; + start = this.createPoint_(flatCoordinates[end], flatCoordinates[end + 1], n++); + p0 = start; + maxX = flatCoordinates[end]; + for (i = end - stride, ii = 0; i >= ii; i -= stride) { + p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); + segments.push(this.insertItem_(p0, p1, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; + p0 = p1; + } + segments.push(this.insertItem_(p1, start, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + } + rtree.load(extents, segments); + + return maxX; +}; + + +/** + * Classifies the points of a polygon list as convex, reflex. Removes collinear vertices. + * @private + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} ccw The orientation of the polygon is counter-clockwise. + * @return {boolean} There were reclassified points. + */ +ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, rtree, ccw) { + var start = list.firstItem(); + var s0 = start; + var s1 = list.nextItem(); + var pointsReclassified = false; + do { + var reflex = ccw ? ol.render.webgl.triangleIsCounterClockwise(s1.p1.x, + s1.p1.y, s0.p1.x, s0.p1.y, s0.p0.x, s0.p0.y) : + ol.render.webgl.triangleIsCounterClockwise(s0.p0.x, s0.p0.y, s0.p1.x, + s0.p1.y, s1.p1.x, s1.p1.y); + if (reflex === undefined) { + this.removeItem_(s0, s1, list, rtree); + pointsReclassified = true; + if (s1 === start) { + start = list.getNextItem(); + } + s1 = s0; + list.prevItem(); + } else if (s0.p1.reflex !== reflex) { + s0.p1.reflex = reflex; + pointsReclassified = true; + } + s0 = s1; + s1 = list.nextItem(); + } while (s0 !== start); + return pointsReclassified; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} hole Linked list of the hole. + * @param {number} holeMaxX Maximum X value of the hole. + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {number} listMaxX Maximum X value of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, + list, listMaxX, rtree) { + this.classifyPoints_(hole, rtree, true); + var seg = hole.firstItem(); + while (seg.p1.x !== holeMaxX) { + seg = hole.nextItem(); + } + + var p1 = seg.p1; + /** @type {ol.WebglPolygonVertex} */ + var p2 = {x: listMaxX, y: p1.y, i: -1}; + var minDist = Infinity; + var i, ii, bestPoint; + /** @type {ol.WebglPolygonVertex} */ + var p5; + + var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, rtree, true); + for (i = 0, ii = intersectingSegments.length; i < ii; ++i) { + var currSeg = intersectingSegments[i]; + if (currSeg.p0.reflex === undefined) { + var intersection = this.calculateIntersection_(p1, p2, currSeg.p0, + currSeg.p1, true); + var dist = Math.abs(p1.x - intersection[0]); + if (dist < minDist) { + minDist = dist; + p5 = {x: intersection[0], y: intersection[1], i: -1}; + seg = currSeg; + } + } + } + if (minDist === Infinity) { + return; + } + bestPoint = seg.p1; + + if (minDist > 0) { + var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1, rtree); + if (pointsInTriangle.length) { + var theta = Infinity; + for (i = 0, ii = pointsInTriangle.length; i < ii; ++i) { + var currPoint = pointsInTriangle[i]; + var currTheta = Math.atan2(p1.y - currPoint.y, p2.x - currPoint.x); + if (currTheta < theta || (currTheta === theta && currPoint.x < bestPoint.x)) { + theta = currTheta; + bestPoint = currPoint; + } + } + } + } + + seg = list.firstItem(); + while (seg.p1 !== bestPoint) { + seg = list.nextItem(); + } + + //We clone the bridge points as they can have different convexity. + var p0Bridge = {x: p1.x, y: p1.y, i: p1.i, reflex: undefined}; + var p1Bridge = {x: seg.p1.x, y: seg.p1.y, i: seg.p1.i, reflex: undefined}; + + hole.getNextItem().p0 = p0Bridge; + this.insertItem_(p1, seg.p1, hole, rtree); + this.insertItem_(p1Bridge, p0Bridge, hole, rtree); + seg.p1 = p1Bridge; + hole.setFirstItem(); + list.concat(hole); +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) { + var ccw = false; + var simple = this.isSimple_(list, rtree); + + // Start clipping ears + while (list.getLength() > 3) { + if (simple) { + if (!this.clipEars_(list, rtree, simple, ccw)) { + if (!this.classifyPoints_(list, rtree, ccw)) { + // Due to the behavior of OL's PIP algorithm, the ear clipping cannot + // introduce touching segments. However, the original data may have some. + if (!this.resolveLocalSelfIntersections_(list, rtree, true)) { + // Something went wrong. + ol.DEBUG && console.assert(false, 'Unexpected simple polygon geometry'); + break; + } + } + } + } else { + if (!this.clipEars_(list, rtree, simple, ccw)) { + // We ran out of ears, try to reclassify. + if (!this.classifyPoints_(list, rtree, ccw)) { + // We have a bad polygon, try to resolve local self-intersections. + if (!this.resolveLocalSelfIntersections_(list, rtree)) { + simple = this.isSimple_(list, rtree); + if (!simple) { + // We have a really bad polygon, try more time consuming methods. + this.splitPolygon_(list, rtree); + break; + } else { + ccw = !this.isClockwise_(list); + this.classifyPoints_(list, rtree, ccw); + } + } + } + } + } + } + if (list.getLength() === 3) { + var numIndices = this.indices.length; + this.indices[numIndices++] = list.getPrevItem().p0.i; + this.indices[numIndices++] = list.getCurrItem().p0.i; + this.indices[numIndices++] = list.getNextItem().p0.i; + } +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} simple The polygon is simple. + * @param {boolean} ccw Orientation of the polygon is counter-clockwise. + * @return {boolean} There were processed ears. + */ +ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, rtree, simple, ccw) { + var numIndices = this.indices.length; + var start = list.firstItem(); + var s0 = list.getPrevItem(); + var s1 = start; + var s2 = list.nextItem(); + var s3 = list.getNextItem(); + var p0, p1, p2; + var processedEars = false; + do { + p0 = s1.p0; + p1 = s1.p1; + p2 = s2.p1; + if (p1.reflex === false) { + // We might have a valid ear + var diagonalIsInside = ccw ? this.diagonalIsInside_(s3.p1, p2, p1, p0, + s0.p0) : this.diagonalIsInside_(s0.p0, p0, p1, p2, s3.p1); + if ((simple || this.getIntersections_({p0: p0, p1: p2}, rtree).length === 0) && + diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0) { + //The diagonal is completely inside the polygon + if (simple || p0.reflex === false || p2.reflex === false || + ol.geom.flat.orient.linearRingIsClockwise([s0.p0.x, s0.p0.y, p0.x, + p0.y, p1.x, p1.y, p2.x, p2.y, s3.p1.x, s3.p1.y], 0, 10, 2) === !ccw) { + //The diagonal is persumably valid, we have an ear + this.indices[numIndices++] = p0.i; + this.indices[numIndices++] = p1.i; + this.indices[numIndices++] = p2.i; + this.removeItem_(s1, s2, list, rtree); + if (s2 === start) { + start = s3; + } + processedEars = true; + } + } + } + // Else we have a reflex point. + s0 = list.getPrevItem(); + s1 = list.getCurrItem(); + s2 = list.nextItem(); + s3 = list.getNextItem(); + } while (s1 !== start && list.getLength() > 3); + + return processedEars; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_touch Resolve touching segments. + * @return {boolean} There were resolved intersections. +*/ +ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = function( + list, rtree, opt_touch) { + var start = list.firstItem(); + list.nextItem(); + var s0 = start; + var s1 = list.nextItem(); + var resolvedIntersections = false; + + do { + var intersection = this.calculateIntersection_(s0.p0, s0.p1, s1.p0, s1.p1, + opt_touch); + if (intersection) { + var breakCond = false; + var numVertices = this.vertices.length; + var numIndices = this.indices.length; + var n = numVertices / 2; + var seg = list.prevItem(); + list.removeItem(); + rtree.remove(seg); + breakCond = (seg === start); + var p; + if (opt_touch) { + if (intersection[0] === s0.p0.x && intersection[1] === s0.p0.y) { + list.prevItem(); + p = s0.p0; + s1.p0 = p; + rtree.remove(s0); + breakCond = breakCond || (s0 === start); + } else { + p = s1.p1; + s0.p1 = p; + rtree.remove(s1); + breakCond = breakCond || (s1 === start); + } + list.removeItem(); + } else { + p = this.createPoint_(intersection[0], intersection[1], n); + s0.p1 = p; + s1.p0 = p; + rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), + Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); + rtree.update([Math.min(s1.p0.x, s1.p1.x), Math.min(s1.p0.y, s1.p1.y), + Math.max(s1.p0.x, s1.p1.x), Math.max(s1.p0.y, s1.p1.y)], s1); + } + + this.indices[numIndices++] = seg.p0.i; + this.indices[numIndices++] = seg.p1.i; + this.indices[numIndices++] = p.i; + + resolvedIntersections = true; + if (breakCond) { + break; + } + } + + s0 = list.getPrevItem(); + s1 = list.nextItem(); + } while (s0 !== start); + return resolvedIntersections; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @return {boolean} The polygon is simple. + */ +ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list, rtree) { + var start = list.firstItem(); + var seg = start; + do { + if (this.getIntersections_(seg, rtree).length) { + return false; + } + seg = list.nextItem(); + } while (seg !== start); + return true; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @return {boolean} Orientation is clockwise. + */ +ol.render.webgl.PolygonReplay.prototype.isClockwise_ = function(list) { + var length = list.getLength() * 2; + var flatCoordinates = new Array(length); + var start = list.firstItem(); + var seg = start; + var i = 0; + do { + flatCoordinates[i++] = seg.p0.x; + flatCoordinates[i++] = seg.p0.y; + seg = list.nextItem(); + } while (seg !== start); + return ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, 0, length, 2); +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.splitPolygon_ = function(list, rtree) { + var start = list.firstItem(); + var s0 = start; + do { + var intersections = this.getIntersections_(s0, rtree); + if (intersections.length) { + var s1 = intersections[0]; + var n = this.vertices.length / 2; + var intersection = this.calculateIntersection_(s0.p0, + s0.p1, s1.p0, s1.p1); + var p = this.createPoint_(intersection[0], intersection[1], n); + var newPolygon = new ol.structs.LinkedList(); + var newRtree = new ol.structs.RBush(); + this.insertItem_(p, s0.p1, newPolygon, newRtree); + s0.p1 = p; + rtree.update([Math.min(s0.p0.x, p.x), Math.min(s0.p0.y, p.y), + Math.max(s0.p0.x, p.x), Math.max(s0.p0.y, p.y)], s0); + var currItem = list.nextItem(); + while (currItem !== s1) { + this.insertItem_(currItem.p0, currItem.p1, newPolygon, newRtree); + rtree.remove(currItem); + list.removeItem(); + currItem = list.getCurrItem(); + } + this.insertItem_(s1.p0, p, newPolygon, newRtree); + s1.p0 = p; + rtree.update([Math.min(s1.p1.x, p.x), Math.min(s1.p1.y, p.y), + Math.max(s1.p1.x, p.x), Math.max(s1.p1.y, p.y)], s1); + this.classifyPoints_(list, rtree, false); + this.triangulate_(list, rtree); + this.classifyPoints_(newPolygon, newRtree, false); + this.triangulate_(newPolygon, newRtree); + break; + } + s0 = list.nextItem(); + } while (s0 !== start); +}; + + +/** + * @private + * @param {number} x X coordinate. + * @param {number} y Y coordinate. + * @param {number} i Index. + * @return {ol.WebglPolygonVertex} List item. + */ +ol.render.webgl.PolygonReplay.prototype.createPoint_ = function(x, y, i) { + var numVertices = this.vertices.length; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + /** @type {ol.WebglPolygonVertex} */ + var p = { + x: x, + y: y, + i: i, + reflex: undefined + }; + return p; +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 First point of segment. + * @param {ol.WebglPolygonVertex} p1 Second point of segment. + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush=} opt_rtree Insert the segment into the R-Tree. + * @return {ol.WebglPolygonSegment} segment. + */ +ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt_rtree) { + var seg = { + p0: p0, + p1: p1 + }; + list.insertItem(seg); + if (opt_rtree) { + opt_rtree.insert([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), + Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)], seg); + } + return seg; +}; + + + /** + * @private + * @param {ol.WebglPolygonSegment} s0 Segment before the remove candidate. + * @param {ol.WebglPolygonSegment} s1 Remove candidate segment. + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list, rtree) { + if (list.getCurrItem() === s1) { + list.removeItem(); + s0.p1 = s1.p1; + rtree.remove(s1); + rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), + Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); + } +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 First point. + * @param {ol.WebglPolygonVertex} p1 Second point. + * @param {ol.WebglPolygonVertex} p2 Third point. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_reflex Only include reflex points. + * @return {Array.<ol.WebglPolygonVertex>} Points in the triangle. + */ +ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1, + p2, rtree, opt_reflex) { + var i, ii, j, p; + var result = []; + var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x, p2.x), + Math.min(p0.y, p1.y, p2.y), Math.max(p0.x, p1.x, p2.x), Math.max(p0.y, + p1.y, p2.y)]); + for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { + for (j in segmentsInExtent[i]) { + p = segmentsInExtent[i][j]; + if (typeof p === 'object' && (!opt_reflex || p.reflex)) { + if ((p.x !== p0.x || p.y !== p0.y) && (p.x !== p1.x || p.y !== p1.y) && + (p.x !== p2.x || p.y !== p2.y) && result.indexOf(p) === -1 && + ol.geom.flat.contains.linearRingContainsXY([p0.x, p0.y, p1.x, p1.y, + p2.x, p2.y], 0, 6, 2, p.x, p.y)) { + result.push(p); + } + } + } + } + return result; +}; + + +/** + * @private + * @param {ol.WebglPolygonSegment} segment Segment. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_touch Touching segments should be considered an intersection. + * @return {Array.<ol.WebglPolygonSegment>} Intersecting segments. + */ +ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, rtree, opt_touch) { + var p0 = segment.p0; + var p1 = segment.p1; + var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x), + Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)]); + var result = []; + var i, ii; + for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { + var currSeg = segmentsInExtent[i]; + if (segment !== currSeg && (opt_touch || currSeg.p0 !== p1 || currSeg.p1 !== p0) && + this.calculateIntersection_(p0, p1, currSeg.p0, currSeg.p1, opt_touch)) { + result.push(currSeg); + } + } + return result; +}; + + +/** + * Line intersection algorithm by Paul Bourke. + * @see http://paulbourke.net/geometry/pointlineplane/ + * + * @private + * @param {ol.WebglPolygonVertex} p0 First point. + * @param {ol.WebglPolygonVertex} p1 Second point. + * @param {ol.WebglPolygonVertex} p2 Third point. + * @param {ol.WebglPolygonVertex} p3 Fourth point. + * @param {boolean=} opt_touch Touching segments should be considered an intersection. + * @return {Array.<number>|undefined} Intersection coordinates. + */ +ol.render.webgl.PolygonReplay.prototype.calculateIntersection_ = function(p0, + p1, p2, p3, opt_touch) { + var denom = (p3.y - p2.y) * (p1.x - p0.x) - (p3.x - p2.x) * (p1.y - p0.y); + if (denom !== 0) { + var ua = ((p3.x - p2.x) * (p0.y - p2.y) - (p3.y - p2.y) * (p0.x - p2.x)) / denom; + var ub = ((p1.x - p0.x) * (p0.y - p2.y) - (p1.y - p0.y) * (p0.x - p2.x)) / denom; + if ((!opt_touch && ua > ol.render.webgl.EPSILON && ua < 1 - ol.render.webgl.EPSILON && + ub > ol.render.webgl.EPSILON && ub < 1 - ol.render.webgl.EPSILON) || (opt_touch && + ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)) { + return [p0.x + ua * (p1.x - p0.x), p0.y + ua * (p1.y - p0.y)]; + } + } + return undefined; +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 Point before the start of the diagonal. + * @param {ol.WebglPolygonVertex} p1 Start point of the diagonal. + * @param {ol.WebglPolygonVertex} p2 Ear candidate. + * @param {ol.WebglPolygonVertex} p3 End point of the diagonal. + * @param {ol.WebglPolygonVertex} p4 Point after the end of the diagonal. + * @return {boolean} Diagonal is inside the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.diagonalIsInside_ = function(p0, p1, p2, p3, p4) { + if (p1.reflex === undefined || p3.reflex === undefined) { + return false; + } + var p1IsLeftOf = (p2.x - p3.x) * (p1.y - p3.y) > (p2.y - p3.y) * (p1.x - p3.x); + var p1IsRightOf = (p4.x - p3.x) * (p1.y - p3.y) < (p4.y - p3.y) * (p1.x - p3.x); + var p3IsLeftOf = (p0.x - p1.x) * (p3.y - p1.y) > (p0.y - p1.y) * (p3.x - p1.x); + var p3IsRightOf = (p2.x - p1.x) * (p3.y - p1.y) < (p2.y - p1.y) * (p3.x - p1.x); + var p1InCone = p3.reflex ? p1IsRightOf || p1IsLeftOf : p1IsRightOf && p1IsLeftOf; + var p3InCone = p1.reflex ? p3IsRightOf || p3IsLeftOf : p3IsRightOf && p3IsLeftOf; + return p1InCone && p3InCone; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) { + var polygons = multiPolygonGeometry.getPolygons(); + var stride = multiPolygonGeometry.getStride(); + var currIndex = this.indices.length; + var currLineIndex = this.lineStringReplay.getCurrentIndex(); + var i, ii, j, jj; + for (i = 0, ii = polygons.length; i < ii; ++i) { + var linearRings = polygons[i].getLinearRings(); + if (linearRings.length > 0) { + var flatCoordinates = linearRings[0].getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + var holes = []; + var holeFlatCoords; + for (j = 1, jj = linearRings.length; j < jj; ++j) { + holeFlatCoords = linearRings[j].getFlatCoordinates(); + holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, + stride, -this.origin[0], -this.origin[1]); + holes.push(holeFlatCoords); + } + this.lineStringReplay.drawPolygonCoordinates(flatCoordinates, holes, stride); + this.drawCoordinates_(flatCoordinates, holes, stride); + } + } + if (this.indices.length > currIndex) { + this.startIndices.push(currIndex); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(currIndex); + this.state_.changed = false; + } + } + if (this.lineStringReplay.getCurrentIndex() > currLineIndex) { + this.lineStringReplay.setPolygonStyle(feature, currLineIndex); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { + var linearRings = polygonGeometry.getLinearRings(); + var stride = polygonGeometry.getStride(); + if (linearRings.length > 0) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(this.indices.length); + this.state_.changed = false; + } + this.lineStringReplay.setPolygonStyle(feature); + + var flatCoordinates = linearRings[0].getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + var holes = []; + var i, ii, holeFlatCoords; + for (i = 1, ii = linearRings.length; i < ii; ++i) { + holeFlatCoords = linearRings[i].getFlatCoordinates(); + holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, + stride, -this.origin[0], -this.origin[1]); + holes.push(holeFlatCoords); + } + this.lineStringReplay.drawPolygonCoordinates(flatCoordinates, holes, stride); + this.drawCoordinates_(flatCoordinates, holes, stride); + } +}; + + +/** + * @inheritDoc + **/ +ol.render.webgl.PolygonReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); + + this.startIndices.push(this.indices.length); + + this.lineStringReplay.finish(context); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } + + this.vertices = null; + this.indices = null; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other PolygonReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + var lineDeleter = this.lineStringReplay.getDeleteResourcesFunction(context); + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + lineDeleter(); + }; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.polygonreplay.defaultshader.fragment; + vertexShader = ol.render.webgl.polygonreplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.polygonreplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 8, 0); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + //Save GL parameters. + var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); + var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); + + if (!hitDetection) { + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.depthFunc(gl.NOTEQUAL); + } + + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); + } else { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, start, end, nextStyle; + end = this.startIndices[this.startIndices.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + this.drawElements(gl, context, start, end); + end = start; + } + } + if (!hitDetection) { + gl.disable(gl.DEPTH_TEST); + gl.clear(gl.DEPTH_BUFFER_BIT); + //Restore GL parameters. + gl.depthMask(tmpDepthMask); + gl.depthFunc(tmpDepthFunc); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices.length - 2; + end = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array<number>} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; + } + } + return undefined; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.PolygonReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices.length - 2; + end = start = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + featureStart = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); + } + start = end = groupStart; + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.<number>} color Color. + */ +ol.render.webgl.PolygonReplay.prototype.setFillStyle_ = function(gl, color) { + gl.uniform4fv(this.defaultLocations_.u_color, color); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + var fillStyleColor = fillStyle ? fillStyle.getColor() : [0, 0, 0, 0]; + if (!(fillStyleColor instanceof CanvasGradient) && + !(fillStyleColor instanceof CanvasPattern)) { + fillStyleColor = ol.color.asArray(fillStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultFillStyle; + } else { + fillStyleColor = ol.render.webgl.defaultFillStyle; + } + if (!this.state_.fillColor || !ol.array.equals(fillStyleColor, this.state_.fillColor)) { + this.state_.fillColor = fillStyleColor; + this.state_.changed = true; + this.styles_.push(fillStyleColor); + } + //Provide a null stroke style, if no strokeStyle is provided. Required for the draw interaction to work. + if (strokeStyle) { + this.lineStringReplay.setFillStrokeStyle(null, strokeStyle); + } else { + var nullStrokeStyle = new ol.style.Stroke({ + color: [0, 0, 0, 0], + lineWidth: 0 + }); + this.lineStringReplay.setFillStrokeStyle(null, nullStrokeStyle); + } +}; + +goog.provide('ol.render.webgl.TextReplay'); + +goog.require('ol'); + +/** + * @constructor + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.TextReplay = function(tolerance, maxExtent) {}; + +/** + * @param {ol.style.Text} textStyle Text style. + */ +ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) {}; + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.TextReplay.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + return undefined; +}; + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.webgl.TextReplay.prototype.drawText = function(flatCoordinates, offset, + end, stride, geometry, feature) {}; + +/** + * @abstract + * @param {ol.webgl.Context} context Context. + */ +ol.render.webgl.TextReplay.prototype.finish = function(context) {}; + +/** + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.TextReplay.prototype.getDeleteResourcesFunction = function(context) { + return ol.nullFunction; +}; + +goog.provide('ol.render.webgl.ReplayGroup'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.render.replay'); +goog.require('ol.render.ReplayGroup'); +goog.require('ol.render.webgl'); +goog.require('ol.render.webgl.CircleReplay'); +goog.require('ol.render.webgl.ImageReplay'); +goog.require('ol.render.webgl.LineStringReplay'); +goog.require('ol.render.webgl.PolygonReplay'); +goog.require('ol.render.webgl.TextReplay'); + +/** + * @constructor + * @extends {ol.render.ReplayGroup} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @param {number=} opt_renderBuffer Render buffer. + * @struct + */ +ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) { + ol.render.ReplayGroup.call(this); + + /** + * @type {ol.Extent} + * @private + */ + this.maxExtent_ = maxExtent; + + /** + * @type {number} + * @private + */ + this.tolerance_ = tolerance; + + /** + * @type {number|undefined} + * @private + */ + this.renderBuffer_ = opt_renderBuffer; + + /** + * @private + * @type {!Object.<string, + * Object.<ol.render.ReplayType, ol.render.webgl.Replay>>} + */ + this.replaysByZIndex_ = {}; + +}; +ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup); + + +/** + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) { + var functions = []; + var zKey; + for (zKey in this.replaysByZIndex_) { + var replays = this.replaysByZIndex_[zKey]; + var replayKey; + for (replayKey in replays) { + functions.push( + replays[replayKey].getDeleteResourcesFunction(context)); + } + } + return function() { + var length = functions.length; + var result; + for (var i = 0; i < length; i++) { + result = functions[i].apply(this, arguments); + } + return result; + }; +}; + + +/** + * @param {ol.webgl.Context} context Context. + */ +ol.render.webgl.ReplayGroup.prototype.finish = function(context) { + var zKey; + for (zKey in this.replaysByZIndex_) { + var replays = this.replaysByZIndex_[zKey]; + var replayKey; + for (replayKey in replays) { + replays[replayKey].finish(context); + } + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { + var zIndexKey = zIndex !== undefined ? zIndex.toString() : '0'; + var replays = this.replaysByZIndex_[zIndexKey]; + if (replays === undefined) { + replays = {}; + this.replaysByZIndex_[zIndexKey] = replays; + } + var replay = replays[replayType]; + if (replay === undefined) { + var Constructor = ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_[replayType]; + ol.DEBUG && console.assert(Constructor !== undefined, + replayType + + ' constructor missing from ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_'); + replay = new Constructor(this.tolerance_, this.maxExtent_); + replays[replayType] = replay; + } + return replay; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { + return ol.obj.isEmpty(this.replaysByZIndex_); +}; + + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + */ +ol.render.webgl.ReplayGroup.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash) { + /** @type {Array.<number>} */ + var zs = Object.keys(this.replaysByZIndex_).map(Number); + zs.sort(ol.array.numberSafeCompareFunction); + + var i, ii, j, jj, replays, replay; + for (i = 0, ii = zs.length; i < ii; ++i) { + replays = this.replaysByZIndex_[zs[i].toString()]; + for (j = 0, jj = ol.render.replay.ORDER.length; j < jj; ++j) { + replay = replays[ol.render.replay.ORDER[j]]; + if (replay !== undefined) { + replay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + undefined, false); + } + } + } +}; + + +/** + * @private + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, + center, resolution, rotation, size, pixelRatio, opacity, + skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent) { + /** @type {Array.<number>} */ + var zs = Object.keys(this.replaysByZIndex_).map(Number); + zs.sort(function(a, b) { + return b - a; + }); + + var i, ii, j, replays, replay, result; + for (i = 0, ii = zs.length; i < ii; ++i) { + replays = this.replaysByZIndex_[zs[i].toString()]; + for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) { + replay = replays[ol.render.replay.ORDER[j]]; + if (replay !== undefined) { + result = replay.replay(context, + center, resolution, rotation, size, pixelRatio, opacity, + skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent); + if (result) { + return result; + } + } + } + } + return undefined; +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} callback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + callback) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + + + /** + * @type {ol.Extent} + */ + var hitExtent; + if (this.renderBuffer_ !== undefined) { + // build an extent around the coordinate, so that only features that + // intersect this extent are checked + hitExtent = ol.extent.buffer( + ol.extent.createOrUpdateFromCoordinate(coordinate), + resolution * this.renderBuffer_); + } + + return this.replayHitDetection_(context, + coordinate, resolution, rotation, ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_, + pixelRatio, opacity, skippedFeaturesHash, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var imageData = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); + + if (imageData[3] > 0) { + var result = callback(feature); + if (result) { + return result; + } + } + }, true, hitExtent); +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @return {boolean} Is there a feature at the given coordinate? + */ +ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + + var hasFeature = this.replayHitDetection_(context, + coordinate, resolution, rotation, ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_, + pixelRatio, opacity, skippedFeaturesHash, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {boolean} Is there a feature? + */ + function(feature) { + var imageData = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); + return imageData[3] > 0; + }, false); + + return hasFeature !== undefined; +}; + +/** + * @const + * @private + * @type {Array.<number>} + */ +ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_ = [1, 1]; + +/** + * @const + * @private + * @type {Object.<ol.render.ReplayType, + * function(new: ol.render.webgl.Replay, number, + * ol.Extent)>} + */ +ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_ = { + 'Circle': ol.render.webgl.CircleReplay, + 'Image': ol.render.webgl.ImageReplay, + 'LineString': ol.render.webgl.LineStringReplay, + 'Polygon': ol.render.webgl.PolygonReplay, + 'Text': ol.render.webgl.TextReplay +}; + +goog.provide('ol.render.webgl.Immediate'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.render.ReplayType'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.webgl.ReplayGroup'); +goog.require('ol.render.webgl'); + + +/** + * @constructor + * @extends {ol.render.VectorContext} + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {ol.Extent} extent Extent. + * @param {number} pixelRatio Pixel ratio. + * @struct + */ +ol.render.webgl.Immediate = function(context, center, resolution, rotation, size, extent, pixelRatio) { + ol.render.VectorContext.call(this); + + /** + * @private + */ + this.context_ = context; + + /** + * @private + */ + this.center_ = center; + + /** + * @private + */ + this.extent_ = extent; + + /** + * @private + */ + this.pixelRatio_ = pixelRatio; + + /** + * @private + */ + this.size_ = size; + + /** + * @private + */ + this.rotation_ = rotation; + + /** + * @private + */ + this.resolution_ = resolution; + + /** + * @private + * @type {ol.style.Image} + */ + this.imageStyle_ = null; + + /** + * @private + * @type {ol.style.Fill} + */ + this.fillStyle_ = null; + + /** + * @private + * @type {ol.style.Stroke} + */ + this.strokeStyle_ = null; + +}; +ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext); + + +/** + * Set the rendering style. Note that since this is an immediate rendering API, + * any `zIndex` on the provided style will be ignored. + * + * @param {ol.style.Style} style The rendering style. + * @api + */ +ol.render.webgl.Immediate.prototype.setStyle = function(style) { + this.setFillStrokeStyle(style.getFill(), style.getStroke()); + this.setImageStyle(style.getImage()); +}; + + +/** + * Render a geometry into the canvas. Call + * {@link ol.render.webgl.Immediate#setStyle} first to set the rendering style. + * + * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render. + * @api + */ +ol.render.webgl.Immediate.prototype.drawGeometry = function(geometry) { + var type = geometry.getType(); + switch (type) { + case ol.geom.GeometryType.POINT: + this.drawPoint(/** @type {ol.geom.Point} */ (geometry), null); + break; + case ol.geom.GeometryType.LINE_STRING: + this.drawLineString(/** @type {ol.geom.LineString} */ (geometry), null); + break; + case ol.geom.GeometryType.POLYGON: + this.drawPolygon(/** @type {ol.geom.Polygon} */ (geometry), null); + break; + case ol.geom.GeometryType.MULTI_POINT: + this.drawMultiPoint(/** @type {ol.geom.MultiPoint} */ (geometry), null); + break; + case ol.geom.GeometryType.MULTI_LINE_STRING: + this.drawMultiLineString(/** @type {ol.geom.MultiLineString} */ (geometry), null); + break; + case ol.geom.GeometryType.MULTI_POLYGON: + this.drawMultiPolygon(/** @type {ol.geom.MultiPolygon} */ (geometry), null); + break; + case ol.geom.GeometryType.GEOMETRY_COLLECTION: + this.drawGeometryCollection(/** @type {ol.geom.GeometryCollection} */ (geometry), null); + break; + case ol.geom.GeometryType.CIRCLE: + this.drawCircle(/** @type {ol.geom.Circle} */ (geometry), null); + break; + default: + ol.DEBUG && console.assert(false, 'Unsupported geometry type: ' + type); + } +}; + + +/** + * @inheritDoc + * @api + */ +ol.render.webgl.Immediate.prototype.drawFeature = function(feature, style) { + var geometry = style.getGeometryFunction()(feature); + if (!geometry || + !ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + this.setStyle(style); + ol.DEBUG && console.assert(geometry, 'geometry must be truthy'); + this.drawGeometry(geometry); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawGeometryCollection = function(geometry, data) { + var geometries = geometry.getGeometriesArray(); + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + this.drawGeometry(geometries[i]); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawPoint = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.ImageReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.IMAGE)); + replay.setImageStyle(this.imageStyle_); + replay.drawPoint(geometry, data); + replay.finish(context); + // default colors + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawMultiPoint = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.ImageReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.IMAGE)); + replay.setImageStyle(this.imageStyle_); + replay.drawMultiPoint(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawLineString = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.LineStringReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.LINE_STRING)); + replay.setFillStrokeStyle(null, this.strokeStyle_); + replay.drawLineString(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawMultiLineString = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.LineStringReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.LINE_STRING)); + replay.setFillStrokeStyle(null, this.strokeStyle_); + replay.drawMultiLineString(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawPolygon = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.PolygonReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.POLYGON)); + replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); + replay.drawPolygon(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawMultiPolygon = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.PolygonReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.POLYGON)); + replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); + replay.drawMultiPolygon(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawCircle = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.CircleReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.CIRCLE)); + replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); + replay.drawCircle(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) { + this.imageStyle_ = imageStyle; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + this.fillStyle_ = fillStyle; + this.strokeStyle_ = strokeStyle; +}; + +// This file is automatically generated, do not edit +goog.provide('ol.renderer.webgl.defaultmapshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.renderer.webgl.defaultmapshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.renderer.webgl.defaultmapshader.Fragment.SOURCE); +}; +ol.inherits(ol.renderer.webgl.defaultmapshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_texture, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n gl_FragColor.a = texColor.a * u_opacity;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Fragment.SOURCE = ol.DEBUG ? + ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE : + ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE; + + +ol.renderer.webgl.defaultmapshader.fragment = new ol.renderer.webgl.defaultmapshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.renderer.webgl.defaultmapshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.renderer.webgl.defaultmapshader.Vertex.SOURCE); +}; +ol.inherits(ol.renderer.webgl.defaultmapshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Vertex.SOURCE = ol.DEBUG ? + ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE : + ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE; + + +ol.renderer.webgl.defaultmapshader.vertex = new ol.renderer.webgl.defaultmapshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.renderer.webgl.defaultmapshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'f'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'e'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_texCoordMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_texCoordMatrix' : 'd'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_texture = gl.getUniformLocation( + program, ol.DEBUG ? 'u_texture' : 'g'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'b'); + + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, ol.DEBUG ? 'a_texCoord' : 'c'); +}; + +goog.provide('ol.renderer.webgl.Layer'); + +goog.require('ol'); +goog.require('ol.render.Event'); +goog.require('ol.render.webgl.Immediate'); +goog.require('ol.renderer.Layer'); +goog.require('ol.renderer.webgl.defaultmapshader'); +goog.require('ol.transform'); +goog.require('ol.vec.Mat4'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); +goog.require('ol.webgl.Context'); + + +/** + * @constructor + * @extends {ol.renderer.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Layer} layer Layer. + */ +ol.renderer.webgl.Layer = function(mapRenderer, layer) { + + ol.renderer.Layer.call(this, layer); + + /** + * @protected + * @type {ol.renderer.webgl.Map} + */ + this.mapRenderer = mapRenderer; + + /** + * @private + * @type {ol.webgl.Buffer} + */ + this.arrayBuffer_ = new ol.webgl.Buffer([ + -1, -1, 0, 0, + 1, -1, 1, 0, + -1, 1, 0, 1, + 1, 1, 1, 1 + ]); + + /** + * @protected + * @type {WebGLTexture} + */ + this.texture = null; + + /** + * @protected + * @type {WebGLFramebuffer} + */ + this.framebuffer = null; + + /** + * @protected + * @type {number|undefined} + */ + this.framebufferDimension = undefined; + + /** + * @protected + * @type {ol.Transform} + */ + this.texCoordMatrix = ol.transform.create(); + + /** + * @protected + * @type {ol.Transform} + */ + this.projectionMatrix = ol.transform.create(); + + /** + * @type {Array.<number>} + * @private + */ + this.tmpMat4_ = ol.vec.Mat4.create(); + + /** + * @private + * @type {ol.renderer.webgl.defaultmapshader.Locations} + */ + this.defaultLocations_ = null; + +}; +ol.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer); + + +/** + * @param {olx.FrameState} frameState Frame state. + * @param {number} framebufferDimension Framebuffer dimension. + * @protected + */ +ol.renderer.webgl.Layer.prototype.bindFramebuffer = function(frameState, framebufferDimension) { + + var gl = this.mapRenderer.getGL(); + + if (this.framebufferDimension === undefined || + this.framebufferDimension != framebufferDimension) { + /** + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLFramebuffer} framebuffer Framebuffer. + * @param {WebGLTexture} texture Texture. + */ + var postRenderFunction = function(gl, framebuffer, texture) { + if (!gl.isContextLost()) { + gl.deleteFramebuffer(framebuffer); + gl.deleteTexture(texture); + } + }.bind(null, gl, this.framebuffer, this.texture); + + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (postRenderFunction) + ); + + var texture = ol.webgl.Context.createEmptyTexture( + gl, framebufferDimension, framebufferDimension); + + var framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D(ol.webgl.FRAMEBUFFER, + ol.webgl.COLOR_ATTACHMENT0, ol.webgl.TEXTURE_2D, texture, 0); + + this.texture = texture; + this.framebuffer = framebuffer; + this.framebufferDimension = framebufferDimension; + + } else { + gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, this.framebuffer); + } + +}; + + +/** + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @param {ol.webgl.Context} context Context. + */ +ol.renderer.webgl.Layer.prototype.composeFrame = function(frameState, layerState, context) { + + this.dispatchComposeEvent_( + ol.render.Event.Type.PRECOMPOSE, context, frameState); + + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.arrayBuffer_); + + var gl = context.getGL(); + + var fragmentShader = ol.renderer.webgl.defaultmapshader.fragment; + var vertexShader = ol.renderer.webgl.defaultmapshader.vertex; + + var program = context.getProgram(fragmentShader, vertexShader); + + var locations; + if (!this.defaultLocations_) { + locations = + new ol.renderer.webgl.defaultmapshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + if (context.useProgram(program)) { + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer( + locations.a_position, 2, ol.webgl.FLOAT, false, 16, 0); + gl.enableVertexAttribArray(locations.a_texCoord); + gl.vertexAttribPointer( + locations.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8); + gl.uniform1i(locations.u_texture, 0); + } + + gl.uniformMatrix4fv(locations.u_texCoordMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getTexCoordMatrix())); + gl.uniformMatrix4fv(locations.u_projectionMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getProjectionMatrix())); + gl.uniform1f(locations.u_opacity, layerState.opacity); + gl.bindTexture(ol.webgl.TEXTURE_2D, this.getTexture()); + gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4); + + this.dispatchComposeEvent_( + ol.render.Event.Type.POSTCOMPOSE, context, frameState); + +}; + + +/** + * @param {ol.render.Event.Type} type Event type. + * @param {ol.webgl.Context} context WebGL context. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState) { + var layer = this.getLayer(); + if (layer.hasListener(type)) { + var viewState = frameState.viewState; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var extent = frameState.extent; + var center = viewState.center; + var rotation = viewState.rotation; + var size = frameState.size; + + var render = new ol.render.webgl.Immediate( + context, center, resolution, rotation, size, extent, pixelRatio); + var composeEvent = new ol.render.Event( + type, render, frameState, null, context); + layer.dispatchEvent(composeEvent); + } +}; + + +/** + * @return {!ol.Transform} Matrix. + */ +ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() { + return this.texCoordMatrix; +}; + + +/** + * @return {WebGLTexture} Texture. + */ +ol.renderer.webgl.Layer.prototype.getTexture = function() { + return this.texture; +}; + + +/** + * @return {!ol.Transform} Matrix. + */ +ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() { + return this.projectionMatrix; +}; + + +/** + * Handle webglcontextlost. + */ +ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() { + this.texture = null; + this.framebuffer = null; + this.framebufferDimension = undefined; +}; + + +/** + * @abstract + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @param {ol.webgl.Context} context Context. + * @return {boolean} whether composeFrame should be called. + */ +ol.renderer.webgl.Layer.prototype.prepareFrame = function(frameState, layerState, context) {}; + + +/** + * @abstract + * @param {ol.Pixel} pixel Pixel. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer + * callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @return {T|undefined} Callback result. + * @template S,T,U + */ +ol.renderer.webgl.Layer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {}; + +goog.provide('ol.ImageCanvas'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.ImageBase'); + + +/** + * @constructor + * @extends {ol.ImageBase} + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {Array.<ol.Attribution>} attributions Attributions. + * @param {HTMLCanvasElement} canvas Canvas. + * @param {ol.ImageCanvasLoader=} opt_loader Optional loader function to + * support asynchronous canvas drawing. + */ +ol.ImageCanvas = function(extent, resolution, pixelRatio, attributions, + canvas, opt_loader) { + + /** + * Optional canvas loader function. + * @type {?ol.ImageCanvasLoader} + * @private + */ + this.loader_ = opt_loader !== undefined ? opt_loader : null; + + var state = opt_loader !== undefined ? + ol.Image.State.IDLE : ol.Image.State.LOADED; + + ol.ImageBase.call(this, extent, resolution, pixelRatio, state, attributions); + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = canvas; + + /** + * @private + * @type {Error} + */ + this.error_ = null; + +}; +ol.inherits(ol.ImageCanvas, ol.ImageBase); + + +/** + * Get any error associated with asynchronous rendering. + * @return {Error} Any error that occurred during rendering. + */ +ol.ImageCanvas.prototype.getError = function() { + return this.error_; +}; + + +/** + * Handle async drawing complete. + * @param {Error} err Any error during drawing. + * @private + */ +ol.ImageCanvas.prototype.handleLoad_ = function(err) { + if (err) { + this.error_ = err; + this.state = ol.Image.State.ERROR; + } else { + this.state = ol.Image.State.LOADED; + } + this.changed(); +}; + + +/** + * Trigger drawing on canvas. + */ +ol.ImageCanvas.prototype.load = function() { + if (this.state == ol.Image.State.IDLE) { + ol.DEBUG && console.assert(this.loader_, 'this.loader_ must be set'); + this.state = ol.Image.State.LOADING; + this.changed(); + this.loader_(this.handleLoad_.bind(this)); + } +}; + + +/** + * @inheritDoc + */ +ol.ImageCanvas.prototype.getImage = function(opt_context) { + return this.canvas_; +}; + +goog.provide('ol.reproj'); + +goog.require('ol'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.math'); +goog.require('ol.proj'); + + +/** + * We need to employ more sophisticated solution + * if the web browser antialiases clipping edges on canvas. + * + * Currently only Chrome does not antialias the edges, but this is probably + * going to be "fixed" in the future: http://crbug.com/424291 + * + * @type {boolean} + * @private + */ +ol.reproj.browserAntialiasesClip_ = (function() { + // Adapted from http://stackoverflow.com/questions/4565112/javascript-how-to-find-out-if-the-user-browser-is-chrome + var isOpera = navigator.userAgent.indexOf('OPR') > -1; + var isIEedge = navigator.userAgent.indexOf('Edge') > -1; + return !( + !navigator.userAgent.match('CriOS') && // Not Chrome on iOS + 'chrome' in window && // Has chrome in window + navigator.vendor === 'Google Inc.' && // Vendor is Google. + isOpera == false && // Not Opera + isIEedge == false // Not Edge + ); +})(); + + +/** + * Calculates ideal resolution to use from the source in order to achieve + * pixel mapping as close as possible to 1:1 during reprojection. + * The resolution is calculated regardless of what resolutions + * are actually available in the dataset (TileGrid, Image, ...). + * + * @param {ol.proj.Projection} sourceProj Source projection. + * @param {ol.proj.Projection} targetProj Target projection. + * @param {ol.Coordinate} targetCenter Target center. + * @param {number} targetResolution Target resolution. + * @return {number} The best resolution to use. Can be +-Infinity, NaN or 0. + */ +ol.reproj.calculateSourceResolution = function(sourceProj, targetProj, + targetCenter, targetResolution) { + + var sourceCenter = ol.proj.transform(targetCenter, targetProj, sourceProj); + + // calculate the ideal resolution of the source data + var sourceResolution = + ol.proj.getPointResolution(targetProj, targetResolution, targetCenter); + + var targetMetersPerUnit = targetProj.getMetersPerUnit(); + if (targetMetersPerUnit !== undefined) { + sourceResolution *= targetMetersPerUnit; + } + var sourceMetersPerUnit = sourceProj.getMetersPerUnit(); + if (sourceMetersPerUnit !== undefined) { + sourceResolution /= sourceMetersPerUnit; + } + + // Based on the projection properties, the point resolution at the specified + // coordinates may be slightly different. We need to reverse-compensate this + // in order to achieve optimal results. + + var compensationFactor = + ol.proj.getPointResolution(sourceProj, sourceResolution, sourceCenter) / + sourceResolution; + + if (isFinite(compensationFactor) && compensationFactor > 0) { + sourceResolution /= compensationFactor; + } + + return sourceResolution; +}; + + +/** + * Enlarge the clipping triangle point by 1 pixel to ensure the edges overlap + * in order to mask gaps caused by antialiasing. + * + * @param {number} centroidX Centroid of the triangle (x coordinate in pixels). + * @param {number} centroidY Centroid of the triangle (y coordinate in pixels). + * @param {number} x X coordinate of the point (in pixels). + * @param {number} y Y coordinate of the point (in pixels). + * @return {ol.Coordinate} New point 1 px farther from the centroid. + * @private + */ +ol.reproj.enlargeClipPoint_ = function(centroidX, centroidY, x, y) { + var dX = x - centroidX, dY = y - centroidY; + var distance = Math.sqrt(dX * dX + dY * dY); + return [Math.round(x + dX / distance), Math.round(y + dY / distance)]; +}; + + +/** + * Renders the source data into new canvas based on the triangulation. + * + * @param {number} width Width of the canvas. + * @param {number} height Height of the canvas. + * @param {number} pixelRatio Pixel ratio. + * @param {number} sourceResolution Source resolution. + * @param {ol.Extent} sourceExtent Extent of the data source. + * @param {number} targetResolution Target resolution. + * @param {ol.Extent} targetExtent Target extent. + * @param {ol.reproj.Triangulation} triangulation Calculated triangulation. + * @param {Array.<{extent: ol.Extent, + * image: (HTMLCanvasElement|Image|HTMLVideoElement)}>} sources + * Array of sources. + * @param {number} gutter Gutter of the sources. + * @param {boolean=} opt_renderEdges Render reprojection edges. + * @return {HTMLCanvasElement} Canvas with reprojected data. + */ +ol.reproj.render = function(width, height, pixelRatio, + sourceResolution, sourceExtent, targetResolution, targetExtent, + triangulation, sources, gutter, opt_renderEdges) { + + var context = ol.dom.createCanvasContext2D(Math.round(pixelRatio * width), + Math.round(pixelRatio * height)); + + if (sources.length === 0) { + return context.canvas; + } + + context.scale(pixelRatio, pixelRatio); + + var sourceDataExtent = ol.extent.createEmpty(); + sources.forEach(function(src, i, arr) { + ol.extent.extend(sourceDataExtent, src.extent); + }); + + var canvasWidthInUnits = ol.extent.getWidth(sourceDataExtent); + var canvasHeightInUnits = ol.extent.getHeight(sourceDataExtent); + var stitchContext = ol.dom.createCanvasContext2D( + Math.round(pixelRatio * canvasWidthInUnits / sourceResolution), + Math.round(pixelRatio * canvasHeightInUnits / sourceResolution)); + + var stitchScale = pixelRatio / sourceResolution; + + sources.forEach(function(src, i, arr) { + var xPos = src.extent[0] - sourceDataExtent[0]; + var yPos = -(src.extent[3] - sourceDataExtent[3]); + var srcWidth = ol.extent.getWidth(src.extent); + var srcHeight = ol.extent.getHeight(src.extent); + + stitchContext.drawImage( + src.image, + gutter, gutter, + src.image.width - 2 * gutter, src.image.height - 2 * gutter, + xPos * stitchScale, yPos * stitchScale, + srcWidth * stitchScale, srcHeight * stitchScale); + }); + + var targetTopLeft = ol.extent.getTopLeft(targetExtent); + + triangulation.getTriangles().forEach(function(triangle, i, arr) { + /* Calculate affine transform (src -> dst) + * Resulting matrix can be used to transform coordinate + * from `sourceProjection` to destination pixels. + * + * To optimize number of context calls and increase numerical stability, + * we also do the following operations: + * trans(-topLeftExtentCorner), scale(1 / targetResolution), scale(1, -1) + * here before solving the linear system so [ui, vi] are pixel coordinates. + * + * Src points: xi, yi + * Dst points: ui, vi + * Affine coefficients: aij + * + * | x0 y0 1 0 0 0 | |a00| |u0| + * | x1 y1 1 0 0 0 | |a01| |u1| + * | x2 y2 1 0 0 0 | x |a02| = |u2| + * | 0 0 0 x0 y0 1 | |a10| |v0| + * | 0 0 0 x1 y1 1 | |a11| |v1| + * | 0 0 0 x2 y2 1 | |a12| |v2| + */ + var source = triangle.source, target = triangle.target; + var x0 = source[0][0], y0 = source[0][1], + x1 = source[1][0], y1 = source[1][1], + x2 = source[2][0], y2 = source[2][1]; + var u0 = (target[0][0] - targetTopLeft[0]) / targetResolution, + v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution; + var u1 = (target[1][0] - targetTopLeft[0]) / targetResolution, + v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution; + var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution, + v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution; + + // Shift all the source points to improve numerical stability + // of all the subsequent calculations. The [x0, y0] is used here. + // This is also used to simplify the linear system. + var sourceNumericalShiftX = x0, sourceNumericalShiftY = y0; + x0 = 0; + y0 = 0; + x1 -= sourceNumericalShiftX; + y1 -= sourceNumericalShiftY; + x2 -= sourceNumericalShiftX; + y2 -= sourceNumericalShiftY; + + var augmentedMatrix = [ + [x1, y1, 0, 0, u1 - u0], + [x2, y2, 0, 0, u2 - u0], + [0, 0, x1, y1, v1 - v0], + [0, 0, x2, y2, v2 - v0] + ]; + var affineCoefs = ol.math.solveLinearSystem(augmentedMatrix); + if (!affineCoefs) { + return; + } + + context.save(); + context.beginPath(); + if (ol.reproj.browserAntialiasesClip_) { + var centroidX = (u0 + u1 + u2) / 3, centroidY = (v0 + v1 + v2) / 3; + var p0 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u0, v0); + var p1 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u1, v1); + var p2 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u2, v2); + + context.moveTo(p1[0], p1[1]); + context.lineTo(p0[0], p0[1]); + context.lineTo(p2[0], p2[1]); + } else { + context.moveTo(u1, v1); + context.lineTo(u0, v0); + context.lineTo(u2, v2); + } + context.clip(); + + context.transform( + affineCoefs[0], affineCoefs[2], affineCoefs[1], affineCoefs[3], u0, v0); + + context.translate(sourceDataExtent[0] - sourceNumericalShiftX, + sourceDataExtent[3] - sourceNumericalShiftY); + + context.scale(sourceResolution / pixelRatio, + -sourceResolution / pixelRatio); + + context.drawImage(stitchContext.canvas, 0, 0); + context.restore(); + }); + + if (opt_renderEdges) { + context.save(); + + context.strokeStyle = 'black'; + context.lineWidth = 1; + + triangulation.getTriangles().forEach(function(triangle, i, arr) { + var target = triangle.target; + var u0 = (target[0][0] - targetTopLeft[0]) / targetResolution, + v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution; + var u1 = (target[1][0] - targetTopLeft[0]) / targetResolution, + v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution; + var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution, + v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution; + + context.beginPath(); + context.moveTo(u1, v1); + context.lineTo(u0, v0); + context.lineTo(u2, v2); + context.closePath(); + context.stroke(); + }); + + context.restore(); + } + return context.canvas; +}; + +goog.provide('ol.reproj.Triangulation'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.math'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Class containing triangulation of the given target extent. + * Used for determining source data and the reprojection itself. + * + * @param {ol.proj.Projection} sourceProj Source projection. + * @param {ol.proj.Projection} targetProj Target projection. + * @param {ol.Extent} targetExtent Target extent to triangulate. + * @param {ol.Extent} maxSourceExtent Maximal source extent that can be used. + * @param {number} errorThreshold Acceptable error (in source units). + * @constructor + */ +ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent, + maxSourceExtent, errorThreshold) { + + /** + * @type {ol.proj.Projection} + * @private + */ + this.sourceProj_ = sourceProj; + + /** + * @type {ol.proj.Projection} + * @private + */ + this.targetProj_ = targetProj; + + /** @type {!Object.<string, ol.Coordinate>} */ + var transformInvCache = {}; + var transformInv = ol.proj.getTransform(this.targetProj_, this.sourceProj_); + + /** + * @param {ol.Coordinate} c A coordinate. + * @return {ol.Coordinate} Transformed coordinate. + * @private + */ + this.transformInv_ = function(c) { + var key = c[0] + '/' + c[1]; + if (!transformInvCache[key]) { + transformInvCache[key] = transformInv(c); + } + return transformInvCache[key]; + }; + + /** + * @type {ol.Extent} + * @private + */ + this.maxSourceExtent_ = maxSourceExtent; + + /** + * @type {number} + * @private + */ + this.errorThresholdSquared_ = errorThreshold * errorThreshold; + + /** + * @type {Array.<ol.ReprojTriangle>} + * @private + */ + this.triangles_ = []; + + /** + * Indicates that the triangulation crosses edge of the source projection. + * @type {boolean} + * @private + */ + this.wrapsXInSource_ = false; + + /** + * @type {boolean} + * @private + */ + this.canWrapXInSource_ = this.sourceProj_.canWrapX() && + !!maxSourceExtent && + !!this.sourceProj_.getExtent() && + (ol.extent.getWidth(maxSourceExtent) == + ol.extent.getWidth(this.sourceProj_.getExtent())); + + /** + * @type {?number} + * @private + */ + this.sourceWorldWidth_ = this.sourceProj_.getExtent() ? + ol.extent.getWidth(this.sourceProj_.getExtent()) : null; + + /** + * @type {?number} + * @private + */ + this.targetWorldWidth_ = this.targetProj_.getExtent() ? + ol.extent.getWidth(this.targetProj_.getExtent()) : null; + + var destinationTopLeft = ol.extent.getTopLeft(targetExtent); + var destinationTopRight = ol.extent.getTopRight(targetExtent); + var destinationBottomRight = ol.extent.getBottomRight(targetExtent); + var destinationBottomLeft = ol.extent.getBottomLeft(targetExtent); + var sourceTopLeft = this.transformInv_(destinationTopLeft); + var sourceTopRight = this.transformInv_(destinationTopRight); + var sourceBottomRight = this.transformInv_(destinationBottomRight); + var sourceBottomLeft = this.transformInv_(destinationBottomLeft); + + this.addQuad_( + destinationTopLeft, destinationTopRight, + destinationBottomRight, destinationBottomLeft, + sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft, + ol.RASTER_REPROJECTION_MAX_SUBDIVISION); + + if (this.wrapsXInSource_) { + // Fix coordinates (ol.proj returns wrapped coordinates, "unwrap" here). + // This significantly simplifies the rest of the reprojection process. + + ol.DEBUG && console.assert(this.sourceWorldWidth_ !== null); + var leftBound = Infinity; + this.triangles_.forEach(function(triangle, i, arr) { + leftBound = Math.min(leftBound, + triangle.source[0][0], triangle.source[1][0], triangle.source[2][0]); + }); + + // Shift triangles to be as close to `leftBound` as possible + // (if the distance is more than `worldWidth / 2` it can be closer. + this.triangles_.forEach(function(triangle) { + if (Math.max(triangle.source[0][0], triangle.source[1][0], + triangle.source[2][0]) - leftBound > this.sourceWorldWidth_ / 2) { + var newTriangle = [[triangle.source[0][0], triangle.source[0][1]], + [triangle.source[1][0], triangle.source[1][1]], + [triangle.source[2][0], triangle.source[2][1]]]; + if ((newTriangle[0][0] - leftBound) > this.sourceWorldWidth_ / 2) { + newTriangle[0][0] -= this.sourceWorldWidth_; + } + if ((newTriangle[1][0] - leftBound) > this.sourceWorldWidth_ / 2) { + newTriangle[1][0] -= this.sourceWorldWidth_; + } + if ((newTriangle[2][0] - leftBound) > this.sourceWorldWidth_ / 2) { + newTriangle[2][0] -= this.sourceWorldWidth_; + } + + // Rarely (if the extent contains both the dateline and prime meridian) + // the shift can in turn break some triangles. + // Detect this here and don't shift in such cases. + var minX = Math.min( + newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]); + var maxX = Math.max( + newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]); + if ((maxX - minX) < this.sourceWorldWidth_ / 2) { + triangle.source = newTriangle; + } + } + }, this); + } + + transformInvCache = {}; +}; + + +/** + * Adds triangle to the triangulation. + * @param {ol.Coordinate} a The target a coordinate. + * @param {ol.Coordinate} b The target b coordinate. + * @param {ol.Coordinate} c The target c coordinate. + * @param {ol.Coordinate} aSrc The source a coordinate. + * @param {ol.Coordinate} bSrc The source b coordinate. + * @param {ol.Coordinate} cSrc The source c coordinate. + * @private + */ +ol.reproj.Triangulation.prototype.addTriangle_ = function(a, b, c, + aSrc, bSrc, cSrc) { + this.triangles_.push({ + source: [aSrc, bSrc, cSrc], + target: [a, b, c] + }); +}; + + +/** + * Adds quad (points in clock-wise order) to the triangulation + * (and reprojects the vertices) if valid. + * Performs quad subdivision if needed to increase precision. + * + * @param {ol.Coordinate} a The target a coordinate. + * @param {ol.Coordinate} b The target b coordinate. + * @param {ol.Coordinate} c The target c coordinate. + * @param {ol.Coordinate} d The target d coordinate. + * @param {ol.Coordinate} aSrc The source a coordinate. + * @param {ol.Coordinate} bSrc The source b coordinate. + * @param {ol.Coordinate} cSrc The source c coordinate. + * @param {ol.Coordinate} dSrc The source d coordinate. + * @param {number} maxSubdivision Maximal allowed subdivision of the quad. + * @private + */ +ol.reproj.Triangulation.prototype.addQuad_ = function(a, b, c, d, + aSrc, bSrc, cSrc, dSrc, maxSubdivision) { + + var sourceQuadExtent = ol.extent.boundingExtent([aSrc, bSrc, cSrc, dSrc]); + var sourceCoverageX = this.sourceWorldWidth_ ? + ol.extent.getWidth(sourceQuadExtent) / this.sourceWorldWidth_ : null; + var sourceWorldWidth = /** @type {number} */ (this.sourceWorldWidth_); + + // when the quad is wrapped in the source projection + // it covers most of the projection extent, but not fully + var wrapsX = this.sourceProj_.canWrapX() && + sourceCoverageX > 0.5 && sourceCoverageX < 1; + + var needsSubdivision = false; + + if (maxSubdivision > 0) { + if (this.targetProj_.isGlobal() && this.targetWorldWidth_) { + var targetQuadExtent = ol.extent.boundingExtent([a, b, c, d]); + var targetCoverageX = + ol.extent.getWidth(targetQuadExtent) / this.targetWorldWidth_; + needsSubdivision |= + targetCoverageX > ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH; + } + if (!wrapsX && this.sourceProj_.isGlobal() && sourceCoverageX) { + needsSubdivision |= + sourceCoverageX > ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH; + } + } + + if (!needsSubdivision && this.maxSourceExtent_) { + if (!ol.extent.intersects(sourceQuadExtent, this.maxSourceExtent_)) { + // whole quad outside source projection extent -> ignore + return; + } + } + + if (!needsSubdivision) { + if (!isFinite(aSrc[0]) || !isFinite(aSrc[1]) || + !isFinite(bSrc[0]) || !isFinite(bSrc[1]) || + !isFinite(cSrc[0]) || !isFinite(cSrc[1]) || + !isFinite(dSrc[0]) || !isFinite(dSrc[1])) { + if (maxSubdivision > 0) { + needsSubdivision = true; + } else { + return; + } + } + } + + if (maxSubdivision > 0) { + if (!needsSubdivision) { + var center = [(a[0] + c[0]) / 2, (a[1] + c[1]) / 2]; + var centerSrc = this.transformInv_(center); + + var dx; + if (wrapsX) { + var centerSrcEstimX = + (ol.math.modulo(aSrc[0], sourceWorldWidth) + + ol.math.modulo(cSrc[0], sourceWorldWidth)) / 2; + dx = centerSrcEstimX - + ol.math.modulo(centerSrc[0], sourceWorldWidth); + } else { + dx = (aSrc[0] + cSrc[0]) / 2 - centerSrc[0]; + } + var dy = (aSrc[1] + cSrc[1]) / 2 - centerSrc[1]; + var centerSrcErrorSquared = dx * dx + dy * dy; + needsSubdivision = centerSrcErrorSquared > this.errorThresholdSquared_; + } + if (needsSubdivision) { + if (Math.abs(a[0] - c[0]) <= Math.abs(a[1] - c[1])) { + // split horizontally (top & bottom) + var bc = [(b[0] + c[0]) / 2, (b[1] + c[1]) / 2]; + var bcSrc = this.transformInv_(bc); + var da = [(d[0] + a[0]) / 2, (d[1] + a[1]) / 2]; + var daSrc = this.transformInv_(da); + + this.addQuad_( + a, b, bc, da, aSrc, bSrc, bcSrc, daSrc, maxSubdivision - 1); + this.addQuad_( + da, bc, c, d, daSrc, bcSrc, cSrc, dSrc, maxSubdivision - 1); + } else { + // split vertically (left & right) + var ab = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]; + var abSrc = this.transformInv_(ab); + var cd = [(c[0] + d[0]) / 2, (c[1] + d[1]) / 2]; + var cdSrc = this.transformInv_(cd); + + this.addQuad_( + a, ab, cd, d, aSrc, abSrc, cdSrc, dSrc, maxSubdivision - 1); + this.addQuad_( + ab, b, c, cd, abSrc, bSrc, cSrc, cdSrc, maxSubdivision - 1); + } + return; + } + } + + if (wrapsX) { + if (!this.canWrapXInSource_) { + return; + } + this.wrapsXInSource_ = true; + } + + this.addTriangle_(a, c, d, aSrc, cSrc, dSrc); + this.addTriangle_(a, b, c, aSrc, bSrc, cSrc); +}; + + +/** + * Calculates extent of the 'source' coordinates from all the triangles. + * + * @return {ol.Extent} Calculated extent. + */ +ol.reproj.Triangulation.prototype.calculateSourceExtent = function() { + var extent = ol.extent.createEmpty(); + + this.triangles_.forEach(function(triangle, i, arr) { + var src = triangle.source; + ol.extent.extendCoordinate(extent, src[0]); + ol.extent.extendCoordinate(extent, src[1]); + ol.extent.extendCoordinate(extent, src[2]); + }); + + return extent; +}; + + +/** + * @return {Array.<ol.ReprojTriangle>} Array of the calculated triangles. + */ +ol.reproj.Triangulation.prototype.getTriangles = function() { + return this.triangles_; +}; + +goog.provide('ol.reproj.Image'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.ImageBase'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.reproj'); +goog.require('ol.reproj.Triangulation'); + + +/** + * @classdesc + * Class encapsulating single reprojected image. + * See {@link ol.source.Image}. + * + * @constructor + * @extends {ol.ImageBase} + * @param {ol.proj.Projection} sourceProj Source projection (of the data). + * @param {ol.proj.Projection} targetProj Target projection. + * @param {ol.Extent} targetExtent Target extent. + * @param {number} targetResolution Target resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.ReprojImageFunctionType} getImageFunction + * Function returning source images (extent, resolution, pixelRatio). + */ +ol.reproj.Image = function(sourceProj, targetProj, + targetExtent, targetResolution, pixelRatio, getImageFunction) { + + /** + * @private + * @type {ol.proj.Projection} + */ + this.targetProj_ = targetProj; + + /** + * @private + * @type {ol.Extent} + */ + this.maxSourceExtent_ = sourceProj.getExtent(); + var maxTargetExtent = targetProj.getExtent(); + + var limitedTargetExtent = maxTargetExtent ? + ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent; + + var targetCenter = ol.extent.getCenter(limitedTargetExtent); + var sourceResolution = ol.reproj.calculateSourceResolution( + sourceProj, targetProj, targetCenter, targetResolution); + + var errorThresholdInPixels = ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD; + + /** + * @private + * @type {!ol.reproj.Triangulation} + */ + this.triangulation_ = new ol.reproj.Triangulation( + sourceProj, targetProj, limitedTargetExtent, this.maxSourceExtent_, + sourceResolution * errorThresholdInPixels); + + /** + * @private + * @type {number} + */ + this.targetResolution_ = targetResolution; + + /** + * @private + * @type {ol.Extent} + */ + this.targetExtent_ = targetExtent; + + var sourceExtent = this.triangulation_.calculateSourceExtent(); + + /** + * @private + * @type {ol.ImageBase} + */ + this.sourceImage_ = + getImageFunction(sourceExtent, sourceResolution, pixelRatio); + + /** + * @private + * @type {number} + */ + this.sourcePixelRatio_ = + this.sourceImage_ ? this.sourceImage_.getPixelRatio() : 1; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = null; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.sourceListenerKey_ = null; + + + var state = ol.Image.State.LOADED; + var attributions = []; + + if (this.sourceImage_) { + state = ol.Image.State.IDLE; + attributions = this.sourceImage_.getAttributions(); + } + + ol.ImageBase.call(this, targetExtent, targetResolution, this.sourcePixelRatio_, + state, attributions); +}; +ol.inherits(ol.reproj.Image, ol.ImageBase); + + +/** + * @inheritDoc + */ +ol.reproj.Image.prototype.disposeInternal = function() { + if (this.state == ol.Image.State.LOADING) { + this.unlistenSource_(); + } + ol.ImageBase.prototype.disposeInternal.call(this); +}; + + +/** + * @inheritDoc + */ +ol.reproj.Image.prototype.getImage = function(opt_context) { + return this.canvas_; +}; + + +/** + * @return {ol.proj.Projection} Projection. + */ +ol.reproj.Image.prototype.getProjection = function() { + return this.targetProj_; +}; + + +/** + * @private + */ +ol.reproj.Image.prototype.reproject_ = function() { + var sourceState = this.sourceImage_.getState(); + if (sourceState == ol.Image.State.LOADED) { + var width = ol.extent.getWidth(this.targetExtent_) / this.targetResolution_; + var height = + ol.extent.getHeight(this.targetExtent_) / this.targetResolution_; + + this.canvas_ = ol.reproj.render(width, height, this.sourcePixelRatio_, + this.sourceImage_.getResolution(), this.maxSourceExtent_, + this.targetResolution_, this.targetExtent_, this.triangulation_, [{ + extent: this.sourceImage_.getExtent(), + image: this.sourceImage_.getImage() + }], 0); + } + this.state = sourceState; + this.changed(); +}; + + +/** + * @inheritDoc + */ +ol.reproj.Image.prototype.load = function() { + if (this.state == ol.Image.State.IDLE) { + this.state = ol.Image.State.LOADING; + this.changed(); + + var sourceState = this.sourceImage_.getState(); + if (sourceState == ol.Image.State.LOADED || + sourceState == ol.Image.State.ERROR) { + this.reproject_(); + } else { + this.sourceListenerKey_ = ol.events.listen(this.sourceImage_, + ol.events.EventType.CHANGE, function(e) { + var sourceState = this.sourceImage_.getState(); + if (sourceState == ol.Image.State.LOADED || + sourceState == ol.Image.State.ERROR) { + this.unlistenSource_(); + this.reproject_(); + } + }, this); + this.sourceImage_.load(); + } + } +}; + + +/** + * @private + */ +ol.reproj.Image.prototype.unlistenSource_ = function() { + ol.DEBUG && console.assert(this.sourceListenerKey_, + 'this.sourceListenerKey_ should not be null'); + ol.events.unlistenByKey(/** @type {!ol.EventsKey} */ (this.sourceListenerKey_)); + this.sourceListenerKey_ = null; +}; + +goog.provide('ol.source.Source'); + +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.Object'); +goog.require('ol.proj'); +goog.require('ol.source.State'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for {@link ol.layer.Layer} sources. + * + * A generic `change` event is triggered when the state of the source changes. + * + * @constructor + * @extends {ol.Object} + * @param {ol.SourceSourceOptions} options Source options. + * @api stable + */ +ol.source.Source = function(options) { + + ol.Object.call(this); + + /** + * @private + * @type {ol.proj.Projection} + */ + this.projection_ = ol.proj.get(options.projection); + + /** + * @private + * @type {Array.<ol.Attribution>} + */ + this.attributions_ = ol.source.Source.toAttributionsArray_(options.attributions); + + /** + * @private + * @type {string|olx.LogoOptions|undefined} + */ + this.logo_ = options.logo; + + /** + * @private + * @type {ol.source.State} + */ + this.state_ = options.state !== undefined ? + options.state : ol.source.State.READY; + + /** + * @private + * @type {boolean} + */ + this.wrapX_ = options.wrapX !== undefined ? options.wrapX : false; + +}; +ol.inherits(ol.source.Source, ol.Object); + +/** + * Turns various ways of defining an attribution to an array of `ol.Attributions`. + * + * @param {ol.AttributionLike|undefined} + * attributionLike The attributions as string, array of strings, + * `ol.Attribution`, array of `ol.Attribution` or undefined. + * @return {Array.<ol.Attribution>} The array of `ol.Attribution` or null if + * `undefined` was given. + */ +ol.source.Source.toAttributionsArray_ = function(attributionLike) { + if (typeof attributionLike === 'string') { + return [new ol.Attribution({html: attributionLike})]; + } else if (attributionLike instanceof ol.Attribution) { + return [attributionLike]; + } else if (Array.isArray(attributionLike)) { + var len = attributionLike.length; + var attributions = new Array(len); + for (var i = 0; i < len; i++) { + var item = attributionLike[i]; + if (typeof item === 'string') { + attributions[i] = new ol.Attribution({html: item}); + } else { + attributions[i] = item; + } + } + return attributions; + } else { + return null; + } +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {number} hitTolerance Hit tolerance in pixels. + * @param {Object.<string, boolean>} skippedFeatureUids Skipped feature uids. + * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature + * callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.source.Source.prototype.forEachFeatureAtCoordinate = ol.nullFunction; + + +/** + * Get the attributions of the source. + * @return {Array.<ol.Attribution>} Attributions. + * @api stable + */ +ol.source.Source.prototype.getAttributions = function() { + return this.attributions_; +}; + + +/** + * Get the logo of the source. + * @return {string|olx.LogoOptions|undefined} Logo. + * @api stable + */ +ol.source.Source.prototype.getLogo = function() { + return this.logo_; +}; + + +/** + * Get the projection of the source. + * @return {ol.proj.Projection} Projection. + * @api + */ +ol.source.Source.prototype.getProjection = function() { + return this.projection_; +}; + + +/** + * @abstract + * @return {Array.<number>|undefined} Resolutions. + */ +ol.source.Source.prototype.getResolutions = function() {}; + + +/** + * Get the state of the source, see {@link ol.source.State} for possible states. + * @return {ol.source.State} State. + * @api + */ +ol.source.Source.prototype.getState = function() { + return this.state_; +}; + + +/** + * @return {boolean|undefined} Wrap X. + */ +ol.source.Source.prototype.getWrapX = function() { + return this.wrapX_; +}; + + +/** + * Refreshes the source and finally dispatches a 'change' event. + * @api + */ +ol.source.Source.prototype.refresh = function() { + this.changed(); +}; + + +/** + * Set the attributions of the source. + * @param {ol.AttributionLike|undefined} attributions Attributions. + * Can be passed as `string`, `Array<string>`, `{@link ol.Attribution}`, + * `Array<{@link ol.Attribution}>` or `undefined`. + * @api + */ +ol.source.Source.prototype.setAttributions = function(attributions) { + this.attributions_ = ol.source.Source.toAttributionsArray_(attributions); + this.changed(); +}; + + +/** + * Set the logo of the source. + * @param {string|olx.LogoOptions|undefined} logo Logo. + */ +ol.source.Source.prototype.setLogo = function(logo) { + this.logo_ = logo; +}; + + +/** + * Set the state of the source. + * @param {ol.source.State} state State. + * @protected + */ +ol.source.Source.prototype.setState = function(state) { + this.state_ = state; + this.changed(); +}; + +goog.provide('ol.source.Image'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.array'); +goog.require('ol.events.Event'); +goog.require('ol.extent'); +goog.require('ol.proj'); +goog.require('ol.reproj.Image'); +goog.require('ol.source.Source'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for sources providing a single image. + * + * @constructor + * @extends {ol.source.Source} + * @param {ol.SourceImageOptions} options Single image source options. + * @api + */ +ol.source.Image = function(options) { + + ol.source.Source.call(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + projection: options.projection, + state: options.state + }); + + /** + * @private + * @type {Array.<number>} + */ + this.resolutions_ = options.resolutions !== undefined ? + options.resolutions : null; + ol.DEBUG && console.assert(!this.resolutions_ || + ol.array.isSorted(this.resolutions_, + function(a, b) { + return b - a; + }, true), 'resolutions must be null or sorted in descending order'); + + + /** + * @private + * @type {ol.reproj.Image} + */ + this.reprojectedImage_ = null; + + + /** + * @private + * @type {number} + */ + this.reprojectedRevision_ = 0; + +}; +ol.inherits(ol.source.Image, ol.source.Source); + + +/** + * @return {Array.<number>} Resolutions. + */ +ol.source.Image.prototype.getResolutions = function() { + return this.resolutions_; +}; + + +/** + * @protected + * @param {number} resolution Resolution. + * @return {number} Resolution. + */ +ol.source.Image.prototype.findNearestResolution = function(resolution) { + if (this.resolutions_) { + var idx = ol.array.linearFindNearest(this.resolutions_, resolution, 0); + resolution = this.resolutions_[idx]; + } + return resolution; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.ImageBase} Single image. + */ +ol.source.Image.prototype.getImage = function(extent, resolution, pixelRatio, projection) { + var sourceProjection = this.getProjection(); + if (!ol.ENABLE_RASTER_REPROJECTION || + !sourceProjection || + !projection || + ol.proj.equivalent(sourceProjection, projection)) { + if (sourceProjection) { + projection = sourceProjection; + } + return this.getImageInternal(extent, resolution, pixelRatio, projection); + } else { + if (this.reprojectedImage_) { + if (this.reprojectedRevision_ == this.getRevision() && + ol.proj.equivalent( + this.reprojectedImage_.getProjection(), projection) && + this.reprojectedImage_.getResolution() == resolution && + this.reprojectedImage_.getPixelRatio() == pixelRatio && + ol.extent.equals(this.reprojectedImage_.getExtent(), extent)) { + return this.reprojectedImage_; + } + this.reprojectedImage_.dispose(); + this.reprojectedImage_ = null; + } + + this.reprojectedImage_ = new ol.reproj.Image( + sourceProjection, projection, extent, resolution, pixelRatio, + function(extent, resolution, pixelRatio) { + return this.getImageInternal(extent, resolution, + pixelRatio, sourceProjection); + }.bind(this)); + this.reprojectedRevision_ = this.getRevision(); + + return this.reprojectedImage_; + } +}; + + +/** + * @abstract + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.ImageBase} Single image. + * @protected + */ +ol.source.Image.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {}; + + +/** + * Handle image change events. + * @param {ol.events.Event} event Event. + * @protected + */ +ol.source.Image.prototype.handleImageChange = function(event) { + var image = /** @type {ol.Image} */ (event.target); + switch (image.getState()) { + case ol.Image.State.LOADING: + this.dispatchEvent( + new ol.source.Image.Event(ol.source.Image.EventType.IMAGELOADSTART, + image)); + break; + case ol.Image.State.LOADED: + this.dispatchEvent( + new ol.source.Image.Event(ol.source.Image.EventType.IMAGELOADEND, + image)); + break; + case ol.Image.State.ERROR: + this.dispatchEvent( + new ol.source.Image.Event(ol.source.Image.EventType.IMAGELOADERROR, + image)); + break; + default: + // pass + } +}; + + +/** + * Default image load function for image sources that use ol.Image image + * instances. + * @param {ol.Image} image Image. + * @param {string} src Source. + */ +ol.source.Image.defaultImageLoadFunction = function(image, src) { + image.getImage().src = src; +}; + + +/** + * @classdesc + * Events emitted by {@link ol.source.Image} instances are instances of this + * type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.source.ImageEvent} + * @param {string} type Type. + * @param {ol.Image} image The image. + */ +ol.source.Image.Event = function(type, image) { + + ol.events.Event.call(this, type); + + /** + * The image related to the event. + * @type {ol.Image} + * @api + */ + this.image = image; + +}; +ol.inherits(ol.source.Image.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.source.Image.EventType = { + + /** + * Triggered when an image starts loading. + * @event ol.source.Image.Event#imageloadstart + * @api + */ + IMAGELOADSTART: 'imageloadstart', + + /** + * Triggered when an image finishes loading. + * @event ol.source.Image.Event#imageloadend + * @api + */ + IMAGELOADEND: 'imageloadend', + + /** + * Triggered if image loading results in an error. + * @event ol.source.Image.Event#imageloaderror + * @api + */ + IMAGELOADERROR: 'imageloaderror' + +}; + +goog.provide('ol.source.ImageCanvas'); + +goog.require('ol'); +goog.require('ol.ImageCanvas'); +goog.require('ol.extent'); +goog.require('ol.source.Image'); + + +/** + * @classdesc + * Base class for image sources where a canvas element is the image. + * + * @constructor + * @extends {ol.source.Image} + * @param {olx.source.ImageCanvasOptions} options Constructor options. + * @api + */ +ol.source.ImageCanvas = function(options) { + + ol.source.Image.call(this, { + attributions: options.attributions, + logo: options.logo, + projection: options.projection, + resolutions: options.resolutions, + state: options.state + }); + + /** + * @private + * @type {ol.CanvasFunctionType} + */ + this.canvasFunction_ = options.canvasFunction; + + /** + * @private + * @type {ol.ImageCanvas} + */ + this.canvas_ = null; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = 0; + + /** + * @private + * @type {number} + */ + this.ratio_ = options.ratio !== undefined ? + options.ratio : 1.5; + +}; +ol.inherits(ol.source.ImageCanvas, ol.source.Image); + + +/** + * @inheritDoc + */ +ol.source.ImageCanvas.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) { + resolution = this.findNearestResolution(resolution); + + var canvas = this.canvas_; + if (canvas && + this.renderedRevision_ == this.getRevision() && + canvas.getResolution() == resolution && + canvas.getPixelRatio() == pixelRatio && + ol.extent.containsExtent(canvas.getExtent(), extent)) { + return canvas; + } + + extent = extent.slice(); + ol.extent.scaleFromCenter(extent, this.ratio_); + var width = ol.extent.getWidth(extent) / resolution; + var height = ol.extent.getHeight(extent) / resolution; + var size = [width * pixelRatio, height * pixelRatio]; + + var canvasElement = this.canvasFunction_( + extent, resolution, pixelRatio, size, projection); + if (canvasElement) { + canvas = new ol.ImageCanvas(extent, resolution, pixelRatio, + this.getAttributions(), canvasElement); + } + this.canvas_ = canvas; + this.renderedRevision_ = this.getRevision(); + + return canvas; +}; + +goog.provide('ol.source.ImageVector'); + +goog.require('ol'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.render.canvas.ReplayGroup'); +goog.require('ol.renderer.vector'); +goog.require('ol.source.ImageCanvas'); +goog.require('ol.style.Style'); +goog.require('ol.transform'); + + +/** + * @classdesc + * An image source whose images are canvas elements into which vector features + * read from a vector source (`ol.source.Vector`) are drawn. An + * `ol.source.ImageVector` object is to be used as the `source` of an image + * layer (`ol.layer.Image`). Image layers are rotated, scaled, and translated, + * as opposed to being re-rendered, during animations and interactions. So, like + * any other image layer, an image layer configured with an + * `ol.source.ImageVector` will exhibit this behaviour. This is in contrast to a + * vector layer, where vector features are re-drawn during animations and + * interactions. + * + * @constructor + * @extends {ol.source.ImageCanvas} + * @param {olx.source.ImageVectorOptions} options Options. + * @api + */ +ol.source.ImageVector = function(options) { + + /** + * @private + * @type {ol.source.Vector} + */ + this.source_ = options.source; + + /** + * @private + * @type {ol.Transform} + */ + this.transform_ = ol.transform.create(); + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.canvasContext_ = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {ol.Size} + */ + this.canvasSize_ = [0, 0]; + + /** + * @private + * @type {number} + */ + this.renderBuffer_ = options.renderBuffer == undefined ? 100 : options.renderBuffer; + + /** + * @private + * @type {ol.render.canvas.ReplayGroup} + */ + this.replayGroup_ = null; + + ol.source.ImageCanvas.call(this, { + attributions: options.attributions, + canvasFunction: this.canvasFunctionInternal_.bind(this), + logo: options.logo, + projection: options.projection, + ratio: options.ratio, + resolutions: options.resolutions, + state: this.source_.getState() + }); + + /** + * User provided style. + * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction} + * @private + */ + this.style_ = null; + + /** + * Style function for use within the library. + * @type {ol.StyleFunction|undefined} + * @private + */ + this.styleFunction_ = undefined; + + this.setStyle(options.style); + + ol.events.listen(this.source_, ol.events.EventType.CHANGE, + this.handleSourceChange_, this); + +}; +ol.inherits(ol.source.ImageVector, ol.source.ImageCanvas); + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Size} size Size. + * @param {ol.proj.Projection} projection Projection; + * @return {HTMLCanvasElement} Canvas element. + * @private + */ +ol.source.ImageVector.prototype.canvasFunctionInternal_ = function(extent, resolution, pixelRatio, size, projection) { + + var replayGroup = new ol.render.canvas.ReplayGroup( + ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, + resolution, this.source_.getOverlaps(), this.renderBuffer_); + + this.source_.loadFeatures(extent, resolution, projection); + + var loading = false; + this.source_.forEachFeatureInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + loading = loading || + this.renderFeature_(feature, resolution, pixelRatio, replayGroup); + }, this); + replayGroup.finish(); + + if (loading) { + return null; + } + + if (this.canvasSize_[0] != size[0] || this.canvasSize_[1] != size[1]) { + this.canvasContext_.canvas.width = size[0]; + this.canvasContext_.canvas.height = size[1]; + this.canvasSize_[0] = size[0]; + this.canvasSize_[1] = size[1]; + } else { + this.canvasContext_.clearRect(0, 0, size[0], size[1]); + } + + var transform = this.getTransform_(ol.extent.getCenter(extent), + resolution, pixelRatio, size); + replayGroup.replay(this.canvasContext_, pixelRatio, transform, 0, {}); + + this.replayGroup_ = replayGroup; + + return this.canvasContext_.canvas; +}; + + +/** + * @inheritDoc + */ +ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function( + coordinate, resolution, rotation, hitTolerance, skippedFeatureUids, callback) { + if (!this.replayGroup_) { + return undefined; + } else { + /** @type {Object.<string, boolean>} */ + var features = {}; + return this.replayGroup_.forEachFeatureAtCoordinate( + coordinate, resolution, 0, hitTolerance, skippedFeatureUids, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var key = ol.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback(feature); + } + }); + } +}; + + +/** + * Get a reference to the wrapped source. + * @return {ol.source.Vector} Source. + * @api + */ +ol.source.ImageVector.prototype.getSource = function() { + return this.source_; +}; + + +/** + * Get the style for features. This returns whatever was passed to the `style` + * option at construction or to the `setStyle` method. + * @return {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction} + * Layer style. + * @api stable + */ +ol.source.ImageVector.prototype.getStyle = function() { + return this.style_; +}; + + +/** + * Get the style function. + * @return {ol.StyleFunction|undefined} Layer style function. + * @api stable + */ +ol.source.ImageVector.prototype.getStyleFunction = function() { + return this.styleFunction_; +}; + + +/** + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Size} size Size. + * @return {!ol.Transform} Transform. + * @private + */ +ol.source.ImageVector.prototype.getTransform_ = function(center, resolution, pixelRatio, size) { + var dx1 = size[0] / 2; + var dy1 = size[1] / 2; + var sx = pixelRatio / resolution; + var sy = -sx; + var dx2 = -center[0]; + var dy2 = -center[1]; + + return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, 0, dx2, dy2); +}; + + +/** + * Handle changes in image style state. + * @param {ol.events.Event} event Image style change event. + * @private + */ +ol.source.ImageVector.prototype.handleImageChange_ = function(event) { + this.changed(); +}; + + +/** + * @private + */ +ol.source.ImageVector.prototype.handleSourceChange_ = function() { + // setState will trigger a CHANGE event, so we always rely + // change events by calling setState. + this.setState(this.source_.getState()); +}; + + +/** + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + * @private + */ +ol.source.ImageVector.prototype.renderFeature_ = function(feature, resolution, pixelRatio, replayGroup) { + var styles; + var styleFunction = feature.getStyleFunction(); + if (styleFunction) { + styles = styleFunction.call(feature, resolution); + } else if (this.styleFunction_) { + styles = this.styleFunction_(feature, resolution); + } + if (!styles) { + return false; + } + var i, ii, loading = false; + if (!Array.isArray(styles)) { + styles = [styles]; + } + for (i = 0, ii = styles.length; i < ii; ++i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleImageChange_, this) || loading; + } + return loading; +}; + + +/** + * Set the style for features. This can be a single style object, an array + * of styles, or a function that takes a feature and resolution and returns + * an array of styles. If it is `undefined` the default style is used. If + * it is `null` the layer has no style (a `null` style), so only features + * that have their own styles will be rendered in the layer. See + * {@link ol.style} for information on the default style. + * @param {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction|undefined} + * style Layer style. + * @api stable + */ +ol.source.ImageVector.prototype.setStyle = function(style) { + this.style_ = style !== undefined ? style : ol.style.Style.defaultFunction; + this.styleFunction_ = !style ? + undefined : ol.style.Style.createFunction(this.style_); + this.changed(); +}; + +goog.provide('ol.renderer.webgl.ImageLayer'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.functions'); +goog.require('ol.proj'); +goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.source.ImageVector'); +goog.require('ol.transform'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Context'); + + +/** + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Image} imageLayer Tile layer. + */ +ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { + + ol.renderer.webgl.Layer.call(this, mapRenderer, imageLayer); + + /** + * The last rendered image. + * @private + * @type {?ol.ImageBase} + */ + this.image_ = null; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitCanvasContext_ = null; + + /** + * @private + * @type {?ol.Transform} + */ + this.hitTransformationMatrix_ = null; + +}; +ol.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); + + +/** + * @param {ol.ImageBase} image Image. + * @private + * @return {WebGLTexture} Texture. + */ +ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) { + + // We meet the conditions to work with non-power of two textures. + // http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support + // http://learningwebgl.com/blog/?p=2101 + + var imageElement = image.getImage(); + var gl = this.mapRenderer.getGL(); + + return ol.webgl.Context.createTexture( + gl, imageElement, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { + var layer = this.getLayer(); + var source = layer.getSource(); + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var skippedFeatureUids = frameState.skippedFeatureUids; + return source.forEachFeatureAtCoordinate( + coordinate, resolution, rotation, hitTolerance, skippedFeatureUids, + + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + return callback.call(thisArg, feature, layer); + }); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.prepareFrame = function(frameState, layerState, context) { + + var gl = this.mapRenderer.getGL(); + + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var viewCenter = viewState.center; + var viewResolution = viewState.resolution; + var viewRotation = viewState.rotation; + + var image = this.image_; + var texture = this.texture; + var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer()); + var imageSource = imageLayer.getSource(); + + var hints = frameState.viewHints; + + var renderedExtent = frameState.extent; + if (layerState.extent !== undefined) { + renderedExtent = ol.extent.getIntersection( + renderedExtent, layerState.extent); + } + if (!hints[ol.View.Hint.ANIMATING] && !hints[ol.View.Hint.INTERACTING] && + !ol.extent.isEmpty(renderedExtent)) { + var projection = viewState.projection; + if (!ol.ENABLE_RASTER_REPROJECTION) { + var sourceProjection = imageSource.getProjection(); + if (sourceProjection) { + ol.DEBUG && console.assert(ol.proj.equivalent(projection, sourceProjection), + 'projection and sourceProjection are equivalent'); + projection = sourceProjection; + } + } + var image_ = imageSource.getImage(renderedExtent, viewResolution, + pixelRatio, projection); + if (image_) { + var loaded = this.loadImage(image_); + if (loaded) { + image = image_; + texture = this.createTexture_(image_); + if (this.texture) { + /** + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLTexture} texture Texture. + */ + var postRenderFunction = function(gl, texture) { + if (!gl.isContextLost()) { + gl.deleteTexture(texture); + } + }.bind(null, gl, this.texture); + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (postRenderFunction) + ); + } + } + } + } + + if (image) { + ol.DEBUG && console.assert(texture, 'texture is truthy'); + + var canvas = this.mapRenderer.getContext().getCanvas(); + + this.updateProjectionMatrix_(canvas.width, canvas.height, + pixelRatio, viewCenter, viewResolution, viewRotation, + image.getExtent()); + this.hitTransformationMatrix_ = null; + + // Translate and scale to flip the Y coord. + var texCoordMatrix = this.texCoordMatrix; + ol.transform.reset(texCoordMatrix); + ol.transform.scale(texCoordMatrix, 1, -1); + ol.transform.translate(texCoordMatrix, 0, -1); + + this.image_ = image; + this.texture = texture; + + this.updateAttributions(frameState.attributions, image.getAttributions()); + this.updateLogos(frameState, imageSource); + } + + return !!image; +}; + + +/** + * @param {number} canvasWidth Canvas width. + * @param {number} canvasHeight Canvas height. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Coordinate} viewCenter View center. + * @param {number} viewResolution View resolution. + * @param {number} viewRotation View rotation. + * @param {ol.Extent} imageExtent Image extent. + * @private + */ +ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = function(canvasWidth, canvasHeight, pixelRatio, + viewCenter, viewResolution, viewRotation, imageExtent) { + + var canvasExtentWidth = canvasWidth * viewResolution; + var canvasExtentHeight = canvasHeight * viewResolution; + + var projectionMatrix = this.projectionMatrix; + ol.transform.reset(projectionMatrix); + ol.transform.scale(projectionMatrix, + pixelRatio * 2 / canvasExtentWidth, + pixelRatio * 2 / canvasExtentHeight); + ol.transform.rotate(projectionMatrix, -viewRotation); + ol.transform.translate(projectionMatrix, + imageExtent[0] - viewCenter[0], + imageExtent[1] - viewCenter[1]); + ol.transform.scale(projectionMatrix, + (imageExtent[2] - imageExtent[0]) / 2, + (imageExtent[3] - imageExtent[1]) / 2); + ol.transform.translate(projectionMatrix, 1, 1); + +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) { + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, 0, ol.functions.TRUE, this); + return hasFeature !== undefined; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { + if (!this.image_ || !this.image_.getImage()) { + return undefined; + } + + if (this.getLayer().getSource() instanceof ol.source.ImageVector) { + // for ImageVector sources use the original hit-detection logic, + // so that for example also transparent polygons are detected + var coordinate = ol.transform.apply( + frameState.pixelToCoordinateTransform, pixel.slice()); + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, 0, ol.functions.TRUE, this); + + if (hasFeature) { + return callback.call(thisArg, this.getLayer(), null); + } else { + return undefined; + } + } else { + var imageSize = + [this.image_.getImage().width, this.image_.getImage().height]; + + if (!this.hitTransformationMatrix_) { + this.hitTransformationMatrix_ = this.getHitTransformationMatrix_( + frameState.size, imageSize); + } + + var pixelOnFrameBuffer = ol.transform.apply( + this.hitTransformationMatrix_, pixel.slice()); + + if (pixelOnFrameBuffer[0] < 0 || pixelOnFrameBuffer[0] > imageSize[0] || + pixelOnFrameBuffer[1] < 0 || pixelOnFrameBuffer[1] > imageSize[1]) { + // outside the image, no need to check + return undefined; + } + + if (!this.hitCanvasContext_) { + this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1); + } + + this.hitCanvasContext_.clearRect(0, 0, 1, 1); + this.hitCanvasContext_.drawImage(this.image_.getImage(), + pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, 0, 0, 1, 1); + + var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data; + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer(), imageData); + } else { + return undefined; + } + } +}; + + +/** + * The transformation matrix to get the pixel on the image for a + * pixel on the map. + * @param {ol.Size} mapSize The map size. + * @param {ol.Size} imageSize The image size. + * @return {ol.Transform} The transformation matrix. + * @private + */ +ol.renderer.webgl.ImageLayer.prototype.getHitTransformationMatrix_ = function(mapSize, imageSize) { + // the first matrix takes a map pixel, flips the y-axis and scales to + // a range between -1 ... 1 + var mapCoordTransform = ol.transform.create(); + ol.transform.translate(mapCoordTransform, -1, -1); + ol.transform.scale(mapCoordTransform, 2 / mapSize[0], 2 / mapSize[1]); + ol.transform.translate(mapCoordTransform, 0, mapSize[1]); + ol.transform.scale(mapCoordTransform, 1, -1); + + // the second matrix is the inverse of the projection matrix used in the + // shader for drawing + var projectionMatrixInv = ol.transform.invert(this.projectionMatrix.slice()); + + // the third matrix scales to the image dimensions and flips the y-axis again + var transform = ol.transform.create(); + ol.transform.translate(transform, 0, imageSize[1]); + ol.transform.scale(transform, 1, -1); + ol.transform.scale(transform, imageSize[0] / 2, imageSize[1] / 2); + ol.transform.translate(transform, 1, 1); + + ol.transform.multiply(transform, projectionMatrixInv); + ol.transform.multiply(transform, mapCoordTransform); + + return transform; +}; + +// This file is automatically generated, do not edit +goog.provide('ol.renderer.webgl.tilelayershader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.renderer.webgl.tilelayershader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.renderer.webgl.tilelayershader.Fragment.SOURCE); +}; +ol.inherits(ol.renderer.webgl.tilelayershader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform sampler2D u_texture;\n\nvoid main(void) {\n gl_FragColor = texture2D(u_texture, v_texCoord);\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Fragment.SOURCE = ol.DEBUG ? + ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE : + ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE; + + +ol.renderer.webgl.tilelayershader.fragment = new ol.renderer.webgl.tilelayershader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.renderer.webgl.tilelayershader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.renderer.webgl.tilelayershader.Vertex.SOURCE); +}; +ol.inherits(ol.renderer.webgl.tilelayershader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nuniform vec4 u_tileOffset;\n\nvoid main(void) {\n gl_Position = vec4(a_position * u_tileOffset.xy + u_tileOffset.zw, 0., 1.);\n v_texCoord = a_texCoord;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Vertex.SOURCE = ol.DEBUG ? + ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE : + ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE; + + +ol.renderer.webgl.tilelayershader.vertex = new ol.renderer.webgl.tilelayershader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.renderer.webgl.tilelayershader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_texture = gl.getUniformLocation( + program, ol.DEBUG ? 'u_texture' : 'e'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_tileOffset = gl.getUniformLocation( + program, ol.DEBUG ? 'u_tileOffset' : 'd'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'b'); + + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, ol.DEBUG ? 'a_texCoord' : 'c'); +}; + +// FIXME large resolutions lead to too large framebuffers :-( +// FIXME animated shaders! check in redraw + +goog.provide('ol.renderer.webgl.TileLayer'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.TileRange'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.math'); +goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.renderer.webgl.tilelayershader'); +goog.require('ol.size'); +goog.require('ol.transform'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Tile} tileLayer Tile layer. + */ +ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { + + ol.renderer.webgl.Layer.call(this, mapRenderer, tileLayer); + + /** + * @private + * @type {ol.webgl.Fragment} + */ + this.fragmentShader_ = ol.renderer.webgl.tilelayershader.fragment; + + /** + * @private + * @type {ol.webgl.Vertex} + */ + this.vertexShader_ = ol.renderer.webgl.tilelayershader.vertex; + + /** + * @private + * @type {ol.renderer.webgl.tilelayershader.Locations} + */ + this.locations_ = null; + + /** + * @private + * @type {ol.webgl.Buffer} + */ + this.renderArrayBuffer_ = new ol.webgl.Buffer([ + 0, 0, 0, 1, + 1, 0, 1, 1, + 0, 1, 0, 0, + 1, 1, 1, 0 + ]); + + /** + * @private + * @type {ol.TileRange} + */ + this.renderedTileRange_ = null; + + /** + * @private + * @type {ol.Extent} + */ + this.renderedFramebufferExtent_ = null; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = -1; + + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; + +}; +ol.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer); + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() { + var context = this.mapRenderer.getContext(); + context.deleteBuffer(this.renderArrayBuffer_); + ol.renderer.webgl.Layer.prototype.disposeInternal.call(this); +}; + + +/** + * Create a function that adds loaded tiles to the tile lookup. + * @param {ol.source.Tile} source Tile source. + * @param {ol.proj.Projection} projection Projection of the tiles. + * @param {Object.<number, Object.<string, ol.Tile>>} tiles Lookup of loaded + * tiles by zoom level. + * @return {function(number, ol.TileRange):boolean} A function that can be + * called with a zoom level and a tile range to add loaded tiles to the + * lookup. + * @protected + */ +ol.renderer.webgl.TileLayer.prototype.createLoadedTileFinder = function(source, projection, tiles) { + var mapRenderer = this.mapRenderer; + + return ( + /** + * @param {number} zoom Zoom level. + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} The tile range is fully loaded. + */ + function(zoom, tileRange) { + function callback(tile) { + var loaded = mapRenderer.isTileTextureLoaded(tile); + if (loaded) { + if (!tiles[zoom]) { + tiles[zoom] = {}; + } + tiles[zoom][tile.tileCoord.toString()] = tile; + } + return loaded; + } + return source.forEachLoadedTile(projection, zoom, tileRange, callback); + }); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() { + ol.renderer.webgl.Layer.prototype.handleWebGLContextLost.call(this); + this.locations_ = null; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.prepareFrame = function(frameState, layerState, context) { + + var mapRenderer = this.mapRenderer; + var gl = context.getGL(); + + var viewState = frameState.viewState; + var projection = viewState.projection; + + var tileLayer = /** @type {ol.layer.Tile} */ (this.getLayer()); + var tileSource = tileLayer.getSource(); + var tileGrid = tileSource.getTileGridForProjection(projection); + var z = tileGrid.getZForResolution(viewState.resolution); + var tileResolution = tileGrid.getResolution(z); + + var tilePixelSize = + tileSource.getTilePixelSize(z, frameState.pixelRatio, projection); + var pixelRatio = tilePixelSize[0] / + ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize_)[0]; + var tilePixelResolution = tileResolution / pixelRatio; + var tileGutter = tileSource.getTilePixelRatio(pixelRatio) * tileSource.getGutter(projection); + + var center = viewState.center; + var extent = frameState.extent; + var tileRange = tileGrid.getTileRangeForExtentAndResolution( + extent, tileResolution); + + var framebufferExtent; + if (this.renderedTileRange_ && + this.renderedTileRange_.equals(tileRange) && + this.renderedRevision_ == tileSource.getRevision()) { + framebufferExtent = this.renderedFramebufferExtent_; + } else { + + var tileRangeSize = tileRange.getSize(); + + var maxDimension = Math.max( + tileRangeSize[0] * tilePixelSize[0], + tileRangeSize[1] * tilePixelSize[1]); + var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension); + var framebufferExtentDimension = tilePixelResolution * framebufferDimension; + var origin = tileGrid.getOrigin(z); + var minX = origin[0] + + tileRange.minX * tilePixelSize[0] * tilePixelResolution; + var minY = origin[1] + + tileRange.minY * tilePixelSize[1] * tilePixelResolution; + framebufferExtent = [ + minX, minY, + minX + framebufferExtentDimension, minY + framebufferExtentDimension + ]; + + this.bindFramebuffer(frameState, framebufferDimension); + gl.viewport(0, 0, framebufferDimension, framebufferDimension); + + gl.clearColor(0, 0, 0, 0); + gl.clear(ol.webgl.COLOR_BUFFER_BIT); + gl.disable(ol.webgl.BLEND); + + var program = context.getProgram(this.fragmentShader_, this.vertexShader_); + context.useProgram(program); + if (!this.locations_) { + this.locations_ = + new ol.renderer.webgl.tilelayershader.Locations(gl, program); + } + + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.renderArrayBuffer_); + gl.enableVertexAttribArray(this.locations_.a_position); + gl.vertexAttribPointer( + this.locations_.a_position, 2, ol.webgl.FLOAT, false, 16, 0); + gl.enableVertexAttribArray(this.locations_.a_texCoord); + gl.vertexAttribPointer( + this.locations_.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8); + gl.uniform1i(this.locations_.u_texture, 0); + + /** + * @type {Object.<number, Object.<string, ol.Tile>>} + */ + var tilesToDrawByZ = {}; + tilesToDrawByZ[z] = {}; + + var findLoadedTiles = this.createLoadedTileFinder( + tileSource, projection, tilesToDrawByZ); + + var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError(); + var allTilesLoaded = true; + var tmpExtent = ol.extent.createEmpty(); + var tmpTileRange = new ol.TileRange(0, 0, 0, 0); + var childTileRange, drawable, fullyLoaded, tile, tileState; + var x, y, tileExtent; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + if (layerState.extent !== undefined) { + // ignore tiles outside layer extent + tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); + if (!ol.extent.intersects(tileExtent, layerState.extent)) { + continue; + } + } + tileState = tile.getState(); + drawable = tileState == ol.Tile.State.LOADED || + tileState == ol.Tile.State.EMPTY || + tileState == ol.Tile.State.ERROR && !useInterimTilesOnError; + if (!drawable) { + tile = tile.getInterimTile(); + } + tileState = tile.getState(); + if (tileState == ol.Tile.State.LOADED) { + if (mapRenderer.isTileTextureLoaded(tile)) { + tilesToDrawByZ[z][tile.tileCoord.toString()] = tile; + continue; + } + } else if (tileState == ol.Tile.State.EMPTY || + (tileState == ol.Tile.State.ERROR && + !useInterimTilesOnError)) { + continue; + } + + allTilesLoaded = false; + fullyLoaded = tileGrid.forEachTileCoordParentTileRange( + tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); + if (!fullyLoaded) { + childTileRange = tileGrid.getTileCoordChildTileRange( + tile.tileCoord, tmpTileRange, tmpExtent); + if (childTileRange) { + findLoadedTiles(z + 1, childTileRange); + } + } + + } + + } + + /** @type {Array.<number>} */ + var zs = Object.keys(tilesToDrawByZ).map(Number); + zs.sort(ol.array.numberSafeCompareFunction); + var u_tileOffset = new Float32Array(4); + var i, ii, tileKey, tilesToDraw; + for (i = 0, ii = zs.length; i < ii; ++i) { + tilesToDraw = tilesToDrawByZ[zs[i]]; + for (tileKey in tilesToDraw) { + tile = tilesToDraw[tileKey]; + tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); + u_tileOffset[0] = 2 * (tileExtent[2] - tileExtent[0]) / + framebufferExtentDimension; + u_tileOffset[1] = 2 * (tileExtent[3] - tileExtent[1]) / + framebufferExtentDimension; + u_tileOffset[2] = 2 * (tileExtent[0] - framebufferExtent[0]) / + framebufferExtentDimension - 1; + u_tileOffset[3] = 2 * (tileExtent[1] - framebufferExtent[1]) / + framebufferExtentDimension - 1; + gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset); + mapRenderer.bindTileTexture(tile, tilePixelSize, + tileGutter * pixelRatio, ol.webgl.LINEAR, ol.webgl.LINEAR); + gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4); + } + } + + if (allTilesLoaded) { + this.renderedTileRange_ = tileRange; + this.renderedFramebufferExtent_ = framebufferExtent; + this.renderedRevision_ = tileSource.getRevision(); + } else { + this.renderedTileRange_ = null; + this.renderedFramebufferExtent_ = null; + this.renderedRevision_ = -1; + frameState.animate = true; + } + + } + + this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); + var tileTextureQueue = mapRenderer.getTileTextureQueue(); + this.manageTilePyramid( + frameState, tileSource, tileGrid, pixelRatio, projection, extent, z, + tileLayer.getPreload(), + /** + * @param {ol.Tile} tile Tile. + */ + function(tile) { + if (tile.getState() == ol.Tile.State.LOADED && + !mapRenderer.isTileTextureLoaded(tile) && + !tileTextureQueue.isKeyQueued(tile.getKey())) { + tileTextureQueue.enqueue([ + tile, + tileGrid.getTileCoordCenter(tile.tileCoord), + tileGrid.getResolution(tile.tileCoord[0]), + tilePixelSize, tileGutter * pixelRatio + ]); + } + }, this); + this.scheduleExpireCache(frameState, tileSource); + this.updateLogos(frameState, tileSource); + + var texCoordMatrix = this.texCoordMatrix; + ol.transform.reset(texCoordMatrix); + ol.transform.translate(texCoordMatrix, + (Math.round(center[0] / tileResolution) * tileResolution - framebufferExtent[0]) / + (framebufferExtent[2] - framebufferExtent[0]), + (Math.round(center[1] / tileResolution) * tileResolution - framebufferExtent[1]) / + (framebufferExtent[3] - framebufferExtent[1])); + if (viewState.rotation !== 0) { + ol.transform.rotate(texCoordMatrix, viewState.rotation); + } + ol.transform.scale(texCoordMatrix, + frameState.size[0] * viewState.resolution / + (framebufferExtent[2] - framebufferExtent[0]), + frameState.size[1] * viewState.resolution / + (framebufferExtent[3] - framebufferExtent[1])); + ol.transform.translate(texCoordMatrix, -0.5, -0.5); + + return true; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { + if (!this.framebuffer) { + return undefined; + } + + var pixelOnMapScaled = [ + pixel[0] / frameState.size[0], + (frameState.size[1] - pixel[1]) / frameState.size[1]]; + + var pixelOnFrameBufferScaled = ol.transform.apply( + this.texCoordMatrix, pixelOnMapScaled.slice()); + var pixelOnFrameBuffer = [ + pixelOnFrameBufferScaled[0] * this.framebufferDimension, + pixelOnFrameBufferScaled[1] * this.framebufferDimension]; + + var gl = this.mapRenderer.getContext().getGL(); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + var imageData = new Uint8Array(4); + gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, + gl.RGBA, gl.UNSIGNED_BYTE, imageData); + + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer(), imageData); + } else { + return undefined; + } +}; + +goog.provide('ol.renderer.webgl.VectorLayer'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.extent'); +goog.require('ol.render.webgl.ReplayGroup'); +goog.require('ol.renderer.vector'); +goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Vector} vectorLayer Vector layer. + */ +ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) { + + ol.renderer.webgl.Layer.call(this, mapRenderer, vectorLayer); + + /** + * @private + * @type {boolean} + */ + this.dirty_ = false; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = -1; + + /** + * @private + * @type {number} + */ + this.renderedResolution_ = NaN; + + /** + * @private + * @type {ol.Extent} + */ + this.renderedExtent_ = ol.extent.createEmpty(); + + /** + * @private + * @type {function(ol.Feature, ol.Feature): number|null} + */ + this.renderedRenderOrder_ = null; + + /** + * @private + * @type {ol.render.webgl.ReplayGroup} + */ + this.replayGroup_ = null; + + /** + * The last layer state. + * @private + * @type {?ol.LayerState} + */ + this.layerState_ = null; + +}; +ol.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer); + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) { + this.layerState_ = layerState; + var viewState = frameState.viewState; + var replayGroup = this.replayGroup_; + var size = frameState.size; + var pixelRatio = frameState.pixelRatio; + var gl = this.mapRenderer.getGL(); + if (replayGroup && !replayGroup.isEmpty()) { + gl.enable(gl.SCISSOR_TEST); + gl.scissor(0, 0, size[0] * pixelRatio, size[1] * pixelRatio); + replayGroup.replay(context, + viewState.center, viewState.resolution, viewState.rotation, + size, pixelRatio, layerState.opacity, + layerState.managed ? frameState.skippedFeatureUids : {}); + gl.disable(gl.SCISSOR_TEST); + } + +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() { + var replayGroup = this.replayGroup_; + if (replayGroup) { + var context = this.mapRenderer.getContext(); + replayGroup.getDeleteResourcesFunction(context)(); + this.replayGroup_ = null; + } + ol.renderer.webgl.Layer.prototype.disposeInternal.call(this); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { + if (!this.replayGroup_ || !this.layerState_) { + return undefined; + } else { + var context = this.mapRenderer.getContext(); + var viewState = frameState.viewState; + var layer = this.getLayer(); + var layerState = this.layerState_; + /** @type {Object.<string, boolean>} */ + var features = {}; + return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, layerState.opacity, + {}, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var key = ol.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); + } +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) { + if (!this.replayGroup_ || !this.layerState_) { + return false; + } else { + var context = this.mapRenderer.getContext(); + var viewState = frameState.viewState; + var layerState = this.layerState_; + return this.replayGroup_.hasFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, layerState.opacity, + frameState.skippedFeatureUids); + } +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { + var coordinate = ol.transform.apply( + frameState.pixelToCoordinateTransform, pixel.slice()); + var hasFeature = this.hasFeatureAtCoordinate(coordinate, frameState); + + if (hasFeature) { + return callback.call(thisArg, this.getLayer(), null); + } else { + return undefined; + } +}; + + +/** + * Handle changes in image style state. + * @param {ol.events.Event} event Image style change event. + * @private + */ +ol.renderer.webgl.VectorLayer.prototype.handleStyleImageChange_ = function(event) { + this.renderIfReadyAndVisible(); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.prepareFrame = function(frameState, layerState, context) { + + var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); + var vectorSource = vectorLayer.getSource(); + + this.updateAttributions( + frameState.attributions, vectorSource.getAttributions()); + this.updateLogos(frameState, vectorSource); + + var animating = frameState.viewHints[ol.View.Hint.ANIMATING]; + var interacting = frameState.viewHints[ol.View.Hint.INTERACTING]; + var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating(); + var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting(); + + if (!this.dirty_ && (!updateWhileAnimating && animating) || + (!updateWhileInteracting && interacting)) { + return true; + } + + var frameStateExtent = frameState.extent; + var viewState = frameState.viewState; + var projection = viewState.projection; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var vectorLayerRevision = vectorLayer.getRevision(); + var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer(); + var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); + + if (vectorLayerRenderOrder === undefined) { + vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; + } + + var extent = ol.extent.buffer(frameStateExtent, + vectorLayerRenderBuffer * resolution); + + if (!this.dirty_ && + this.renderedResolution_ == resolution && + this.renderedRevision_ == vectorLayerRevision && + this.renderedRenderOrder_ == vectorLayerRenderOrder && + ol.extent.containsExtent(this.renderedExtent_, extent)) { + return true; + } + + if (this.replayGroup_) { + frameState.postRenderFunctions.push( + this.replayGroup_.getDeleteResourcesFunction(context)); + } + + this.dirty_ = false; + + var replayGroup = new ol.render.webgl.ReplayGroup( + ol.renderer.vector.getTolerance(resolution, pixelRatio), + extent, vectorLayer.getRenderBuffer()); + vectorSource.loadFeatures(extent, resolution, projection); + /** + * @param {ol.Feature} feature Feature. + * @this {ol.renderer.webgl.VectorLayer} + */ + var renderFeature = function(feature) { + var styles; + var styleFunction = feature.getStyleFunction(); + if (styleFunction) { + styles = styleFunction.call(feature, resolution); + } else { + styleFunction = vectorLayer.getStyleFunction(); + if (styleFunction) { + styles = styleFunction(feature, resolution); + } + } + if (styles) { + var dirty = this.renderFeature( + feature, resolution, pixelRatio, styles, replayGroup); + this.dirty_ = this.dirty_ || dirty; + } + }; + if (vectorLayerRenderOrder) { + /** @type {Array.<ol.Feature>} */ + var features = []; + vectorSource.forEachFeatureInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + features.push(feature); + }, this); + features.sort(vectorLayerRenderOrder); + features.forEach(renderFeature, this); + } else { + vectorSource.forEachFeatureInExtent(extent, renderFeature, this); + } + replayGroup.finish(context); + + this.renderedResolution_ = resolution; + this.renderedRevision_ = vectorLayerRevision; + this.renderedRenderOrder_ = vectorLayerRenderOrder; + this.renderedExtent_ = extent; + this.replayGroup_ = replayGroup; + + return true; +}; + + +/** + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of + * styles. + * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + */ +ol.renderer.webgl.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) { + if (!styles) { + return false; + } + var loading = false; + if (Array.isArray(styles)) { + for (var i = styles.length - 1, ii = 0; i >= ii; --i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleStyleImageChange_, this) || loading; + } + } else { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles, + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleStyleImageChange_, this) || loading; + } + return loading; +}; + +goog.provide('ol.structs.LRUCache'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.obj'); + + +/** + * Implements a Least-Recently-Used cache where the keys do not conflict with + * Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring + * items from the cache is the responsibility of the user. + * @constructor + * @struct + * @template T + */ +ol.structs.LRUCache = function() { + + /** + * @private + * @type {number} + */ + this.count_ = 0; + + /** + * @private + * @type {!Object.<string, ol.LRUCacheEntry>} + */ + this.entries_ = {}; + + /** + * @private + * @type {?ol.LRUCacheEntry} + */ + this.oldest_ = null; + + /** + * @private + * @type {?ol.LRUCacheEntry} + */ + this.newest_ = null; + +}; + + +if (ol.DEBUG) { + /** + * FIXME empty description for jsdoc + */ + ol.structs.LRUCache.prototype.assertValid = function() { + if (this.count_ === 0) { + console.assert(ol.obj.isEmpty(this.entries_), + 'entries must be an empty object (count = 0)'); + console.assert(!this.oldest_, + 'oldest must be null (count = 0)'); + console.assert(!this.newest_, + 'newest must be null (count = 0)'); + } else { + console.assert(Object.keys(this.entries_).length == this.count_, + 'number of entries matches count'); + console.assert(this.oldest_, + 'we have an oldest entry'); + console.assert(!this.oldest_.older, + 'no entry is older than oldest'); + console.assert(this.newest_, + 'we have a newest entry'); + console.assert(!this.newest_.newer, + 'no entry is newer than newest'); + var i, entry; + var older = null; + i = 0; + for (entry = this.oldest_; entry; entry = entry.newer) { + console.assert(entry.older === older, + 'entry.older links to correct older'); + older = entry; + ++i; + } + console.assert(i == this.count_, 'iterated correct amount of times'); + var newer = null; + i = 0; + for (entry = this.newest_; entry; entry = entry.older) { + console.assert(entry.newer === newer, + 'entry.newer links to correct newer'); + newer = entry; + ++i; + } + console.assert(i == this.count_, 'iterated correct amount of times'); + } + }; +} + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.LRUCache.prototype.clear = function() { + this.count_ = 0; + this.entries_ = {}; + this.oldest_ = null; + this.newest_ = null; +}; + + +/** + * @param {string} key Key. + * @return {boolean} Contains key. + */ +ol.structs.LRUCache.prototype.containsKey = function(key) { + return this.entries_.hasOwnProperty(key); +}; + + +/** + * @param {function(this: S, T, string, ol.structs.LRUCache): ?} f The function + * to call for every entry from the oldest to the newer. This function takes + * 3 arguments (the entry value, the entry key and the LRUCache object). + * The return value is ignored. + * @param {S=} opt_this The object to use as `this` in `f`. + * @template S + */ +ol.structs.LRUCache.prototype.forEach = function(f, opt_this) { + var entry = this.oldest_; + while (entry) { + f.call(opt_this, entry.value_, entry.key_, this); + entry = entry.newer; + } +}; + + +/** + * @param {string} key Key. + * @return {T} Value. + */ +ol.structs.LRUCache.prototype.get = function(key) { + var entry = this.entries_[key]; + ol.asserts.assert(entry !== undefined, + 15); // Tried to get a value for a key that does not exist in the cache + if (entry === this.newest_) { + return entry.value_; + } else if (entry === this.oldest_) { + this.oldest_ = /** @type {ol.LRUCacheEntry} */ (this.oldest_.newer); + this.oldest_.older = null; + } else { + entry.newer.older = entry.older; + entry.older.newer = entry.newer; + } + entry.newer = null; + entry.older = this.newest_; + this.newest_.newer = entry; + this.newest_ = entry; + return entry.value_; +}; + + +/** + * @return {number} Count. + */ +ol.structs.LRUCache.prototype.getCount = function() { + return this.count_; +}; + + +/** + * @return {Array.<string>} Keys. + */ +ol.structs.LRUCache.prototype.getKeys = function() { + var keys = new Array(this.count_); + var i = 0; + var entry; + for (entry = this.newest_; entry; entry = entry.older) { + keys[i++] = entry.key_; + } + ol.DEBUG && console.assert(i == this.count_, 'iterated correct number of times'); + return keys; +}; + + +/** + * @return {Array.<T>} Values. + */ +ol.structs.LRUCache.prototype.getValues = function() { + var values = new Array(this.count_); + var i = 0; + var entry; + for (entry = this.newest_; entry; entry = entry.older) { + values[i++] = entry.value_; + } + ol.DEBUG && console.assert(i == this.count_, 'iterated correct number of times'); + return values; +}; + + +/** + * @return {T} Last value. + */ +ol.structs.LRUCache.prototype.peekLast = function() { + ol.DEBUG && console.assert(this.oldest_, 'oldest must not be null'); + return this.oldest_.value_; +}; + + +/** + * @return {string} Last key. + */ +ol.structs.LRUCache.prototype.peekLastKey = function() { + ol.DEBUG && console.assert(this.oldest_, 'oldest must not be null'); + return this.oldest_.key_; +}; + + +/** + * @return {T} value Value. + */ +ol.structs.LRUCache.prototype.pop = function() { + ol.DEBUG && console.assert(this.oldest_, 'oldest must not be null'); + ol.DEBUG && console.assert(this.newest_, 'newest must not be null'); + var entry = this.oldest_; + ol.DEBUG && console.assert(entry.key_ in this.entries_, + 'oldest is indexed in entries'); + delete this.entries_[entry.key_]; + if (entry.newer) { + entry.newer.older = null; + } + this.oldest_ = /** @type {ol.LRUCacheEntry} */ (entry.newer); + if (!this.oldest_) { + this.newest_ = null; + } + --this.count_; + return entry.value_; +}; + + +/** + * @param {string} key Key. + * @param {T} value Value. + */ +ol.structs.LRUCache.prototype.replace = function(key, value) { + this.get(key); // update `newest_` + this.entries_[key].value_ = value; +}; + + +/** + * @param {string} key Key. + * @param {T} value Value. + */ +ol.structs.LRUCache.prototype.set = function(key, value) { + ol.DEBUG && console.assert(!(key in {}), + 'key is not a standard property of objects (e.g. "__proto__")'); + ol.asserts.assert(!(key in this.entries_), + 16); // Tried to set a value for a key that is used already + var entry = /** @type {ol.LRUCacheEntry} */ ({ + key_: key, + newer: null, + older: this.newest_, + value_: value + }); + if (!this.newest_) { + this.oldest_ = entry; + } else { + this.newest_.newer = entry; + } + this.newest_ = entry; + this.entries_[key] = entry; + ++this.count_; +}; + +// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE) + +goog.provide('ol.renderer.webgl.Map'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.css'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.layer.Image'); +goog.require('ol.layer.Layer'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); +goog.require('ol.render.Event'); +goog.require('ol.render.webgl.Immediate'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.Type'); +goog.require('ol.renderer.webgl.ImageLayer'); +goog.require('ol.renderer.webgl.TileLayer'); +goog.require('ol.renderer.webgl.VectorLayer'); +goog.require('ol.source.State'); +goog.require('ol.structs.LRUCache'); +goog.require('ol.structs.PriorityQueue'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Context'); +goog.require('ol.webgl.ContextEventType'); + + +/** + * @constructor + * @extends {ol.renderer.Map} + * @param {Element} container Container. + * @param {ol.Map} map Map. + */ +ol.renderer.webgl.Map = function(container, map) { + + ol.renderer.Map.call(this, container, map); + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = /** @type {HTMLCanvasElement} */ + (document.createElement('CANVAS')); + this.canvas_.style.width = '100%'; + this.canvas_.style.height = '100%'; + this.canvas_.className = ol.css.CLASS_UNSELECTABLE; + container.insertBefore(this.canvas_, container.childNodes[0] || null); + + /** + * @private + * @type {number} + */ + this.clipTileCanvasWidth_ = 0; + + /** + * @private + * @type {number} + */ + this.clipTileCanvasHeight_ = 0; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.clipTileContext_ = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = true; + + /** + * @private + * @type {WebGLRenderingContext} + */ + this.gl_ = ol.webgl.getContext(this.canvas_, { + antialias: true, + depth: true, + failIfMajorPerformanceCaveat: true, + preserveDrawingBuffer: false, + stencil: true + }); + ol.DEBUG && console.assert(this.gl_, 'got a WebGLRenderingContext'); + + /** + * @private + * @type {ol.webgl.Context} + */ + this.context_ = new ol.webgl.Context(this.canvas_, this.gl_); + + ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST, + this.handleWebGLContextLost, this); + ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED, + this.handleWebGLContextRestored, this); + + /** + * @private + * @type {ol.structs.LRUCache.<ol.WebglTextureCacheEntry|null>} + */ + this.textureCache_ = new ol.structs.LRUCache(); + + /** + * @private + * @type {ol.Coordinate} + */ + this.focus_ = null; + + /** + * @private + * @type {ol.structs.PriorityQueue.<Array>} + */ + this.tileTextureQueue_ = new ol.structs.PriorityQueue( + /** + * @param {Array.<*>} element Element. + * @return {number} Priority. + * @this {ol.renderer.webgl.Map} + */ + (function(element) { + var tileCenter = /** @type {ol.Coordinate} */ (element[1]); + var tileResolution = /** @type {number} */ (element[2]); + var deltaX = tileCenter[0] - this.focus_[0]; + var deltaY = tileCenter[1] - this.focus_[1]; + return 65536 * Math.log(tileResolution) + + Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; + }).bind(this), + /** + * @param {Array.<*>} element Element. + * @return {string} Key. + */ + function(element) { + return /** @type {ol.Tile} */ (element[0]).getKey(); + }); + + + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + * @return {boolean} false. + * @this {ol.renderer.webgl.Map} + */ + this.loadNextTileTexture_ = + function(map, frameState) { + if (!this.tileTextureQueue_.isEmpty()) { + this.tileTextureQueue_.reprioritize(); + var element = this.tileTextureQueue_.dequeue(); + var tile = /** @type {ol.Tile} */ (element[0]); + var tileSize = /** @type {ol.Size} */ (element[3]); + var tileGutter = /** @type {number} */ (element[4]); + this.bindTileTexture( + tile, tileSize, tileGutter, ol.webgl.LINEAR, ol.webgl.LINEAR); + } + return false; + }.bind(this); + + + /** + * @private + * @type {number} + */ + this.textureCacheFrameMarkerCount_ = 0; + + this.initializeGL_(); + +}; +ol.inherits(ol.renderer.webgl.Map, ol.renderer.Map); + + +/** + * @param {ol.Tile} tile Tile. + * @param {ol.Size} tileSize Tile size. + * @param {number} tileGutter Tile gutter. + * @param {number} magFilter Mag filter. + * @param {number} minFilter Min filter. + */ +ol.renderer.webgl.Map.prototype.bindTileTexture = function(tile, tileSize, tileGutter, magFilter, minFilter) { + var gl = this.getGL(); + var tileKey = tile.getKey(); + if (this.textureCache_.containsKey(tileKey)) { + var textureCacheEntry = this.textureCache_.get(tileKey); + gl.bindTexture(ol.webgl.TEXTURE_2D, textureCacheEntry.texture); + if (textureCacheEntry.magFilter != magFilter) { + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter); + textureCacheEntry.magFilter = magFilter; + } + if (textureCacheEntry.minFilter != minFilter) { + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter); + textureCacheEntry.minFilter = minFilter; + } + } else { + var texture = gl.createTexture(); + gl.bindTexture(ol.webgl.TEXTURE_2D, texture); + if (tileGutter > 0) { + var clipTileCanvas = this.clipTileContext_.canvas; + var clipTileContext = this.clipTileContext_; + if (this.clipTileCanvasWidth_ !== tileSize[0] || + this.clipTileCanvasHeight_ !== tileSize[1]) { + clipTileCanvas.width = tileSize[0]; + clipTileCanvas.height = tileSize[1]; + this.clipTileCanvasWidth_ = tileSize[0]; + this.clipTileCanvasHeight_ = tileSize[1]; + } else { + clipTileContext.clearRect(0, 0, tileSize[0], tileSize[1]); + } + clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter, + tileSize[0], tileSize[1], 0, 0, tileSize[0], tileSize[1]); + gl.texImage2D(ol.webgl.TEXTURE_2D, 0, + ol.webgl.RGBA, ol.webgl.RGBA, + ol.webgl.UNSIGNED_BYTE, clipTileCanvas); + } else { + gl.texImage2D(ol.webgl.TEXTURE_2D, 0, + ol.webgl.RGBA, ol.webgl.RGBA, + ol.webgl.UNSIGNED_BYTE, tile.getImage()); + } + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter); + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter); + gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S, + ol.webgl.CLAMP_TO_EDGE); + gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T, + ol.webgl.CLAMP_TO_EDGE); + this.textureCache_.set(tileKey, { + texture: texture, + magFilter: magFilter, + minFilter: minFilter + }); + } +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) { + if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) { + return new ol.renderer.webgl.ImageLayer(this, layer); + } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) { + return new ol.renderer.webgl.TileLayer(this, layer); + } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) { + return new ol.renderer.webgl.VectorLayer(this, layer); + } else { + ol.DEBUG && console.assert(false, 'unexpected layer configuration'); + return null; + } +}; + + +/** + * @param {ol.render.Event.Type} type Event type. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ = function(type, frameState) { + var map = this.getMap(); + if (map.hasListener(type)) { + var context = this.context_; + + var extent = frameState.extent; + var size = frameState.size; + var viewState = frameState.viewState; + var pixelRatio = frameState.pixelRatio; + + var resolution = viewState.resolution; + var center = viewState.center; + var rotation = viewState.rotation; + + var vectorContext = new ol.render.webgl.Immediate(context, + center, resolution, rotation, size, extent, pixelRatio); + var composeEvent = new ol.render.Event(type, vectorContext, + frameState, null, context); + map.dispatchEvent(composeEvent); + } +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.disposeInternal = function() { + var gl = this.getGL(); + if (!gl.isContextLost()) { + this.textureCache_.forEach( + /** + * @param {?ol.WebglTextureCacheEntry} textureCacheEntry + * Texture cache entry. + */ + function(textureCacheEntry) { + if (textureCacheEntry) { + gl.deleteTexture(textureCacheEntry.texture); + } + }); + } + this.context_.dispose(); + ol.renderer.Map.prototype.disposeInternal.call(this); +}; + + +/** + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.webgl.Map.prototype.expireCache_ = function(map, frameState) { + var gl = this.getGL(); + var textureCacheEntry; + while (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > + ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { + textureCacheEntry = this.textureCache_.peekLast(); + if (!textureCacheEntry) { + if (+this.textureCache_.peekLastKey() == frameState.index) { + break; + } else { + --this.textureCacheFrameMarkerCount_; + } + } else { + gl.deleteTexture(textureCacheEntry.texture); + } + this.textureCache_.pop(); + } +}; + + +/** + * @return {ol.webgl.Context} The context. + */ +ol.renderer.webgl.Map.prototype.getContext = function() { + return this.context_; +}; + + +/** + * @return {WebGLRenderingContext} GL. + */ +ol.renderer.webgl.Map.prototype.getGL = function() { + return this.gl_; +}; + + +/** + * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue. + */ +ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() { + return this.tileTextureQueue_; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.getType = function() { + return ol.renderer.Type.WEBGL; +}; + + +/** + * @param {ol.events.Event} event Event. + * @protected + */ +ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) { + event.preventDefault(); + this.textureCache_.clear(); + this.textureCacheFrameMarkerCount_ = 0; + + var renderers = this.getLayerRenderers(); + for (var id in renderers) { + var renderer = /** @type {ol.renderer.webgl.Layer} */ (renderers[id]); + renderer.handleWebGLContextLost(); + } +}; + + +/** + * @protected + */ +ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() { + this.initializeGL_(); + this.getMap().render(); +}; + + +/** + * @private + */ +ol.renderer.webgl.Map.prototype.initializeGL_ = function() { + var gl = this.gl_; + gl.activeTexture(ol.webgl.TEXTURE0); + gl.blendFuncSeparate( + ol.webgl.SRC_ALPHA, ol.webgl.ONE_MINUS_SRC_ALPHA, + ol.webgl.ONE, ol.webgl.ONE_MINUS_SRC_ALPHA); + gl.disable(ol.webgl.CULL_FACE); + gl.disable(ol.webgl.DEPTH_TEST); + gl.disable(ol.webgl.SCISSOR_TEST); + gl.disable(ol.webgl.STENCIL_TEST); +}; + + +/** + * @param {ol.Tile} tile Tile. + * @return {boolean} Is tile texture loaded. + */ +ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) { + return this.textureCache_.containsKey(tile.getKey()); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { + + var context = this.getContext(); + var gl = this.getGL(); + + if (gl.isContextLost()) { + return false; + } + + if (!frameState) { + if (this.renderedVisible_) { + this.canvas_.style.display = 'none'; + this.renderedVisible_ = false; + } + return false; + } + + this.focus_ = frameState.focus; + + this.textureCache_.set((-frameState.index).toString(), null); + ++this.textureCacheFrameMarkerCount_; + + this.dispatchComposeEvent_(ol.render.Event.Type.PRECOMPOSE, frameState); + + /** @type {Array.<ol.LayerState>} */ + var layerStatesToDraw = []; + var layerStatesArray = frameState.layerStatesArray; + ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex); + + var viewResolution = frameState.viewState.resolution; + var i, ii, layerRenderer, layerState; + for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerState = layerStatesArray[i]; + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerState.sourceState == ol.source.State.READY) { + layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer)); + if (layerRenderer.prepareFrame(frameState, layerState, context)) { + layerStatesToDraw.push(layerState); + } + } + } + + var width = frameState.size[0] * frameState.pixelRatio; + var height = frameState.size[1] * frameState.pixelRatio; + if (this.canvas_.width != width || this.canvas_.height != height) { + this.canvas_.width = width; + this.canvas_.height = height; + } + + gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, null); + + gl.clearColor(0, 0, 0, 0); + gl.clear(ol.webgl.COLOR_BUFFER_BIT); + gl.enable(ol.webgl.BLEND); + gl.viewport(0, 0, this.canvas_.width, this.canvas_.height); + + for (i = 0, ii = layerStatesToDraw.length; i < ii; ++i) { + layerState = layerStatesToDraw[i]; + layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer)); + layerRenderer.composeFrame(frameState, layerState, context); + } + + if (!this.renderedVisible_) { + this.canvas_.style.display = ''; + this.renderedVisible_ = true; + } + + this.calculateMatrices2D(frameState); + + if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > + ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (this.expireCache_.bind(this)) + ); + } + + if (!this.tileTextureQueue_.isEmpty()) { + frameState.postRenderFunctions.push(this.loadNextTileTexture_); + frameState.animate = true; + } + + this.dispatchComposeEvent_(ol.render.Event.Type.POSTCOMPOSE, frameState); + + this.scheduleRemoveUnusedLayerRenderers(frameState); + this.scheduleExpireIconCache(frameState); + +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg, + layerFilter, thisArg2) { + var result; + + if (this.getGL().isContextLost()) { + return false; + } + + var viewState = frameState.viewState; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + result = layerRenderer.forEachFeatureAtCoordinate( + coordinate, frameState, hitTolerance, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) { + var hasFeature = false; + + if (this.getGL().isContextLost()) { + return false; + } + + var viewState = frameState.viewState; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + hasFeature = + layerRenderer.hasFeatureAtCoordinate(coordinate, frameState); + if (hasFeature) { + return true; + } + } + } + return hasFeature; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg, + layerFilter, thisArg2) { + if (this.getGL().isContextLost()) { + return false; + } + + var viewState = frameState.viewState; + var result; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg, layer)) { + var layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layer)); + result = layerRenderer.forEachLayerAtPixel( + pixel, frameState, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; +}; + +// FIXME recheck layer/map projection compatibility when projection changes +// FIXME layer renderers should skip when they can't reproject +// FIXME add tilt and height? + +goog.provide('ol.Map'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserEventHandler'); +goog.require('ol.MapEvent'); +goog.require('ol.Object'); +goog.require('ol.TileQueue'); +goog.require('ol.View'); +goog.require('ol.asserts'); +goog.require('ol.control'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.functions'); +goog.require('ol.has'); +goog.require('ol.interaction'); +goog.require('ol.layer.Group'); +goog.require('ol.obj'); +goog.require('ol.proj.common'); +goog.require('ol.renderer.Type'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.canvas.Map'); +goog.require('ol.renderer.webgl.Map'); +goog.require('ol.size'); +goog.require('ol.structs.PriorityQueue'); +goog.require('ol.transform'); + + +/** + * @const + * @type {string} + */ +ol.OL3_URL = 'https://openlayers.org/'; + + +/** + * @const + * @type {string} + */ +ol.OL3_LOGO_URL = 'data:image/png;base64,' + + 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBI' + + 'WXMAAAHGAAABxgEXwfpGAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAA' + + 'AhNQTFRF////AP//AICAgP//AFVVQECA////K1VVSbbbYL/fJ05idsTYJFtbbcjbJllmZszW' + + 'WMTOIFhoHlNiZszTa9DdUcHNHlNlV8XRIVdiasrUHlZjIVZjaMnVH1RlIFRkH1RkH1ZlasvY' + + 'asvXVsPQH1VkacnVa8vWIVZjIFRjVMPQa8rXIVVkXsXRsNveIFVkIFZlIVVj3eDeh6GmbMvX' + + 'H1ZkIFRka8rWbMvXIFVkIFVjIFVkbMvWH1VjbMvWIFVlbcvWIFVla8vVIFVkbMvWbMvVH1Vk' + + 'bMvWIFVlbcvWIFVkbcvVbMvWjNPbIFVkU8LPwMzNIFVkbczWIFVkbsvWbMvXIFVkRnB8bcvW' + + '2+TkW8XRIFVkIlZlJVloJlpoKlxrLl9tMmJwOWd0Omh1RXF8TneCT3iDUHiDU8LPVMLPVcLP' + + 'VcPQVsPPVsPQV8PQWMTQWsTQW8TQXMXSXsXRX4SNX8bSYMfTYcfTYsfTY8jUZcfSZsnUaIqT' + + 'acrVasrVa8jTa8rWbI2VbMvWbcvWdJObdcvUdszUd8vVeJaee87Yfc3WgJyjhqGnitDYjaar' + + 'ldPZnrK2oNbborW5o9bbo9fbpLa6q9ndrL3ArtndscDDutzfu8fJwN7gwt7gxc/QyuHhy+Hi' + + 'zeHi0NfX0+Pj19zb1+Tj2uXk29/e3uLg3+Lh3+bl4uXj4ufl4+fl5Ofl5ufl5ujm5+jmySDn' + + 'BAAAAFp0Uk5TAAECAgMEBAYHCA0NDg4UGRogIiMmKSssLzU7PkJJT1JTVFliY2hrdHZ3foSF' + + 'hYeJjY2QkpugqbG1tre5w8zQ09XY3uXn6+zx8vT09vf4+Pj5+fr6/P39/f3+gz7SsAAAAVVJ' + + 'REFUOMtjYKA7EBDnwCPLrObS1BRiLoJLnte6CQy8FLHLCzs2QUG4FjZ5GbcmBDDjxJBXDWxC' + + 'Brb8aM4zbkIDzpLYnAcE9VXlJSWlZRU13koIeW57mGx5XjoMZEUqwxWYQaQbSzLSkYGfKFSe' + + '0QMsX5WbjgY0YS4MBplemI4BdGBW+DQ11eZiymfqQuXZIjqwyadPNoSZ4L+0FVM6e+oGI6g8' + + 'a9iKNT3o8kVzNkzRg5lgl7p4wyRUL9Yt2jAxVh6mQCogae6GmflI8p0r13VFWTHBQ0rWPW7a' + + 'hgWVcPm+9cuLoyy4kCJDzCm6d8PSFoh0zvQNC5OjDJhQopPPJqph1doJBUD5tnkbZiUEqaCn' + + 'B3bTqLTFG1bPn71kw4b+GFdpLElKIzRxxgYgWNYc5SCENVHKeUaltHdXx0dZ8uBI1hJ2UUDg' + + 'q82CM2MwKeibqAvSO7MCABq0wXEPiqWEAAAAAElFTkSuQmCC'; + + +/** + * @type {Array.<ol.renderer.Type>} + * @const + */ +ol.DEFAULT_RENDERER_TYPES = [ + ol.renderer.Type.CANVAS, + ol.renderer.Type.WEBGL +]; + + +/** + * @classdesc + * The map is the core component of OpenLayers. For a map to render, a view, + * one or more layers, and a target container are needed: + * + * var map = new ol.Map({ + * view: new ol.View({ + * center: [0, 0], + * zoom: 1 + * }), + * layers: [ + * new ol.layer.Tile({ + * source: new ol.source.OSM() + * }) + * ], + * target: 'map' + * }); + * + * The above snippet creates a map using a {@link ol.layer.Tile} to display + * {@link ol.source.OSM} OSM data and render it to a DOM element with the + * id `map`. + * + * The constructor places a viewport container (with CSS class name + * `ol-viewport`) in the target element (see `getViewport()`), and then two + * further elements within the viewport: one with CSS class name + * `ol-overlaycontainer-stopevent` for controls and some overlays, and one with + * CSS class name `ol-overlaycontainer` for other overlays (see the `stopEvent` + * option of {@link ol.Overlay} for the difference). The map itself is placed in + * a further element within the viewport. + * + * Layers are stored as a `ol.Collection` in layerGroups. A top-level group is + * provided by the library. This is what is accessed by `getLayerGroup` and + * `setLayerGroup`. Layers entered in the options are added to this group, and + * `addLayer` and `removeLayer` change the layer collection in the group. + * `getLayers` is a convenience function for `getLayerGroup().getLayers()`. + * Note that `ol.layer.Group` is a subclass of `ol.layer.Base`, so layers + * entered in the options or added with `addLayer` can be groups, which can + * contain further groups, and so on. + * + * @constructor + * @extends {ol.Object} + * @param {olx.MapOptions} options Map options. + * @fires ol.MapBrowserEvent + * @fires ol.MapEvent + * @fires ol.render.Event#postcompose + * @fires ol.render.Event#precompose + * @api stable + */ +ol.Map = function(options) { + + ol.Object.call(this); + + var optionsInternal = ol.Map.createOptionsInternal(options); + + /** + * @type {boolean} + * @private + */ + this.loadTilesWhileAnimating_ = + options.loadTilesWhileAnimating !== undefined ? + options.loadTilesWhileAnimating : false; + + /** + * @type {boolean} + * @private + */ + this.loadTilesWhileInteracting_ = + options.loadTilesWhileInteracting !== undefined ? + options.loadTilesWhileInteracting : false; + + /** + * @private + * @type {number} + */ + this.pixelRatio_ = options.pixelRatio !== undefined ? + options.pixelRatio : ol.has.DEVICE_PIXEL_RATIO; + + /** + * @private + * @type {Object.<string, string>} + */ + this.logos_ = optionsInternal.logos; + + /** + * @private + * @type {number|undefined} + */ + this.animationDelayKey_; + + /** + * @private + */ + this.animationDelay_ = function() { + this.animationDelayKey_ = undefined; + this.renderFrame_.call(this, Date.now()); + }.bind(this); + + /** + * @private + * @type {ol.Transform} + */ + this.coordinateToPixelTransform_ = ol.transform.create(); + + /** + * @private + * @type {ol.Transform} + */ + this.pixelToCoordinateTransform_ = ol.transform.create(); + + /** + * @private + * @type {number} + */ + this.frameIndex_ = 0; + + /** + * @private + * @type {?olx.FrameState} + */ + this.frameState_ = null; + + /** + * The extent at the previous 'moveend' event. + * @private + * @type {ol.Extent} + */ + this.previousExtent_ = ol.extent.createEmpty(); + + /** + * @private + * @type {?ol.EventsKey} + */ + this.viewPropertyListenerKey_ = null; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.viewChangeListenerKey_ = null; + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.layerGroupPropertyListenerKeys_ = null; + + /** + * @private + * @type {Element} + */ + this.viewport_ = document.createElement('DIV'); + this.viewport_.className = 'ol-viewport' + (ol.has.TOUCH ? ' ol-touch' : ''); + this.viewport_.style.position = 'relative'; + this.viewport_.style.overflow = 'hidden'; + this.viewport_.style.width = '100%'; + this.viewport_.style.height = '100%'; + // prevent page zoom on IE >= 10 browsers + this.viewport_.style.msTouchAction = 'none'; + this.viewport_.style.touchAction = 'none'; + + /** + * @private + * @type {!Element} + */ + this.overlayContainer_ = document.createElement('DIV'); + this.overlayContainer_.className = 'ol-overlaycontainer'; + this.viewport_.appendChild(this.overlayContainer_); + + /** + * @private + * @type {!Element} + */ + this.overlayContainerStopEvent_ = document.createElement('DIV'); + this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent'; + var overlayEvents = [ + ol.events.EventType.CLICK, + ol.events.EventType.DBLCLICK, + ol.events.EventType.MOUSEDOWN, + ol.events.EventType.TOUCHSTART, + ol.events.EventType.MSPOINTERDOWN, + ol.MapBrowserEvent.EventType.POINTERDOWN, + ol.events.EventType.MOUSEWHEEL, + ol.events.EventType.WHEEL + ]; + for (var i = 0, ii = overlayEvents.length; i < ii; ++i) { + ol.events.listen(this.overlayContainerStopEvent_, overlayEvents[i], + ol.events.Event.stopPropagation); + } + this.viewport_.appendChild(this.overlayContainerStopEvent_); + + /** + * @private + * @type {ol.MapBrowserEventHandler} + */ + this.mapBrowserEventHandler_ = new ol.MapBrowserEventHandler(this); + for (var key in ol.MapBrowserEvent.EventType) { + ol.events.listen(this.mapBrowserEventHandler_, ol.MapBrowserEvent.EventType[key], + this.handleMapBrowserEvent, this); + } + + /** + * @private + * @type {Element|Document} + */ + this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget; + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.keyHandlerKeys_ = null; + + ol.events.listen(this.viewport_, ol.events.EventType.WHEEL, + this.handleBrowserEvent, this); + ol.events.listen(this.viewport_, ol.events.EventType.MOUSEWHEEL, + this.handleBrowserEvent, this); + + /** + * @type {ol.Collection.<ol.control.Control>} + * @private + */ + this.controls_ = optionsInternal.controls; + + /** + * @type {ol.Collection.<ol.interaction.Interaction>} + * @private + */ + this.interactions_ = optionsInternal.interactions; + + /** + * @type {ol.Collection.<ol.Overlay>} + * @private + */ + this.overlays_ = optionsInternal.overlays; + + /** + * A lookup of overlays by id. + * @private + * @type {Object.<string, ol.Overlay>} + */ + this.overlayIdIndex_ = {}; + + /** + * @type {ol.renderer.Map} + * @private + */ + this.renderer_ = new optionsInternal.rendererConstructor(this.viewport_, this); + + /** + * @type {function(Event)|undefined} + * @private + */ + this.handleResize_; + + /** + * @private + * @type {ol.Coordinate} + */ + this.focus_ = null; + + /** + * @private + * @type {Array.<ol.PreRenderFunction>} + */ + this.preRenderFunctions_ = []; + + /** + * @private + * @type {Array.<ol.PostRenderFunction>} + */ + this.postRenderFunctions_ = []; + + /** + * @private + * @type {ol.TileQueue} + */ + this.tileQueue_ = new ol.TileQueue( + this.getTilePriority.bind(this), + this.handleTileChange_.bind(this)); + + /** + * Uids of features to skip at rendering time. + * @type {Object.<string, boolean>} + * @private + */ + this.skippedFeatureUids_ = {}; + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Map.Property.LAYERGROUP), + this.handleLayerGroupChanged_, this); + ol.events.listen(this, ol.Object.getChangeEventType(ol.Map.Property.VIEW), + this.handleViewChanged_, this); + ol.events.listen(this, ol.Object.getChangeEventType(ol.Map.Property.SIZE), + this.handleSizeChanged_, this); + ol.events.listen(this, ol.Object.getChangeEventType(ol.Map.Property.TARGET), + this.handleTargetChanged_, this); + + // setProperties will trigger the rendering of the map if the map + // is "defined" already. + this.setProperties(optionsInternal.values); + + this.controls_.forEach( + /** + * @param {ol.control.Control} control Control. + * @this {ol.Map} + */ + function(control) { + control.setMap(this); + }, this); + + ol.events.listen(this.controls_, ol.Collection.EventType.ADD, + /** + * @param {ol.Collection.Event} event Collection event. + */ + function(event) { + event.element.setMap(this); + }, this); + + ol.events.listen(this.controls_, ol.Collection.EventType.REMOVE, + /** + * @param {ol.Collection.Event} event Collection event. + */ + function(event) { + event.element.setMap(null); + }, this); + + this.interactions_.forEach( + /** + * @param {ol.interaction.Interaction} interaction Interaction. + * @this {ol.Map} + */ + function(interaction) { + interaction.setMap(this); + }, this); + + ol.events.listen(this.interactions_, ol.Collection.EventType.ADD, + /** + * @param {ol.Collection.Event} event Collection event. + */ + function(event) { + event.element.setMap(this); + }, this); + + ol.events.listen(this.interactions_, ol.Collection.EventType.REMOVE, + /** + * @param {ol.Collection.Event} event Collection event. + */ + function(event) { + event.element.setMap(null); + }, this); + + this.overlays_.forEach(this.addOverlayInternal_, this); + + ol.events.listen(this.overlays_, ol.Collection.EventType.ADD, + /** + * @param {ol.Collection.Event} event Collection event. + */ + function(event) { + this.addOverlayInternal_(/** @type {ol.Overlay} */ (event.element)); + }, this); + + ol.events.listen(this.overlays_, ol.Collection.EventType.REMOVE, + /** + * @param {ol.Collection.Event} event Collection event. + */ + function(event) { + var overlay = /** @type {ol.Overlay} */ (event.element); + var id = overlay.getId(); + if (id !== undefined) { + delete this.overlayIdIndex_[id.toString()]; + } + event.element.setMap(null); + }, this); + +}; +ol.inherits(ol.Map, ol.Object); + + +/** + * Add the given control to the map. + * @param {ol.control.Control} control Control. + * @api stable + */ +ol.Map.prototype.addControl = function(control) { + this.getControls().push(control); +}; + + +/** + * Add the given interaction to the map. + * @param {ol.interaction.Interaction} interaction Interaction to add. + * @api stable + */ +ol.Map.prototype.addInteraction = function(interaction) { + this.getInteractions().push(interaction); +}; + + +/** + * Adds the given layer to the top of this map. If you want to add a layer + * elsewhere in the stack, use `getLayers()` and the methods available on + * {@link ol.Collection}. + * @param {ol.layer.Base} layer Layer. + * @api stable + */ +ol.Map.prototype.addLayer = function(layer) { + var layers = this.getLayerGroup().getLayers(); + layers.push(layer); +}; + + +/** + * Add the given overlay to the map. + * @param {ol.Overlay} overlay Overlay. + * @api stable + */ +ol.Map.prototype.addOverlay = function(overlay) { + this.getOverlays().push(overlay); +}; + + +/** + * This deals with map's overlay collection changes. + * @param {ol.Overlay} overlay Overlay. + * @private + */ +ol.Map.prototype.addOverlayInternal_ = function(overlay) { + var id = overlay.getId(); + if (id !== undefined) { + this.overlayIdIndex_[id.toString()] = overlay; + } + overlay.setMap(this); +}; + + +/** + * Deprecated (use {@link ol.View#animate} instead). + * Add functions to be called before rendering. This can be used for attaching + * animations before updating the map's view. The {@link ol.animation} + * namespace provides several static methods for creating prerender functions. + * @param {...ol.PreRenderFunction} var_args Any number of pre-render functions. + * @api + */ +ol.Map.prototype.beforeRender = function(var_args) { + ol.DEBUG && console.warn('map.beforeRender() is deprecated. Use view.animate() instead.'); + this.render(); + Array.prototype.push.apply(this.preRenderFunctions_, arguments); +}; + + +/** + * + * @inheritDoc + */ +ol.Map.prototype.disposeInternal = function() { + this.mapBrowserEventHandler_.dispose(); + this.renderer_.dispose(); + ol.events.unlisten(this.viewport_, ol.events.EventType.WHEEL, + this.handleBrowserEvent, this); + ol.events.unlisten(this.viewport_, ol.events.EventType.MOUSEWHEEL, + this.handleBrowserEvent, this); + if (this.handleResize_ !== undefined) { + window.removeEventListener(ol.events.EventType.RESIZE, + this.handleResize_, false); + this.handleResize_ = undefined; + } + if (this.animationDelayKey_) { + cancelAnimationFrame(this.animationDelayKey_); + this.animationDelayKey_ = undefined; + } + this.setTarget(null); + ol.Object.prototype.disposeInternal.call(this); +}; + + +/** + * Detect features that intersect a pixel on the viewport, and execute a + * callback with each intersecting feature. Layers included in the detection can + * be configured through `opt_layerFilter`. + * @param {ol.Pixel} pixel Pixel. + * @param {function(this: S, (ol.Feature|ol.render.Feature), + * ol.layer.Layer): T} callback Feature callback. The callback will be + * called with two arguments. The first argument is one + * {@link ol.Feature feature} or + * {@link ol.render.Feature render feature} at the pixel, the second is + * the {@link ol.layer.Layer layer} of the feature and will be null for + * unmanaged layers. To stop detection, callback functions can return a + * truthy value. + * @param {olx.AtPixelOptions=} opt_options Optional options. + * @return {T|undefined} Callback result, i.e. the return value of last + * callback execution, or the first truthy callback return value. + * @template S,T + * @api stable + */ +ol.Map.prototype.forEachFeatureAtPixel = function(pixel, callback, opt_options) { + if (!this.frameState_) { + return; + } + var coordinate = this.getCoordinateFromPixel(pixel); + opt_options = opt_options !== undefined ? opt_options : {}; + var hitTolerance = opt_options.hitTolerance !== undefined ? + opt_options.hitTolerance * this.frameState_.pixelRatio : 0; + var layerFilter = opt_options.layerFilter !== undefined ? + opt_options.layerFilter : ol.functions.TRUE; + return this.renderer_.forEachFeatureAtCoordinate( + coordinate, this.frameState_, hitTolerance, callback, null, + layerFilter, null); +}; + + +/** + * Detect layers that have a color value at a pixel on the viewport, and + * execute a callback with each matching layer. Layers included in the + * detection can be configured through `opt_layerFilter`. + * @param {ol.Pixel} pixel Pixel. + * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback + * Layer callback. This callback will recieve two arguments: first is the + * {@link ol.layer.Layer layer}, second argument is an array representing + * [R, G, B, A] pixel values (0 - 255) and will be `null` for layer types + * that do not currently support this argument. To stop detection, callback + * functions can return a truthy value. + * @param {S=} opt_this Value to use as `this` when executing `callback`. + * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer + * filter function. The filter function will receive one argument, the + * {@link ol.layer.Layer layer-candidate} and it should return a boolean + * value. Only layers which are visible and for which this function returns + * `true` will be tested for features. By default, all visible layers will + * be tested. + * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result, i.e. the return value of last + * callback execution, or the first truthy callback return value. + * @template S,T,U + * @api stable + */ +ol.Map.prototype.forEachLayerAtPixel = function(pixel, callback, opt_this, opt_layerFilter, opt_this2) { + if (!this.frameState_) { + return; + } + var thisArg = opt_this !== undefined ? opt_this : null; + var layerFilter = opt_layerFilter !== undefined ? + opt_layerFilter : ol.functions.TRUE; + var thisArg2 = opt_this2 !== undefined ? opt_this2 : null; + return this.renderer_.forEachLayerAtPixel( + pixel, this.frameState_, callback, thisArg, + layerFilter, thisArg2); +}; + + +/** + * Detect if features intersect a pixel on the viewport. Layers included in the + * detection can be configured through `opt_layerFilter`. + * @param {ol.Pixel} pixel Pixel. + * @param {olx.AtPixelOptions=} opt_options Optional options. + * @return {boolean} Is there a feature at the given pixel? + * @template U + * @api + */ +ol.Map.prototype.hasFeatureAtPixel = function(pixel, opt_options) { + if (!this.frameState_) { + return false; + } + var coordinate = this.getCoordinateFromPixel(pixel); + opt_options = opt_options !== undefined ? opt_options : {}; + var layerFilter = opt_options.layerFilter !== undefined ? + opt_options.layerFilter : ol.functions.TRUE; + var hitTolerance = opt_options.hitTolerance !== undefined ? + opt_options.hitTolerance * this.frameState_.pixelRatio : 0; + return this.renderer_.hasFeatureAtCoordinate( + coordinate, this.frameState_, hitTolerance, layerFilter, null); +}; + + +/** + * Returns the coordinate in view projection for a browser event. + * @param {Event} event Event. + * @return {ol.Coordinate} Coordinate. + * @api stable + */ +ol.Map.prototype.getEventCoordinate = function(event) { + return this.getCoordinateFromPixel(this.getEventPixel(event)); +}; + + +/** + * Returns the map pixel position for a browser event relative to the viewport. + * @param {Event} event Event. + * @return {ol.Pixel} Pixel. + * @api stable + */ +ol.Map.prototype.getEventPixel = function(event) { + var viewportPosition = this.viewport_.getBoundingClientRect(); + var eventPosition = event.changedTouches ? event.changedTouches[0] : event; + return [ + eventPosition.clientX - viewportPosition.left, + eventPosition.clientY - viewportPosition.top + ]; +}; + + +/** + * Get the target in which this map is rendered. + * Note that this returns what is entered as an option or in setTarget: + * if that was an element, it returns an element; if a string, it returns that. + * @return {Element|string|undefined} The Element or id of the Element that the + * map is rendered in. + * @observable + * @api stable + */ +ol.Map.prototype.getTarget = function() { + return /** @type {Element|string|undefined} */ ( + this.get(ol.Map.Property.TARGET)); +}; + + +/** + * Get the DOM element into which this map is rendered. In contrast to + * `getTarget` this method always return an `Element`, or `null` if the + * map has no target. + * @return {Element} The element that the map is rendered in. + * @api + */ +ol.Map.prototype.getTargetElement = function() { + var target = this.getTarget(); + if (target !== undefined) { + return typeof target === 'string' ? + document.getElementById(target) : + target; + } else { + return null; + } +}; + + +/** + * Get the coordinate for a given pixel. This returns a coordinate in the + * map view projection. + * @param {ol.Pixel} pixel Pixel position in the map viewport. + * @return {ol.Coordinate} The coordinate for the pixel position. + * @api stable + */ +ol.Map.prototype.getCoordinateFromPixel = function(pixel) { + var frameState = this.frameState_; + if (!frameState) { + return null; + } else { + return ol.transform.apply(frameState.pixelToCoordinateTransform, pixel.slice()); + } +}; + + +/** + * Get the map controls. Modifying this collection changes the controls + * associated with the map. + * @return {ol.Collection.<ol.control.Control>} Controls. + * @api stable + */ +ol.Map.prototype.getControls = function() { + return this.controls_; +}; + + +/** + * Get the map overlays. Modifying this collection changes the overlays + * associated with the map. + * @return {ol.Collection.<ol.Overlay>} Overlays. + * @api stable + */ +ol.Map.prototype.getOverlays = function() { + return this.overlays_; +}; + + +/** + * Get an overlay by its identifier (the value returned by overlay.getId()). + * Note that the index treats string and numeric identifiers as the same. So + * `map.getOverlayById(2)` will return an overlay with id `'2'` or `2`. + * @param {string|number} id Overlay identifier. + * @return {ol.Overlay} Overlay. + * @api + */ +ol.Map.prototype.getOverlayById = function(id) { + var overlay = this.overlayIdIndex_[id.toString()]; + return overlay !== undefined ? overlay : null; +}; + + +/** + * Get the map interactions. Modifying this collection changes the interactions + * associated with the map. + * + * Interactions are used for e.g. pan, zoom and rotate. + * @return {ol.Collection.<ol.interaction.Interaction>} Interactions. + * @api stable + */ +ol.Map.prototype.getInteractions = function() { + return this.interactions_; +}; + + +/** + * Get the layergroup associated with this map. + * @return {ol.layer.Group} A layer group containing the layers in this map. + * @observable + * @api stable + */ +ol.Map.prototype.getLayerGroup = function() { + return /** @type {ol.layer.Group} */ (this.get(ol.Map.Property.LAYERGROUP)); +}; + + +/** + * Get the collection of layers associated with this map. + * @return {!ol.Collection.<ol.layer.Base>} Layers. + * @api stable + */ +ol.Map.prototype.getLayers = function() { + var layers = this.getLayerGroup().getLayers(); + return layers; +}; + + +/** + * Get the pixel for a coordinate. This takes a coordinate in the map view + * projection and returns the corresponding pixel. + * @param {ol.Coordinate} coordinate A map coordinate. + * @return {ol.Pixel} A pixel position in the map viewport. + * @api stable + */ +ol.Map.prototype.getPixelFromCoordinate = function(coordinate) { + var frameState = this.frameState_; + if (!frameState) { + return null; + } else { + return ol.transform.apply(frameState.coordinateToPixelTransform, + coordinate.slice(0, 2)); + } +}; + + +/** + * Get the map renderer. + * @return {ol.renderer.Map} Renderer + */ +ol.Map.prototype.getRenderer = function() { + return this.renderer_; +}; + + +/** + * Get the size of this map. + * @return {ol.Size|undefined} The size in pixels of the map in the DOM. + * @observable + * @api stable + */ +ol.Map.prototype.getSize = function() { + return /** @type {ol.Size|undefined} */ (this.get(ol.Map.Property.SIZE)); +}; + + +/** + * Get the view associated with this map. A view manages properties such as + * center and resolution. + * @return {ol.View} The view that controls this map. + * @observable + * @api stable + */ +ol.Map.prototype.getView = function() { + return /** @type {ol.View} */ (this.get(ol.Map.Property.VIEW)); +}; + + +/** + * Get the element that serves as the map viewport. + * @return {Element} Viewport. + * @api stable + */ +ol.Map.prototype.getViewport = function() { + return this.viewport_; +}; + + +/** + * Get the element that serves as the container for overlays. Elements added to + * this container will let mousedown and touchstart events through to the map, + * so clicks and gestures on an overlay will trigger {@link ol.MapBrowserEvent} + * events. + * @return {!Element} The map's overlay container. + */ +ol.Map.prototype.getOverlayContainer = function() { + return this.overlayContainer_; +}; + + +/** + * Get the element that serves as a container for overlays that don't allow + * event propagation. Elements added to this container won't let mousedown and + * touchstart events through to the map, so clicks and gestures on an overlay + * don't trigger any {@link ol.MapBrowserEvent}. + * @return {!Element} The map's overlay container that stops events. + */ +ol.Map.prototype.getOverlayContainerStopEvent = function() { + return this.overlayContainerStopEvent_; +}; + + +/** + * @param {ol.Tile} tile Tile. + * @param {string} tileSourceKey Tile source key. + * @param {ol.Coordinate} tileCenter Tile center. + * @param {number} tileResolution Tile resolution. + * @return {number} Tile priority. + */ +ol.Map.prototype.getTilePriority = function(tile, tileSourceKey, tileCenter, tileResolution) { + // Filter out tiles at higher zoom levels than the current zoom level, or that + // are outside the visible extent. + var frameState = this.frameState_; + if (!frameState || !(tileSourceKey in frameState.wantedTiles)) { + return ol.structs.PriorityQueue.DROP; + } + if (!frameState.wantedTiles[tileSourceKey][tile.getKey()]) { + return ol.structs.PriorityQueue.DROP; + } + // Prioritize the highest zoom level tiles closest to the focus. + // Tiles at higher zoom levels are prioritized using Math.log(tileResolution). + // Within a zoom level, tiles are prioritized by the distance in pixels + // between the center of the tile and the focus. The factor of 65536 means + // that the prioritization should behave as desired for tiles up to + // 65536 * Math.log(2) = 45426 pixels from the focus. + var deltaX = tileCenter[0] - frameState.focus[0]; + var deltaY = tileCenter[1] - frameState.focus[1]; + return 65536 * Math.log(tileResolution) + + Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; +}; + + +/** + * @param {Event} browserEvent Browser event. + * @param {string=} opt_type Type. + */ +ol.Map.prototype.handleBrowserEvent = function(browserEvent, opt_type) { + var type = opt_type || browserEvent.type; + var mapBrowserEvent = new ol.MapBrowserEvent(type, this, browserEvent); + this.handleMapBrowserEvent(mapBrowserEvent); +}; + + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle. + */ +ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) { + if (!this.frameState_) { + // With no view defined, we cannot translate pixels into geographical + // coordinates so interactions cannot be used. + return; + } + this.focus_ = mapBrowserEvent.coordinate; + mapBrowserEvent.frameState = this.frameState_; + var interactionsArray = this.getInteractions().getArray(); + var i; + if (this.dispatchEvent(mapBrowserEvent) !== false) { + for (i = interactionsArray.length - 1; i >= 0; i--) { + var interaction = interactionsArray[i]; + if (!interaction.getActive()) { + continue; + } + var cont = interaction.handleEvent(mapBrowserEvent); + if (!cont) { + break; + } + } + } +}; + + +/** + * @protected + */ +ol.Map.prototype.handlePostRender = function() { + + var frameState = this.frameState_; + + // Manage the tile queue + // Image loads are expensive and a limited resource, so try to use them + // efficiently: + // * When the view is static we allow a large number of parallel tile loads + // to complete the frame as quickly as possible. + // * When animating or interacting, image loads can cause janks, so we reduce + // the maximum number of loads per frame and limit the number of parallel + // tile loads to remain reactive to view changes and to reduce the chance of + // loading tiles that will quickly disappear from view. + var tileQueue = this.tileQueue_; + if (!tileQueue.isEmpty()) { + var maxTotalLoading = 16; + var maxNewLoads = maxTotalLoading; + if (frameState) { + var hints = frameState.viewHints; + if (hints[ol.View.Hint.ANIMATING]) { + maxTotalLoading = this.loadTilesWhileAnimating_ ? 8 : 0; + maxNewLoads = 2; + } + if (hints[ol.View.Hint.INTERACTING]) { + maxTotalLoading = this.loadTilesWhileInteracting_ ? 8 : 0; + maxNewLoads = 2; + } + } + if (tileQueue.getTilesLoading() < maxTotalLoading) { + tileQueue.reprioritize(); // FIXME only call if view has changed + tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads); + } + } + + var postRenderFunctions = this.postRenderFunctions_; + var i, ii; + for (i = 0, ii = postRenderFunctions.length; i < ii; ++i) { + postRenderFunctions[i](this, frameState); + } + postRenderFunctions.length = 0; +}; + + +/** + * @private + */ +ol.Map.prototype.handleSizeChanged_ = function() { + this.render(); +}; + + +/** + * @private + */ +ol.Map.prototype.handleTargetChanged_ = function() { + // target may be undefined, null, a string or an Element. + // If it's a string we convert it to an Element before proceeding. + // If it's not now an Element we remove the viewport from the DOM. + // If it's an Element we append the viewport element to it. + + var targetElement; + if (this.getTarget()) { + targetElement = this.getTargetElement(); + ol.DEBUG && console.assert(targetElement !== null, + 'expects a non-null value for targetElement'); + } + + if (this.keyHandlerKeys_) { + for (var i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) { + ol.events.unlistenByKey(this.keyHandlerKeys_[i]); + } + this.keyHandlerKeys_ = null; + } + + if (!targetElement) { + ol.dom.removeNode(this.viewport_); + if (this.handleResize_ !== undefined) { + window.removeEventListener(ol.events.EventType.RESIZE, + this.handleResize_, false); + this.handleResize_ = undefined; + } + } else { + targetElement.appendChild(this.viewport_); + + var keyboardEventTarget = !this.keyboardEventTarget_ ? + targetElement : this.keyboardEventTarget_; + this.keyHandlerKeys_ = [ + ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYDOWN, + this.handleBrowserEvent, this), + ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYPRESS, + this.handleBrowserEvent, this) + ]; + + if (!this.handleResize_) { + this.handleResize_ = this.updateSize.bind(this); + window.addEventListener(ol.events.EventType.RESIZE, + this.handleResize_, false); + } + } + + this.updateSize(); + // updateSize calls setSize, so no need to call this.render + // ourselves here. +}; + + +/** + * @private + */ +ol.Map.prototype.handleTileChange_ = function() { + this.render(); +}; + + +/** + * @private + */ +ol.Map.prototype.handleViewPropertyChanged_ = function() { + this.render(); +}; + + +/** + * @private + */ +ol.Map.prototype.handleViewChanged_ = function() { + if (this.viewPropertyListenerKey_) { + ol.events.unlistenByKey(this.viewPropertyListenerKey_); + this.viewPropertyListenerKey_ = null; + } + if (this.viewChangeListenerKey_) { + ol.events.unlistenByKey(this.viewChangeListenerKey_); + this.viewChangeListenerKey_ = null; + } + var view = this.getView(); + if (view) { + this.viewPropertyListenerKey_ = ol.events.listen( + view, ol.Object.EventType.PROPERTYCHANGE, + this.handleViewPropertyChanged_, this); + this.viewChangeListenerKey_ = ol.events.listen( + view, ol.events.EventType.CHANGE, + this.handleViewPropertyChanged_, this); + } + this.render(); +}; + + +/** + * @private + */ +ol.Map.prototype.handleLayerGroupChanged_ = function() { + if (this.layerGroupPropertyListenerKeys_) { + this.layerGroupPropertyListenerKeys_.forEach(ol.events.unlistenByKey); + this.layerGroupPropertyListenerKeys_ = null; + } + var layerGroup = this.getLayerGroup(); + if (layerGroup) { + this.layerGroupPropertyListenerKeys_ = [ + ol.events.listen( + layerGroup, ol.Object.EventType.PROPERTYCHANGE, + this.render, this), + ol.events.listen( + layerGroup, ol.events.EventType.CHANGE, + this.render, this) + ]; + } + this.render(); +}; + + +/** + * @return {boolean} Is rendered. + */ +ol.Map.prototype.isRendered = function() { + return !!this.frameState_; +}; + + +/** + * Requests an immediate render in a synchronous manner. + * @api stable + */ +ol.Map.prototype.renderSync = function() { + if (this.animationDelayKey_) { + cancelAnimationFrame(this.animationDelayKey_); + } + this.animationDelay_(); +}; + + +/** + * Request a map rendering (at the next animation frame). + * @api stable + */ +ol.Map.prototype.render = function() { + if (this.animationDelayKey_ === undefined) { + this.animationDelayKey_ = requestAnimationFrame( + this.animationDelay_); + } +}; + + +/** + * Remove the given control from the map. + * @param {ol.control.Control} control Control. + * @return {ol.control.Control|undefined} The removed control (or undefined + * if the control was not found). + * @api stable + */ +ol.Map.prototype.removeControl = function(control) { + return this.getControls().remove(control); +}; + + +/** + * Remove the given interaction from the map. + * @param {ol.interaction.Interaction} interaction Interaction to remove. + * @return {ol.interaction.Interaction|undefined} The removed interaction (or + * undefined if the interaction was not found). + * @api stable + */ +ol.Map.prototype.removeInteraction = function(interaction) { + return this.getInteractions().remove(interaction); +}; + + +/** + * Removes the given layer from the map. + * @param {ol.layer.Base} layer Layer. + * @return {ol.layer.Base|undefined} The removed layer (or undefined if the + * layer was not found). + * @api stable + */ +ol.Map.prototype.removeLayer = function(layer) { + var layers = this.getLayerGroup().getLayers(); + return layers.remove(layer); +}; + + +/** + * Remove the given overlay from the map. + * @param {ol.Overlay} overlay Overlay. + * @return {ol.Overlay|undefined} The removed overlay (or undefined + * if the overlay was not found). + * @api stable + */ +ol.Map.prototype.removeOverlay = function(overlay) { + return this.getOverlays().remove(overlay); +}; + + +/** + * @param {number} time Time. + * @private + */ +ol.Map.prototype.renderFrame_ = function(time) { + var i, ii, viewState; + + var size = this.getSize(); + var view = this.getView(); + var extent = ol.extent.createEmpty(); + /** @type {?olx.FrameState} */ + var frameState = null; + if (size !== undefined && ol.size.hasArea(size) && view && view.isDef()) { + var viewHints = view.getHints(this.frameState_ ? this.frameState_.viewHints : undefined); + var layerStatesArray = this.getLayerGroup().getLayerStatesArray(); + var layerStates = {}; + for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerStates[ol.getUid(layerStatesArray[i].layer)] = layerStatesArray[i]; + } + viewState = view.getState(); + frameState = /** @type {olx.FrameState} */ ({ + animate: false, + attributions: {}, + coordinateToPixelTransform: this.coordinateToPixelTransform_, + extent: extent, + focus: !this.focus_ ? viewState.center : this.focus_, + index: this.frameIndex_++, + layerStates: layerStates, + layerStatesArray: layerStatesArray, + logos: ol.obj.assign({}, this.logos_), + pixelRatio: this.pixelRatio_, + pixelToCoordinateTransform: this.pixelToCoordinateTransform_, + postRenderFunctions: [], + size: size, + skippedFeatureUids: this.skippedFeatureUids_, + tileQueue: this.tileQueue_, + time: time, + usedTiles: {}, + viewState: viewState, + viewHints: viewHints, + wantedTiles: {} + }); + } + + if (frameState) { + var preRenderFunctions = this.preRenderFunctions_; + var n = 0, preRenderFunction; + for (i = 0, ii = preRenderFunctions.length; i < ii; ++i) { + preRenderFunction = preRenderFunctions[i]; + if (preRenderFunction(this, frameState)) { + preRenderFunctions[n++] = preRenderFunction; + } + } + preRenderFunctions.length = n; + + frameState.extent = ol.extent.getForViewAndSize(viewState.center, + viewState.resolution, viewState.rotation, frameState.size, extent); + } + + this.frameState_ = frameState; + this.renderer_.renderFrame(frameState); + + if (frameState) { + if (frameState.animate) { + this.render(); + } + Array.prototype.push.apply( + this.postRenderFunctions_, frameState.postRenderFunctions); + + var idle = this.preRenderFunctions_.length === 0 && + !frameState.viewHints[ol.View.Hint.ANIMATING] && + !frameState.viewHints[ol.View.Hint.INTERACTING] && + !ol.extent.equals(frameState.extent, this.previousExtent_); + + if (idle) { + this.dispatchEvent( + new ol.MapEvent(ol.MapEvent.Type.MOVEEND, this, frameState)); + ol.extent.clone(frameState.extent, this.previousExtent_); + } + } + + this.dispatchEvent( + new ol.MapEvent(ol.MapEvent.Type.POSTRENDER, this, frameState)); + + setTimeout(this.handlePostRender.bind(this), 0); + +}; + + +/** + * Sets the layergroup of this map. + * @param {ol.layer.Group} layerGroup A layer group containing the layers in + * this map. + * @observable + * @api stable + */ +ol.Map.prototype.setLayerGroup = function(layerGroup) { + this.set(ol.Map.Property.LAYERGROUP, layerGroup); +}; + + +/** + * Set the size of this map. + * @param {ol.Size|undefined} size The size in pixels of the map in the DOM. + * @observable + * @api + */ +ol.Map.prototype.setSize = function(size) { + this.set(ol.Map.Property.SIZE, size); +}; + + +/** + * Set the target element to render this map into. + * @param {Element|string|undefined} target The Element or id of the Element + * that the map is rendered in. + * @observable + * @api stable + */ +ol.Map.prototype.setTarget = function(target) { + this.set(ol.Map.Property.TARGET, target); +}; + + +/** + * Set the view for this map. + * @param {ol.View} view The view that controls this map. + * @observable + * @api stable + */ +ol.Map.prototype.setView = function(view) { + this.set(ol.Map.Property.VIEW, view); +}; + + +/** + * @param {ol.Feature} feature Feature. + */ +ol.Map.prototype.skipFeature = function(feature) { + var featureUid = ol.getUid(feature).toString(); + this.skippedFeatureUids_[featureUid] = true; + this.render(); +}; + + +/** + * Force a recalculation of the map viewport size. This should be called when + * third-party code changes the size of the map viewport. + * @api stable + */ +ol.Map.prototype.updateSize = function() { + var targetElement = this.getTargetElement(); + + if (!targetElement) { + this.setSize(undefined); + } else { + var computedStyle = getComputedStyle(targetElement); + this.setSize([ + targetElement.offsetWidth - + parseFloat(computedStyle['borderLeftWidth']) - + parseFloat(computedStyle['paddingLeft']) - + parseFloat(computedStyle['paddingRight']) - + parseFloat(computedStyle['borderRightWidth']), + targetElement.offsetHeight - + parseFloat(computedStyle['borderTopWidth']) - + parseFloat(computedStyle['paddingTop']) - + parseFloat(computedStyle['paddingBottom']) - + parseFloat(computedStyle['borderBottomWidth']) + ]); + } +}; + + +/** + * @param {ol.Feature} feature Feature. + */ +ol.Map.prototype.unskipFeature = function(feature) { + var featureUid = ol.getUid(feature).toString(); + delete this.skippedFeatureUids_[featureUid]; + this.render(); +}; + + +/** + * @param {olx.MapOptions} options Map options. + * @return {ol.MapOptionsInternal} Internal map options. + */ +ol.Map.createOptionsInternal = function(options) { + + /** + * @type {Element|Document} + */ + var keyboardEventTarget = null; + if (options.keyboardEventTarget !== undefined) { + keyboardEventTarget = typeof options.keyboardEventTarget === 'string' ? + document.getElementById(options.keyboardEventTarget) : + options.keyboardEventTarget; + } + + /** + * @type {Object.<string, *>} + */ + var values = {}; + + var logos = {}; + if (options.logo === undefined || + (typeof options.logo === 'boolean' && options.logo)) { + logos[ol.OL3_LOGO_URL] = ol.OL3_URL; + } else { + var logo = options.logo; + if (typeof logo === 'string') { + logos[logo] = ''; + } else if (logo instanceof HTMLElement) { + logos[ol.getUid(logo).toString()] = logo; + } else if (logo) { + ol.asserts.assert(typeof logo.href == 'string', 44); // `logo.href` should be a string. + ol.asserts.assert(typeof logo.src == 'string', 45); // `logo.src` should be a string. + logos[logo.src] = logo.href; + } + } + + var layerGroup = (options.layers instanceof ol.layer.Group) ? + options.layers : new ol.layer.Group({layers: options.layers}); + values[ol.Map.Property.LAYERGROUP] = layerGroup; + + values[ol.Map.Property.TARGET] = options.target; + + values[ol.Map.Property.VIEW] = options.view !== undefined ? + options.view : new ol.View(); + + /** + * @type {function(new: ol.renderer.Map, Element, ol.Map)} + */ + var rendererConstructor = ol.renderer.Map; + + /** + * @type {Array.<ol.renderer.Type>} + */ + var rendererTypes; + if (options.renderer !== undefined) { + if (Array.isArray(options.renderer)) { + rendererTypes = options.renderer; + } else if (typeof options.renderer === 'string') { + rendererTypes = [options.renderer]; + } else { + ol.asserts.assert(false, 46); // Incorrect format for `renderer` option + } + if (rendererTypes.indexOf(/** @type {ol.renderer.Type} */ ('dom')) >= 0) { + ol.DEBUG && console.assert(false, 'The DOM render has been removed'); + rendererTypes = rendererTypes.concat(ol.DEFAULT_RENDERER_TYPES); + } + } else { + rendererTypes = ol.DEFAULT_RENDERER_TYPES; + } + + var i, ii; + for (i = 0, ii = rendererTypes.length; i < ii; ++i) { + /** @type {ol.renderer.Type} */ + var rendererType = rendererTypes[i]; + if (ol.ENABLE_CANVAS && rendererType == ol.renderer.Type.CANVAS) { + if (ol.has.CANVAS) { + rendererConstructor = ol.renderer.canvas.Map; + break; + } + } else if (ol.ENABLE_WEBGL && rendererType == ol.renderer.Type.WEBGL) { + if (ol.has.WEBGL) { + rendererConstructor = ol.renderer.webgl.Map; + break; + } + } + } + + var controls; + if (options.controls !== undefined) { + if (Array.isArray(options.controls)) { + controls = new ol.Collection(options.controls.slice()); + } else { + ol.asserts.assert(options.controls instanceof ol.Collection, + 47); // Expected `controls` to be an array or an `ol.Collection` + controls = options.controls; + } + } else { + controls = ol.control.defaults(); + } + + var interactions; + if (options.interactions !== undefined) { + if (Array.isArray(options.interactions)) { + interactions = new ol.Collection(options.interactions.slice()); + } else { + ol.asserts.assert(options.interactions instanceof ol.Collection, + 48); // Expected `interactions` to be an array or an `ol.Collection` + interactions = options.interactions; + } + } else { + interactions = ol.interaction.defaults(); + } + + var overlays; + if (options.overlays !== undefined) { + if (Array.isArray(options.overlays)) { + overlays = new ol.Collection(options.overlays.slice()); + } else { + ol.asserts.assert(options.overlays instanceof ol.Collection, + 49); // Expected `overlays` to be an array or an `ol.Collection` + overlays = options.overlays; + } + } else { + overlays = new ol.Collection(); + } + + return { + controls: controls, + interactions: interactions, + keyboardEventTarget: keyboardEventTarget, + logos: logos, + overlays: overlays, + rendererConstructor: rendererConstructor, + values: values + }; + +}; + +/** + * @enum {string} + */ +ol.Map.Property = { + LAYERGROUP: 'layergroup', + SIZE: 'size', + TARGET: 'target', + VIEW: 'view' +}; + + +ol.proj.common.add(); + +goog.provide('ol.Overlay'); + +goog.require('ol'); +goog.require('ol.MapEvent'); +goog.require('ol.Object'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.extent'); + + +/** + * @classdesc + * An element to be displayed over the map and attached to a single map + * location. Like {@link ol.control.Control}, Overlays are visible widgets. + * Unlike Controls, they are not in a fixed position on the screen, but are tied + * to a geographical coordinate, so panning the map will move an Overlay but not + * a Control. + * + * Example: + * + * var popup = new ol.Overlay({ + * element: document.getElementById('popup') + * }); + * popup.setPosition(coordinate); + * map.addOverlay(popup); + * + * @constructor + * @extends {ol.Object} + * @param {olx.OverlayOptions} options Overlay options. + * @api stable + */ +ol.Overlay = function(options) { + + ol.Object.call(this); + + /** + * @private + * @type {number|string|undefined} + */ + this.id_ = options.id; + + /** + * @private + * @type {boolean} + */ + this.insertFirst_ = options.insertFirst !== undefined ? + options.insertFirst : true; + + /** + * @private + * @type {boolean} + */ + this.stopEvent_ = options.stopEvent !== undefined ? options.stopEvent : true; + + /** + * @private + * @type {Element} + */ + this.element_ = document.createElement('DIV'); + this.element_.className = 'ol-overlay-container'; + this.element_.style.position = 'absolute'; + + /** + * @protected + * @type {boolean} + */ + this.autoPan = options.autoPan !== undefined ? options.autoPan : false; + + /** + * @private + * @type {olx.OverlayPanOptions} + */ + this.autoPanAnimation_ = options.autoPanAnimation || + /** @type {olx.OverlayPanOptions} */ ({}); + + /** + * @private + * @type {number} + */ + this.autoPanMargin_ = options.autoPanMargin !== undefined ? + options.autoPanMargin : 20; + + /** + * @private + * @type {{bottom_: string, + * left_: string, + * right_: string, + * top_: string, + * visible: boolean}} + */ + this.rendered_ = { + bottom_: '', + left_: '', + right_: '', + top_: '', + visible: true + }; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.mapPostrenderListenerKey_ = null; + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Overlay.Property.ELEMENT), + this.handleElementChanged, this); + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Overlay.Property.MAP), + this.handleMapChanged, this); + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Overlay.Property.OFFSET), + this.handleOffsetChanged, this); + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Overlay.Property.POSITION), + this.handlePositionChanged, this); + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Overlay.Property.POSITIONING), + this.handlePositioningChanged, this); + + if (options.element !== undefined) { + this.setElement(options.element); + } + + this.setOffset(options.offset !== undefined ? options.offset : [0, 0]); + + this.setPositioning(options.positioning !== undefined ? + /** @type {ol.Overlay.Positioning} */ (options.positioning) : + ol.Overlay.Positioning.TOP_LEFT); + + if (options.position !== undefined) { + this.setPosition(options.position); + } + +}; +ol.inherits(ol.Overlay, ol.Object); + + +/** + * Get the DOM element of this overlay. + * @return {Element|undefined} The Element containing the overlay. + * @observable + * @api stable + */ +ol.Overlay.prototype.getElement = function() { + return /** @type {Element|undefined} */ ( + this.get(ol.Overlay.Property.ELEMENT)); +}; + + +/** + * Get the overlay identifier which is set on constructor. + * @return {number|string|undefined} Id. + * @api + */ +ol.Overlay.prototype.getId = function() { + return this.id_; +}; + + +/** + * Get the map associated with this overlay. + * @return {ol.Map|undefined} The map that the overlay is part of. + * @observable + * @api stable + */ +ol.Overlay.prototype.getMap = function() { + return /** @type {ol.Map|undefined} */ ( + this.get(ol.Overlay.Property.MAP)); +}; + + +/** + * Get the offset of this overlay. + * @return {Array.<number>} The offset. + * @observable + * @api stable + */ +ol.Overlay.prototype.getOffset = function() { + return /** @type {Array.<number>} */ ( + this.get(ol.Overlay.Property.OFFSET)); +}; + + +/** + * Get the current position of this overlay. + * @return {ol.Coordinate|undefined} The spatial point that the overlay is + * anchored at. + * @observable + * @api stable + */ +ol.Overlay.prototype.getPosition = function() { + return /** @type {ol.Coordinate|undefined} */ ( + this.get(ol.Overlay.Property.POSITION)); +}; + + +/** + * Get the current positioning of this overlay. + * @return {ol.Overlay.Positioning} How the overlay is positioned + * relative to its point on the map. + * @observable + * @api stable + */ +ol.Overlay.prototype.getPositioning = function() { + return /** @type {ol.Overlay.Positioning} */ ( + this.get(ol.Overlay.Property.POSITIONING)); +}; + + +/** + * @protected + */ +ol.Overlay.prototype.handleElementChanged = function() { + ol.dom.removeChildren(this.element_); + var element = this.getElement(); + if (element) { + this.element_.appendChild(element); + } +}; + + +/** + * @protected + */ +ol.Overlay.prototype.handleMapChanged = function() { + if (this.mapPostrenderListenerKey_) { + ol.dom.removeNode(this.element_); + ol.events.unlistenByKey(this.mapPostrenderListenerKey_); + this.mapPostrenderListenerKey_ = null; + } + var map = this.getMap(); + if (map) { + this.mapPostrenderListenerKey_ = ol.events.listen(map, + ol.MapEvent.Type.POSTRENDER, this.render, this); + this.updatePixelPosition(); + var container = this.stopEvent_ ? + map.getOverlayContainerStopEvent() : map.getOverlayContainer(); + if (this.insertFirst_) { + container.insertBefore(this.element_, container.childNodes[0] || null); + } else { + container.appendChild(this.element_); + } + } +}; + + +/** + * @protected + */ +ol.Overlay.prototype.render = function() { + this.updatePixelPosition(); +}; + + +/** + * @protected + */ +ol.Overlay.prototype.handleOffsetChanged = function() { + this.updatePixelPosition(); +}; + + +/** + * @protected + */ +ol.Overlay.prototype.handlePositionChanged = function() { + this.updatePixelPosition(); + if (this.get(ol.Overlay.Property.POSITION) !== undefined && this.autoPan) { + this.panIntoView_(); + } +}; + + +/** + * @protected + */ +ol.Overlay.prototype.handlePositioningChanged = function() { + this.updatePixelPosition(); +}; + + +/** + * Set the DOM element to be associated with this overlay. + * @param {Element|undefined} element The Element containing the overlay. + * @observable + * @api stable + */ +ol.Overlay.prototype.setElement = function(element) { + this.set(ol.Overlay.Property.ELEMENT, element); +}; + + +/** + * Set the map to be associated with this overlay. + * @param {ol.Map|undefined} map The map that the overlay is part of. + * @observable + * @api stable + */ +ol.Overlay.prototype.setMap = function(map) { + this.set(ol.Overlay.Property.MAP, map); +}; + + +/** + * Set the offset for this overlay. + * @param {Array.<number>} offset Offset. + * @observable + * @api stable + */ +ol.Overlay.prototype.setOffset = function(offset) { + this.set(ol.Overlay.Property.OFFSET, offset); +}; + + +/** + * Set the position for this overlay. If the position is `undefined` the + * overlay is hidden. + * @param {ol.Coordinate|undefined} position The spatial point that the overlay + * is anchored at. + * @observable + * @api stable + */ +ol.Overlay.prototype.setPosition = function(position) { + this.set(ol.Overlay.Property.POSITION, position); +}; + + +/** + * Pan the map so that the overlay is entirely visible in the current viewport + * (if necessary). + * @private + */ +ol.Overlay.prototype.panIntoView_ = function() { + var map = this.getMap(); + + if (map === undefined || !map.getTargetElement()) { + return; + } + + var mapRect = this.getRect_(map.getTargetElement(), map.getSize()); + var element = /** @type {!Element} */ (this.getElement()); + var overlayRect = this.getRect_(element, + [ol.dom.outerWidth(element), ol.dom.outerHeight(element)]); + + var margin = this.autoPanMargin_; + if (!ol.extent.containsExtent(mapRect, overlayRect)) { + // the overlay is not completely inside the viewport, so pan the map + var offsetLeft = overlayRect[0] - mapRect[0]; + var offsetRight = mapRect[2] - overlayRect[2]; + var offsetTop = overlayRect[1] - mapRect[1]; + var offsetBottom = mapRect[3] - overlayRect[3]; + + var delta = [0, 0]; + if (offsetLeft < 0) { + // move map to the left + delta[0] = offsetLeft - margin; + } else if (offsetRight < 0) { + // move map to the right + delta[0] = Math.abs(offsetRight) + margin; + } + if (offsetTop < 0) { + // move map up + delta[1] = offsetTop - margin; + } else if (offsetBottom < 0) { + // move map down + delta[1] = Math.abs(offsetBottom) + margin; + } + + if (delta[0] !== 0 || delta[1] !== 0) { + var center = /** @type {ol.Coordinate} */ (map.getView().getCenter()); + var centerPx = map.getPixelFromCoordinate(center); + var newCenterPx = [ + centerPx[0] + delta[0], + centerPx[1] + delta[1] + ]; + + map.getView().animate({ + center: map.getCoordinateFromPixel(newCenterPx), + duration: this.autoPanAnimation_.duration, + easing: this.autoPanAnimation_.easing + }); + } + } +}; + + +/** + * Get the extent of an element relative to the document + * @param {Element|undefined} element The element. + * @param {ol.Size|undefined} size The size of the element. + * @return {ol.Extent} The extent. + * @private + */ +ol.Overlay.prototype.getRect_ = function(element, size) { + var box = element.getBoundingClientRect(); + var offsetX = box.left + window.pageXOffset; + var offsetY = box.top + window.pageYOffset; + return [ + offsetX, + offsetY, + offsetX + size[0], + offsetY + size[1] + ]; +}; + + +/** + * Set the positioning for this overlay. + * @param {ol.Overlay.Positioning} positioning how the overlay is + * positioned relative to its point on the map. + * @observable + * @api stable + */ +ol.Overlay.prototype.setPositioning = function(positioning) { + this.set(ol.Overlay.Property.POSITIONING, positioning); +}; + + +/** + * Modify the visibility of the element. + * @param {boolean} visible Element visibility. + * @protected + */ +ol.Overlay.prototype.setVisible = function(visible) { + if (this.rendered_.visible !== visible) { + this.element_.style.display = visible ? '' : 'none'; + this.rendered_.visible = visible; + } +}; + + +/** + * Update pixel position. + * @protected + */ +ol.Overlay.prototype.updatePixelPosition = function() { + var map = this.getMap(); + var position = this.getPosition(); + if (map === undefined || !map.isRendered() || position === undefined) { + this.setVisible(false); + return; + } + + var pixel = map.getPixelFromCoordinate(position); + var mapSize = map.getSize(); + this.updateRenderedPosition(pixel, mapSize); +}; + + +/** + * @param {ol.Pixel} pixel The pixel location. + * @param {ol.Size|undefined} mapSize The map size. + * @protected + */ +ol.Overlay.prototype.updateRenderedPosition = function(pixel, mapSize) { + var style = this.element_.style; + var offset = this.getOffset(); + + var positioning = this.getPositioning(); + ol.DEBUG && console.assert(positioning !== undefined, + 'positioning should be defined'); + + var offsetX = offset[0]; + var offsetY = offset[1]; + if (positioning == ol.Overlay.Positioning.BOTTOM_RIGHT || + positioning == ol.Overlay.Positioning.CENTER_RIGHT || + positioning == ol.Overlay.Positioning.TOP_RIGHT) { + if (this.rendered_.left_ !== '') { + this.rendered_.left_ = style.left = ''; + } + var right = Math.round(mapSize[0] - pixel[0] - offsetX) + 'px'; + if (this.rendered_.right_ != right) { + this.rendered_.right_ = style.right = right; + } + } else { + if (this.rendered_.right_ !== '') { + this.rendered_.right_ = style.right = ''; + } + if (positioning == ol.Overlay.Positioning.BOTTOM_CENTER || + positioning == ol.Overlay.Positioning.CENTER_CENTER || + positioning == ol.Overlay.Positioning.TOP_CENTER) { + offsetX -= this.element_.offsetWidth / 2; + } + var left = Math.round(pixel[0] + offsetX) + 'px'; + if (this.rendered_.left_ != left) { + this.rendered_.left_ = style.left = left; + } + } + if (positioning == ol.Overlay.Positioning.BOTTOM_LEFT || + positioning == ol.Overlay.Positioning.BOTTOM_CENTER || + positioning == ol.Overlay.Positioning.BOTTOM_RIGHT) { + if (this.rendered_.top_ !== '') { + this.rendered_.top_ = style.top = ''; + } + var bottom = Math.round(mapSize[1] - pixel[1] - offsetY) + 'px'; + if (this.rendered_.bottom_ != bottom) { + this.rendered_.bottom_ = style.bottom = bottom; + } + } else { + if (this.rendered_.bottom_ !== '') { + this.rendered_.bottom_ = style.bottom = ''; + } + if (positioning == ol.Overlay.Positioning.CENTER_LEFT || + positioning == ol.Overlay.Positioning.CENTER_CENTER || + positioning == ol.Overlay.Positioning.CENTER_RIGHT) { + offsetY -= this.element_.offsetHeight / 2; + } + var top = Math.round(pixel[1] + offsetY) + 'px'; + if (this.rendered_.top_ != top) { + this.rendered_.top_ = style.top = top; + } + } + + this.setVisible(true); +}; + + +/** + * Overlay position: `'bottom-left'`, `'bottom-center'`, `'bottom-right'`, + * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`, + * `'top-center'`, `'top-right'` + * @enum {string} + */ +ol.Overlay.Positioning = { + BOTTOM_LEFT: 'bottom-left', + BOTTOM_CENTER: 'bottom-center', + BOTTOM_RIGHT: 'bottom-right', + CENTER_LEFT: 'center-left', + CENTER_CENTER: 'center-center', + CENTER_RIGHT: 'center-right', + TOP_LEFT: 'top-left', + TOP_CENTER: 'top-center', + TOP_RIGHT: 'top-right' +}; + + +/** + * @enum {string} + */ +ol.Overlay.Property = { + ELEMENT: 'element', + MAP: 'map', + OFFSET: 'offset', + POSITION: 'position', + POSITIONING: 'positioning' +}; + +goog.provide('ol.control.OverviewMap'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.Map'); +goog.require('ol.MapEvent'); +goog.require('ol.Object'); +goog.require('ol.Overlay'); +goog.require('ol.View'); +goog.require('ol.control.Control'); +goog.require('ol.coordinate'); +goog.require('ol.css'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); + + +/** + * Create a new control with a map acting as an overview map for an other + * defined map. + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.OverviewMapOptions=} opt_options OverviewMap options. + * @api + */ +ol.control.OverviewMap = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + /** + * @type {boolean} + * @private + */ + this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true; + + /** + * @private + * @type {boolean} + */ + this.collapsible_ = options.collapsible !== undefined ? + options.collapsible : true; + + if (!this.collapsible_) { + this.collapsed_ = false; + } + + var className = options.className !== undefined ? options.className : 'ol-overviewmap'; + + var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Overview map'; + + var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00AB'; + + if (typeof collapseLabel === 'string') { + /** + * @private + * @type {Node} + */ + this.collapseLabel_ = document.createElement('span'); + this.collapseLabel_.textContent = collapseLabel; + } else { + this.collapseLabel_ = collapseLabel; + } + + var label = options.label !== undefined ? options.label : '\u00BB'; + + + if (typeof label === 'string') { + /** + * @private + * @type {Node} + */ + this.label_ = document.createElement('span'); + this.label_.textContent = label; + } else { + this.label_ = label; + } + + var activeLabel = (this.collapsible_ && !this.collapsed_) ? + this.collapseLabel_ : this.label_; + var button = document.createElement('button'); + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild(activeLabel); + + ol.events.listen(button, ol.events.EventType.CLICK, + this.handleClick_, this); + + var ovmapDiv = document.createElement('DIV'); + ovmapDiv.className = 'ol-overviewmap-map'; + + /** + * @type {ol.Map} + * @private + */ + this.ovmap_ = new ol.Map({ + controls: new ol.Collection(), + interactions: new ol.Collection(), + target: ovmapDiv, + view: options.view + }); + var ovmap = this.ovmap_; + + if (options.layers) { + options.layers.forEach( + /** + * @param {ol.layer.Layer} layer Layer. + */ + function(layer) { + ovmap.addLayer(layer); + }, this); + } + + var box = document.createElement('DIV'); + box.className = 'ol-overviewmap-box'; + box.style.boxSizing = 'border-box'; + + /** + * @type {ol.Overlay} + * @private + */ + this.boxOverlay_ = new ol.Overlay({ + position: [0, 0], + positioning: ol.Overlay.Positioning.BOTTOM_LEFT, + element: box + }); + this.ovmap_.addOverlay(this.boxOverlay_); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL + + (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') + + (this.collapsible_ ? '' : ' ol-uncollapsible'); + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(ovmapDiv); + element.appendChild(button); + + var render = options.render ? options.render : ol.control.OverviewMap.render; + + ol.control.Control.call(this, { + element: element, + render: render, + target: options.target + }); +}; +ol.inherits(ol.control.OverviewMap, ol.control.Control); + + +/** + * @inheritDoc + * @api + */ +ol.control.OverviewMap.prototype.setMap = function(map) { + var oldMap = this.getMap(); + if (map === oldMap) { + return; + } + if (oldMap) { + var oldView = oldMap.getView(); + if (oldView) { + this.unbindView_(oldView); + } + } + ol.control.Control.prototype.setMap.call(this, map); + + if (map) { + this.listenerKeys.push(ol.events.listen( + map, ol.Object.EventType.PROPERTYCHANGE, + this.handleMapPropertyChange_, this)); + + // TODO: to really support map switching, this would need to be reworked + if (this.ovmap_.getLayers().getLength() === 0) { + this.ovmap_.setLayerGroup(map.getLayerGroup()); + } + + var view = map.getView(); + if (view) { + this.bindView_(view); + if (view.isDef()) { + this.ovmap_.updateSize(); + this.resetExtent_(); + } + } + } +}; + + +/** + * Handle map property changes. This only deals with changes to the map's view. + * @param {ol.Object.Event} event The propertychange event. + * @private + */ +ol.control.OverviewMap.prototype.handleMapPropertyChange_ = function(event) { + if (event.key === ol.Map.Property.VIEW) { + var oldView = /** @type {ol.View} */ (event.oldValue); + if (oldView) { + this.unbindView_(oldView); + } + var newView = this.getMap().getView(); + this.bindView_(newView); + } +}; + + +/** + * Register listeners for view property changes. + * @param {ol.View} view The view. + * @private + */ +ol.control.OverviewMap.prototype.bindView_ = function(view) { + ol.events.listen(view, + ol.Object.getChangeEventType(ol.View.Property.ROTATION), + this.handleRotationChanged_, this); +}; + + +/** + * Unregister listeners for view property changes. + * @param {ol.View} view The view. + * @private + */ +ol.control.OverviewMap.prototype.unbindView_ = function(view) { + ol.events.unlisten(view, + ol.Object.getChangeEventType(ol.View.Property.ROTATION), + this.handleRotationChanged_, this); +}; + + +/** + * Handle rotation changes to the main map. + * TODO: This should rotate the extent rectrangle instead of the + * overview map's view. + * @private + */ +ol.control.OverviewMap.prototype.handleRotationChanged_ = function() { + this.ovmap_.getView().setRotation(this.getMap().getView().getRotation()); +}; + + +/** + * Update the overview map element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.OverviewMap} + * @api + */ +ol.control.OverviewMap.render = function(mapEvent) { + this.validateExtent_(); + this.updateBox_(); +}; + + +/** + * Reset the overview map extent if the box size (width or + * height) is less than the size of the overview map size times minRatio + * or is greater than the size of the overview size times maxRatio. + * + * If the map extent was not reset, the box size can fits in the defined + * ratio sizes. This method then checks if is contained inside the overview + * map current extent. If not, recenter the overview map to the current + * main map center location. + * @private + */ +ol.control.OverviewMap.prototype.validateExtent_ = function() { + var map = this.getMap(); + var ovmap = this.ovmap_; + + if (!map.isRendered() || !ovmap.isRendered()) { + return; + } + + var mapSize = /** @type {ol.Size} */ (map.getSize()); + + var view = map.getView(); + var extent = view.calculateExtent(mapSize); + + var ovmapSize = /** @type {ol.Size} */ (ovmap.getSize()); + + var ovview = ovmap.getView(); + var ovextent = ovview.calculateExtent(ovmapSize); + + var topLeftPixel = + ovmap.getPixelFromCoordinate(ol.extent.getTopLeft(extent)); + var bottomRightPixel = + ovmap.getPixelFromCoordinate(ol.extent.getBottomRight(extent)); + + var boxWidth = Math.abs(topLeftPixel[0] - bottomRightPixel[0]); + var boxHeight = Math.abs(topLeftPixel[1] - bottomRightPixel[1]); + + var ovmapWidth = ovmapSize[0]; + var ovmapHeight = ovmapSize[1]; + + if (boxWidth < ovmapWidth * ol.OVERVIEWMAP_MIN_RATIO || + boxHeight < ovmapHeight * ol.OVERVIEWMAP_MIN_RATIO || + boxWidth > ovmapWidth * ol.OVERVIEWMAP_MAX_RATIO || + boxHeight > ovmapHeight * ol.OVERVIEWMAP_MAX_RATIO) { + this.resetExtent_(); + } else if (!ol.extent.containsExtent(ovextent, extent)) { + this.recenter_(); + } +}; + + +/** + * Reset the overview map extent to half calculated min and max ratio times + * the extent of the main map. + * @private + */ +ol.control.OverviewMap.prototype.resetExtent_ = function() { + if (ol.OVERVIEWMAP_MAX_RATIO === 0 || ol.OVERVIEWMAP_MIN_RATIO === 0) { + return; + } + + var map = this.getMap(); + var ovmap = this.ovmap_; + + var mapSize = /** @type {ol.Size} */ (map.getSize()); + + var view = map.getView(); + var extent = view.calculateExtent(mapSize); + + var ovmapSize = /** @type {ol.Size} */ (ovmap.getSize()); + + var ovview = ovmap.getView(); + + // get how many times the current map overview could hold different + // box sizes using the min and max ratio, pick the step in the middle used + // to calculate the extent from the main map to set it to the overview map, + var steps = Math.log( + ol.OVERVIEWMAP_MAX_RATIO / ol.OVERVIEWMAP_MIN_RATIO) / Math.LN2; + var ratio = 1 / (Math.pow(2, steps / 2) * ol.OVERVIEWMAP_MIN_RATIO); + ol.extent.scaleFromCenter(extent, ratio); + ovview.fit(extent, ovmapSize); +}; + + +/** + * Set the center of the overview map to the map center without changing its + * resolution. + * @private + */ +ol.control.OverviewMap.prototype.recenter_ = function() { + var map = this.getMap(); + var ovmap = this.ovmap_; + + var view = map.getView(); + + var ovview = ovmap.getView(); + + ovview.setCenter(view.getCenter()); +}; + + +/** + * Update the box using the main map extent + * @private + */ +ol.control.OverviewMap.prototype.updateBox_ = function() { + var map = this.getMap(); + var ovmap = this.ovmap_; + + if (!map.isRendered() || !ovmap.isRendered()) { + return; + } + + var mapSize = /** @type {ol.Size} */ (map.getSize()); + + var view = map.getView(); + + var ovview = ovmap.getView(); + + var rotation = view.getRotation(); + + var overlay = this.boxOverlay_; + var box = this.boxOverlay_.getElement(); + var extent = view.calculateExtent(mapSize); + var ovresolution = ovview.getResolution(); + var bottomLeft = ol.extent.getBottomLeft(extent); + var topRight = ol.extent.getTopRight(extent); + + // set position using bottom left coordinates + var rotateBottomLeft = this.calculateCoordinateRotate_(rotation, bottomLeft); + overlay.setPosition(rotateBottomLeft); + + // set box size calculated from map extent size and overview map resolution + if (box) { + box.style.width = Math.abs((bottomLeft[0] - topRight[0]) / ovresolution) + 'px'; + box.style.height = Math.abs((topRight[1] - bottomLeft[1]) / ovresolution) + 'px'; + } +}; + + +/** + * @param {number} rotation Target rotation. + * @param {ol.Coordinate} coordinate Coordinate. + * @return {ol.Coordinate|undefined} Coordinate for rotation and center anchor. + * @private + */ +ol.control.OverviewMap.prototype.calculateCoordinateRotate_ = function( + rotation, coordinate) { + var coordinateRotate; + + var map = this.getMap(); + var view = map.getView(); + + var currentCenter = view.getCenter(); + + if (currentCenter) { + coordinateRotate = [ + coordinate[0] - currentCenter[0], + coordinate[1] - currentCenter[1] + ]; + ol.coordinate.rotate(coordinateRotate, rotation); + ol.coordinate.add(coordinateRotate, currentCenter); + } + return coordinateRotate; +}; + + +/** + * @param {Event} event The event to handle + * @private + */ +ol.control.OverviewMap.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleToggle_(); +}; + + +/** + * @private + */ +ol.control.OverviewMap.prototype.handleToggle_ = function() { + this.element.classList.toggle('ol-collapsed'); + if (this.collapsed_) { + ol.dom.replaceNode(this.collapseLabel_, this.label_); + } else { + ol.dom.replaceNode(this.label_, this.collapseLabel_); + } + this.collapsed_ = !this.collapsed_; + + // manage overview map if it had not been rendered before and control + // is expanded + var ovmap = this.ovmap_; + if (!this.collapsed_ && !ovmap.isRendered()) { + ovmap.updateSize(); + this.resetExtent_(); + ol.events.listenOnce(ovmap, ol.MapEvent.Type.POSTRENDER, + function(event) { + this.updateBox_(); + }, + this); + } +}; + + +/** + * Return `true` if the overview map is collapsible, `false` otherwise. + * @return {boolean} True if the widget is collapsible. + * @api stable + */ +ol.control.OverviewMap.prototype.getCollapsible = function() { + return this.collapsible_; +}; + + +/** + * Set whether the overview map should be collapsible. + * @param {boolean} collapsible True if the widget is collapsible. + * @api stable + */ +ol.control.OverviewMap.prototype.setCollapsible = function(collapsible) { + if (this.collapsible_ === collapsible) { + return; + } + this.collapsible_ = collapsible; + this.element.classList.toggle('ol-uncollapsible'); + if (!collapsible && this.collapsed_) { + this.handleToggle_(); + } +}; + + +/** + * Collapse or expand the overview map according to the passed parameter. Will + * not do anything if the overview map isn't collapsible or if the current + * collapsed state is already the one requested. + * @param {boolean} collapsed True if the widget is collapsed. + * @api stable + */ +ol.control.OverviewMap.prototype.setCollapsed = function(collapsed) { + if (!this.collapsible_ || this.collapsed_ === collapsed) { + return; + } + this.handleToggle_(); +}; + + +/** + * Determine if the overview map is collapsed. + * @return {boolean} The overview map is collapsed. + * @api stable + */ +ol.control.OverviewMap.prototype.getCollapsed = function() { + return this.collapsed_; +}; + + +/** + * Return the overview map. + * @return {ol.Map} Overview map. + * @api + */ +ol.control.OverviewMap.prototype.getOverviewMap = function() { + return this.ovmap_; +}; + +goog.provide('ol.control.ScaleLine'); + +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.asserts'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.events'); +goog.require('ol.proj'); +goog.require('ol.proj.Units'); + + +/** + * @classdesc + * A control displaying rough y-axis distances, calculated for the center of the + * viewport. For conformal projections (e.g. EPSG:3857, the default view + * projection in OpenLayers), the scale is valid for all directions. + * No scale line will be shown when the y-axis distance of a pixel at the + * viewport center cannot be calculated in the view projection. + * By default the scale line will show in the bottom left portion of the map, + * but this can be changed by using the css selector `.ol-scale-line`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ScaleLineOptions=} opt_options Scale line options. + * @api stable + */ +ol.control.ScaleLine = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var className = options.className !== undefined ? options.className : 'ol-scale-line'; + + /** + * @private + * @type {Element} + */ + this.innerElement_ = document.createElement('DIV'); + this.innerElement_.className = className + '-inner'; + + /** + * @private + * @type {Element} + */ + this.element_ = document.createElement('DIV'); + this.element_.className = className + ' ' + ol.css.CLASS_UNSELECTABLE; + this.element_.appendChild(this.innerElement_); + + /** + * @private + * @type {?olx.ViewState} + */ + this.viewState_ = null; + + /** + * @private + * @type {number} + */ + this.minWidth_ = options.minWidth !== undefined ? options.minWidth : 64; + + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = false; + + /** + * @private + * @type {number|undefined} + */ + this.renderedWidth_ = undefined; + + /** + * @private + * @type {string} + */ + this.renderedHTML_ = ''; + + var render = options.render ? options.render : ol.control.ScaleLine.render; + + ol.control.Control.call(this, { + element: this.element_, + render: render, + target: options.target + }); + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.control.ScaleLine.Property.UNITS), + this.handleUnitsChanged_, this); + + this.setUnits(/** @type {ol.control.ScaleLine.Units} */ (options.units) || + ol.control.ScaleLine.Units.METRIC); + +}; +ol.inherits(ol.control.ScaleLine, ol.control.Control); + + +/** + * @const + * @type {Array.<number>} + */ +ol.control.ScaleLine.LEADING_DIGITS = [1, 2, 5]; + + +/** + * Return the units to use in the scale line. + * @return {ol.control.ScaleLine.Units|undefined} The units to use in the scale + * line. + * @observable + * @api stable + */ +ol.control.ScaleLine.prototype.getUnits = function() { + return /** @type {ol.control.ScaleLine.Units|undefined} */ ( + this.get(ol.control.ScaleLine.Property.UNITS)); +}; + + +/** + * Update the scale line element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.ScaleLine} + * @api + */ +ol.control.ScaleLine.render = function(mapEvent) { + var frameState = mapEvent.frameState; + if (!frameState) { + this.viewState_ = null; + } else { + this.viewState_ = frameState.viewState; + } + this.updateElement_(); +}; + + +/** + * @private + */ +ol.control.ScaleLine.prototype.handleUnitsChanged_ = function() { + this.updateElement_(); +}; + + +/** + * Set the units to use in the scale line. + * @param {ol.control.ScaleLine.Units} units The units to use in the scale line. + * @observable + * @api stable + */ +ol.control.ScaleLine.prototype.setUnits = function(units) { + this.set(ol.control.ScaleLine.Property.UNITS, units); +}; + + +/** + * @private + */ +ol.control.ScaleLine.prototype.updateElement_ = function() { + var viewState = this.viewState_; + + if (!viewState) { + if (this.renderedVisible_) { + this.element_.style.display = 'none'; + this.renderedVisible_ = false; + } + return; + } + + var center = viewState.center; + var projection = viewState.projection; + var metersPerUnit = projection.getMetersPerUnit(); + var pointResolution = + ol.proj.getPointResolution(projection, viewState.resolution, center) * + metersPerUnit; + + var nominalCount = this.minWidth_ * pointResolution; + var suffix = ''; + var units = this.getUnits(); + if (units == ol.control.ScaleLine.Units.DEGREES) { + var metersPerDegree = ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES]; + pointResolution /= metersPerDegree; + if (nominalCount < metersPerDegree / 60) { + suffix = '\u2033'; // seconds + pointResolution *= 3600; + } else if (nominalCount < metersPerDegree) { + suffix = '\u2032'; // minutes + pointResolution *= 60; + } else { + suffix = '\u00b0'; // degrees + } + } else if (units == ol.control.ScaleLine.Units.IMPERIAL) { + if (nominalCount < 0.9144) { + suffix = 'in'; + pointResolution /= 0.0254; + } else if (nominalCount < 1609.344) { + suffix = 'ft'; + pointResolution /= 0.3048; + } else { + suffix = 'mi'; + pointResolution /= 1609.344; + } + } else if (units == ol.control.ScaleLine.Units.NAUTICAL) { + pointResolution /= 1852; + suffix = 'nm'; + } else if (units == ol.control.ScaleLine.Units.METRIC) { + if (nominalCount < 1) { + suffix = 'mm'; + pointResolution *= 1000; + } else if (nominalCount < 1000) { + suffix = 'm'; + } else { + suffix = 'km'; + pointResolution /= 1000; + } + } else if (units == ol.control.ScaleLine.Units.US) { + if (nominalCount < 0.9144) { + suffix = 'in'; + pointResolution *= 39.37; + } else if (nominalCount < 1609.344) { + suffix = 'ft'; + pointResolution /= 0.30480061; + } else { + suffix = 'mi'; + pointResolution /= 1609.3472; + } + } else { + ol.asserts.assert(false, 33); // Invalid units + } + + var i = 3 * Math.floor( + Math.log(this.minWidth_ * pointResolution) / Math.log(10)); + var count, width; + while (true) { + count = ol.control.ScaleLine.LEADING_DIGITS[((i % 3) + 3) % 3] * + Math.pow(10, Math.floor(i / 3)); + width = Math.round(count / pointResolution); + if (isNaN(width)) { + this.element_.style.display = 'none'; + this.renderedVisible_ = false; + return; + } else if (width >= this.minWidth_) { + break; + } + ++i; + } + + var html = count + ' ' + suffix; + if (this.renderedHTML_ != html) { + this.innerElement_.innerHTML = html; + this.renderedHTML_ = html; + } + + if (this.renderedWidth_ != width) { + this.innerElement_.style.width = width + 'px'; + this.renderedWidth_ = width; + } + + if (!this.renderedVisible_) { + this.element_.style.display = ''; + this.renderedVisible_ = true; + } + +}; + + +/** + * @enum {string} + * @api + */ +ol.control.ScaleLine.Property = { + UNITS: 'units' +}; + + +/** + * Units for the scale line. Supported values are `'degrees'`, `'imperial'`, + * `'nautical'`, `'metric'`, `'us'`. + * @enum {string} + */ +ol.control.ScaleLine.Units = { + DEGREES: 'degrees', + IMPERIAL: 'imperial', + NAUTICAL: 'nautical', + METRIC: 'metric', + US: 'us' +}; + +// FIXME should possibly show tooltip when dragging? + +goog.provide('ol.control.ZoomSlider'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.easing'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.EventType'); +goog.require('ol.math'); +goog.require('ol.pointer.EventType'); +goog.require('ol.pointer.PointerEventHandler'); + + +/** + * @classdesc + * A slider type of control for zooming. + * + * Example: + * + * map.addControl(new ol.control.ZoomSlider()); + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ZoomSliderOptions=} opt_options Zoom slider options. + * @api stable + */ +ol.control.ZoomSlider = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + /** + * Will hold the current resolution of the view. + * + * @type {number|undefined} + * @private + */ + this.currentResolution_ = undefined; + + /** + * The direction of the slider. Will be determined from actual display of the + * container and defaults to ol.control.ZoomSlider.direction.VERTICAL. + * + * @type {ol.control.ZoomSlider.direction} + * @private + */ + this.direction_ = ol.control.ZoomSlider.direction.VERTICAL; + + /** + * @type {boolean} + * @private + */ + this.dragging_; + + /** + * @type {!Array.<ol.EventsKey>} + * @private + */ + this.dragListenerKeys_ = []; + + /** + * @type {number} + * @private + */ + this.heightLimit_ = 0; + + /** + * @type {number} + * @private + */ + this.widthLimit_ = 0; + + /** + * @type {number|undefined} + * @private + */ + this.previousX_; + + /** + * @type {number|undefined} + * @private + */ + this.previousY_; + + /** + * The calculated thumb size (border box plus margins). Set when initSlider_ + * is called. + * @type {ol.Size} + * @private + */ + this.thumbSize_ = null; + + /** + * Whether the slider is initialized. + * @type {boolean} + * @private + */ + this.sliderInitialized_ = false; + + /** + * @type {number} + * @private + */ + this.duration_ = options.duration !== undefined ? options.duration : 200; + + var className = options.className !== undefined ? options.className : 'ol-zoomslider'; + var thumbElement = document.createElement('button'); + thumbElement.setAttribute('type', 'button'); + thumbElement.className = className + '-thumb ' + ol.css.CLASS_UNSELECTABLE; + var containerElement = document.createElement('div'); + containerElement.className = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + ol.css.CLASS_CONTROL; + containerElement.appendChild(thumbElement); + /** + * @type {ol.pointer.PointerEventHandler} + * @private + */ + this.dragger_ = new ol.pointer.PointerEventHandler(containerElement); + + ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERDOWN, + this.handleDraggerStart_, this); + ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERMOVE, + this.handleDraggerDrag_, this); + ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERUP, + this.handleDraggerEnd_, this); + + ol.events.listen(containerElement, ol.events.EventType.CLICK, + this.handleContainerClick_, this); + ol.events.listen(thumbElement, ol.events.EventType.CLICK, + ol.events.Event.stopPropagation); + + var render = options.render ? options.render : ol.control.ZoomSlider.render; + + ol.control.Control.call(this, { + element: containerElement, + render: render + }); +}; +ol.inherits(ol.control.ZoomSlider, ol.control.Control); + + +/** + * @inheritDoc + */ +ol.control.ZoomSlider.prototype.disposeInternal = function() { + this.dragger_.dispose(); + ol.control.Control.prototype.disposeInternal.call(this); +}; + + +/** + * The enum for available directions. + * + * @enum {number} + */ +ol.control.ZoomSlider.direction = { + VERTICAL: 0, + HORIZONTAL: 1 +}; + + +/** + * @inheritDoc + */ +ol.control.ZoomSlider.prototype.setMap = function(map) { + ol.control.Control.prototype.setMap.call(this, map); + if (map) { + map.render(); + } +}; + + +/** + * Initializes the slider element. This will determine and set this controls + * direction_ and also constrain the dragging of the thumb to always be within + * the bounds of the container. + * + * @private + */ +ol.control.ZoomSlider.prototype.initSlider_ = function() { + var container = this.element; + var containerSize = { + width: container.offsetWidth, height: container.offsetHeight + }; + + var thumb = container.firstElementChild; + var computedStyle = getComputedStyle(thumb); + var thumbWidth = thumb.offsetWidth + + parseFloat(computedStyle['marginRight']) + + parseFloat(computedStyle['marginLeft']); + var thumbHeight = thumb.offsetHeight + + parseFloat(computedStyle['marginTop']) + + parseFloat(computedStyle['marginBottom']); + this.thumbSize_ = [thumbWidth, thumbHeight]; + + if (containerSize.width > containerSize.height) { + this.direction_ = ol.control.ZoomSlider.direction.HORIZONTAL; + this.widthLimit_ = containerSize.width - thumbWidth; + } else { + this.direction_ = ol.control.ZoomSlider.direction.VERTICAL; + this.heightLimit_ = containerSize.height - thumbHeight; + } + this.sliderInitialized_ = true; +}; + + +/** + * Update the zoomslider element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.ZoomSlider} + * @api + */ +ol.control.ZoomSlider.render = function(mapEvent) { + if (!mapEvent.frameState) { + return; + } + if (!this.sliderInitialized_) { + this.initSlider_(); + } + var res = mapEvent.frameState.viewState.resolution; + if (res !== this.currentResolution_) { + this.currentResolution_ = res; + this.setThumbPosition_(res); + } +}; + + +/** + * @param {Event} event The browser event to handle. + * @private + */ +ol.control.ZoomSlider.prototype.handleContainerClick_ = function(event) { + var view = this.getMap().getView(); + + var relativePosition = this.getRelativePosition_( + event.offsetX - this.thumbSize_[0] / 2, + event.offsetY - this.thumbSize_[1] / 2); + + var resolution = this.getResolutionForPosition_(relativePosition); + + view.animate({ + resolution: view.constrainResolution(resolution), + duration: this.duration_, + easing: ol.easing.easeOut + }); +}; + + +/** + * Handle dragger start events. + * @param {ol.pointer.PointerEvent} event The drag event. + * @private + */ +ol.control.ZoomSlider.prototype.handleDraggerStart_ = function(event) { + if (!this.dragging_ && + event.originalEvent.target === this.element.firstElementChild) { + this.getMap().getView().setHint(ol.View.Hint.INTERACTING, 1); + this.previousX_ = event.clientX; + this.previousY_ = event.clientY; + this.dragging_ = true; + + if (this.dragListenerKeys_.length === 0) { + var drag = this.handleDraggerDrag_; + var end = this.handleDraggerEnd_; + this.dragListenerKeys_.push( + ol.events.listen(document, ol.events.EventType.MOUSEMOVE, drag, this), + ol.events.listen(document, ol.events.EventType.TOUCHMOVE, drag, this), + ol.events.listen(document, ol.pointer.EventType.POINTERMOVE, drag, this), + ol.events.listen(document, ol.events.EventType.MOUSEUP, end, this), + ol.events.listen(document, ol.events.EventType.TOUCHEND, end, this), + ol.events.listen(document, ol.pointer.EventType.POINTERUP, end, this) + ); + } + } +}; + + +/** + * Handle dragger drag events. + * + * @param {ol.pointer.PointerEvent|Event} event The drag event. + * @private + */ +ol.control.ZoomSlider.prototype.handleDraggerDrag_ = function(event) { + if (this.dragging_) { + var element = this.element.firstElementChild; + var deltaX = event.clientX - this.previousX_ + parseInt(element.style.left, 10); + var deltaY = event.clientY - this.previousY_ + parseInt(element.style.top, 10); + var relativePosition = this.getRelativePosition_(deltaX, deltaY); + this.currentResolution_ = this.getResolutionForPosition_(relativePosition); + this.getMap().getView().setResolution(this.currentResolution_); + this.setThumbPosition_(this.currentResolution_); + this.previousX_ = event.clientX; + this.previousY_ = event.clientY; + } +}; + + +/** + * Handle dragger end events. + * @param {ol.pointer.PointerEvent|Event} event The drag event. + * @private + */ +ol.control.ZoomSlider.prototype.handleDraggerEnd_ = function(event) { + if (this.dragging_) { + var view = this.getMap().getView(); + view.setHint(ol.View.Hint.INTERACTING, -1); + + view.animate({ + resolution: view.constrainResolution(this.currentResolution_), + duration: this.duration_, + easing: ol.easing.easeOut + }); + + this.dragging_ = false; + this.previousX_ = undefined; + this.previousY_ = undefined; + this.dragListenerKeys_.forEach(ol.events.unlistenByKey); + this.dragListenerKeys_.length = 0; + } +}; + + +/** + * Positions the thumb inside its container according to the given resolution. + * + * @param {number} res The res. + * @private + */ +ol.control.ZoomSlider.prototype.setThumbPosition_ = function(res) { + var position = this.getPositionForResolution_(res); + var thumb = this.element.firstElementChild; + + if (this.direction_ == ol.control.ZoomSlider.direction.HORIZONTAL) { + thumb.style.left = this.widthLimit_ * position + 'px'; + } else { + thumb.style.top = this.heightLimit_ * position + 'px'; + } +}; + + +/** + * Calculates the relative position of the thumb given x and y offsets. The + * relative position scales from 0 to 1. The x and y offsets are assumed to be + * in pixel units within the dragger limits. + * + * @param {number} x Pixel position relative to the left of the slider. + * @param {number} y Pixel position relative to the top of the slider. + * @return {number} The relative position of the thumb. + * @private + */ +ol.control.ZoomSlider.prototype.getRelativePosition_ = function(x, y) { + var amount; + if (this.direction_ === ol.control.ZoomSlider.direction.HORIZONTAL) { + amount = x / this.widthLimit_; + } else { + amount = y / this.heightLimit_; + } + return ol.math.clamp(amount, 0, 1); +}; + + +/** + * Calculates the corresponding resolution of the thumb given its relative + * position (where 0 is the minimum and 1 is the maximum). + * + * @param {number} position The relative position of the thumb. + * @return {number} The corresponding resolution. + * @private + */ +ol.control.ZoomSlider.prototype.getResolutionForPosition_ = function(position) { + var fn = this.getMap().getView().getResolutionForValueFunction(); + return fn(1 - position); +}; + + +/** + * Determines the relative position of the slider for the given resolution. A + * relative position of 0 corresponds to the minimum view resolution. A + * relative position of 1 corresponds to the maximum view resolution. + * + * @param {number} res The resolution. + * @return {number} The relative position value (between 0 and 1). + * @private + */ +ol.control.ZoomSlider.prototype.getPositionForResolution_ = function(res) { + var fn = this.getMap().getView().getValueForResolutionFunction(); + return 1 - fn(res); +}; + +goog.provide('ol.control.ZoomToExtent'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.control.Control'); +goog.require('ol.css'); + + +/** + * @classdesc + * A button control which, when pressed, changes the map view to a specific + * extent. To style this control use the css selector `.ol-zoom-extent`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ZoomToExtentOptions=} opt_options Options. + * @api stable + */ +ol.control.ZoomToExtent = function(opt_options) { + var options = opt_options ? opt_options : {}; + + /** + * @type {ol.Extent} + * @private + */ + this.extent_ = options.extent ? options.extent : null; + + var className = options.className !== undefined ? options.className : + 'ol-zoom-extent'; + + var label = options.label !== undefined ? options.label : 'E'; + var tipLabel = options.tipLabel !== undefined ? + options.tipLabel : 'Fit to extent'; + var button = document.createElement('button'); + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild( + typeof label === 'string' ? document.createTextNode(label) : label + ); + + ol.events.listen(button, ol.events.EventType.CLICK, + this.handleClick_, this); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(button); + + ol.control.Control.call(this, { + element: element, + target: options.target + }); +}; +ol.inherits(ol.control.ZoomToExtent, ol.control.Control); + + +/** + * @param {Event} event The event to handle + * @private + */ +ol.control.ZoomToExtent.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleZoomToExtent_(); +}; + + +/** + * @private + */ +ol.control.ZoomToExtent.prototype.handleZoomToExtent_ = function() { + var map = this.getMap(); + var view = map.getView(); + var extent = !this.extent_ ? view.getProjection().getExtent() : this.extent_; + var size = /** @type {ol.Size} */ (map.getSize()); + view.fit(extent, size); +}; + +goog.provide('ol.DeviceOrientation'); + +goog.require('ol.events'); +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.has'); +goog.require('ol.math'); + + +/** + * @classdesc + * The ol.DeviceOrientation class provides access to information from + * DeviceOrientation events. See the [HTML 5 DeviceOrientation Specification]( + * http://www.w3.org/TR/orientation-event/) for more details. + * + * Many new computers, and especially mobile phones + * and tablets, provide hardware support for device orientation. Web + * developers targeting mobile devices will be especially interested in this + * class. + * + * Device orientation data are relative to a common starting point. For mobile + * devices, the starting point is to lay your phone face up on a table with the + * top of the phone pointing north. This represents the zero state. All + * angles are then relative to this state. For computers, it is the same except + * the screen is open at 90 degrees. + * + * Device orientation is reported as three angles - `alpha`, `beta`, and + * `gamma` - relative to the starting position along the three planar axes X, Y + * and Z. The X axis runs from the left edge to the right edge through the + * middle of the device. Similarly, the Y axis runs from the bottom to the top + * of the device through the middle. The Z axis runs from the back to the front + * through the middle. In the starting position, the X axis points to the + * right, the Y axis points away from you and the Z axis points straight up + * from the device lying flat. + * + * The three angles representing the device orientation are relative to the + * three axes. `alpha` indicates how much the device has been rotated around the + * Z axis, which is commonly interpreted as the compass heading (see note + * below). `beta` indicates how much the device has been rotated around the X + * axis, or how much it is tilted from front to back. `gamma` indicates how + * much the device has been rotated around the Y axis, or how much it is tilted + * from left to right. + * + * For most browsers, the `alpha` value returns the compass heading so if the + * device points north, it will be 0. With Safari on iOS, the 0 value of + * `alpha` is calculated from when device orientation was first requested. + * ol.DeviceOrientation provides the `heading` property which normalizes this + * behavior across all browsers for you. + * + * It is important to note that the HTML 5 DeviceOrientation specification + * indicates that `alpha`, `beta` and `gamma` are in degrees while the + * equivalent properties in ol.DeviceOrientation are in radians for consistency + * with all other uses of angles throughout OpenLayers. + * + * To get notified of device orientation changes, register a listener for the + * generic `change` event on your `ol.DeviceOrientation` instance. + * + * @see {@link http://www.w3.org/TR/orientation-event/} + * + * @constructor + * @extends {ol.Object} + * @param {olx.DeviceOrientationOptions=} opt_options Options. + * @api + */ +ol.DeviceOrientation = function(opt_options) { + + ol.Object.call(this); + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.listenerKey_ = null; + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.DeviceOrientation.Property.TRACKING), + this.handleTrackingChanged_, this); + + this.setTracking(options.tracking !== undefined ? options.tracking : false); + +}; +ol.inherits(ol.DeviceOrientation, ol.Object); + + +/** + * @inheritDoc + */ +ol.DeviceOrientation.prototype.disposeInternal = function() { + this.setTracking(false); + ol.Object.prototype.disposeInternal.call(this); +}; + + +/** + * @private + * @param {Event} originalEvent Event. + */ +ol.DeviceOrientation.prototype.orientationChange_ = function(originalEvent) { + var event = /** @type {DeviceOrientationEvent} */ (originalEvent); + if (event.alpha !== null) { + var alpha = ol.math.toRadians(event.alpha); + this.set(ol.DeviceOrientation.Property.ALPHA, alpha); + // event.absolute is undefined in iOS. + if (typeof event.absolute === 'boolean' && event.absolute) { + this.set(ol.DeviceOrientation.Property.HEADING, alpha); + } else if (typeof event.webkitCompassHeading === 'number' && + event.webkitCompassAccuracy != -1) { + var heading = ol.math.toRadians(event.webkitCompassHeading); + this.set(ol.DeviceOrientation.Property.HEADING, heading); + } + } + if (event.beta !== null) { + this.set(ol.DeviceOrientation.Property.BETA, + ol.math.toRadians(event.beta)); + } + if (event.gamma !== null) { + this.set(ol.DeviceOrientation.Property.GAMMA, + ol.math.toRadians(event.gamma)); + } + this.changed(); +}; + + +/** + * Rotation around the device z-axis (in radians). + * @return {number|undefined} The euler angle in radians of the device from the + * standard Z axis. + * @observable + * @api + */ +ol.DeviceOrientation.prototype.getAlpha = function() { + return /** @type {number|undefined} */ ( + this.get(ol.DeviceOrientation.Property.ALPHA)); +}; + + +/** + * Rotation around the device x-axis (in radians). + * @return {number|undefined} The euler angle in radians of the device from the + * planar X axis. + * @observable + * @api + */ +ol.DeviceOrientation.prototype.getBeta = function() { + return /** @type {number|undefined} */ ( + this.get(ol.DeviceOrientation.Property.BETA)); +}; + + +/** + * Rotation around the device y-axis (in radians). + * @return {number|undefined} The euler angle in radians of the device from the + * planar Y axis. + * @observable + * @api + */ +ol.DeviceOrientation.prototype.getGamma = function() { + return /** @type {number|undefined} */ ( + this.get(ol.DeviceOrientation.Property.GAMMA)); +}; + + +/** + * The heading of the device relative to north (in radians). + * @return {number|undefined} The heading of the device relative to north, in + * radians, normalizing for different browser behavior. + * @observable + * @api + */ +ol.DeviceOrientation.prototype.getHeading = function() { + return /** @type {number|undefined} */ ( + this.get(ol.DeviceOrientation.Property.HEADING)); +}; + + +/** + * Determine if orientation is being tracked. + * @return {boolean} Changes in device orientation are being tracked. + * @observable + * @api + */ +ol.DeviceOrientation.prototype.getTracking = function() { + return /** @type {boolean} */ ( + this.get(ol.DeviceOrientation.Property.TRACKING)); +}; + + +/** + * @private + */ +ol.DeviceOrientation.prototype.handleTrackingChanged_ = function() { + if (ol.has.DEVICE_ORIENTATION) { + var tracking = this.getTracking(); + if (tracking && !this.listenerKey_) { + this.listenerKey_ = ol.events.listen(window, 'deviceorientation', + this.orientationChange_, this); + } else if (!tracking && this.listenerKey_ !== null) { + ol.events.unlistenByKey(this.listenerKey_); + this.listenerKey_ = null; + } + } +}; + + +/** + * Enable or disable tracking of device orientation events. + * @param {boolean} tracking The status of tracking changes to alpha, beta and + * gamma. If true, changes are tracked and reported immediately. + * @observable + * @api + */ +ol.DeviceOrientation.prototype.setTracking = function(tracking) { + this.set(ol.DeviceOrientation.Property.TRACKING, tracking); +}; + + +/** + * @enum {string} + */ +ol.DeviceOrientation.Property = { + ALPHA: 'alpha', + BETA: 'beta', + GAMMA: 'gamma', + HEADING: 'heading', + TRACKING: 'tracking' +}; + +goog.provide('ol.Feature'); + +goog.require('ol.asserts'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.geom.Geometry'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * A vector object for geographic features with a geometry and other + * attribute properties, similar to the features in vector file formats like + * GeoJSON. + * + * Features can be styled individually with `setStyle`; otherwise they use the + * style of their vector layer. + * + * Note that attribute properties are set as {@link ol.Object} properties on + * the feature object, so they are observable, and have get/set accessors. + * + * Typically, a feature has a single geometry property. You can set the + * geometry using the `setGeometry` method and get it with `getGeometry`. + * It is possible to store more than one geometry on a feature using attribute + * properties. By default, the geometry used for rendering is identified by + * the property name `geometry`. If you want to use another geometry property + * for rendering, use the `setGeometryName` method to change the attribute + * property associated with the geometry for the feature. For example: + * + * ```js + * var feature = new ol.Feature({ + * geometry: new ol.geom.Polygon(polyCoords), + * labelPoint: new ol.geom.Point(labelCoords), + * name: 'My Polygon' + * }); + * + * // get the polygon geometry + * var poly = feature.getGeometry(); + * + * // Render the feature as a point using the coordinates from labelPoint + * feature.setGeometryName('labelPoint'); + * + * // get the point geometry + * var point = feature.getGeometry(); + * ``` + * + * @constructor + * @extends {ol.Object} + * @param {ol.geom.Geometry|Object.<string, *>=} opt_geometryOrProperties + * You may pass a Geometry object directly, or an object literal + * containing properties. If you pass an object literal, you may + * include a Geometry associated with a `geometry` key. + * @api stable + */ +ol.Feature = function(opt_geometryOrProperties) { + + ol.Object.call(this); + + /** + * @private + * @type {number|string|undefined} + */ + this.id_ = undefined; + + /** + * @type {string} + * @private + */ + this.geometryName_ = 'geometry'; + + /** + * User provided style. + * @private + * @type {ol.style.Style|Array.<ol.style.Style>| + * ol.FeatureStyleFunction} + */ + this.style_ = null; + + /** + * @private + * @type {ol.FeatureStyleFunction|undefined} + */ + this.styleFunction_ = undefined; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.geometryChangeKey_ = null; + + ol.events.listen( + this, ol.Object.getChangeEventType(this.geometryName_), + this.handleGeometryChanged_, this); + + if (opt_geometryOrProperties !== undefined) { + if (opt_geometryOrProperties instanceof ol.geom.Geometry || + !opt_geometryOrProperties) { + var geometry = opt_geometryOrProperties; + this.setGeometry(geometry); + } else { + /** @type {Object.<string, *>} */ + var properties = opt_geometryOrProperties; + this.setProperties(properties); + } + } +}; +ol.inherits(ol.Feature, ol.Object); + + +/** + * Clone this feature. If the original feature has a geometry it + * is also cloned. The feature id is not set in the clone. + * @return {ol.Feature} The clone. + * @api stable + */ +ol.Feature.prototype.clone = function() { + var clone = new ol.Feature(this.getProperties()); + clone.setGeometryName(this.getGeometryName()); + var geometry = this.getGeometry(); + if (geometry) { + clone.setGeometry(geometry.clone()); + } + var style = this.getStyle(); + if (style) { + clone.setStyle(style); + } + return clone; +}; + + +/** + * Get the feature's default geometry. A feature may have any number of named + * geometries. The "default" geometry (the one that is rendered by default) is + * set when calling {@link ol.Feature#setGeometry}. + * @return {ol.geom.Geometry|undefined} The default geometry for the feature. + * @api stable + * @observable + */ +ol.Feature.prototype.getGeometry = function() { + return /** @type {ol.geom.Geometry|undefined} */ ( + this.get(this.geometryName_)); +}; + + +/** + * Get the feature identifier. This is a stable identifier for the feature and + * is either set when reading data from a remote source or set explicitly by + * calling {@link ol.Feature#setId}. + * @return {number|string|undefined} Id. + * @api stable + * @observable + */ +ol.Feature.prototype.getId = function() { + return this.id_; +}; + + +/** + * Get the name of the feature's default geometry. By default, the default + * geometry is named `geometry`. + * @return {string} Get the property name associated with the default geometry + * for this feature. + * @api stable + */ +ol.Feature.prototype.getGeometryName = function() { + return this.geometryName_; +}; + + +/** + * Get the feature's style. Will return what was provided to the + * {@link ol.Feature#setStyle} method. + * @return {ol.style.Style|Array.<ol.style.Style>| + * ol.FeatureStyleFunction} The feature style. + * @api stable + * @observable + */ +ol.Feature.prototype.getStyle = function() { + return this.style_; +}; + + +/** + * Get the feature's style function. + * @return {ol.FeatureStyleFunction|undefined} Return a function + * representing the current style of this feature. + * @api stable + */ +ol.Feature.prototype.getStyleFunction = function() { + return this.styleFunction_; +}; + + +/** + * @private + */ +ol.Feature.prototype.handleGeometryChange_ = function() { + this.changed(); +}; + + +/** + * @private + */ +ol.Feature.prototype.handleGeometryChanged_ = function() { + if (this.geometryChangeKey_) { + ol.events.unlistenByKey(this.geometryChangeKey_); + this.geometryChangeKey_ = null; + } + var geometry = this.getGeometry(); + if (geometry) { + this.geometryChangeKey_ = ol.events.listen(geometry, + ol.events.EventType.CHANGE, this.handleGeometryChange_, this); + } + this.changed(); +}; + + +/** + * Set the default geometry for the feature. This will update the property + * with the name returned by {@link ol.Feature#getGeometryName}. + * @param {ol.geom.Geometry|undefined} geometry The new geometry. + * @api stable + * @observable + */ +ol.Feature.prototype.setGeometry = function(geometry) { + this.set(this.geometryName_, geometry); +}; + + +/** + * Set the style for the feature. This can be a single style object, an array + * of styles, or a function that takes a resolution and returns an array of + * styles. If it is `null` the feature has no style (a `null` style). + * @param {ol.style.Style|Array.<ol.style.Style>| + * ol.FeatureStyleFunction} style Style for this feature. + * @api stable + * @observable + */ +ol.Feature.prototype.setStyle = function(style) { + this.style_ = style; + this.styleFunction_ = !style ? + undefined : ol.Feature.createStyleFunction(style); + this.changed(); +}; + + +/** + * Set the feature id. The feature id is considered stable and may be used when + * requesting features or comparing identifiers returned from a remote source. + * The feature id can be used with the {@link ol.source.Vector#getFeatureById} + * method. + * @param {number|string|undefined} id The feature id. + * @api stable + * @observable + */ +ol.Feature.prototype.setId = function(id) { + this.id_ = id; + this.changed(); +}; + + +/** + * Set the property name to be used when getting the feature's default geometry. + * When calling {@link ol.Feature#getGeometry}, the value of the property with + * this name will be returned. + * @param {string} name The property name of the default geometry. + * @api stable + */ +ol.Feature.prototype.setGeometryName = function(name) { + ol.events.unlisten( + this, ol.Object.getChangeEventType(this.geometryName_), + this.handleGeometryChanged_, this); + this.geometryName_ = name; + ol.events.listen( + this, ol.Object.getChangeEventType(this.geometryName_), + this.handleGeometryChanged_, this); + this.handleGeometryChanged_(); +}; + + +/** + * Convert the provided object into a feature style function. Functions passed + * through unchanged. Arrays of ol.style.Style or single style objects wrapped + * in a new feature style function. + * @param {ol.FeatureStyleFunction|!Array.<ol.style.Style>|!ol.style.Style} obj + * A feature style function, a single style, or an array of styles. + * @return {ol.FeatureStyleFunction} A style function. + */ +ol.Feature.createStyleFunction = function(obj) { + var styleFunction; + + if (typeof obj === 'function') { + styleFunction = obj; + } else { + /** + * @type {Array.<ol.style.Style>} + */ + var styles; + if (Array.isArray(obj)) { + styles = obj; + } else { + ol.asserts.assert(obj instanceof ol.style.Style, + 41); // Expected an `ol.style.Style` or an array of `ol.style.Style` + styles = [obj]; + } + styleFunction = function() { + return styles; + }; + } + return styleFunction; +}; + +goog.provide('ol.format.FormatType'); + + +/** + * @enum {string} + */ +ol.format.FormatType = { + ARRAY_BUFFER: 'arraybuffer', + JSON: 'json', + TEXT: 'text', + XML: 'xml' +}; + +goog.provide('ol.xml'); + +goog.require('ol'); +goog.require('ol.array'); + + +/** + * This document should be used when creating nodes for XML serializations. This + * document is also used by {@link ol.xml.createElementNS} and + * {@link ol.xml.setAttributeNS} + * @const + * @type {Document} + */ +ol.xml.DOCUMENT = document.implementation.createDocument('', '', null); + + +/** + * @param {string} namespaceURI Namespace URI. + * @param {string} qualifiedName Qualified name. + * @return {Node} Node. + */ +ol.xml.createElementNS = function(namespaceURI, qualifiedName) { + return ol.xml.DOCUMENT.createElementNS(namespaceURI, qualifiedName); +}; + + +/** + * Recursively grab all text content of child nodes into a single string. + * @param {Node} node Node. + * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line + * breaks. + * @return {string} All text content. + * @api + */ +ol.xml.getAllTextContent = function(node, normalizeWhitespace) { + return ol.xml.getAllTextContent_(node, normalizeWhitespace, []).join(''); +}; + + +/** + * Recursively grab all text content of child nodes into a single string. + * @param {Node} node Node. + * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line + * breaks. + * @param {Array.<string>} accumulator Accumulator. + * @private + * @return {Array.<string>} Accumulator. + */ +ol.xml.getAllTextContent_ = function(node, normalizeWhitespace, accumulator) { + if (node.nodeType == Node.CDATA_SECTION_NODE || + node.nodeType == Node.TEXT_NODE) { + if (normalizeWhitespace) { + accumulator.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, '')); + } else { + accumulator.push(node.nodeValue); + } + } else { + var n; + for (n = node.firstChild; n; n = n.nextSibling) { + ol.xml.getAllTextContent_(n, normalizeWhitespace, accumulator); + } + } + return accumulator; +}; + + +/** + * @param {?} value Value. + * @return {boolean} Is document. + */ +ol.xml.isDocument = function(value) { + return value instanceof Document; +}; + + +/** + * @param {?} value Value. + * @return {boolean} Is node. + */ +ol.xml.isNode = function(value) { + return value instanceof Node; +}; + + +/** + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @return {string} Value + */ +ol.xml.getAttributeNS = function(node, namespaceURI, name) { + return node.getAttributeNS(namespaceURI, name) || ''; +}; + + +/** + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @param {string|number} value Value. + */ +ol.xml.setAttributeNS = function(node, namespaceURI, name, value) { + node.setAttributeNS(namespaceURI, name, value); +}; + + +/** + * Parse an XML string to an XML Document. + * @param {string} xml XML. + * @return {Document} Document. + * @api + */ +ol.xml.parse = function(xml) { + return new DOMParser().parseFromString(xml, 'application/xml'); +}; + + +/** + * Make an array extender function for extending the array at the top of the + * object stack. + * @param {function(this: T, Node, Array.<*>): (Array.<*>|undefined)} + * valueReader Value reader. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.XmlParser} Parser. + * @template T + */ +ol.xml.makeArrayExtender = function(valueReader, opt_this) { + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(opt_this, node, objectStack); + if (value !== undefined) { + ol.DEBUG && console.assert(Array.isArray(value), + 'valueReader function is expected to return an array of values'); + var array = /** @type {Array.<*>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(array), + 'objectStack is supposed to be an array of arrays'); + ol.array.extend(array, value); + } + }); +}; + + +/** + * Make an array pusher function for pushing to the array at the top of the + * object stack. + * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.XmlParser} Parser. + * @template T + */ +ol.xml.makeArrayPusher = function(valueReader, opt_this) { + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(opt_this !== undefined ? opt_this : this, + node, objectStack); + if (value !== undefined) { + var array = objectStack[objectStack.length - 1]; + ol.DEBUG && console.assert(Array.isArray(array), + 'objectStack is supposed to be an array of arrays'); + array.push(value); + } + }); +}; + + +/** + * Make an object stack replacer function for replacing the object at the + * top of the stack. + * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.XmlParser} Parser. + * @template T + */ +ol.xml.makeReplacer = function(valueReader, opt_this) { + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(opt_this !== undefined ? opt_this : this, + node, objectStack); + if (value !== undefined) { + objectStack[objectStack.length - 1] = value; + } + }); +}; + + +/** + * Make an object property pusher function for adding a property to the + * object at the top of the stack. + * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. + * @param {string=} opt_property Property. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.XmlParser} Parser. + * @template T + */ +ol.xml.makeObjectPropertyPusher = function(valueReader, opt_property, opt_this) { + ol.DEBUG && console.assert(valueReader !== undefined, + 'undefined valueReader, expected function(this: T, Node, Array.<*>)'); + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(opt_this !== undefined ? opt_this : this, + node, objectStack); + if (value !== undefined) { + var object = /** @type {Object} */ + (objectStack[objectStack.length - 1]); + var property = opt_property !== undefined ? + opt_property : node.localName; + var array; + if (property in object) { + array = object[property]; + } else { + array = object[property] = []; + } + array.push(value); + } + }); +}; + + +/** + * Make an object property setter function. + * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. + * @param {string=} opt_property Property. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.XmlParser} Parser. + * @template T + */ +ol.xml.makeObjectPropertySetter = function(valueReader, opt_property, opt_this) { + ol.DEBUG && console.assert(valueReader !== undefined, + 'undefined valueReader, expected function(this: T, Node, Array.<*>)'); + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(opt_this !== undefined ? opt_this : this, + node, objectStack); + if (value !== undefined) { + var object = /** @type {Object} */ + (objectStack[objectStack.length - 1]); + var property = opt_property !== undefined ? + opt_property : node.localName; + object[property] = value; + } + }); +}; + + +/** + * Create a serializer that appends nodes written by its `nodeWriter` to its + * designated parent. The parent is the `node` of the + * {@link ol.XmlNodeStackItem} at the top of the `objectStack`. + * @param {function(this: T, Node, V, Array.<*>)} + * nodeWriter Node writer. + * @param {T=} opt_this The object to use as `this` in `nodeWriter`. + * @return {ol.XmlSerializer} Serializer. + * @template T, V + */ +ol.xml.makeChildAppender = function(nodeWriter, opt_this) { + return function(node, value, objectStack) { + nodeWriter.call(opt_this !== undefined ? opt_this : this, + node, value, objectStack); + var parent = objectStack[objectStack.length - 1]; + var parentNode = parent.node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode) || + ol.xml.isDocument(parentNode), + 'expected parentNode %s to be a Node or a Document', parentNode); + parentNode.appendChild(node); + }; +}; + + +/** + * Create a serializer that calls the provided `nodeWriter` from + * {@link ol.xml.serialize}. This can be used by the parent writer to have the + * 'nodeWriter' called with an array of values when the `nodeWriter` was + * designed to serialize a single item. An example would be a LineString + * geometry writer, which could be reused for writing MultiLineString + * geometries. + * @param {function(this: T, Node, V, Array.<*>)} + * nodeWriter Node writer. + * @param {T=} opt_this The object to use as `this` in `nodeWriter`. + * @return {ol.XmlSerializer} Serializer. + * @template T, V + */ +ol.xml.makeArraySerializer = function(nodeWriter, opt_this) { + var serializersNS, nodeFactory; + return function(node, value, objectStack) { + if (serializersNS === undefined) { + serializersNS = {}; + var serializers = {}; + serializers[node.localName] = nodeWriter; + serializersNS[node.namespaceURI] = serializers; + nodeFactory = ol.xml.makeSimpleNodeFactory(node.localName); + } + ol.xml.serialize(serializersNS, nodeFactory, value, objectStack); + }; +}; + + +/** + * Create a node factory which can use the `opt_keys` passed to + * {@link ol.xml.serialize} or {@link ol.xml.pushSerializeAndPop} as node names, + * or a fixed node name. The namespace of the created nodes can either be fixed, + * or the parent namespace will be used. + * @param {string=} opt_nodeName Fixed node name which will be used for all + * created nodes. If not provided, the 3rd argument to the resulting node + * factory needs to be provided and will be the nodeName. + * @param {string=} opt_namespaceURI Fixed namespace URI which will be used for + * all created nodes. If not provided, the namespace of the parent node will + * be used. + * @return {function(*, Array.<*>, string=): (Node|undefined)} Node factory. + */ +ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) { + var fixedNodeName = opt_nodeName; + return ( + /** + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node} Node. + */ + function(value, objectStack, opt_nodeName) { + var context = objectStack[objectStack.length - 1]; + var node = context.node; + ol.DEBUG && console.assert(ol.xml.isNode(node) || ol.xml.isDocument(node), + 'expected node %s to be a Node or a Document', node); + var nodeName = fixedNodeName; + if (nodeName === undefined) { + nodeName = opt_nodeName; + } + var namespaceURI = opt_namespaceURI; + if (opt_namespaceURI === undefined) { + namespaceURI = node.namespaceURI; + } + ol.DEBUG && console.assert(nodeName !== undefined, 'nodeName was undefined'); + return ol.xml.createElementNS(namespaceURI, /** @type {string} */ (nodeName)); + } + ); +}; + + +/** + * A node factory that creates a node using the parent's `namespaceURI` and the + * `nodeName` passed by {@link ol.xml.serialize} or + * {@link ol.xml.pushSerializeAndPop} to the node factory. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + */ +ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory(); + + +/** + * Create an array of `values` to be used with {@link ol.xml.serialize} or + * {@link ol.xml.pushSerializeAndPop}, where `orderedKeys` has to be provided as + * `opt_key` argument. + * @param {Object.<string, V>} object Key-value pairs for the sequence. Keys can + * be a subset of the `orderedKeys`. + * @param {Array.<string>} orderedKeys Keys in the order of the sequence. + * @return {Array.<V>} Values in the order of the sequence. The resulting array + * has the same length as the `orderedKeys` array. Values that are not + * present in `object` will be `undefined` in the resulting array. + * @template V + */ +ol.xml.makeSequence = function(object, orderedKeys) { + var length = orderedKeys.length; + var sequence = new Array(length); + for (var i = 0; i < length; ++i) { + sequence[i] = object[orderedKeys[i]]; + } + return sequence; +}; + + +/** + * Create a namespaced structure, using the same values for each namespace. + * This can be used as a starting point for versioned parsers, when only a few + * values are version specific. + * @param {Array.<string>} namespaceURIs Namespace URIs. + * @param {T} structure Structure. + * @param {Object.<string, T>=} opt_structureNS Namespaced structure to add to. + * @return {Object.<string, T>} Namespaced structure. + * @template T + */ +ol.xml.makeStructureNS = function(namespaceURIs, structure, opt_structureNS) { + /** + * @type {Object.<string, *>} + */ + var structureNS = opt_structureNS !== undefined ? opt_structureNS : {}; + var i, ii; + for (i = 0, ii = namespaceURIs.length; i < ii; ++i) { + structureNS[namespaceURIs[i]] = structure; + } + return structureNS; +}; + + +/** + * Parse a node using the parsers and object stack. + * @param {Object.<string, Object.<string, ol.XmlParser>>} parsersNS + * Parsers by namespace. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @param {*=} opt_this The object to use as `this`. + */ +ol.xml.parseNode = function(parsersNS, node, objectStack, opt_this) { + var n; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var parsers = parsersNS[n.namespaceURI]; + if (parsers !== undefined) { + var parser = parsers[n.localName]; + if (parser !== undefined) { + parser.call(opt_this, n, objectStack); + } + } + } +}; + + +/** + * Push an object on top of the stack, parse and return the popped object. + * @param {T} object Object. + * @param {Object.<string, Object.<string, ol.XmlParser>>} parsersNS + * Parsers by namespace. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @param {*=} opt_this The object to use as `this`. + * @return {T} Object. + * @template T + */ +ol.xml.pushParseAndPop = function( + object, parsersNS, node, objectStack, opt_this) { + objectStack.push(object); + ol.xml.parseNode(parsersNS, node, objectStack, opt_this); + return objectStack.pop(); +}; + + +/** + * Walk through an array of `values` and call a serializer for each value. + * @param {Object.<string, Object.<string, ol.XmlSerializer>>} serializersNS + * Namespaced serializers. + * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory + * Node factory. The `nodeFactory` creates the node whose namespace and name + * will be used to choose a node writer from `serializersNS`. This + * separation allows us to decide what kind of node to create, depending on + * the value we want to serialize. An example for this would be different + * geometry writers based on the geometry type. + * @param {Array.<*>} values Values to serialize. An example would be an array + * of {@link ol.Feature} instances. + * @param {Array.<*>} objectStack Node stack. + * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the + * `nodeFactory`. This is used for serializing object literals where the + * node name relates to the property key. The array length of `opt_keys` has + * to match the length of `values`. For serializing a sequence, `opt_keys` + * determines the order of the sequence. + * @param {T=} opt_this The object to use as `this` for the node factory and + * serializers. + * @template T + */ +ol.xml.serialize = function( + serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) { + var length = (opt_keys !== undefined ? opt_keys : values).length; + var value, node; + for (var i = 0; i < length; ++i) { + value = values[i]; + if (value !== undefined) { + node = nodeFactory.call(opt_this, value, objectStack, + opt_keys !== undefined ? opt_keys[i] : undefined); + if (node !== undefined) { + serializersNS[node.namespaceURI][node.localName] + .call(opt_this, node, value, objectStack); + } + } + } +}; + + +/** + * @param {O} object Object. + * @param {Object.<string, Object.<string, ol.XmlSerializer>>} serializersNS + * Namespaced serializers. + * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory + * Node factory. The `nodeFactory` creates the node whose namespace and name + * will be used to choose a node writer from `serializersNS`. This + * separation allows us to decide what kind of node to create, depending on + * the value we want to serialize. An example for this would be different + * geometry writers based on the geometry type. + * @param {Array.<*>} values Values to serialize. An example would be an array + * of {@link ol.Feature} instances. + * @param {Array.<*>} objectStack Node stack. + * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the + * `nodeFactory`. This is used for serializing object literals where the + * node name relates to the property key. The array length of `opt_keys` has + * to match the length of `values`. For serializing a sequence, `opt_keys` + * determines the order of the sequence. + * @param {T=} opt_this The object to use as `this` for the node factory and + * serializers. + * @return {O|undefined} Object. + * @template O, T + */ +ol.xml.pushSerializeAndPop = function(object, + serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) { + objectStack.push(object); + ol.xml.serialize( + serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this); + return objectStack.pop(); +}; + +goog.provide('ol.featureloader'); + +goog.require('ol'); +goog.require('ol.format.FormatType'); +goog.require('ol.xml'); + + +/** + * @param {string|ol.FeatureUrlFunction} url Feature URL service. + * @param {ol.format.Feature} format Feature format. + * @param {function(this:ol.VectorTile, Array.<ol.Feature>, ol.proj.Projection)|function(this:ol.source.Vector, Array.<ol.Feature>)} success + * Function called with the loaded features and optionally with the data + * projection. Called with the vector tile or source as `this`. + * @param {function(this:ol.VectorTile)|function(this:ol.source.Vector)} failure + * Function called when loading failed. Called with the vector tile or + * source as `this`. + * @return {ol.FeatureLoader} The feature loader. + */ +ol.featureloader.loadFeaturesXhr = function(url, format, success, failure) { + return ( + /** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {ol.proj.Projection} projection Projection. + * @this {ol.source.Vector|ol.VectorTile} + */ + function(extent, resolution, projection) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', + typeof url === 'function' ? url(extent, resolution, projection) : url, + true); + if (format.getType() == ol.format.FormatType.ARRAY_BUFFER) { + xhr.responseType = 'arraybuffer'; + } + /** + * @param {Event} event Event. + * @private + */ + xhr.onload = function(event) { + // status will be 0 for file:// urls + if (!xhr.status || xhr.status >= 200 && xhr.status < 300) { + var type = format.getType(); + /** @type {Document|Node|Object|string|undefined} */ + var source; + if (type == ol.format.FormatType.JSON || + type == ol.format.FormatType.TEXT) { + source = xhr.responseText; + } else if (type == ol.format.FormatType.XML) { + source = xhr.responseXML; + if (!source) { + source = ol.xml.parse(xhr.responseText); + } + } else if (type == ol.format.FormatType.ARRAY_BUFFER) { + source = /** @type {ArrayBuffer} */ (xhr.response); + } + if (source) { + success.call(this, format.readFeatures(source, + {featureProjection: projection}), + format.readProjection(source)); + } else { + failure.call(this); + } + } else { + failure.call(this); + } + }.bind(this); + xhr.send(); + }); +}; + + +/** + * Create an XHR feature loader for a `url` and `format`. The feature loader + * loads features (with XHR), parses the features, and adds them to the + * vector source. + * @param {string|ol.FeatureUrlFunction} url Feature URL service. + * @param {ol.format.Feature} format Feature format. + * @return {ol.FeatureLoader} The feature loader. + * @api + */ +ol.featureloader.xhr = function(url, format) { + return ol.featureloader.loadFeaturesXhr(url, format, + /** + * @param {Array.<ol.Feature>} features The loaded features. + * @param {ol.proj.Projection} dataProjection Data projection. + * @this {ol.source.Vector} + */ + function(features, dataProjection) { + this.addFeatures(features); + }, /* FIXME handle error */ ol.nullFunction); +}; + +goog.provide('ol.format.Feature'); + +goog.require('ol.geom.Geometry'); +goog.require('ol.obj'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for feature formats. + * {ol.format.Feature} subclasses provide the ability to decode and encode + * {@link ol.Feature} objects from a variety of commonly used geospatial + * file formats. See the documentation for each format for more details. + * + * @constructor + * @api stable + */ +ol.format.Feature = function() { + + /** + * @protected + * @type {ol.proj.Projection} + */ + this.defaultDataProjection = null; + + /** + * @protected + * @type {ol.proj.Projection} + */ + this.defaultFeatureProjection = null; + +}; + + +/** + * @abstract + * @return {Array.<string>} Extensions. + */ +ol.format.Feature.prototype.getExtensions = function() {}; + + +/** + * Adds the data projection to the read options. + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {olx.format.ReadOptions|undefined} Options. + * @protected + */ +ol.format.Feature.prototype.getReadOptions = function(source, opt_options) { + var options; + if (opt_options) { + options = { + dataProjection: opt_options.dataProjection ? + opt_options.dataProjection : this.readProjection(source), + featureProjection: opt_options.featureProjection + }; + } + return this.adaptOptions(options); +}; + + +/** + * Sets the `defaultDataProjection` on the options, if no `dataProjection` + * is set. + * @param {olx.format.WriteOptions|olx.format.ReadOptions|undefined} options + * Options. + * @protected + * @return {olx.format.WriteOptions|olx.format.ReadOptions|undefined} + * Updated options. + */ +ol.format.Feature.prototype.adaptOptions = function(options) { + return ol.obj.assign({ + dataProjection: this.defaultDataProjection, + featureProjection: this.defaultFeatureProjection + }, options); +}; + + +/** + * @abstract + * @return {ol.format.FormatType} Format. + */ +ol.format.Feature.prototype.getType = function() {}; + + +/** + * Read a single feature from a source. + * + * @abstract + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + */ +ol.format.Feature.prototype.readFeature = function(source, opt_options) {}; + + +/** + * Read all features from a source. + * + * @abstract + * @param {Document|Node|ArrayBuffer|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + */ +ol.format.Feature.prototype.readFeatures = function(source, opt_options) {}; + + +/** + * Read a single geometry from a source. + * + * @abstract + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.Feature.prototype.readGeometry = function(source, opt_options) {}; + + +/** + * Read the projection from a source. + * + * @abstract + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + */ +ol.format.Feature.prototype.readProjection = function(source) {}; + + +/** + * Encode a feature in this format. + * + * @abstract + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Result. + */ +ol.format.Feature.prototype.writeFeature = function(feature, opt_options) {}; + + +/** + * Encode an array of features in this format. + * + * @abstract + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Result. + */ +ol.format.Feature.prototype.writeFeatures = function(features, opt_options) {}; + + +/** + * Write a single geometry in this format. + * + * @abstract + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Result. + */ +ol.format.Feature.prototype.writeGeometry = function(geometry, opt_options) {}; + + +/** + * @param {ol.geom.Geometry|ol.Extent} geometry Geometry. + * @param {boolean} write Set to true for writing, false for reading. + * @param {(olx.format.WriteOptions|olx.format.ReadOptions)=} opt_options + * Options. + * @return {ol.geom.Geometry|ol.Extent} Transformed geometry. + * @protected + */ +ol.format.Feature.transformWithOptions = function( + geometry, write, opt_options) { + var featureProjection = opt_options ? + ol.proj.get(opt_options.featureProjection) : null; + var dataProjection = opt_options ? + ol.proj.get(opt_options.dataProjection) : null; + /** + * @type {ol.geom.Geometry|ol.Extent} + */ + var transformed; + if (featureProjection && dataProjection && + !ol.proj.equivalent(featureProjection, dataProjection)) { + if (geometry instanceof ol.geom.Geometry) { + transformed = (write ? geometry.clone() : geometry).transform( + write ? featureProjection : dataProjection, + write ? dataProjection : featureProjection); + } else { + // FIXME this is necessary because ol.format.GML treats extents + // as geometries + transformed = ol.proj.transformExtent( + write ? geometry.slice() : geometry, + write ? featureProjection : dataProjection, + write ? dataProjection : featureProjection); + } + } else { + transformed = geometry; + } + if (write && opt_options && opt_options.decimals) { + var power = Math.pow(10, opt_options.decimals); + // if decimals option on write, round each coordinate appropriately + /** + * @param {Array.<number>} coordinates Coordinates. + * @return {Array.<number>} Transformed coordinates. + */ + var transform = function(coordinates) { + for (var i = 0, ii = coordinates.length; i < ii; ++i) { + coordinates[i] = Math.round(coordinates[i] * power) / power; + } + return coordinates; + }; + if (Array.isArray(transformed)) { + transform(transformed); + } else { + transformed.applyTransform(transform); + } + } + return transformed; +}; + +goog.provide('ol.format.JSONFeature'); + +goog.require('ol'); +goog.require('ol.format.Feature'); +goog.require('ol.format.FormatType'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for JSON feature formats. + * + * @constructor + * @extends {ol.format.Feature} + */ +ol.format.JSONFeature = function() { + ol.format.Feature.call(this); +}; +ol.inherits(ol.format.JSONFeature, ol.format.Feature); + + +/** + * @param {Document|Node|Object|string} source Source. + * @private + * @return {Object} Object. + */ +ol.format.JSONFeature.prototype.getObject_ = function(source) { + if (typeof source === 'string') { + var object = JSON.parse(source); + return object ? /** @type {Object} */ (object) : null; + } else if (source !== null) { + return source; + } else { + return null; + } +}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.getType = function() { + return ol.format.FormatType.JSON; +}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.readFeature = function(source, opt_options) { + return this.readFeatureFromObject( + this.getObject_(source), this.getReadOptions(source, opt_options)); +}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.readFeatures = function(source, opt_options) { + return this.readFeaturesFromObject( + this.getObject_(source), this.getReadOptions(source, opt_options)); +}; + + +/** + * @abstract + * @param {Object} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {ol.Feature} Feature. + */ +ol.format.JSONFeature.prototype.readFeatureFromObject = function(object, opt_options) {}; + + +/** + * @abstract + * @param {Object} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {Array.<ol.Feature>} Features. + */ +ol.format.JSONFeature.prototype.readFeaturesFromObject = function(object, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.readGeometry = function(source, opt_options) { + return this.readGeometryFromObject( + this.getObject_(source), this.getReadOptions(source, opt_options)); +}; + + +/** + * @abstract + * @param {Object} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.JSONFeature.prototype.readGeometryFromObject = function(object, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.readProjection = function(source) { + return this.readProjectionFromObject(this.getObject_(source)); +}; + + +/** + * @abstract + * @param {Object} object Object. + * @protected + * @return {ol.proj.Projection} Projection. + */ +ol.format.JSONFeature.prototype.readProjectionFromObject = function(object) {}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.writeFeature = function(feature, opt_options) { + return JSON.stringify(this.writeFeatureObject(feature, opt_options)); +}; + + +/** + * @abstract + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. + */ +ol.format.JSONFeature.prototype.writeFeatureObject = function(feature, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.writeFeatures = function(features, opt_options) { + return JSON.stringify(this.writeFeaturesObject(features, opt_options)); +}; + + +/** + * @abstract + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. + */ +ol.format.JSONFeature.prototype.writeFeaturesObject = function(features, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.writeGeometry = function(geometry, opt_options) { + return JSON.stringify(this.writeGeometryObject(geometry, opt_options)); +}; + + +/** + * @abstract + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. + */ +ol.format.JSONFeature.prototype.writeGeometryObject = function(geometry, opt_options) {}; + +goog.provide('ol.geom.flat.interpolate'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.math'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} fraction Fraction. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Destination. + */ +ol.geom.flat.interpolate.lineString = function(flatCoordinates, offset, end, stride, fraction, opt_dest) { + // FIXME does not work when vertices are repeated + // FIXME interpolate extra dimensions + ol.DEBUG && console.assert(0 <= fraction && fraction <= 1, + 'fraction should be in between 0 and 1'); + var pointX = NaN; + var pointY = NaN; + var n = (end - offset) / stride; + if (n === 0) { + ol.DEBUG && console.assert(false, 'n cannot be 0'); + } else if (n == 1) { + pointX = flatCoordinates[offset]; + pointY = flatCoordinates[offset + 1]; + } else if (n == 2) { + pointX = (1 - fraction) * flatCoordinates[offset] + + fraction * flatCoordinates[offset + stride]; + pointY = (1 - fraction) * flatCoordinates[offset + 1] + + fraction * flatCoordinates[offset + stride + 1]; + } else { + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + var length = 0; + var cumulativeLengths = [0]; + var i; + for (i = offset + stride; i < end; i += stride) { + var x2 = flatCoordinates[i]; + var y2 = flatCoordinates[i + 1]; + length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + cumulativeLengths.push(length); + x1 = x2; + y1 = y2; + } + var target = fraction * length; + var index = ol.array.binarySearch(cumulativeLengths, target); + if (index < 0) { + var t = (target - cumulativeLengths[-index - 2]) / + (cumulativeLengths[-index - 1] - cumulativeLengths[-index - 2]); + var o = offset + (-index - 2) * stride; + pointX = ol.math.lerp( + flatCoordinates[o], flatCoordinates[o + stride], t); + pointY = ol.math.lerp( + flatCoordinates[o + 1], flatCoordinates[o + stride + 1], t); + } else { + pointX = flatCoordinates[offset + index * stride]; + pointY = flatCoordinates[offset + index * stride + 1]; + } + } + if (opt_dest) { + opt_dest[0] = pointX; + opt_dest[1] = pointY; + return opt_dest; + } else { + return [pointX, pointY]; + } +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} m M. + * @param {boolean} extrapolate Extrapolate. + * @return {ol.Coordinate} Coordinate. + */ +ol.geom.flat.interpolate.lineStringCoordinateAtM = function(flatCoordinates, offset, end, stride, m, extrapolate) { + if (end == offset) { + return null; + } + var coordinate; + if (m < flatCoordinates[offset + stride - 1]) { + if (extrapolate) { + coordinate = flatCoordinates.slice(offset, offset + stride); + coordinate[stride - 1] = m; + return coordinate; + } else { + return null; + } + } else if (flatCoordinates[end - 1] < m) { + if (extrapolate) { + coordinate = flatCoordinates.slice(end - stride, end); + coordinate[stride - 1] = m; + return coordinate; + } else { + return null; + } + } + // FIXME use O(1) search + if (m == flatCoordinates[offset + stride - 1]) { + return flatCoordinates.slice(offset, offset + stride); + } + var lo = offset / stride; + var hi = end / stride; + while (lo < hi) { + var mid = (lo + hi) >> 1; + if (m < flatCoordinates[(mid + 1) * stride - 1]) { + hi = mid; + } else { + lo = mid + 1; + } + } + var m0 = flatCoordinates[lo * stride - 1]; + if (m == m0) { + return flatCoordinates.slice((lo - 1) * stride, (lo - 1) * stride + stride); + } + var m1 = flatCoordinates[(lo + 1) * stride - 1]; + ol.DEBUG && console.assert(m0 < m, 'm0 should be less than m'); + ol.DEBUG && console.assert(m <= m1, 'm should be less than or equal to m1'); + var t = (m - m0) / (m1 - m0); + coordinate = []; + var i; + for (i = 0; i < stride - 1; ++i) { + coordinate.push(ol.math.lerp(flatCoordinates[(lo - 1) * stride + i], + flatCoordinates[lo * stride + i], t)); + } + coordinate.push(m); + ol.DEBUG && console.assert(coordinate.length == stride, + 'length of coordinate array should match stride'); + return coordinate; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} m M. + * @param {boolean} extrapolate Extrapolate. + * @param {boolean} interpolate Interpolate. + * @return {ol.Coordinate} Coordinate. + */ +ol.geom.flat.interpolate.lineStringsCoordinateAtM = function( + flatCoordinates, offset, ends, stride, m, extrapolate, interpolate) { + if (interpolate) { + return ol.geom.flat.interpolate.lineStringCoordinateAtM( + flatCoordinates, offset, ends[ends.length - 1], stride, m, extrapolate); + } + var coordinate; + if (m < flatCoordinates[stride - 1]) { + if (extrapolate) { + coordinate = flatCoordinates.slice(0, stride); + coordinate[stride - 1] = m; + return coordinate; + } else { + return null; + } + } + if (flatCoordinates[flatCoordinates.length - 1] < m) { + if (extrapolate) { + coordinate = flatCoordinates.slice(flatCoordinates.length - stride); + coordinate[stride - 1] = m; + return coordinate; + } else { + return null; + } + } + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + if (offset == end) { + continue; + } + if (m < flatCoordinates[offset + stride - 1]) { + return null; + } else if (m <= flatCoordinates[end - 1]) { + return ol.geom.flat.interpolate.lineStringCoordinateAtM( + flatCoordinates, offset, end, stride, m, false); + } + offset = end; + } + ol.DEBUG && console.assert(false, + 'ol.geom.flat.interpolate.lineStringsCoordinateAtM should have returned'); + return null; +}; + +goog.provide('ol.geom.flat.length'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} Length. + */ +ol.geom.flat.length.lineString = function(flatCoordinates, offset, end, stride) { + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + var length = 0; + var i; + for (i = offset + stride; i < end; i += stride) { + var x2 = flatCoordinates[i]; + var y2 = flatCoordinates[i + 1]; + length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + x1 = x2; + y1 = y2; + } + return length; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} Perimeter. + */ +ol.geom.flat.length.linearRing = function(flatCoordinates, offset, end, stride) { + var perimeter = + ol.geom.flat.length.lineString(flatCoordinates, offset, end, stride); + var dx = flatCoordinates[end - stride] - flatCoordinates[offset]; + var dy = flatCoordinates[end - stride + 1] - flatCoordinates[offset + 1]; + perimeter += Math.sqrt(dx * dx + dy * dy); + return perimeter; +}; + +goog.provide('ol.geom.LineString'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.interpolate'); +goog.require('ol.geom.flat.intersectsextent'); +goog.require('ol.geom.flat.length'); +goog.require('ol.geom.flat.segments'); +goog.require('ol.geom.flat.simplify'); + + +/** + * @classdesc + * Linestring geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.LineString = function(coordinates, opt_layout) { + + ol.geom.SimpleGeometry.call(this); + + /** + * @private + * @type {ol.Coordinate} + */ + this.flatMidpoint_ = null; + + /** + * @private + * @type {number} + */ + this.flatMidpointRevision_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDelta_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDeltaRevision_ = -1; + + this.setCoordinates(coordinates, opt_layout); + +}; +ol.inherits(ol.geom.LineString, ol.geom.SimpleGeometry); + + +/** + * Append the passed coordinate to the coordinates of the linestring. + * @param {ol.Coordinate} coordinate Coordinate. + * @api stable + */ +ol.geom.LineString.prototype.appendCoordinate = function(coordinate) { + ol.DEBUG && console.assert(coordinate.length == this.stride, + 'length of coordinate array should match stride'); + if (!this.flatCoordinates) { + this.flatCoordinates = coordinate.slice(); + } else { + ol.array.extend(this.flatCoordinates, coordinate); + } + this.changed(); +}; + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.LineString} Clone. + * @api stable + */ +ol.geom.LineString.prototype.clone = function() { + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return lineString; +}; + + +/** + * @inheritDoc + */ +ol.geom.LineString.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getClosestPoint( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + this.maxDelta_, false, x, y, closestPoint, minSquaredDistance); +}; + + +/** + * Iterate over each segment, calling the provided callback. + * If the callback returns a truthy value the function returns that + * value immediately. Otherwise the function returns `false`. + * + * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function + * called for each segment. + * @param {S=} opt_this The object to be used as the value of 'this' + * within callback. + * @return {T|boolean} Value. + * @template T,S + * @api + */ +ol.geom.LineString.prototype.forEachSegment = function(callback, opt_this) { + return ol.geom.flat.segments.forEach(this.flatCoordinates, 0, + this.flatCoordinates.length, this.stride, callback, opt_this); +}; + + +/** + * Returns the coordinate at `m` using linear interpolation, or `null` if no + * such coordinate exists. + * + * `opt_extrapolate` controls extrapolation beyond the range of Ms in the + * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first + * M will return the first coordinate and Ms greater than the last M will + * return the last coordinate. + * + * @param {number} m M. + * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`. + * @return {ol.Coordinate} Coordinate. + * @api stable + */ +ol.geom.LineString.prototype.getCoordinateAtM = function(m, opt_extrapolate) { + if (this.layout != ol.geom.GeometryLayout.XYM && + this.layout != ol.geom.GeometryLayout.XYZM) { + return null; + } + var extrapolate = opt_extrapolate !== undefined ? opt_extrapolate : false; + return ol.geom.flat.interpolate.lineStringCoordinateAtM(this.flatCoordinates, 0, + this.flatCoordinates.length, this.stride, m, extrapolate); +}; + + +/** + * Return the coordinates of the linestring. + * @return {Array.<ol.Coordinate>} Coordinates. + * @api stable + */ +ol.geom.LineString.prototype.getCoordinates = function() { + return ol.geom.flat.inflate.coordinates( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; + + +/** + * Return the coordinate at the provided fraction along the linestring. + * The `fraction` is a number between 0 and 1, where 0 is the start of the + * linestring and 1 is the end. + * @param {number} fraction Fraction. + * @param {ol.Coordinate=} opt_dest Optional coordinate whose values will + * be modified. If not provided, a new coordinate will be returned. + * @return {ol.Coordinate} Coordinate of the interpolated point. + * @api + */ +ol.geom.LineString.prototype.getCoordinateAt = function(fraction, opt_dest) { + return ol.geom.flat.interpolate.lineString( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + fraction, opt_dest); +}; + + +/** + * Return the length of the linestring on projected plane. + * @return {number} Length (on projected plane). + * @api stable + */ +ol.geom.LineString.prototype.getLength = function() { + return ol.geom.flat.length.lineString( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; + + +/** + * @return {Array.<number>} Flat midpoint. + */ +ol.geom.LineString.prototype.getFlatMidpoint = function() { + if (this.flatMidpointRevision_ != this.getRevision()) { + this.flatMidpoint_ = this.getCoordinateAt(0.5, this.flatMidpoint_); + this.flatMidpointRevision_ = this.getRevision(); + } + return this.flatMidpoint_; +}; + + +/** + * @inheritDoc + */ +ol.geom.LineString.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + squaredTolerance, simplifiedFlatCoordinates, 0); + var simplifiedLineString = new ol.geom.LineString(null); + simplifiedLineString.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates); + return simplifiedLineString; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.LineString.prototype.getType = function() { + return ol.geom.GeometryType.LINE_STRING; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.LineString.prototype.intersectsExtent = function(extent) { + return ol.geom.flat.intersectsextent.lineString( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + extent); +}; + + +/** + * Set the coordinates of the linestring. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.LineString.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, coordinates, 1); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + this.flatCoordinates.length = ol.geom.flat.deflate.coordinates( + this.flatCoordinates, 0, coordinates, this.stride); + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + */ +ol.geom.LineString.prototype.setFlatCoordinates = function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; + +goog.provide('ol.geom.MultiLineString'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.interpolate'); +goog.require('ol.geom.flat.intersectsextent'); +goog.require('ol.geom.flat.simplify'); + + +/** + * @classdesc + * Multi-linestring geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.MultiLineString = function(coordinates, opt_layout) { + + ol.geom.SimpleGeometry.call(this); + + /** + * @type {Array.<number>} + * @private + */ + this.ends_ = []; + + /** + * @private + * @type {number} + */ + this.maxDelta_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDeltaRevision_ = -1; + + this.setCoordinates(coordinates, opt_layout); + +}; +ol.inherits(ol.geom.MultiLineString, ol.geom.SimpleGeometry); + + +/** + * Append the passed linestring to the multilinestring. + * @param {ol.geom.LineString} lineString LineString. + * @api stable + */ +ol.geom.MultiLineString.prototype.appendLineString = function(lineString) { + ol.DEBUG && console.assert(lineString.getLayout() == this.layout, + 'layout of lineString should match the layout'); + if (!this.flatCoordinates) { + this.flatCoordinates = lineString.getFlatCoordinates().slice(); + } else { + ol.array.extend( + this.flatCoordinates, lineString.getFlatCoordinates().slice()); + } + this.ends_.push(this.flatCoordinates.length); + this.changed(); +}; + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.MultiLineString} Clone. + * @api stable + */ +ol.geom.MultiLineString.prototype.clone = function() { + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setFlatCoordinates( + this.layout, this.flatCoordinates.slice(), this.ends_.slice()); + return multiLineString; +}; + + +/** + * @inheritDoc + */ +ol.geom.MultiLineString.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta( + this.flatCoordinates, 0, this.ends_, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getsClosestPoint( + this.flatCoordinates, 0, this.ends_, this.stride, + this.maxDelta_, false, x, y, closestPoint, minSquaredDistance); +}; + + +/** + * Returns the coordinate at `m` using linear interpolation, or `null` if no + * such coordinate exists. + * + * `opt_extrapolate` controls extrapolation beyond the range of Ms in the + * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first + * M will return the first coordinate and Ms greater than the last M will + * return the last coordinate. + * + * `opt_interpolate` controls interpolation between consecutive LineStrings + * within the MultiLineString. If `opt_interpolate` is `true` the coordinates + * will be linearly interpolated between the last coordinate of one LineString + * and the first coordinate of the next LineString. If `opt_interpolate` is + * `false` then the function will return `null` for Ms falling between + * LineStrings. + * + * @param {number} m M. + * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`. + * @param {boolean=} opt_interpolate Interpolate. Default is `false`. + * @return {ol.Coordinate} Coordinate. + * @api stable + */ +ol.geom.MultiLineString.prototype.getCoordinateAtM = function(m, opt_extrapolate, opt_interpolate) { + if ((this.layout != ol.geom.GeometryLayout.XYM && + this.layout != ol.geom.GeometryLayout.XYZM) || + this.flatCoordinates.length === 0) { + return null; + } + var extrapolate = opt_extrapolate !== undefined ? opt_extrapolate : false; + var interpolate = opt_interpolate !== undefined ? opt_interpolate : false; + return ol.geom.flat.interpolate.lineStringsCoordinateAtM(this.flatCoordinates, 0, + this.ends_, this.stride, m, extrapolate, interpolate); +}; + + +/** + * Return the coordinates of the multilinestring. + * @return {Array.<Array.<ol.Coordinate>>} Coordinates. + * @api stable + */ +ol.geom.MultiLineString.prototype.getCoordinates = function() { + return ol.geom.flat.inflate.coordinatess( + this.flatCoordinates, 0, this.ends_, this.stride); +}; + + +/** + * @return {Array.<number>} Ends. + */ +ol.geom.MultiLineString.prototype.getEnds = function() { + return this.ends_; +}; + + +/** + * Return the linestring at the specified index. + * @param {number} index Index. + * @return {ol.geom.LineString} LineString. + * @api stable + */ +ol.geom.MultiLineString.prototype.getLineString = function(index) { + ol.DEBUG && console.assert(0 <= index && index < this.ends_.length, + 'index should be in between 0 and length of the this.ends_ array'); + if (index < 0 || this.ends_.length <= index) { + return null; + } + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice( + index === 0 ? 0 : this.ends_[index - 1], this.ends_[index])); + return lineString; +}; + + +/** + * Return the linestrings of this multilinestring. + * @return {Array.<ol.geom.LineString>} LineStrings. + * @api stable + */ +ol.geom.MultiLineString.prototype.getLineStrings = function() { + var flatCoordinates = this.flatCoordinates; + var ends = this.ends_; + var layout = this.layout; + /** @type {Array.<ol.geom.LineString>} */ + var lineStrings = []; + var offset = 0; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(layout, flatCoordinates.slice(offset, end)); + lineStrings.push(lineString); + offset = end; + } + return lineStrings; +}; + + +/** + * @return {Array.<number>} Flat midpoints. + */ +ol.geom.MultiLineString.prototype.getFlatMidpoints = function() { + var midpoints = []; + var flatCoordinates = this.flatCoordinates; + var offset = 0; + var ends = this.ends_; + var stride = this.stride; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var midpoint = ol.geom.flat.interpolate.lineString( + flatCoordinates, offset, end, stride, 0.5); + ol.array.extend(midpoints, midpoint); + offset = end; + } + return midpoints; +}; + + +/** + * @inheritDoc + */ +ol.geom.MultiLineString.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + var simplifiedEnds = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeuckers( + this.flatCoordinates, 0, this.ends_, this.stride, squaredTolerance, + simplifiedFlatCoordinates, 0, simplifiedEnds); + var simplifiedMultiLineString = new ol.geom.MultiLineString(null); + simplifiedMultiLineString.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds); + return simplifiedMultiLineString; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.MultiLineString.prototype.getType = function() { + return ol.geom.GeometryType.MULTI_LINE_STRING; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.MultiLineString.prototype.intersectsExtent = function(extent) { + return ol.geom.flat.intersectsextent.lineStrings( + this.flatCoordinates, 0, this.ends_, this.stride, extent); +}; + + +/** + * Set the coordinates of the multilinestring. + * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.MultiLineString.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_); + } else { + this.setLayout(opt_layout, coordinates, 2); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + var ends = ol.geom.flat.deflate.coordinatess( + this.flatCoordinates, 0, coordinates, this.stride, this.ends_); + this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1]; + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<number>} ends Ends. + */ +ol.geom.MultiLineString.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) { + if (!flatCoordinates) { + ol.DEBUG && console.assert(ends && ends.length === 0, + 'ends must be truthy and ends.length should be 0'); + } else if (ends.length === 0) { + ol.DEBUG && console.assert(flatCoordinates.length === 0, + 'flatCoordinates should be an empty array'); + } else { + ol.DEBUG && console.assert(flatCoordinates.length == ends[ends.length - 1], + 'length of flatCoordinates array should match the last value of ends'); + } + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.ends_ = ends; + this.changed(); +}; + + +/** + * @param {Array.<ol.geom.LineString>} lineStrings LineStrings. + */ +ol.geom.MultiLineString.prototype.setLineStrings = function(lineStrings) { + var layout = this.getLayout(); + var flatCoordinates = []; + var ends = []; + var i, ii; + for (i = 0, ii = lineStrings.length; i < ii; ++i) { + var lineString = lineStrings[i]; + if (i === 0) { + layout = lineString.getLayout(); + } else { + // FIXME better handle the case of non-matching layouts + ol.DEBUG && console.assert(lineString.getLayout() == layout, + 'layout of lineString should match layout'); + } + ol.array.extend(flatCoordinates, lineString.getFlatCoordinates()); + ends.push(flatCoordinates.length); + } + this.setFlatCoordinates(layout, flatCoordinates, ends); +}; + +goog.provide('ol.geom.MultiPoint'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.math'); + + +/** + * @classdesc + * Multi-point geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.MultiPoint = function(coordinates, opt_layout) { + ol.geom.SimpleGeometry.call(this); + this.setCoordinates(coordinates, opt_layout); +}; +ol.inherits(ol.geom.MultiPoint, ol.geom.SimpleGeometry); + + +/** + * Append the passed point to this multipoint. + * @param {ol.geom.Point} point Point. + * @api stable + */ +ol.geom.MultiPoint.prototype.appendPoint = function(point) { + ol.DEBUG && console.assert(point.getLayout() == this.layout, + 'the layout of point should match layout'); + if (!this.flatCoordinates) { + this.flatCoordinates = point.getFlatCoordinates().slice(); + } else { + ol.array.extend(this.flatCoordinates, point.getFlatCoordinates()); + } + this.changed(); +}; + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.MultiPoint} Clone. + * @api stable + */ +ol.geom.MultiPoint.prototype.clone = function() { + var multiPoint = new ol.geom.MultiPoint(null); + multiPoint.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return multiPoint; +}; + + +/** + * @inheritDoc + */ +ol.geom.MultiPoint.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + var flatCoordinates = this.flatCoordinates; + var stride = this.stride; + var i, ii, j; + for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { + var squaredDistance = ol.math.squaredDistance( + x, y, flatCoordinates[i], flatCoordinates[i + 1]); + if (squaredDistance < minSquaredDistance) { + minSquaredDistance = squaredDistance; + for (j = 0; j < stride; ++j) { + closestPoint[j] = flatCoordinates[i + j]; + } + closestPoint.length = stride; + } + } + return minSquaredDistance; +}; + + +/** + * Return the coordinates of the multipoint. + * @return {Array.<ol.Coordinate>} Coordinates. + * @api stable + */ +ol.geom.MultiPoint.prototype.getCoordinates = function() { + return ol.geom.flat.inflate.coordinates( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; + + +/** + * Return the point at the specified index. + * @param {number} index Index. + * @return {ol.geom.Point} Point. + * @api stable + */ +ol.geom.MultiPoint.prototype.getPoint = function(index) { + var n = !this.flatCoordinates ? + 0 : this.flatCoordinates.length / this.stride; + ol.DEBUG && console.assert(0 <= index && index < n, + 'index should be in between 0 and n'); + if (index < 0 || n <= index) { + return null; + } + var point = new ol.geom.Point(null); + point.setFlatCoordinates(this.layout, this.flatCoordinates.slice( + index * this.stride, (index + 1) * this.stride)); + return point; +}; + + +/** + * Return the points of this multipoint. + * @return {Array.<ol.geom.Point>} Points. + * @api stable + */ +ol.geom.MultiPoint.prototype.getPoints = function() { + var flatCoordinates = this.flatCoordinates; + var layout = this.layout; + var stride = this.stride; + /** @type {Array.<ol.geom.Point>} */ + var points = []; + var i, ii; + for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { + var point = new ol.geom.Point(null); + point.setFlatCoordinates(layout, flatCoordinates.slice(i, i + stride)); + points.push(point); + } + return points; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.MultiPoint.prototype.getType = function() { + return ol.geom.GeometryType.MULTI_POINT; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.MultiPoint.prototype.intersectsExtent = function(extent) { + var flatCoordinates = this.flatCoordinates; + var stride = this.stride; + var i, ii, x, y; + for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { + x = flatCoordinates[i]; + y = flatCoordinates[i + 1]; + if (ol.extent.containsXY(extent, x, y)) { + return true; + } + } + return false; +}; + + +/** + * Set the coordinates of the multipoint. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.MultiPoint.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, coordinates, 1); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + this.flatCoordinates.length = ol.geom.flat.deflate.coordinates( + this.flatCoordinates, 0, coordinates, this.stride); + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + */ +ol.geom.MultiPoint.prototype.setFlatCoordinates = function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; + +goog.provide('ol.geom.flat.center'); + +goog.require('ol.extent'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @return {Array.<number>} Flat centers. + */ +ol.geom.flat.center.linearRingss = function(flatCoordinates, offset, endss, stride) { + var flatCenters = []; + var i, ii; + var extent = ol.extent.createEmpty(); + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + extent = ol.extent.createOrUpdateFromFlatCoordinates( + flatCoordinates, offset, ends[0], stride); + flatCenters.push((extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2); + offset = ends[ends.length - 1]; + } + return flatCenters; +}; + +goog.provide('ol.geom.MultiPolygon'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.area'); +goog.require('ol.geom.flat.center'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.contains'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.interiorpoint'); +goog.require('ol.geom.flat.intersectsextent'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.simplify'); + + +/** + * @classdesc + * Multi-polygon geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.MultiPolygon = function(coordinates, opt_layout) { + + ol.geom.SimpleGeometry.call(this); + + /** + * @type {Array.<Array.<number>>} + * @private + */ + this.endss_ = []; + + /** + * @private + * @type {number} + */ + this.flatInteriorPointsRevision_ = -1; + + /** + * @private + * @type {Array.<number>} + */ + this.flatInteriorPoints_ = null; + + /** + * @private + * @type {number} + */ + this.maxDelta_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDeltaRevision_ = -1; + + /** + * @private + * @type {number} + */ + this.orientedRevision_ = -1; + + /** + * @private + * @type {Array.<number>} + */ + this.orientedFlatCoordinates_ = null; + + this.setCoordinates(coordinates, opt_layout); + +}; +ol.inherits(ol.geom.MultiPolygon, ol.geom.SimpleGeometry); + + +/** + * Append the passed polygon to this multipolygon. + * @param {ol.geom.Polygon} polygon Polygon. + * @api stable + */ +ol.geom.MultiPolygon.prototype.appendPolygon = function(polygon) { + ol.DEBUG && console.assert(polygon.getLayout() == this.layout, + 'layout of polygon should match layout'); + /** @type {Array.<number>} */ + var ends; + if (!this.flatCoordinates) { + this.flatCoordinates = polygon.getFlatCoordinates().slice(); + ends = polygon.getEnds().slice(); + this.endss_.push(); + } else { + var offset = this.flatCoordinates.length; + ol.array.extend(this.flatCoordinates, polygon.getFlatCoordinates()); + ends = polygon.getEnds().slice(); + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + ends[i] += offset; + } + } + this.endss_.push(ends); + this.changed(); +}; + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.MultiPolygon} Clone. + * @api stable + */ +ol.geom.MultiPolygon.prototype.clone = function() { + var multiPolygon = new ol.geom.MultiPolygon(null); + + var len = this.endss_.length; + var newEndss = new Array(len); + for (var i = 0; i < len; ++i) { + newEndss[i] = this.endss_[i].slice(); + } + + multiPolygon.setFlatCoordinates( + this.layout, this.flatCoordinates.slice(), newEndss); + return multiPolygon; +}; + + +/** + * @inheritDoc + */ +ol.geom.MultiPolygon.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getssMaxSquaredDelta( + this.flatCoordinates, 0, this.endss_, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getssClosestPoint( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, + this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); +}; + + +/** + * @inheritDoc + */ +ol.geom.MultiPolygon.prototype.containsXY = function(x, y) { + return ol.geom.flat.contains.linearRingssContainsXY( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, x, y); +}; + + +/** + * Return the area of the multipolygon on projected plane. + * @return {number} Area (on projected plane). + * @api stable + */ +ol.geom.MultiPolygon.prototype.getArea = function() { + return ol.geom.flat.area.linearRingss( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride); +}; + + +/** + * Get the coordinate array for this geometry. This array has the structure + * of a GeoJSON coordinate array for multi-polygons. + * + * @param {boolean=} opt_right Orient coordinates according to the right-hand + * rule (counter-clockwise for exterior and clockwise for interior rings). + * If `false`, coordinates will be oriented according to the left-hand rule + * (clockwise for exterior and counter-clockwise for interior rings). + * By default, coordinate orientation will depend on how the geometry was + * constructed. + * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinates. + * @api stable + */ +ol.geom.MultiPolygon.prototype.getCoordinates = function(opt_right) { + var flatCoordinates; + if (opt_right !== undefined) { + flatCoordinates = this.getOrientedFlatCoordinates().slice(); + ol.geom.flat.orient.orientLinearRingss( + flatCoordinates, 0, this.endss_, this.stride, opt_right); + } else { + flatCoordinates = this.flatCoordinates; + } + + return ol.geom.flat.inflate.coordinatesss( + flatCoordinates, 0, this.endss_, this.stride); +}; + + +/** + * @return {Array.<Array.<number>>} Endss. + */ +ol.geom.MultiPolygon.prototype.getEndss = function() { + return this.endss_; +}; + + +/** + * @return {Array.<number>} Flat interior points. + */ +ol.geom.MultiPolygon.prototype.getFlatInteriorPoints = function() { + if (this.flatInteriorPointsRevision_ != this.getRevision()) { + var flatCenters = ol.geom.flat.center.linearRingss( + this.flatCoordinates, 0, this.endss_, this.stride); + this.flatInteriorPoints_ = ol.geom.flat.interiorpoint.linearRingss( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, + flatCenters); + this.flatInteriorPointsRevision_ = this.getRevision(); + } + return this.flatInteriorPoints_; +}; + + +/** + * Return the interior points as {@link ol.geom.MultiPoint multipoint}. + * @return {ol.geom.MultiPoint} Interior points. + * @api stable + */ +ol.geom.MultiPolygon.prototype.getInteriorPoints = function() { + var interiorPoints = new ol.geom.MultiPoint(null); + interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XY, + this.getFlatInteriorPoints().slice()); + return interiorPoints; +}; + + +/** + * @return {Array.<number>} Oriented flat coordinates. + */ +ol.geom.MultiPolygon.prototype.getOrientedFlatCoordinates = function() { + if (this.orientedRevision_ != this.getRevision()) { + var flatCoordinates = this.flatCoordinates; + if (ol.geom.flat.orient.linearRingssAreOriented( + flatCoordinates, 0, this.endss_, this.stride)) { + this.orientedFlatCoordinates_ = flatCoordinates; + } else { + this.orientedFlatCoordinates_ = flatCoordinates.slice(); + this.orientedFlatCoordinates_.length = + ol.geom.flat.orient.orientLinearRingss( + this.orientedFlatCoordinates_, 0, this.endss_, this.stride); + } + this.orientedRevision_ = this.getRevision(); + } + return this.orientedFlatCoordinates_; +}; + + +/** + * @inheritDoc + */ +ol.geom.MultiPolygon.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + var simplifiedEndss = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizess( + this.flatCoordinates, 0, this.endss_, this.stride, + Math.sqrt(squaredTolerance), + simplifiedFlatCoordinates, 0, simplifiedEndss); + var simplifiedMultiPolygon = new ol.geom.MultiPolygon(null); + simplifiedMultiPolygon.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEndss); + return simplifiedMultiPolygon; +}; + + +/** + * Return the polygon at the specified index. + * @param {number} index Index. + * @return {ol.geom.Polygon} Polygon. + * @api stable + */ +ol.geom.MultiPolygon.prototype.getPolygon = function(index) { + ol.DEBUG && console.assert(0 <= index && index < this.endss_.length, + 'index should be in between 0 and the length of this.endss_'); + if (index < 0 || this.endss_.length <= index) { + return null; + } + var offset; + if (index === 0) { + offset = 0; + } else { + var prevEnds = this.endss_[index - 1]; + offset = prevEnds[prevEnds.length - 1]; + } + var ends = this.endss_[index].slice(); + var end = ends[ends.length - 1]; + if (offset !== 0) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + ends[i] -= offset; + } + } + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + this.layout, this.flatCoordinates.slice(offset, end), ends); + return polygon; +}; + + +/** + * Return the polygons of this multipolygon. + * @return {Array.<ol.geom.Polygon>} Polygons. + * @api stable + */ +ol.geom.MultiPolygon.prototype.getPolygons = function() { + var layout = this.layout; + var flatCoordinates = this.flatCoordinates; + var endss = this.endss_; + var polygons = []; + var offset = 0; + var i, ii, j, jj; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i].slice(); + var end = ends[ends.length - 1]; + if (offset !== 0) { + for (j = 0, jj = ends.length; j < jj; ++j) { + ends[j] -= offset; + } + } + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + layout, flatCoordinates.slice(offset, end), ends); + polygons.push(polygon); + offset = end; + } + return polygons; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.MultiPolygon.prototype.getType = function() { + return ol.geom.GeometryType.MULTI_POLYGON; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.MultiPolygon.prototype.intersectsExtent = function(extent) { + return ol.geom.flat.intersectsextent.linearRingss( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, extent); +}; + + +/** + * Set the coordinates of the multipolygon. + * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.MultiPolygon.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.endss_); + } else { + this.setLayout(opt_layout, coordinates, 3); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + var endss = ol.geom.flat.deflate.coordinatesss( + this.flatCoordinates, 0, coordinates, this.stride, this.endss_); + if (endss.length === 0) { + this.flatCoordinates.length = 0; + } else { + var lastEnds = endss[endss.length - 1]; + this.flatCoordinates.length = lastEnds.length === 0 ? + 0 : lastEnds[lastEnds.length - 1]; + } + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<Array.<number>>} endss Endss. + */ +ol.geom.MultiPolygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, endss) { + ol.DEBUG && console.assert(endss, 'endss must be truthy'); + if (!flatCoordinates || flatCoordinates.length === 0) { + ol.DEBUG && console.assert(endss.length === 0, 'the length of endss should be 0'); + } else { + ol.DEBUG && console.assert(endss.length > 0, 'endss cannot be an empty array'); + var ends = endss[endss.length - 1]; + ol.DEBUG && console.assert(flatCoordinates.length == ends[ends.length - 1], + 'the length of flatCoordinates should be the last value of ends'); + } + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.endss_ = endss; + this.changed(); +}; + + +/** + * @param {Array.<ol.geom.Polygon>} polygons Polygons. + */ +ol.geom.MultiPolygon.prototype.setPolygons = function(polygons) { + var layout = this.getLayout(); + var flatCoordinates = []; + var endss = []; + var i, ii, ends; + for (i = 0, ii = polygons.length; i < ii; ++i) { + var polygon = polygons[i]; + if (i === 0) { + layout = polygon.getLayout(); + } else { + // FIXME better handle the case of non-matching layouts + ol.DEBUG && console.assert(polygon.getLayout() == layout, + 'layout of polygon should be layout'); + } + var offset = flatCoordinates.length; + ends = polygon.getEnds(); + var j, jj; + for (j = 0, jj = ends.length; j < jj; ++j) { + ends[j] += offset; + } + ol.array.extend(flatCoordinates, polygon.getFlatCoordinates()); + endss.push(ends); + } + this.setFlatCoordinates(layout, flatCoordinates, endss); +}; + +goog.provide('ol.format.EsriJSON'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.array'); +goog.require('ol.asserts'); +goog.require('ol.extent'); +goog.require('ol.format.Feature'); +goog.require('ol.format.JSONFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.obj'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Feature format for reading and writing data in the EsriJSON format. + * + * @constructor + * @extends {ol.format.JSONFeature} + * @param {olx.format.EsriJSONOptions=} opt_options Options. + * @api + */ +ol.format.EsriJSON = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.JSONFeature.call(this); + + /** + * Name of the geometry attribute for features. + * @type {string|undefined} + * @private + */ + this.geometryName_ = options.geometryName; + +}; +ol.inherits(ol.format.EsriJSON, ol.format.JSONFeature); + + +/** + * @param {EsriJSONGeometry} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @private + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.EsriJSON.readGeometry_ = function(object, opt_options) { + if (!object) { + return null; + } + /** @type {ol.geom.GeometryType} */ + var type; + if (typeof object.x === 'number' && typeof object.y === 'number') { + type = ol.geom.GeometryType.POINT; + } else if (object.points) { + type = ol.geom.GeometryType.MULTI_POINT; + } else if (object.paths) { + if (object.paths.length === 1) { + type = ol.geom.GeometryType.LINE_STRING; + } else { + type = ol.geom.GeometryType.MULTI_LINE_STRING; + } + } else if (object.rings) { + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + var rings = ol.format.EsriJSON.convertRings_(object.rings, layout); + object = /** @type {EsriJSONGeometry} */(ol.obj.assign({}, object)); + if (rings.length === 1) { + type = ol.geom.GeometryType.POLYGON; + object.rings = rings[0]; + } else { + type = ol.geom.GeometryType.MULTI_POLYGON; + object.rings = rings; + } + } + var geometryReader = ol.format.EsriJSON.GEOMETRY_READERS_[type]; + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions( + geometryReader(object), false, opt_options)); +}; + + +/** + * Determines inner and outer rings. + * Checks if any polygons in this array contain any other polygons in this + * array. It is used for checking for holes. + * Logic inspired by: https://github.com/Esri/terraformer-arcgis-parser + * @param {Array.<!Array.<!Array.<number>>>} rings Rings. + * @param {ol.geom.GeometryLayout} layout Geometry layout. + * @private + * @return {Array.<!Array.<!Array.<number>>>} Transoformed rings. + */ +ol.format.EsriJSON.convertRings_ = function(rings, layout) { + var outerRings = []; + var holes = []; + var i, ii; + for (i = 0, ii = rings.length; i < ii; ++i) { + var flatRing = ol.array.flatten(rings[i]); + // is this ring an outer ring? is it clockwise? + var clockwise = ol.geom.flat.orient.linearRingIsClockwise(flatRing, 0, + flatRing.length, layout.length); + if (clockwise) { + outerRings.push([rings[i]]); + } else { + holes.push(rings[i]); + } + } + while (holes.length) { + var hole = holes.shift(); + var matched = false; + // loop over all outer rings and see if they contain our hole. + for (i = outerRings.length - 1; i >= 0; i--) { + var outerRing = outerRings[i][0]; + if (ol.extent.containsExtent(new ol.geom.LinearRing( + outerRing).getExtent(), + new ol.geom.LinearRing(hole).getExtent())) { + // the hole is contained push it into our polygon + outerRings[i].push(hole); + matched = true; + break; + } + } + if (!matched) { + // no outer rings contain this hole turn it into and outer + // ring (reverse it) + outerRings.push([hole.reverse()]); + } + } + return outerRings; +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} Point. + */ +ol.format.EsriJSON.readPointGeometry_ = function(object) { + ol.DEBUG && console.assert(typeof object.x === 'number', 'object.x should be number'); + ol.DEBUG && console.assert(typeof object.y === 'number', 'object.y should be number'); + var point; + if (object.m !== undefined && object.z !== undefined) { + point = new ol.geom.Point([object.x, object.y, object.z, object.m], + ol.geom.GeometryLayout.XYZM); + } else if (object.z !== undefined) { + point = new ol.geom.Point([object.x, object.y, object.z], + ol.geom.GeometryLayout.XYZ); + } else if (object.m !== undefined) { + point = new ol.geom.Point([object.x, object.y, object.m], + ol.geom.GeometryLayout.XYM); + } else { + point = new ol.geom.Point([object.x, object.y]); + } + return point; +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} LineString. + */ +ol.format.EsriJSON.readLineStringGeometry_ = function(object) { + ol.DEBUG && console.assert(Array.isArray(object.paths), + 'object.paths should be an array'); + ol.DEBUG && console.assert(object.paths.length === 1, + 'object.paths array length should be 1'); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.LineString(object.paths[0], layout); +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} MultiLineString. + */ +ol.format.EsriJSON.readMultiLineStringGeometry_ = function(object) { + ol.DEBUG && console.assert(Array.isArray(object.paths), + 'object.paths should be an array'); + ol.DEBUG && console.assert(object.paths.length > 1, + 'object.paths array length should be more than 1'); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.MultiLineString(object.paths, layout); +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.GeometryLayout} The geometry layout to use. + */ +ol.format.EsriJSON.getGeometryLayout_ = function(object) { + var layout = ol.geom.GeometryLayout.XY; + if (object.hasZ === true && object.hasM === true) { + layout = ol.geom.GeometryLayout.XYZM; + } else if (object.hasZ === true) { + layout = ol.geom.GeometryLayout.XYZ; + } else if (object.hasM === true) { + layout = ol.geom.GeometryLayout.XYM; + } + return layout; +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} MultiPoint. + */ +ol.format.EsriJSON.readMultiPointGeometry_ = function(object) { + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.MultiPoint(object.points, layout); +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} MultiPolygon. + */ +ol.format.EsriJSON.readMultiPolygonGeometry_ = function(object) { + ol.DEBUG && console.assert(object.rings.length > 1, + 'object.rings should have length larger than 1'); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.MultiPolygon( + /** @type {Array.<Array.<Array.<Array.<number>>>>} */(object.rings), + layout); +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} Polygon. + */ +ol.format.EsriJSON.readPolygonGeometry_ = function(object) { + ol.DEBUG && console.assert(object.rings); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.Polygon(object.rings, layout); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONGeometry} EsriJSON geometry. + */ +ol.format.EsriJSON.writePointGeometry_ = function(geometry, opt_options) { + var coordinates = /** @type {ol.geom.Point} */ (geometry).getCoordinates(); + var esriJSON; + var layout = /** @type {ol.geom.Point} */ (geometry).getLayout(); + if (layout === ol.geom.GeometryLayout.XYZ) { + esriJSON = /** @type {EsriJSONPoint} */ ({ + x: coordinates[0], + y: coordinates[1], + z: coordinates[2] + }); + } else if (layout === ol.geom.GeometryLayout.XYM) { + esriJSON = /** @type {EsriJSONPoint} */ ({ + x: coordinates[0], + y: coordinates[1], + m: coordinates[2] + }); + } else if (layout === ol.geom.GeometryLayout.XYZM) { + esriJSON = /** @type {EsriJSONPoint} */ ({ + x: coordinates[0], + y: coordinates[1], + z: coordinates[2], + m: coordinates[3] + }); + } else if (layout === ol.geom.GeometryLayout.XY) { + esriJSON = /** @type {EsriJSONPoint} */ ({ + x: coordinates[0], + y: coordinates[1] + }); + } else { + ol.asserts.assert(false, 34); // Invalid geometry layout + } + return /** @type {EsriJSONGeometry} */ (esriJSON); +}; + + +/** + * @param {ol.geom.SimpleGeometry} geometry Geometry. + * @private + * @return {Object} Object with boolean hasZ and hasM keys. + */ +ol.format.EsriJSON.getHasZM_ = function(geometry) { + var layout = geometry.getLayout(); + return { + hasZ: (layout === ol.geom.GeometryLayout.XYZ || + layout === ol.geom.GeometryLayout.XYZM), + hasM: (layout === ol.geom.GeometryLayout.XYM || + layout === ol.geom.GeometryLayout.XYZM) + }; +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONPolyline} EsriJSON geometry. + */ +ol.format.EsriJSON.writeLineStringGeometry_ = function(geometry, opt_options) { + var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.LineString} */ (geometry)); + return /** @type {EsriJSONPolyline} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + paths: [ + /** @type {ol.geom.LineString} */ (geometry).getCoordinates() + ] + }); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONPolygon} EsriJSON geometry. + */ +ol.format.EsriJSON.writePolygonGeometry_ = function(geometry, opt_options) { + // Esri geometries use the left-hand rule + var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.Polygon} */ (geometry)); + return /** @type {EsriJSONPolygon} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + rings: /** @type {ol.geom.Polygon} */ (geometry).getCoordinates(false) + }); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONPolyline} EsriJSON geometry. + */ +ol.format.EsriJSON.writeMultiLineStringGeometry_ = function(geometry, opt_options) { + var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiLineString} */ (geometry)); + return /** @type {EsriJSONPolyline} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + paths: /** @type {ol.geom.MultiLineString} */ (geometry).getCoordinates() + }); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONMultipoint} EsriJSON geometry. + */ +ol.format.EsriJSON.writeMultiPointGeometry_ = function(geometry, opt_options) { + var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPoint} */ (geometry)); + return /** @type {EsriJSONMultipoint} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + points: /** @type {ol.geom.MultiPoint} */ (geometry).getCoordinates() + }); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONPolygon} EsriJSON geometry. + */ +ol.format.EsriJSON.writeMultiPolygonGeometry_ = function(geometry, + opt_options) { + var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPolygon} */ (geometry)); + var coordinates = /** @type {ol.geom.MultiPolygon} */ (geometry).getCoordinates(false); + var output = []; + for (var i = 0; i < coordinates.length; i++) { + for (var x = coordinates[i].length - 1; x >= 0; x--) { + output.push(coordinates[i][x]); + } + } + return /** @type {EsriJSONPolygon} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + rings: output + }); +}; + + +/** + * @const + * @private + * @type {Object.<ol.geom.GeometryType, function(EsriJSONGeometry): ol.geom.Geometry>} + */ +ol.format.EsriJSON.GEOMETRY_READERS_ = {}; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.POINT] = + ol.format.EsriJSON.readPointGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.LINE_STRING] = + ol.format.EsriJSON.readLineStringGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.POLYGON] = + ol.format.EsriJSON.readPolygonGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POINT] = + ol.format.EsriJSON.readMultiPointGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_LINE_STRING] = + ol.format.EsriJSON.readMultiLineStringGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POLYGON] = + ol.format.EsriJSON.readMultiPolygonGeometry_; + + +/** + * @const + * @private + * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (EsriJSONGeometry)>} + */ +ol.format.EsriJSON.GEOMETRY_WRITERS_ = {}; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.POINT] = + ol.format.EsriJSON.writePointGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.LINE_STRING] = + ol.format.EsriJSON.writeLineStringGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.POLYGON] = + ol.format.EsriJSON.writePolygonGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_POINT] = + ol.format.EsriJSON.writeMultiPointGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_LINE_STRING] = + ol.format.EsriJSON.writeMultiLineStringGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_POLYGON] = + ol.format.EsriJSON.writeMultiPolygonGeometry_; + + +/** + * Read a feature from a EsriJSON Feature source. Only works for Feature, + * use `readFeatures` to read FeatureCollection source. + * + * @function + * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api + */ +ol.format.EsriJSON.prototype.readFeature; + + +/** + * Read all features from a EsriJSON source. Works with both Feature and + * FeatureCollection sources. + * + * @function + * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api + */ +ol.format.EsriJSON.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.EsriJSON.prototype.readFeatureFromObject = function( + object, opt_options) { + var esriJSONFeature = /** @type {EsriJSONFeature} */ (object); + ol.DEBUG && console.assert(esriJSONFeature.geometry || + esriJSONFeature.attributes, + 'geometry or attributes should be defined'); + var geometry = ol.format.EsriJSON.readGeometry_(esriJSONFeature.geometry, + opt_options); + var feature = new ol.Feature(); + if (this.geometryName_) { + feature.setGeometryName(this.geometryName_); + } + feature.setGeometry(geometry); + if (opt_options && opt_options.idField && + esriJSONFeature.attributes[opt_options.idField]) { + ol.DEBUG && console.assert( + typeof esriJSONFeature.attributes[opt_options.idField] === 'number', + 'objectIdFieldName value should be a number'); + feature.setId(/** @type {number} */( + esriJSONFeature.attributes[opt_options.idField])); + } + if (esriJSONFeature.attributes) { + feature.setProperties(esriJSONFeature.attributes); + } + return feature; +}; + + +/** + * @inheritDoc + */ +ol.format.EsriJSON.prototype.readFeaturesFromObject = function( + object, opt_options) { + var esriJSONObject = /** @type {EsriJSONObject} */ (object); + var options = opt_options ? opt_options : {}; + if (esriJSONObject.features) { + var esriJSONFeatureCollection = /** @type {EsriJSONFeatureCollection} */ + (object); + /** @type {Array.<ol.Feature>} */ + var features = []; + var esriJSONFeatures = esriJSONFeatureCollection.features; + var i, ii; + options.idField = object.objectIdFieldName; + for (i = 0, ii = esriJSONFeatures.length; i < ii; ++i) { + features.push(this.readFeatureFromObject(esriJSONFeatures[i], + options)); + } + return features; + } else { + return [this.readFeatureFromObject(object, options)]; + } +}; + + +/** + * Read a geometry from a EsriJSON source. + * + * @function + * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. + * @api + */ +ol.format.EsriJSON.prototype.readGeometry; + + +/** + * @inheritDoc + */ +ol.format.EsriJSON.prototype.readGeometryFromObject = function( + object, opt_options) { + return ol.format.EsriJSON.readGeometry_( + /** @type {EsriJSONGeometry} */ (object), opt_options); +}; + + +/** + * Read the projection from a EsriJSON source. + * + * @function + * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api + */ +ol.format.EsriJSON.prototype.readProjection; + + +/** + * @inheritDoc + */ +ol.format.EsriJSON.prototype.readProjectionFromObject = function(object) { + var esriJSONObject = /** @type {EsriJSONObject} */ (object); + if (esriJSONObject.spatialReference && esriJSONObject.spatialReference.wkid) { + var crs = esriJSONObject.spatialReference.wkid; + return ol.proj.get('EPSG:' + crs); + } else { + return null; + } +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONGeometry} EsriJSON geometry. + */ +ol.format.EsriJSON.writeGeometry_ = function(geometry, opt_options) { + var geometryWriter = ol.format.EsriJSON.GEOMETRY_WRITERS_[geometry.getType()]; + return geometryWriter(/** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, true, opt_options)), + opt_options); +}; + + +/** + * Encode a geometry as a EsriJSON string. + * + * @function + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} EsriJSON. + * @api + */ +ol.format.EsriJSON.prototype.writeGeometry; + + +/** + * Encode a geometry as a EsriJSON object. + * + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {EsriJSONGeometry} Object. + * @api + */ +ol.format.EsriJSON.prototype.writeGeometryObject = function(geometry, + opt_options) { + return ol.format.EsriJSON.writeGeometry_(geometry, + this.adaptOptions(opt_options)); +}; + + +/** + * Encode a feature as a EsriJSON Feature string. + * + * @function + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} EsriJSON. + * @api + */ +ol.format.EsriJSON.prototype.writeFeature; + + +/** + * Encode a feature as a esriJSON Feature object. + * + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. + * @api + */ +ol.format.EsriJSON.prototype.writeFeatureObject = function( + feature, opt_options) { + opt_options = this.adaptOptions(opt_options); + var object = {}; + var geometry = feature.getGeometry(); + if (geometry) { + object['geometry'] = + ol.format.EsriJSON.writeGeometry_(geometry, opt_options); + } + var properties = feature.getProperties(); + delete properties[feature.getGeometryName()]; + if (!ol.obj.isEmpty(properties)) { + object['attributes'] = properties; + } else { + object['attributes'] = {}; + } + if (opt_options && opt_options.featureProjection) { + object['spatialReference'] = /** @type {EsriJSONCRS} */({ + wkid: ol.proj.get( + opt_options.featureProjection).getCode().split(':').pop() + }); + } + return object; +}; + + +/** + * Encode an array of features as EsriJSON. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} EsriJSON. + * @api + */ +ol.format.EsriJSON.prototype.writeFeatures; + + +/** + * Encode an array of features as a EsriJSON object. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} EsriJSON Object. + * @api + */ +ol.format.EsriJSON.prototype.writeFeaturesObject = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + var objects = []; + var i, ii; + for (i = 0, ii = features.length; i < ii; ++i) { + objects.push(this.writeFeatureObject(features[i], opt_options)); + } + return /** @type {EsriJSONFeatureCollection} */ ({ + 'features': objects + }); +}; + +goog.provide('ol.format.filter.Filter'); + +goog.require('ol'); + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @struct + * @api + */ +ol.format.filter.Filter = function(tagName) { + + /** + * @private + * @type {!string} + */ + this.tagName_ = tagName; +}; + +/** + * The XML tag name for a filter. + * @returns {!string} Name. + */ +ol.format.filter.Filter.prototype.getTagName = function() { + return this.tagName_; +}; + +goog.provide('ol.format.filter.Logical'); + +goog.require('ol'); +goog.require('ol.format.filter.Filter'); + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature logical filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @extends {ol.format.filter.Filter} + */ +ol.format.filter.Logical = function(tagName) { + ol.format.filter.Filter.call(this, tagName); +}; +ol.inherits(ol.format.filter.Logical, ol.format.filter.Filter); + +goog.provide('ol.format.filter.LogicalBinary'); + +goog.require('ol'); +goog.require('ol.format.filter.Logical'); + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature binary logical filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @param {!ol.format.filter.Filter} conditionA First filter condition. + * @param {!ol.format.filter.Filter} conditionB Second filter condition. + * @extends {ol.format.filter.Logical} + */ +ol.format.filter.LogicalBinary = function(tagName, conditionA, conditionB) { + + ol.format.filter.Logical.call(this, tagName); + + /** + * @public + * @type {!ol.format.filter.Filter} + */ + this.conditionA = conditionA; + + /** + * @public + * @type {!ol.format.filter.Filter} + */ + this.conditionB = conditionB; + +}; +ol.inherits(ol.format.filter.LogicalBinary, ol.format.filter.Logical); + +goog.provide('ol.format.filter.And'); + +goog.require('ol'); +goog.require('ol.format.filter.LogicalBinary'); + +/** + * @classdesc + * Represents a logical `<And>` operator between two filter conditions. + * + * @constructor + * @param {!ol.format.filter.Filter} conditionA First filter condition. + * @param {!ol.format.filter.Filter} conditionB Second filter condition. + * @extends {ol.format.filter.LogicalBinary} + * @api + */ +ol.format.filter.And = function(conditionA, conditionB) { + ol.format.filter.LogicalBinary.call(this, 'And', conditionA, conditionB); +}; +ol.inherits(ol.format.filter.And, ol.format.filter.LogicalBinary); + +goog.provide('ol.format.filter.Bbox'); + +goog.require('ol'); +goog.require('ol.format.filter.Filter'); + + +/** + * @classdesc + * Represents a `<BBOX>` operator to test whether a geometry-valued property + * intersects a fixed bounding box + * + * @constructor + * @param {!string} geometryName Geometry name to use. + * @param {!ol.Extent} extent Extent. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @extends {ol.format.filter.Filter} + * @api + */ +ol.format.filter.Bbox = function(geometryName, extent, opt_srsName) { + + ol.format.filter.Filter.call(this, 'BBOX'); + + /** + * @public + * @type {!string} + */ + this.geometryName = geometryName; + + /** + * @public + * @type {ol.Extent} + */ + this.extent = extent; + + /** + * @public + * @type {string|undefined} + */ + this.srsName = opt_srsName; +}; +ol.inherits(ol.format.filter.Bbox, ol.format.filter.Filter); + +goog.provide('ol.format.filter.Comparison'); + +goog.require('ol'); +goog.require('ol.format.filter.Filter'); + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature property comparison filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @param {!string} propertyName Name of the context property to compare. + * @extends {ol.format.filter.Filter} + * @api + */ +ol.format.filter.Comparison = function(tagName, propertyName) { + + ol.format.filter.Filter.call(this, tagName); + + /** + * @public + * @type {!string} + */ + this.propertyName = propertyName; +}; +ol.inherits(ol.format.filter.Comparison, ol.format.filter.Filter); + +goog.provide('ol.format.filter.ComparisonBinary'); + +goog.require('ol'); +goog.require('ol.format.filter.Comparison'); + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature property binary comparison filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @extends {ol.format.filter.Comparison} + * @api + */ +ol.format.filter.ComparisonBinary = function( + tagName, propertyName, expression, opt_matchCase) { + + ol.format.filter.Comparison.call(this, tagName, propertyName); + + /** + * @public + * @type {!(string|number)} + */ + this.expression = expression; + + /** + * @public + * @type {boolean|undefined} + */ + this.matchCase = opt_matchCase; +}; +ol.inherits(ol.format.filter.ComparisonBinary, ol.format.filter.Comparison); + +goog.provide('ol.format.filter.EqualTo'); + +goog.require('ol'); +goog.require('ol.format.filter.ComparisonBinary'); + + +/** + * @classdesc + * Represents a `<PropertyIsEqualTo>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @extends {ol.format.filter.ComparisonBinary} + * @api + */ +ol.format.filter.EqualTo = function(propertyName, expression, opt_matchCase) { + ol.format.filter.ComparisonBinary.call(this, 'PropertyIsEqualTo', propertyName, expression, opt_matchCase); +}; +ol.inherits(ol.format.filter.EqualTo, ol.format.filter.ComparisonBinary); + +goog.provide('ol.format.filter.GreaterThan'); + +goog.require('ol'); +goog.require('ol.format.filter.ComparisonBinary'); + + +/** + * @classdesc + * Represents a `<PropertyIsGreaterThan>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @extends {ol.format.filter.ComparisonBinary} + * @api + */ +ol.format.filter.GreaterThan = function(propertyName, expression) { + ol.format.filter.ComparisonBinary.call(this, 'PropertyIsGreaterThan', propertyName, expression); +}; +ol.inherits(ol.format.filter.GreaterThan, ol.format.filter.ComparisonBinary); + +goog.provide('ol.format.filter.GreaterThanOrEqualTo'); + +goog.require('ol'); +goog.require('ol.format.filter.ComparisonBinary'); + + +/** + * @classdesc + * Represents a `<PropertyIsGreaterThanOrEqualTo>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @extends {ol.format.filter.ComparisonBinary} + * @api + */ +ol.format.filter.GreaterThanOrEqualTo = function(propertyName, expression) { + ol.format.filter.ComparisonBinary.call(this, 'PropertyIsGreaterThanOrEqualTo', propertyName, expression); +}; +ol.inherits(ol.format.filter.GreaterThanOrEqualTo, ol.format.filter.ComparisonBinary); + +goog.provide('ol.format.filter.Spatial'); + +goog.require('ol'); +goog.require('ol.format.filter.Filter'); + + +/** + * @classdesc + * Represents a spatial operator to test whether a geometry-valued property + * relates to a given geometry. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @param {!string} geometryName Geometry name to use. + * @param {!ol.geom.Geometry} geometry Geometry. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @extends {ol.format.filter.Filter} + * @api + */ +ol.format.filter.Spatial = function(tagName, geometryName, geometry, opt_srsName) { + + ol.format.filter.Filter.call(this, tagName); + + /** + * @public + * @type {!string} + */ + this.geometryName = geometryName || 'the_geom'; + + /** + * @public + * @type {ol.geom.Geometry} + */ + this.geometry = geometry; + + /** + * @public + * @type {string|undefined} + */ + this.srsName = opt_srsName; +}; +ol.inherits(ol.format.filter.Spatial, ol.format.filter.Filter); + +goog.provide('ol.format.filter.Intersects'); + +goog.require('ol'); +goog.require('ol.format.filter.Spatial'); + + +/** + * @classdesc + * Represents a `<Intersects>` operator to test whether a geometry-valued property + * intersects a given geometry. + * + * @constructor + * @param {!string} geometryName Geometry name to use. + * @param {!ol.geom.Geometry} geometry Geometry. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @extends {ol.format.filter.Spatial} + * @api + */ +ol.format.filter.Intersects = function(geometryName, geometry, opt_srsName) { + + ol.format.filter.Spatial.call(this, 'Intersects', geometryName, geometry, opt_srsName); + +}; +ol.inherits(ol.format.filter.Intersects, ol.format.filter.Spatial); + +goog.provide('ol.format.filter.IsBetween'); + +goog.require('ol'); +goog.require('ol.format.filter.Comparison'); + + +/** + * @classdesc + * Represents a `<PropertyIsBetween>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} lowerBoundary The lower bound of the range. + * @param {!number} upperBoundary The upper bound of the range. + * @extends {ol.format.filter.Comparison} + * @api + */ +ol.format.filter.IsBetween = function(propertyName, lowerBoundary, upperBoundary) { + ol.format.filter.Comparison.call(this, 'PropertyIsBetween', propertyName); + + /** + * @public + * @type {!number} + */ + this.lowerBoundary = lowerBoundary; + + /** + * @public + * @type {!number} + */ + this.upperBoundary = upperBoundary; +}; +ol.inherits(ol.format.filter.IsBetween, ol.format.filter.Comparison); + +goog.provide('ol.format.filter.IsLike'); + +goog.require('ol'); +goog.require('ol.format.filter.Comparison'); + + +/** + * @classdesc + * Represents a `<PropertyIsLike>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!string} pattern Text pattern. + * @param {string=} opt_wildCard Pattern character which matches any sequence of + * zero or more string characters. Default is '*'. + * @param {string=} opt_singleChar pattern character which matches any single + * string character. Default is '.'. + * @param {string=} opt_escapeChar Escape character which can be used to escape + * the pattern characters. Default is '!'. + * @param {boolean=} opt_matchCase Case-sensitive? + * @extends {ol.format.filter.Comparison} + * @api + */ +ol.format.filter.IsLike = function(propertyName, pattern, + opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase) { + ol.format.filter.Comparison.call(this, 'PropertyIsLike', propertyName); + + /** + * @public + * @type {!string} + */ + this.pattern = pattern; + + /** + * @public + * @type {!string} + */ + this.wildCard = (opt_wildCard !== undefined) ? opt_wildCard : '*'; + + /** + * @public + * @type {!string} + */ + this.singleChar = (opt_singleChar !== undefined) ? opt_singleChar : '.'; + + /** + * @public + * @type {!string} + */ + this.escapeChar = (opt_escapeChar !== undefined) ? opt_escapeChar : '!'; + + /** + * @public + * @type {boolean|undefined} + */ + this.matchCase = opt_matchCase; +}; +ol.inherits(ol.format.filter.IsLike, ol.format.filter.Comparison); + +goog.provide('ol.format.filter.IsNull'); + +goog.require('ol'); +goog.require('ol.format.filter.Comparison'); + + +/** + * @classdesc + * Represents a `<PropertyIsNull>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @extends {ol.format.filter.Comparison} + * @api + */ +ol.format.filter.IsNull = function(propertyName) { + ol.format.filter.Comparison.call(this, 'PropertyIsNull', propertyName); +}; +ol.inherits(ol.format.filter.IsNull, ol.format.filter.Comparison); + +goog.provide('ol.format.filter.LessThan'); + +goog.require('ol'); +goog.require('ol.format.filter.ComparisonBinary'); + + +/** + * @classdesc + * Represents a `<PropertyIsLessThan>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @extends {ol.format.filter.ComparisonBinary} + * @api + */ +ol.format.filter.LessThan = function(propertyName, expression) { + ol.format.filter.ComparisonBinary.call(this, 'PropertyIsLessThan', propertyName, expression); +}; +ol.inherits(ol.format.filter.LessThan, ol.format.filter.ComparisonBinary); + +goog.provide('ol.format.filter.LessThanOrEqualTo'); + +goog.require('ol'); +goog.require('ol.format.filter.ComparisonBinary'); + + +/** + * @classdesc + * Represents a `<PropertyIsLessThanOrEqualTo>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @extends {ol.format.filter.ComparisonBinary} + * @api + */ +ol.format.filter.LessThanOrEqualTo = function(propertyName, expression) { + ol.format.filter.ComparisonBinary.call(this, 'PropertyIsLessThanOrEqualTo', propertyName, expression); +}; +ol.inherits(ol.format.filter.LessThanOrEqualTo, ol.format.filter.ComparisonBinary); + +goog.provide('ol.format.filter.Not'); + +goog.require('ol'); +goog.require('ol.format.filter.Logical'); + + +/** + * @classdesc + * Represents a logical `<Not>` operator for a filter condition. + * + * @constructor + * @param {!ol.format.filter.Filter} condition Filter condition. + * @extends {ol.format.filter.Logical} + * @api + */ +ol.format.filter.Not = function(condition) { + + ol.format.filter.Logical.call(this, 'Not'); + + /** + * @public + * @type {!ol.format.filter.Filter} + */ + this.condition = condition; +}; +ol.inherits(ol.format.filter.Not, ol.format.filter.Logical); + +goog.provide('ol.format.filter.NotEqualTo'); + +goog.require('ol'); +goog.require('ol.format.filter.ComparisonBinary'); + + +/** + * @classdesc + * Represents a `<PropertyIsNotEqualTo>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @extends {ol.format.filter.ComparisonBinary} + * @api + */ +ol.format.filter.NotEqualTo = function(propertyName, expression, opt_matchCase) { + ol.format.filter.ComparisonBinary.call(this, 'PropertyIsNotEqualTo', propertyName, expression, opt_matchCase); +}; +ol.inherits(ol.format.filter.NotEqualTo, ol.format.filter.ComparisonBinary); + +goog.provide('ol.format.filter.Or'); + +goog.require('ol'); +goog.require('ol.format.filter.LogicalBinary'); + + +/** + * @classdesc + * Represents a logical `<Or>` operator between two filter conditions. + * + * @constructor + * @param {!ol.format.filter.Filter} conditionA First filter condition. + * @param {!ol.format.filter.Filter} conditionB Second filter condition. + * @extends {ol.format.filter.LogicalBinary} + * @api + */ +ol.format.filter.Or = function(conditionA, conditionB) { + ol.format.filter.LogicalBinary.call(this, 'Or', conditionA, conditionB); +}; +ol.inherits(ol.format.filter.Or, ol.format.filter.LogicalBinary); + +goog.provide('ol.format.filter.Within'); + +goog.require('ol'); +goog.require('ol.format.filter.Spatial'); + + +/** + * @classdesc + * Represents a `<Within>` operator to test whether a geometry-valued property + * is within a given geometry. + * + * @constructor + * @param {!string} geometryName Geometry name to use. + * @param {!ol.geom.Geometry} geometry Geometry. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @extends {ol.format.filter.Spatial} + * @api + */ +ol.format.filter.Within = function(geometryName, geometry, opt_srsName) { + + ol.format.filter.Spatial.call(this, 'Within', geometryName, geometry, opt_srsName); + +}; +ol.inherits(ol.format.filter.Within, ol.format.filter.Spatial); + +goog.provide('ol.format.filter'); + +goog.require('ol'); +goog.require('ol.format.filter.And'); +goog.require('ol.format.filter.Bbox'); +goog.require('ol.format.filter.EqualTo'); +goog.require('ol.format.filter.GreaterThan'); +goog.require('ol.format.filter.GreaterThanOrEqualTo'); +goog.require('ol.format.filter.Intersects'); +goog.require('ol.format.filter.IsBetween'); +goog.require('ol.format.filter.IsLike'); +goog.require('ol.format.filter.IsNull'); +goog.require('ol.format.filter.LessThan'); +goog.require('ol.format.filter.LessThanOrEqualTo'); +goog.require('ol.format.filter.Not'); +goog.require('ol.format.filter.NotEqualTo'); +goog.require('ol.format.filter.Or'); +goog.require('ol.format.filter.Within'); + + +/** + * Create a logical `<And>` operator between two filter conditions. + * + * @param {!ol.format.filter.Filter} conditionA First filter condition. + * @param {!ol.format.filter.Filter} conditionB Second filter condition. + * @returns {!ol.format.filter.And} `<And>` operator. + * @api + */ +ol.format.filter.and = function(conditionA, conditionB) { + return new ol.format.filter.And(conditionA, conditionB); +}; + + +/** + * Create a logical `<Or>` operator between two filter conditions. + * + * @param {!ol.format.filter.Filter} conditionA First filter condition. + * @param {!ol.format.filter.Filter} conditionB Second filter condition. + * @returns {!ol.format.filter.Or} `<Or>` operator. + * @api + */ +ol.format.filter.or = function(conditionA, conditionB) { + return new ol.format.filter.Or(conditionA, conditionB); +}; + + +/** + * Represents a logical `<Not>` operator for a filter condition. + * + * @param {!ol.format.filter.Filter} condition Filter condition. + * @returns {!ol.format.filter.Not} `<Not>` operator. + * @api + */ +ol.format.filter.not = function(condition) { + return new ol.format.filter.Not(condition); +}; + + +/** + * Create a `<BBOX>` operator to test whether a geometry-valued property + * intersects a fixed bounding box + * + * @param {!string} geometryName Geometry name to use. + * @param {!ol.Extent} extent Extent. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @returns {!ol.format.filter.Bbox} `<BBOX>` operator. + * @api + */ +ol.format.filter.bbox = function(geometryName, extent, opt_srsName) { + return new ol.format.filter.Bbox(geometryName, extent, opt_srsName); +}; + +/** + * Create a `<Intersects>` operator to test whether a geometry-valued property + * intersects a given geometry. + * + * @param {!string} geometryName Geometry name to use. + * @param {!ol.geom.Geometry} geometry Geometry. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @returns {!ol.format.filter.Intersects} `<Intersects>` operator. + * @api + */ +ol.format.filter.intersects = function(geometryName, geometry, opt_srsName) { + return new ol.format.filter.Intersects(geometryName, geometry, opt_srsName); +}; + +/** + * Create a `<Within>` operator to test whether a geometry-valued property + * is within a given geometry. + * + * @param {!string} geometryName Geometry name to use. + * @param {!ol.geom.Geometry} geometry Geometry. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @returns {!ol.format.filter.Within} `<Within>` operator. + * @api + */ +ol.format.filter.within = function(geometryName, geometry, opt_srsName) { + return new ol.format.filter.Within(geometryName, geometry, opt_srsName); +}; + + +/** + * Creates a `<PropertyIsEqualTo>` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @returns {!ol.format.filter.EqualTo} `<PropertyIsEqualTo>` operator. + * @api + */ +ol.format.filter.equalTo = function(propertyName, expression, opt_matchCase) { + return new ol.format.filter.EqualTo(propertyName, expression, opt_matchCase); +}; + + +/** + * Creates a `<PropertyIsNotEqualTo>` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @returns {!ol.format.filter.NotEqualTo} `<PropertyIsNotEqualTo>` operator. + * @api + */ +ol.format.filter.notEqualTo = function(propertyName, expression, opt_matchCase) { + return new ol.format.filter.NotEqualTo(propertyName, expression, opt_matchCase); +}; + + +/** + * Creates a `<PropertyIsLessThan>` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @returns {!ol.format.filter.LessThan} `<PropertyIsLessThan>` operator. + * @api + */ +ol.format.filter.lessThan = function(propertyName, expression) { + return new ol.format.filter.LessThan(propertyName, expression); +}; + + +/** + * Creates a `<PropertyIsLessThanOrEqualTo>` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @returns {!ol.format.filter.LessThanOrEqualTo} `<PropertyIsLessThanOrEqualTo>` operator. + * @api + */ +ol.format.filter.lessThanOrEqualTo = function(propertyName, expression) { + return new ol.format.filter.LessThanOrEqualTo(propertyName, expression); +}; + + +/** + * Creates a `<PropertyIsGreaterThan>` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @returns {!ol.format.filter.GreaterThan} `<PropertyIsGreaterThan>` operator. + * @api + */ +ol.format.filter.greaterThan = function(propertyName, expression) { + return new ol.format.filter.GreaterThan(propertyName, expression); +}; + + +/** + * Creates a `<PropertyIsGreaterThanOrEqualTo>` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @returns {!ol.format.filter.GreaterThanOrEqualTo} `<PropertyIsGreaterThanOrEqualTo>` operator. + * @api + */ +ol.format.filter.greaterThanOrEqualTo = function(propertyName, expression) { + return new ol.format.filter.GreaterThanOrEqualTo(propertyName, expression); +}; + + +/** + * Creates a `<PropertyIsNull>` comparison operator to test whether a property value + * is null. + * + * @param {!string} propertyName Name of the context property to compare. + * @returns {!ol.format.filter.IsNull} `<PropertyIsNull>` operator. + * @api + */ +ol.format.filter.isNull = function(propertyName) { + return new ol.format.filter.IsNull(propertyName); +}; + + +/** + * Creates a `<PropertyIsBetween>` comparison operator to test whether an expression + * value lies within a range given by a lower and upper bound (inclusive). + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} lowerBoundary The lower bound of the range. + * @param {!number} upperBoundary The upper bound of the range. + * @returns {!ol.format.filter.IsBetween} `<PropertyIsBetween>` operator. + * @api + */ +ol.format.filter.between = function(propertyName, lowerBoundary, upperBoundary) { + return new ol.format.filter.IsBetween(propertyName, lowerBoundary, upperBoundary); +}; + + +/** + * Represents a `<PropertyIsLike>` comparison operator that matches a string property + * value against a text pattern. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!string} pattern Text pattern. + * @param {string=} opt_wildCard Pattern character which matches any sequence of + * zero or more string characters. Default is '*'. + * @param {string=} opt_singleChar pattern character which matches any single + * string character. Default is '.'. + * @param {string=} opt_escapeChar Escape character which can be used to escape + * the pattern characters. Default is '!'. + * @param {boolean=} opt_matchCase Case-sensitive? + * @returns {!ol.format.filter.IsLike} `<PropertyIsLike>` operator. + * @api + */ +ol.format.filter.like = function(propertyName, pattern, + opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase) { + return new ol.format.filter.IsLike(propertyName, pattern, + opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase); +}; + +goog.provide('ol.geom.GeometryCollection'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.obj'); + + +/** + * @classdesc + * An array of {@link ol.geom.Geometry} objects. + * + * @constructor + * @extends {ol.geom.Geometry} + * @param {Array.<ol.geom.Geometry>=} opt_geometries Geometries. + * @api stable + */ +ol.geom.GeometryCollection = function(opt_geometries) { + + ol.geom.Geometry.call(this); + + /** + * @private + * @type {Array.<ol.geom.Geometry>} + */ + this.geometries_ = opt_geometries ? opt_geometries : null; + + this.listenGeometriesChange_(); +}; +ol.inherits(ol.geom.GeometryCollection, ol.geom.Geometry); + + +/** + * @param {Array.<ol.geom.Geometry>} geometries Geometries. + * @private + * @return {Array.<ol.geom.Geometry>} Cloned geometries. + */ +ol.geom.GeometryCollection.cloneGeometries_ = function(geometries) { + var clonedGeometries = []; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + clonedGeometries.push(geometries[i].clone()); + } + return clonedGeometries; +}; + + +/** + * @private + */ +ol.geom.GeometryCollection.prototype.unlistenGeometriesChange_ = function() { + var i, ii; + if (!this.geometries_) { + return; + } + for (i = 0, ii = this.geometries_.length; i < ii; ++i) { + ol.events.unlisten( + this.geometries_[i], ol.events.EventType.CHANGE, + this.changed, this); + } +}; + + +/** + * @private + */ +ol.geom.GeometryCollection.prototype.listenGeometriesChange_ = function() { + var i, ii; + if (!this.geometries_) { + return; + } + for (i = 0, ii = this.geometries_.length; i < ii; ++i) { + ol.events.listen( + this.geometries_[i], ol.events.EventType.CHANGE, + this.changed, this); + } +}; + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.GeometryCollection} Clone. + * @api stable + */ +ol.geom.GeometryCollection.prototype.clone = function() { + var geometryCollection = new ol.geom.GeometryCollection(null); + geometryCollection.setGeometries(this.geometries_); + return geometryCollection; +}; + + +/** + * @inheritDoc + */ +ol.geom.GeometryCollection.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + minSquaredDistance = geometries[i].closestPointXY( + x, y, closestPoint, minSquaredDistance); + } + return minSquaredDistance; +}; + + +/** + * @inheritDoc + */ +ol.geom.GeometryCollection.prototype.containsXY = function(x, y) { + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + if (geometries[i].containsXY(x, y)) { + return true; + } + } + return false; +}; + + +/** + * @inheritDoc + */ +ol.geom.GeometryCollection.prototype.computeExtent = function(extent) { + ol.extent.createOrUpdateEmpty(extent); + var geometries = this.geometries_; + for (var i = 0, ii = geometries.length; i < ii; ++i) { + ol.extent.extend(extent, geometries[i].getExtent()); + } + return extent; +}; + + +/** + * Return the geometries that make up this geometry collection. + * @return {Array.<ol.geom.Geometry>} Geometries. + * @api stable + */ +ol.geom.GeometryCollection.prototype.getGeometries = function() { + return ol.geom.GeometryCollection.cloneGeometries_(this.geometries_); +}; + + +/** + * @return {Array.<ol.geom.Geometry>} Geometries. + */ +ol.geom.GeometryCollection.prototype.getGeometriesArray = function() { + return this.geometries_; +}; + + +/** + * @inheritDoc + */ +ol.geom.GeometryCollection.prototype.getSimplifiedGeometry = function(squaredTolerance) { + if (this.simplifiedGeometryRevision != this.getRevision()) { + ol.obj.clear(this.simplifiedGeometryCache); + this.simplifiedGeometryMaxMinSquaredTolerance = 0; + this.simplifiedGeometryRevision = this.getRevision(); + } + if (squaredTolerance < 0 || + (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 && + squaredTolerance < this.simplifiedGeometryMaxMinSquaredTolerance)) { + return this; + } + var key = squaredTolerance.toString(); + if (this.simplifiedGeometryCache.hasOwnProperty(key)) { + return this.simplifiedGeometryCache[key]; + } else { + var simplifiedGeometries = []; + var geometries = this.geometries_; + var simplified = false; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + var geometry = geometries[i]; + var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance); + simplifiedGeometries.push(simplifiedGeometry); + if (simplifiedGeometry !== geometry) { + simplified = true; + } + } + if (simplified) { + var simplifiedGeometryCollection = new ol.geom.GeometryCollection(null); + simplifiedGeometryCollection.setGeometriesArray(simplifiedGeometries); + this.simplifiedGeometryCache[key] = simplifiedGeometryCollection; + return simplifiedGeometryCollection; + } else { + this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance; + return this; + } + } +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.GeometryCollection.prototype.getType = function() { + return ol.geom.GeometryType.GEOMETRY_COLLECTION; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.GeometryCollection.prototype.intersectsExtent = function(extent) { + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + if (geometries[i].intersectsExtent(extent)) { + return true; + } + } + return false; +}; + + +/** + * @return {boolean} Is empty. + */ +ol.geom.GeometryCollection.prototype.isEmpty = function() { + return this.geometries_.length === 0; +}; + + +/** + * @inheritDoc + * @api + */ +ol.geom.GeometryCollection.prototype.rotate = function(angle, anchor) { + var geometries = this.geometries_; + for (var i = 0, ii = geometries.length; i < ii; ++i) { + geometries[i].rotate(angle, anchor); + } + this.changed(); +}; + + +/** + * @inheritDoc + * @api + */ +ol.geom.GeometryCollection.prototype.scale = function(sx, opt_sy, opt_anchor) { + var anchor = opt_anchor; + if (!anchor) { + anchor = ol.extent.getCenter(this.getExtent()); + } + var geometries = this.geometries_; + for (var i = 0, ii = geometries.length; i < ii; ++i) { + geometries[i].scale(sx, opt_sy, anchor); + } + this.changed(); +}; + + +/** + * Set the geometries that make up this geometry collection. + * @param {Array.<ol.geom.Geometry>} geometries Geometries. + * @api stable + */ +ol.geom.GeometryCollection.prototype.setGeometries = function(geometries) { + this.setGeometriesArray( + ol.geom.GeometryCollection.cloneGeometries_(geometries)); +}; + + +/** + * @param {Array.<ol.geom.Geometry>} geometries Geometries. + */ +ol.geom.GeometryCollection.prototype.setGeometriesArray = function(geometries) { + this.unlistenGeometriesChange_(); + this.geometries_ = geometries; + this.listenGeometriesChange_(); + this.changed(); +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.GeometryCollection.prototype.applyTransform = function(transformFn) { + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + geometries[i].applyTransform(transformFn); + } + this.changed(); +}; + + +/** + * Translate the geometry. + * @param {number} deltaX Delta X. + * @param {number} deltaY Delta Y. + * @api + */ +ol.geom.GeometryCollection.prototype.translate = function(deltaX, deltaY) { + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + geometries[i].translate(deltaX, deltaY); + } + this.changed(); +}; + + +/** + * @inheritDoc + */ +ol.geom.GeometryCollection.prototype.disposeInternal = function() { + this.unlistenGeometriesChange_(); + ol.geom.Geometry.prototype.disposeInternal.call(this); +}; + +// TODO: serialize dataProjection as crs member when writing +// see https://github.com/openlayers/ol3/issues/2078 + +goog.provide('ol.format.GeoJSON'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.JSONFeature'); +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.obj'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Feature format for reading and writing data in the GeoJSON format. + * + * @constructor + * @extends {ol.format.JSONFeature} + * @param {olx.format.GeoJSONOptions=} opt_options Options. + * @api stable + */ +ol.format.GeoJSON = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.JSONFeature.call(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get( + options.defaultDataProjection ? + options.defaultDataProjection : 'EPSG:4326'); + + + if (options.featureProjection) { + this.defaultFeatureProjection = ol.proj.get(options.featureProjection); + } + + /** + * Name of the geometry attribute for features. + * @type {string|undefined} + * @private + */ + this.geometryName_ = options.geometryName; + +}; +ol.inherits(ol.format.GeoJSON, ol.format.JSONFeature); + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.GeoJSON.EXTENSIONS_ = ['.geojson']; + + +/** + * @param {GeoJSONGeometry|GeoJSONGeometryCollection} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @private + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.GeoJSON.readGeometry_ = function(object, opt_options) { + if (!object) { + return null; + } + var geometryReader = ol.format.GeoJSON.GEOMETRY_READERS_[object.type]; + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions( + geometryReader(object), false, opt_options)); +}; + + +/** + * @param {GeoJSONGeometryCollection} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @private + * @return {ol.geom.GeometryCollection} Geometry collection. + */ +ol.format.GeoJSON.readGeometryCollectionGeometry_ = function( + object, opt_options) { + ol.DEBUG && console.assert(object.type == 'GeometryCollection', + 'object.type should be GeometryCollection'); + var geometries = object.geometries.map( + /** + * @param {GeoJSONGeometry} geometry Geometry. + * @return {ol.geom.Geometry} geometry Geometry. + */ + function(geometry) { + return ol.format.GeoJSON.readGeometry_(geometry, opt_options); + }); + return new ol.geom.GeometryCollection(geometries); +}; + + +/** + * @param {GeoJSONGeometry} object Object. + * @private + * @return {ol.geom.Point} Point. + */ +ol.format.GeoJSON.readPointGeometry_ = function(object) { + ol.DEBUG && console.assert(object.type == 'Point', + 'object.type should be Point'); + return new ol.geom.Point(object.coordinates); +}; + + +/** + * @param {GeoJSONGeometry} object Object. + * @private + * @return {ol.geom.LineString} LineString. + */ +ol.format.GeoJSON.readLineStringGeometry_ = function(object) { + ol.DEBUG && console.assert(object.type == 'LineString', + 'object.type should be LineString'); + return new ol.geom.LineString(object.coordinates); +}; + + +/** + * @param {GeoJSONGeometry} object Object. + * @private + * @return {ol.geom.MultiLineString} MultiLineString. + */ +ol.format.GeoJSON.readMultiLineStringGeometry_ = function(object) { + ol.DEBUG && console.assert(object.type == 'MultiLineString', + 'object.type should be MultiLineString'); + return new ol.geom.MultiLineString(object.coordinates); +}; + + +/** + * @param {GeoJSONGeometry} object Object. + * @private + * @return {ol.geom.MultiPoint} MultiPoint. + */ +ol.format.GeoJSON.readMultiPointGeometry_ = function(object) { + ol.DEBUG && console.assert(object.type == 'MultiPoint', + 'object.type should be MultiPoint'); + return new ol.geom.MultiPoint(object.coordinates); +}; + + +/** + * @param {GeoJSONGeometry} object Object. + * @private + * @return {ol.geom.MultiPolygon} MultiPolygon. + */ +ol.format.GeoJSON.readMultiPolygonGeometry_ = function(object) { + ol.DEBUG && console.assert(object.type == 'MultiPolygon', + 'object.type should be MultiPolygon'); + return new ol.geom.MultiPolygon(object.coordinates); +}; + + +/** + * @param {GeoJSONGeometry} object Object. + * @private + * @return {ol.geom.Polygon} Polygon. + */ +ol.format.GeoJSON.readPolygonGeometry_ = function(object) { + ol.DEBUG && console.assert(object.type == 'Polygon', + 'object.type should be Polygon'); + return new ol.geom.Polygon(object.coordinates); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry|GeoJSONGeometryCollection} GeoJSON geometry. + */ +ol.format.GeoJSON.writeGeometry_ = function(geometry, opt_options) { + var geometryWriter = ol.format.GeoJSON.GEOMETRY_WRITERS_[geometry.getType()]; + return geometryWriter(/** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, true, opt_options)), + opt_options); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @private + * @return {GeoJSONGeometryCollection} Empty GeoJSON geometry collection. + */ +ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ = function(geometry) { + return /** @type {GeoJSONGeometryCollection} */ ({ + type: 'GeometryCollection', + geometries: [] + }); +}; + + +/** + * @param {ol.geom.GeometryCollection} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometryCollection} GeoJSON geometry collection. + */ +ol.format.GeoJSON.writeGeometryCollectionGeometry_ = function( + geometry, opt_options) { + var geometries = geometry.getGeometriesArray().map(function(geometry) { + var options = ol.obj.assign({}, opt_options); + delete options.featureProjection; + return ol.format.GeoJSON.writeGeometry_(geometry, options); + }); + return /** @type {GeoJSONGeometryCollection} */ ({ + type: 'GeometryCollection', + geometries: geometries + }); +}; + + +/** + * @param {ol.geom.LineString} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writeLineStringGeometry_ = function(geometry, opt_options) { + return /** @type {GeoJSONGeometry} */ ({ + type: 'LineString', + coordinates: geometry.getCoordinates() + }); +}; + + +/** + * @param {ol.geom.MultiLineString} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writeMultiLineStringGeometry_ = function(geometry, opt_options) { + return /** @type {GeoJSONGeometry} */ ({ + type: 'MultiLineString', + coordinates: geometry.getCoordinates() + }); +}; + + +/** + * @param {ol.geom.MultiPoint} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writeMultiPointGeometry_ = function(geometry, opt_options) { + return /** @type {GeoJSONGeometry} */ ({ + type: 'MultiPoint', + coordinates: geometry.getCoordinates() + }); +}; + + +/** + * @param {ol.geom.MultiPolygon} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writeMultiPolygonGeometry_ = function(geometry, opt_options) { + var right; + if (opt_options) { + right = opt_options.rightHanded; + } + return /** @type {GeoJSONGeometry} */ ({ + type: 'MultiPolygon', + coordinates: geometry.getCoordinates(right) + }); +}; + + +/** + * @param {ol.geom.Point} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writePointGeometry_ = function(geometry, opt_options) { + return /** @type {GeoJSONGeometry} */ ({ + type: 'Point', + coordinates: geometry.getCoordinates() + }); +}; + + +/** + * @param {ol.geom.Polygon} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writePolygonGeometry_ = function(geometry, opt_options) { + var right; + if (opt_options) { + right = opt_options.rightHanded; + } + return /** @type {GeoJSONGeometry} */ ({ + type: 'Polygon', + coordinates: geometry.getCoordinates(right) + }); +}; + + +/** + * @const + * @private + * @type {Object.<string, function(GeoJSONObject): ol.geom.Geometry>} + */ +ol.format.GeoJSON.GEOMETRY_READERS_ = { + 'Point': ol.format.GeoJSON.readPointGeometry_, + 'LineString': ol.format.GeoJSON.readLineStringGeometry_, + 'Polygon': ol.format.GeoJSON.readPolygonGeometry_, + 'MultiPoint': ol.format.GeoJSON.readMultiPointGeometry_, + 'MultiLineString': ol.format.GeoJSON.readMultiLineStringGeometry_, + 'MultiPolygon': ol.format.GeoJSON.readMultiPolygonGeometry_, + 'GeometryCollection': ol.format.GeoJSON.readGeometryCollectionGeometry_ +}; + + +/** + * @const + * @private + * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (GeoJSONGeometry|GeoJSONGeometryCollection)>} + */ +ol.format.GeoJSON.GEOMETRY_WRITERS_ = { + 'Point': ol.format.GeoJSON.writePointGeometry_, + 'LineString': ol.format.GeoJSON.writeLineStringGeometry_, + 'Polygon': ol.format.GeoJSON.writePolygonGeometry_, + 'MultiPoint': ol.format.GeoJSON.writeMultiPointGeometry_, + 'MultiLineString': ol.format.GeoJSON.writeMultiLineStringGeometry_, + 'MultiPolygon': ol.format.GeoJSON.writeMultiPolygonGeometry_, + 'GeometryCollection': ol.format.GeoJSON.writeGeometryCollectionGeometry_, + 'Circle': ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ +}; + + +/** + * @inheritDoc + */ +ol.format.GeoJSON.prototype.getExtensions = function() { + return ol.format.GeoJSON.EXTENSIONS_; +}; + + +/** + * Read a feature from a GeoJSON Feature source. Only works for Feature or + * geometry types. Use {@link ol.format.GeoJSON#readFeatures} to read + * FeatureCollection source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable + */ +ol.format.GeoJSON.prototype.readFeature; + + +/** + * Read all features from a GeoJSON source. Works for all GeoJSON types. + * If the source includes only geometries, features will be created with those + * geometries. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.GeoJSON.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.GeoJSON.prototype.readFeatureFromObject = function( + object, opt_options) { + + ol.DEBUG && console.assert(object.type !== 'FeatureCollection', 'Expected a Feature or geometry'); + + /** + * @type {GeoJSONFeature} + */ + var geoJSONFeature = null; + if (object.type === 'Feature') { + geoJSONFeature = /** @type {GeoJSONFeature} */ (object); + } else { + geoJSONFeature = /** @type {GeoJSONFeature} */ ({ + type: 'Feature', + geometry: /** @type {GeoJSONGeometry|GeoJSONGeometryCollection} */ (object) + }); + } + + var geometry = ol.format.GeoJSON.readGeometry_(geoJSONFeature.geometry, opt_options); + var feature = new ol.Feature(); + if (this.geometryName_) { + feature.setGeometryName(this.geometryName_); + } + feature.setGeometry(geometry); + if (geoJSONFeature.id !== undefined) { + feature.setId(geoJSONFeature.id); + } + if (geoJSONFeature.properties) { + feature.setProperties(geoJSONFeature.properties); + } + return feature; +}; + + +/** + * @inheritDoc + */ +ol.format.GeoJSON.prototype.readFeaturesFromObject = function( + object, opt_options) { + var geoJSONObject = /** @type {GeoJSONObject} */ (object); + /** @type {Array.<ol.Feature>} */ + var features = null; + if (geoJSONObject.type === 'FeatureCollection') { + var geoJSONFeatureCollection = /** @type {GeoJSONFeatureCollection} */ + (object); + features = []; + var geoJSONFeatures = geoJSONFeatureCollection.features; + var i, ii; + for (i = 0, ii = geoJSONFeatures.length; i < ii; ++i) { + features.push(this.readFeatureFromObject(geoJSONFeatures[i], + opt_options)); + } + } else { + features = [this.readFeatureFromObject(object, opt_options)]; + } + return features; +}; + + +/** + * Read a geometry from a GeoJSON source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. + * @api stable + */ +ol.format.GeoJSON.prototype.readGeometry; + + +/** + * @inheritDoc + */ +ol.format.GeoJSON.prototype.readGeometryFromObject = function( + object, opt_options) { + return ol.format.GeoJSON.readGeometry_( + /** @type {GeoJSONGeometry} */ (object), opt_options); +}; + + +/** + * Read the projection from a GeoJSON source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.GeoJSON.prototype.readProjection; + + +/** + * @inheritDoc + */ +ol.format.GeoJSON.prototype.readProjectionFromObject = function(object) { + var geoJSONObject = /** @type {GeoJSONObject} */ (object); + var crs = geoJSONObject.crs; + var projection; + if (crs) { + if (crs.type == 'name') { + projection = ol.proj.get(crs.properties.name); + } else if (crs.type == 'EPSG') { + // 'EPSG' is not part of the GeoJSON specification, but is generated by + // GeoServer. + // TODO: remove this when http://jira.codehaus.org/browse/GEOS-5996 + // is fixed and widely deployed. + projection = ol.proj.get('EPSG:' + crs.properties.code); + } else { + ol.asserts.assert(false, 36); // Unknown SRS type + } + } else { + projection = this.defaultDataProjection; + } + return /** @type {ol.proj.Projection} */ (projection); +}; + + +/** + * Encode a feature as a GeoJSON Feature string. + * + * @function + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} GeoJSON. + * @api stable + */ +ol.format.GeoJSON.prototype.writeFeature; + + +/** + * Encode a feature as a GeoJSON Feature object. + * + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {GeoJSONFeature} Object. + * @api stable + */ +ol.format.GeoJSON.prototype.writeFeatureObject = function(feature, opt_options) { + opt_options = this.adaptOptions(opt_options); + + var object = /** @type {GeoJSONFeature} */ ({ + 'type': 'Feature' + }); + var id = feature.getId(); + if (id !== undefined) { + object.id = id; + } + var geometry = feature.getGeometry(); + if (geometry) { + object.geometry = + ol.format.GeoJSON.writeGeometry_(geometry, opt_options); + } else { + object.geometry = null; + } + var properties = feature.getProperties(); + delete properties[feature.getGeometryName()]; + if (!ol.obj.isEmpty(properties)) { + object.properties = properties; + } else { + object.properties = null; + } + return object; +}; + + +/** + * Encode an array of features as GeoJSON. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} GeoJSON. + * @api stable + */ +ol.format.GeoJSON.prototype.writeFeatures; + + +/** + * Encode an array of features as a GeoJSON object. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {GeoJSONFeatureCollection} GeoJSON Object. + * @api stable + */ +ol.format.GeoJSON.prototype.writeFeaturesObject = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + var objects = []; + var i, ii; + for (i = 0, ii = features.length; i < ii; ++i) { + objects.push(this.writeFeatureObject(features[i], opt_options)); + } + return /** @type {GeoJSONFeatureCollection} */ ({ + type: 'FeatureCollection', + features: objects + }); +}; + + +/** + * Encode a geometry as a GeoJSON string. + * + * @function + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} GeoJSON. + * @api stable + */ +ol.format.GeoJSON.prototype.writeGeometry; + + +/** + * Encode a geometry as a GeoJSON object. + * + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {GeoJSONGeometry|GeoJSONGeometryCollection} Object. + * @api stable + */ +ol.format.GeoJSON.prototype.writeGeometryObject = function(geometry, + opt_options) { + return ol.format.GeoJSON.writeGeometry_(geometry, + this.adaptOptions(opt_options)); +}; + +goog.provide('ol.format.XMLFeature'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.format.Feature'); +goog.require('ol.format.FormatType'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for XML feature formats. + * + * @constructor + * @extends {ol.format.Feature} + */ +ol.format.XMLFeature = function() { + + /** + * @type {XMLSerializer} + * @private + */ + this.xmlSerializer_ = new XMLSerializer(); + + ol.format.Feature.call(this); +}; +ol.inherits(ol.format.XMLFeature, ol.format.Feature); + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.getType = function() { + return ol.format.FormatType.XML; +}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.readFeature = function(source, opt_options) { + if (ol.xml.isDocument(source)) { + return this.readFeatureFromDocument( + /** @type {Document} */ (source), opt_options); + } else if (ol.xml.isNode(source)) { + return this.readFeatureFromNode(/** @type {Node} */ (source), opt_options); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readFeatureFromDocument(doc, opt_options); + } else { + return null; + } +}; + + +/** + * @param {Document} doc Document. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {ol.Feature} Feature. + */ +ol.format.XMLFeature.prototype.readFeatureFromDocument = function( + doc, opt_options) { + var features = this.readFeaturesFromDocument(doc, opt_options); + if (features.length > 0) { + return features[0]; + } else { + return null; + } +}; + + +/** + * @abstract + * @param {Node} node Node. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {ol.Feature} Feature. + */ +ol.format.XMLFeature.prototype.readFeatureFromNode = function(node, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.readFeatures = function(source, opt_options) { + if (ol.xml.isDocument(source)) { + return this.readFeaturesFromDocument( + /** @type {Document} */ (source), opt_options); + } else if (ol.xml.isNode(source)) { + return this.readFeaturesFromNode(/** @type {Node} */ (source), opt_options); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readFeaturesFromDocument(doc, opt_options); + } else { + return []; + } +}; + + +/** + * @param {Document} doc Document. + * @param {olx.format.ReadOptions=} opt_options Options. + * @protected + * @return {Array.<ol.Feature>} Features. + */ +ol.format.XMLFeature.prototype.readFeaturesFromDocument = function( + doc, opt_options) { + /** @type {Array.<ol.Feature>} */ + var features = []; + var n; + for (n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + ol.array.extend(features, this.readFeaturesFromNode(n, opt_options)); + } + } + return features; +}; + + +/** + * @abstract + * @param {Node} node Node. + * @param {olx.format.ReadOptions=} opt_options Options. + * @protected + * @return {Array.<ol.Feature>} Features. + */ +ol.format.XMLFeature.prototype.readFeaturesFromNode = function(node, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.readGeometry = function(source, opt_options) { + if (ol.xml.isDocument(source)) { + return this.readGeometryFromDocument( + /** @type {Document} */ (source), opt_options); + } else if (ol.xml.isNode(source)) { + return this.readGeometryFromNode(/** @type {Node} */ (source), opt_options); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readGeometryFromDocument(doc, opt_options); + } else { + return null; + } +}; + + +/** + * @abstract + * @param {Document} doc Document. + * @param {olx.format.ReadOptions=} opt_options Options. + * @protected + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.XMLFeature.prototype.readGeometryFromDocument = function(doc, opt_options) {}; + + +/** + * @abstract + * @param {Node} node Node. + * @param {olx.format.ReadOptions=} opt_options Options. + * @protected + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.XMLFeature.prototype.readGeometryFromNode = function(node, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.readProjection = function(source) { + if (ol.xml.isDocument(source)) { + return this.readProjectionFromDocument(/** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readProjectionFromNode(/** @type {Node} */ (source)); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readProjectionFromDocument(doc); + } else { + return null; + } +}; + + +/** + * @param {Document} doc Document. + * @protected + * @return {ol.proj.Projection} Projection. + */ +ol.format.XMLFeature.prototype.readProjectionFromDocument = function(doc) { + return this.defaultDataProjection; +}; + + +/** + * @param {Node} node Node. + * @protected + * @return {ol.proj.Projection} Projection. + */ +ol.format.XMLFeature.prototype.readProjectionFromNode = function(node) { + return this.defaultDataProjection; +}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.writeFeature = function(feature, opt_options) { + var node = this.writeFeatureNode(feature, opt_options); + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return this.xmlSerializer_.serializeToString(node); +}; + + +/** + * @abstract + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Options. + * @protected + * @return {Node} Node. + */ +ol.format.XMLFeature.prototype.writeFeatureNode = function(feature, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.writeFeatures = function(features, opt_options) { + var node = this.writeFeaturesNode(features, opt_options); + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return this.xmlSerializer_.serializeToString(node); +}; + + +/** + * @abstract + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + */ +ol.format.XMLFeature.prototype.writeFeaturesNode = function(features, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.writeGeometry = function(geometry, opt_options) { + var node = this.writeGeometryNode(geometry, opt_options); + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return this.xmlSerializer_.serializeToString(node); +}; + + +/** + * @abstract + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + */ +ol.format.XMLFeature.prototype.writeGeometryNode = function(geometry, opt_options) {}; + +// FIXME Envelopes should not be treated as geometries! readEnvelope_ is part +// of GEOMETRY_PARSERS_ and methods using GEOMETRY_PARSERS_ do not expect +// envelopes/extents, only geometries! +goog.provide('ol.format.GMLBase'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Feature base format for reading and writing data in the GML format. + * This class cannot be instantiated, it contains only base content that + * is shared with versioned format classes ol.format.GML2 and + * ol.format.GML3. + * + * @constructor + * @param {olx.format.GMLOptions=} opt_options + * Optional configuration object. + * @extends {ol.format.XMLFeature} + */ +ol.format.GMLBase = function(opt_options) { + var options = /** @type {olx.format.GMLOptions} */ + (opt_options ? opt_options : {}); + + /** + * @protected + * @type {Array.<string>|string|undefined} + */ + this.featureType = options.featureType; + + /** + * @protected + * @type {Object.<string, string>|string|undefined} + */ + this.featureNS = options.featureNS; + + /** + * @protected + * @type {string} + */ + this.srsName = options.srsName; + + /** + * @protected + * @type {string} + */ + this.schemaLocation = ''; + + /** + * @type {Object.<string, Object.<string, Object>>} + */ + this.FEATURE_COLLECTION_PARSERS = {}; + this.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS] = { + 'featureMember': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readFeaturesInternal), + 'featureMembers': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readFeaturesInternal) + }; + + ol.format.XMLFeature.call(this); +}; +ol.inherits(ol.format.GMLBase, ol.format.XMLFeature); + + +/** + * @const + * @type {string} + */ +ol.format.GMLBase.GMLNS = 'http://www.opengis.net/gml'; + + +/** + * A regular expression that matches if a string only contains whitespace + * characters. It will e.g. match `''`, `' '`, `'\n'` etc. The non-breaking + * space (0xa0) is explicitly included as IE doesn't include it in its + * definition of `\s`. + * + * Information from `goog.string.isEmptyOrWhitespace`: https://github.com/google/closure-library/blob/e877b1e/closure/goog/string/string.js#L156-L160 + * + * @const + * @type {RegExp} + * @private + */ +ol.format.GMLBase.ONLY_WHITESPACE_RE_ = /^[\s\xa0]*$/; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Array.<ol.Feature> | undefined} Features. + */ +ol.format.GMLBase.prototype.readFeaturesInternal = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + var localName = node.localName; + var features = null; + if (localName == 'FeatureCollection') { + if (node.namespaceURI === 'http://www.opengis.net/wfs') { + features = ol.xml.pushParseAndPop([], + this.FEATURE_COLLECTION_PARSERS, node, + objectStack, this); + } else { + features = ol.xml.pushParseAndPop(null, + this.FEATURE_COLLECTION_PARSERS, node, + objectStack, this); + } + } else if (localName == 'featureMembers' || localName == 'featureMember') { + var context = objectStack[0]; + var featureType = context['featureType']; + var featureNS = context['featureNS']; + var i, ii, prefix = 'p', defaultPrefix = 'p0'; + if (!featureType && node.childNodes) { + featureType = [], featureNS = {}; + for (i = 0, ii = node.childNodes.length; i < ii; ++i) { + var child = node.childNodes[i]; + if (child.nodeType === 1) { + var ft = child.nodeName.split(':').pop(); + if (featureType.indexOf(ft) === -1) { + var key = ''; + var count = 0; + var uri = child.namespaceURI; + for (var candidate in featureNS) { + if (featureNS[candidate] === uri) { + key = candidate; + break; + } + ++count; + } + if (!key) { + key = prefix + count; + featureNS[key] = uri; + } + featureType.push(key + ':' + ft); + } + } + } + if (localName != 'featureMember') { + // recheck featureType for each featureMember + context['featureType'] = featureType; + context['featureNS'] = featureNS; + } + } + if (typeof featureNS === 'string') { + var ns = featureNS; + featureNS = {}; + featureNS[defaultPrefix] = ns; + } + var parsersNS = {}; + var featureTypes = Array.isArray(featureType) ? featureType : [featureType]; + for (var p in featureNS) { + var parsers = {}; + for (i = 0, ii = featureTypes.length; i < ii; ++i) { + var featurePrefix = featureTypes[i].indexOf(':') === -1 ? + defaultPrefix : featureTypes[i].split(':')[0]; + if (featurePrefix === p) { + parsers[featureTypes[i].split(':').pop()] = + (localName == 'featureMembers') ? + ol.xml.makeArrayPusher(this.readFeatureElement, this) : + ol.xml.makeReplacer(this.readFeatureElement, this); + } + } + parsersNS[featureNS[p]] = parsers; + } + if (localName == 'featureMember') { + features = ol.xml.pushParseAndPop(undefined, parsersNS, node, objectStack); + } else { + features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack); + } + } + if (features === null) { + features = []; + } + return features; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.Geometry|undefined} Geometry. + */ +ol.format.GMLBase.prototype.readGeometryElement = function(node, objectStack) { + var context = /** @type {Object} */ (objectStack[0]); + context['srsName'] = node.firstElementChild.getAttribute('srsName'); + /** @type {ol.geom.Geometry} */ + var geometry = ol.xml.pushParseAndPop(null, + this.GEOMETRY_PARSERS_, node, objectStack, this); + if (geometry) { + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, false, context)); + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.Feature} Feature. + */ +ol.format.GMLBase.prototype.readFeatureElement = function(node, objectStack) { + var n; + var fid = node.getAttribute('fid') || + ol.xml.getAttributeNS(node, ol.format.GMLBase.GMLNS, 'id'); + var values = {}, geometryName; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var localName = n.localName; + // Assume attribute elements have one child node and that the child + // is a text or CDATA node (to be treated as text). + // Otherwise assume it is a geometry node. + if (n.childNodes.length === 0 || + (n.childNodes.length === 1 && + (n.firstChild.nodeType === 3 || n.firstChild.nodeType === 4))) { + var value = ol.xml.getAllTextContent(n, false); + if (ol.format.GMLBase.ONLY_WHITESPACE_RE_.test(value)) { + value = undefined; + } + values[localName] = value; + } else { + // boundedBy is an extent and must not be considered as a geometry + if (localName !== 'boundedBy') { + geometryName = localName; + } + values[localName] = this.readGeometryElement(n, objectStack); + } + } + var feature = new ol.Feature(values); + if (geometryName) { + feature.setGeometryName(geometryName); + } + if (fid) { + feature.setId(fid); + } + return feature; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.Point|undefined} Point. + */ +ol.format.GMLBase.prototype.readPoint = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Point', 'localName should be Point'); + var flatCoordinates = + this.readFlatCoordinatesFromNode_(node, objectStack); + if (flatCoordinates) { + var point = new ol.geom.Point(null); + ol.DEBUG && console.assert(flatCoordinates.length == 3, + 'flatCoordinates should have a length of 3'); + point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + return point; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.MultiPoint|undefined} MultiPoint. + */ +ol.format.GMLBase.prototype.readMultiPoint = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MultiPoint', + 'localName should be MultiPoint'); + /** @type {Array.<Array.<number>>} */ + var coordinates = ol.xml.pushParseAndPop([], + this.MULTIPOINT_PARSERS_, node, objectStack, this); + if (coordinates) { + return new ol.geom.MultiPoint(coordinates); + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.MultiLineString|undefined} MultiLineString. + */ +ol.format.GMLBase.prototype.readMultiLineString = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MultiLineString', + 'localName should be MultiLineString'); + /** @type {Array.<ol.geom.LineString>} */ + var lineStrings = ol.xml.pushParseAndPop([], + this.MULTILINESTRING_PARSERS_, node, objectStack, this); + if (lineStrings) { + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setLineStrings(lineStrings); + return multiLineString; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.MultiPolygon|undefined} MultiPolygon. + */ +ol.format.GMLBase.prototype.readMultiPolygon = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MultiPolygon', + 'localName should be MultiPolygon'); + /** @type {Array.<ol.geom.Polygon>} */ + var polygons = ol.xml.pushParseAndPop([], + this.MULTIPOLYGON_PARSERS_, node, objectStack, this); + if (polygons) { + var multiPolygon = new ol.geom.MultiPolygon(null); + multiPolygon.setPolygons(polygons); + return multiPolygon; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GMLBase.prototype.pointMemberParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'pointMember' || + node.localName == 'pointMembers', + 'localName should be pointMember or pointMembers'); + ol.xml.parseNode(this.POINTMEMBER_PARSERS_, + node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GMLBase.prototype.lineStringMemberParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'lineStringMember' || + node.localName == 'lineStringMembers', + 'localName should be LineStringMember or LineStringMembers'); + ol.xml.parseNode(this.LINESTRINGMEMBER_PARSERS_, + node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GMLBase.prototype.polygonMemberParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'polygonMember' || + node.localName == 'polygonMembers', + 'localName should be polygonMember or polygonMembers'); + ol.xml.parseNode(this.POLYGONMEMBER_PARSERS_, node, + objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.LineString|undefined} LineString. + */ +ol.format.GMLBase.prototype.readLineString = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LineString', + 'localName should be LineString'); + var flatCoordinates = + this.readFlatCoordinatesFromNode_(node, objectStack); + if (flatCoordinates) { + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + return lineString; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} LinearRing flat coordinates. + */ +ol.format.GMLBase.prototype.readFlatLinearRing_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LinearRing', + 'localName should be LinearRing'); + var ring = ol.xml.pushParseAndPop(null, + this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, + objectStack, this); + if (ring) { + return ring; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.LinearRing|undefined} LinearRing. + */ +ol.format.GMLBase.prototype.readLinearRing = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LinearRing', + 'localName should be LinearRing'); + var flatCoordinates = + this.readFlatCoordinatesFromNode_(node, objectStack); + if (flatCoordinates) { + var ring = new ol.geom.LinearRing(null); + ring.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + return ring; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.Polygon|undefined} Polygon. + */ +ol.format.GMLBase.prototype.readPolygon = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Polygon', + 'localName should be Polygon'); + /** @type {Array.<Array.<number>>} */ + var flatLinearRings = ol.xml.pushParseAndPop([null], + this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this); + if (flatLinearRings && flatLinearRings[0]) { + var polygon = new ol.geom.Polygon(null); + var flatCoordinates = flatLinearRings[0]; + var ends = [flatCoordinates.length]; + var i, ii; + for (i = 1, ii = flatLinearRings.length; i < ii; ++i) { + ol.array.extend(flatCoordinates, flatLinearRings[i]); + ends.push(flatCoordinates.length); + } + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XYZ, flatCoordinates, ends); + return polygon; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>} Flat coordinates. + */ +ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return ol.xml.pushParseAndPop(null, + this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, + objectStack, this); +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GMLBase.prototype.MULTIPOINT_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'pointMember': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.pointMemberParser_), + 'pointMembers': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.pointMemberParser_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GMLBase.prototype.MULTILINESTRING_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'lineStringMember': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.lineStringMemberParser_), + 'lineStringMembers': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.lineStringMemberParser_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GMLBase.prototype.MULTIPOLYGON_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'polygonMember': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.polygonMemberParser_), + 'polygonMembers': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.polygonMemberParser_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GMLBase.prototype.POINTMEMBER_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'Point': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GMLBase.prototype.LINESTRINGMEMBER_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'LineString': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.readLineString) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GMLBase.prototype.POLYGONMEMBER_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'Polygon': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.readPolygon) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @protected + */ +ol.format.GMLBase.prototype.RING_PARSERS = { + 'http://www.opengis.net/gml' : { + 'LinearRing': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readFlatLinearRing_) + } +}; + + +/** + * @inheritDoc + */ +ol.format.GMLBase.prototype.readGeometryFromNode = function(node, opt_options) { + var geometry = this.readGeometryElement(node, + [this.getReadOptions(node, opt_options ? opt_options : {})]); + return geometry ? geometry : null; +}; + + +/** + * Read all features from a GML FeatureCollection. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.GMLBase.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.GMLBase.prototype.readFeaturesFromNode = function(node, opt_options) { + var options = { + featureType: this.featureType, + featureNS: this.featureNS + }; + if (opt_options) { + ol.obj.assign(options, this.getReadOptions(node, opt_options)); + } + var features = this.readFeaturesInternal(node, [options]); + return features || []; +}; + + +/** + * @inheritDoc + */ +ol.format.GMLBase.prototype.readProjectionFromNode = function(node) { + return ol.proj.get(this.srsName ? this.srsName : + node.firstElementChild.getAttribute('srsName')); +}; + +goog.provide('ol.format.XSD'); + +goog.require('ol'); +goog.require('ol.xml'); +goog.require('ol.string'); + + +/** + * @const + * @type {string} + */ +ol.format.XSD.NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema'; + + +/** + * @param {Node} node Node. + * @return {boolean|undefined} Boolean. + */ +ol.format.XSD.readBoolean = function(node) { + var s = ol.xml.getAllTextContent(node, false); + return ol.format.XSD.readBooleanString(s); +}; + + +/** + * @param {string} string String. + * @return {boolean|undefined} Boolean. + */ +ol.format.XSD.readBooleanString = function(string) { + var m = /^\s*(true|1)|(false|0)\s*$/.exec(string); + if (m) { + return m[1] !== undefined || false; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @return {number|undefined} DateTime in seconds. + */ +ol.format.XSD.readDateTime = function(node) { + var s = ol.xml.getAllTextContent(node, false); + var dateTime = Date.parse(s); + return isNaN(dateTime) ? undefined : dateTime / 1000; +}; + + +/** + * @param {Node} node Node. + * @return {number|undefined} Decimal. + */ +ol.format.XSD.readDecimal = function(node) { + var s = ol.xml.getAllTextContent(node, false); + return ol.format.XSD.readDecimalString(s); +}; + + +/** + * @param {string} string String. + * @return {number|undefined} Decimal. + */ +ol.format.XSD.readDecimalString = function(string) { + // FIXME check spec + var m = /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(string); + if (m) { + return parseFloat(m[1]); + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @return {number|undefined} Non negative integer. + */ +ol.format.XSD.readNonNegativeInteger = function(node) { + var s = ol.xml.getAllTextContent(node, false); + return ol.format.XSD.readNonNegativeIntegerString(s); +}; + + +/** + * @param {string} string String. + * @return {number|undefined} Non negative integer. + */ +ol.format.XSD.readNonNegativeIntegerString = function(string) { + var m = /^\s*(\d+)\s*$/.exec(string); + if (m) { + return parseInt(m[1], 10); + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @return {string|undefined} String. + */ +ol.format.XSD.readString = function(node) { + return ol.xml.getAllTextContent(node, false).trim(); +}; + + +/** + * @param {Node} node Node to append a TextNode with the boolean to. + * @param {boolean} bool Boolean. + */ +ol.format.XSD.writeBooleanTextNode = function(node, bool) { + ol.format.XSD.writeStringTextNode(node, (bool) ? '1' : '0'); +}; + + +/** + * @param {Node} node Node to append a CDATA Section with the string to. + * @param {string} string String. + */ +ol.format.XSD.writeCDATASection = function(node, string) { + node.appendChild(ol.xml.DOCUMENT.createCDATASection(string)); +}; + + +/** + * @param {Node} node Node to append a TextNode with the dateTime to. + * @param {number} dateTime DateTime in seconds. + */ +ol.format.XSD.writeDateTimeTextNode = function(node, dateTime) { + var date = new Date(dateTime * 1000); + var string = date.getUTCFullYear() + '-' + + ol.string.padNumber(date.getUTCMonth() + 1, 2) + '-' + + ol.string.padNumber(date.getUTCDate(), 2) + 'T' + + ol.string.padNumber(date.getUTCHours(), 2) + ':' + + ol.string.padNumber(date.getUTCMinutes(), 2) + ':' + + ol.string.padNumber(date.getUTCSeconds(), 2) + 'Z'; + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; + + +/** + * @param {Node} node Node to append a TextNode with the decimal to. + * @param {number} decimal Decimal. + */ +ol.format.XSD.writeDecimalTextNode = function(node, decimal) { + var string = decimal.toPrecision(); + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; + + +/** + * @param {Node} node Node to append a TextNode with the decimal to. + * @param {number} nonNegativeInteger Non negative integer. + */ +ol.format.XSD.writeNonNegativeIntegerTextNode = function(node, nonNegativeInteger) { + ol.DEBUG && console.assert(nonNegativeInteger >= 0, 'value should be more than 0'); + ol.DEBUG && console.assert(nonNegativeInteger == (nonNegativeInteger | 0), + 'value should be an integer value'); + var string = nonNegativeInteger.toString(); + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; + + +/** + * @param {Node} node Node to append a TextNode with the string to. + * @param {string} string String. + */ +ol.format.XSD.writeStringTextNode = function(node, string) { + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; + +goog.provide('ol.format.GML3'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.format.Feature'); +goog.require('ol.format.GMLBase'); +goog.require('ol.format.XSD'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Polygon'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Feature format for reading and writing data in the GML format + * version 3.1.1. + * Currently only supports GML 3.1.1 Simple Features profile. + * + * @constructor + * @param {olx.format.GMLOptions=} opt_options + * Optional configuration object. + * @extends {ol.format.GMLBase} + * @api + */ +ol.format.GML3 = function(opt_options) { + var options = /** @type {olx.format.GMLOptions} */ + (opt_options ? opt_options : {}); + + ol.format.GMLBase.call(this, options); + + /** + * @private + * @type {boolean} + */ + this.surface_ = options.surface !== undefined ? options.surface : false; + + /** + * @private + * @type {boolean} + */ + this.curve_ = options.curve !== undefined ? options.curve : false; + + /** + * @private + * @type {boolean} + */ + this.multiCurve_ = options.multiCurve !== undefined ? + options.multiCurve : true; + + /** + * @private + * @type {boolean} + */ + this.multiSurface_ = options.multiSurface !== undefined ? + options.multiSurface : true; + + /** + * @inheritDoc + */ + this.schemaLocation = options.schemaLocation ? + options.schemaLocation : ol.format.GML3.schemaLocation_; + +}; +ol.inherits(ol.format.GML3, ol.format.GMLBase); + + +/** + * @const + * @type {string} + * @private + */ +ol.format.GML3.schemaLocation_ = ol.format.GMLBase.GMLNS + + ' http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' + + '1.0.0/gmlsf.xsd'; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.MultiLineString|undefined} MultiLineString. + */ +ol.format.GML3.prototype.readMultiCurve_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MultiCurve', + 'localName should be MultiCurve'); + /** @type {Array.<ol.geom.LineString>} */ + var lineStrings = ol.xml.pushParseAndPop([], + this.MULTICURVE_PARSERS_, node, objectStack, this); + if (lineStrings) { + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setLineStrings(lineStrings); + return multiLineString; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.MultiPolygon|undefined} MultiPolygon. + */ +ol.format.GML3.prototype.readMultiSurface_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MultiSurface', + 'localName should be MultiSurface'); + /** @type {Array.<ol.geom.Polygon>} */ + var polygons = ol.xml.pushParseAndPop([], + this.MULTISURFACE_PARSERS_, node, objectStack, this); + if (polygons) { + var multiPolygon = new ol.geom.MultiPolygon(null); + multiPolygon.setPolygons(polygons); + return multiPolygon; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GML3.prototype.curveMemberParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'curveMember' || + node.localName == 'curveMembers', + 'localName should be curveMember or curveMembers'); + ol.xml.parseNode(this.CURVEMEMBER_PARSERS_, node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GML3.prototype.surfaceMemberParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'surfaceMember' || + node.localName == 'surfaceMembers', + 'localName should be surfaceMember or surfaceMembers'); + ol.xml.parseNode(this.SURFACEMEMBER_PARSERS_, + node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<(Array.<number>)>|undefined} flat coordinates. + */ +ol.format.GML3.prototype.readPatch_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'patches', + 'localName should be patches'); + return ol.xml.pushParseAndPop([null], + this.PATCHES_PARSERS_, node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} flat coordinates. + */ +ol.format.GML3.prototype.readSegment_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'segments', + 'localName should be segments'); + return ol.xml.pushParseAndPop([null], + this.SEGMENTS_PARSERS_, node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<(Array.<number>)>|undefined} flat coordinates. + */ +ol.format.GML3.prototype.readPolygonPatch_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'npde.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'PolygonPatch', + 'localName should be PolygonPatch'); + return ol.xml.pushParseAndPop([null], + this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} flat coordinates. + */ +ol.format.GML3.prototype.readLineStringSegment_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LineStringSegment', + 'localName should be LineStringSegment'); + return ol.xml.pushParseAndPop([null], + this.GEOMETRY_FLAT_COORDINATES_PARSERS_, + node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GML3.prototype.interiorParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'interior', + 'localName should be interior'); + /** @type {Array.<number>|undefined} */ + var flatLinearRing = ol.xml.pushParseAndPop(undefined, + this.RING_PARSERS, node, objectStack, this); + if (flatLinearRing) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + ol.DEBUG && console.assert(flatLinearRings.length > 0, + 'flatLinearRings should have an array length of 1 or more'); + flatLinearRings.push(flatLinearRing); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GML3.prototype.exteriorParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'exterior', + 'localName should be exterior'); + /** @type {Array.<number>|undefined} */ + var flatLinearRing = ol.xml.pushParseAndPop(undefined, + this.RING_PARSERS, node, objectStack, this); + if (flatLinearRing) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + ol.DEBUG && console.assert(flatLinearRings.length > 0, + 'flatLinearRings should have an array length of 1 or more'); + flatLinearRings[0] = flatLinearRing; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Polygon|undefined} Polygon. + */ +ol.format.GML3.prototype.readSurface_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Surface', + 'localName should be Surface'); + /** @type {Array.<Array.<number>>} */ + var flatLinearRings = ol.xml.pushParseAndPop([null], + this.SURFACE_PARSERS_, node, objectStack, this); + if (flatLinearRings && flatLinearRings[0]) { + var polygon = new ol.geom.Polygon(null); + var flatCoordinates = flatLinearRings[0]; + var ends = [flatCoordinates.length]; + var i, ii; + for (i = 1, ii = flatLinearRings.length; i < ii; ++i) { + ol.array.extend(flatCoordinates, flatLinearRings[i]); + ends.push(flatCoordinates.length); + } + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XYZ, flatCoordinates, ends); + return polygon; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.LineString|undefined} LineString. + */ +ol.format.GML3.prototype.readCurve_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Curve', 'localName should be Curve'); + /** @type {Array.<number>} */ + var flatCoordinates = ol.xml.pushParseAndPop([null], + this.CURVE_PARSERS_, node, objectStack, this); + if (flatCoordinates) { + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + return lineString; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Extent|undefined} Envelope. + */ +ol.format.GML3.prototype.readEnvelope_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Envelope', + 'localName should be Envelope'); + /** @type {Array.<number>} */ + var flatCoordinates = ol.xml.pushParseAndPop([null], + this.ENVELOPE_PARSERS_, node, objectStack, this); + return ol.extent.createOrUpdate(flatCoordinates[1][0], + flatCoordinates[1][1], flatCoordinates[2][0], + flatCoordinates[2][1]); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} Flat coordinates. + */ +ol.format.GML3.prototype.readFlatPos_ = function(node, objectStack) { + var s = ol.xml.getAllTextContent(node, false); + var re = /^\s*([+\-]?\d*\.?\d+(?:[eE][+\-]?\d+)?)\s*/; + /** @type {Array.<number>} */ + var flatCoordinates = []; + var m; + while ((m = re.exec(s))) { + flatCoordinates.push(parseFloat(m[1])); + s = s.substr(m[0].length); + } + if (s !== '') { + return undefined; + } + var context = objectStack[0]; + var containerSrs = context['srsName']; + var axisOrientation = 'enu'; + if (containerSrs) { + var proj = ol.proj.get(containerSrs); + axisOrientation = proj.getAxisOrientation(); + } + if (axisOrientation === 'neu') { + var i, ii; + for (i = 0, ii = flatCoordinates.length; i < ii; i += 3) { + var y = flatCoordinates[i]; + var x = flatCoordinates[i + 1]; + flatCoordinates[i] = x; + flatCoordinates[i + 1] = y; + } + } + var len = flatCoordinates.length; + if (len == 2) { + flatCoordinates.push(0); + } + if (len === 0) { + return undefined; + } + return flatCoordinates; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} Flat coordinates. + */ +ol.format.GML3.prototype.readFlatPosList_ = function(node, objectStack) { + var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, ''); + var context = objectStack[0]; + var containerSrs = context['srsName']; + var containerDimension = node.parentNode.getAttribute('srsDimension'); + var axisOrientation = 'enu'; + if (containerSrs) { + var proj = ol.proj.get(containerSrs); + axisOrientation = proj.getAxisOrientation(); + } + var coords = s.split(/\s+/); + // The "dimension" attribute is from the GML 3.0.1 spec. + var dim = 2; + if (node.getAttribute('srsDimension')) { + dim = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('srsDimension')); + } else if (node.getAttribute('dimension')) { + dim = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('dimension')); + } else if (containerDimension) { + dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension); + } + var x, y, z; + var flatCoordinates = []; + for (var i = 0, ii = coords.length; i < ii; i += dim) { + x = parseFloat(coords[i]); + y = parseFloat(coords[i + 1]); + z = (dim === 3) ? parseFloat(coords[i + 2]) : 0; + if (axisOrientation.substr(0, 2) === 'en') { + flatCoordinates.push(x, y, z); + } else { + flatCoordinates.push(y, x, z); + } + } + return flatCoordinates; +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'pos': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPos_), + 'posList': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPosList_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.FLAT_LINEAR_RINGS_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'interior': ol.format.GML3.prototype.interiorParser_, + 'exterior': ol.format.GML3.prototype.exteriorParser_ + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.GEOMETRY_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint), + 'MultiPoint': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiPoint), + 'LineString': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readLineString), + 'MultiLineString': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiLineString), + 'LinearRing' : ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readLinearRing), + 'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon), + 'MultiPolygon': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiPolygon), + 'Surface': ol.xml.makeReplacer(ol.format.GML3.prototype.readSurface_), + 'MultiSurface': ol.xml.makeReplacer( + ol.format.GML3.prototype.readMultiSurface_), + 'Curve': ol.xml.makeReplacer(ol.format.GML3.prototype.readCurve_), + 'MultiCurve': ol.xml.makeReplacer( + ol.format.GML3.prototype.readMultiCurve_), + 'Envelope': ol.xml.makeReplacer(ol.format.GML3.prototype.readEnvelope_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.MULTICURVE_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'curveMember': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.curveMemberParser_), + 'curveMembers': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.curveMemberParser_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.MULTISURFACE_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'surfaceMember': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.surfaceMemberParser_), + 'surfaceMembers': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.surfaceMemberParser_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.CURVEMEMBER_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'LineString': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.readLineString), + 'Curve': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readCurve_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.SURFACEMEMBER_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'Polygon': ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readPolygon), + 'Surface': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readSurface_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.SURFACE_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'patches': ol.xml.makeReplacer(ol.format.GML3.prototype.readPatch_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.CURVE_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'segments': ol.xml.makeReplacer(ol.format.GML3.prototype.readSegment_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.ENVELOPE_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'lowerCorner': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.readFlatPosList_), + 'upperCorner': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.readFlatPosList_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.PATCHES_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'PolygonPatch': ol.xml.makeReplacer( + ol.format.GML3.prototype.readPolygonPatch_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.SEGMENTS_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'LineStringSegment': ol.xml.makeReplacer( + ol.format.GML3.prototype.readLineStringSegment_) + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Point} value Point geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writePos_ = function(node, value, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + var axisOrientation = 'enu'; + if (srsName) { + axisOrientation = ol.proj.get(srsName).getAxisOrientation(); + } + var point = value.getCoordinates(); + var coords; + // only 2d for simple features profile + if (axisOrientation.substr(0, 2) === 'en') { + coords = (point[0] + ' ' + point[1]); + } else { + coords = (point[1] + ' ' + point[0]); + } + ol.format.XSD.writeStringTextNode(node, coords); +}; + + +/** + * @param {Array.<number>} point Point geometry. + * @param {string=} opt_srsName Optional srsName + * @return {string} The coords string. + * @private + */ +ol.format.GML3.prototype.getCoords_ = function(point, opt_srsName) { + var axisOrientation = 'enu'; + if (opt_srsName) { + axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation(); + } + return ((axisOrientation.substr(0, 2) === 'en') ? + point[0] + ' ' + point[1] : + point[1] + ' ' + point[0]); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LineString|ol.geom.LinearRing} value Geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writePosList_ = function(node, value, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + // only 2d for simple features profile + var points = value.getCoordinates(); + var len = points.length; + var parts = new Array(len); + var point; + for (var i = 0; i < len; ++i) { + point = points[i]; + parts[i] = this.getCoords_(point, srsName); + } + ol.format.XSD.writeStringTextNode(node, parts.join(' ')); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Point} geometry Point geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writePoint_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + if (srsName) { + node.setAttribute('srsName', srsName); + } + var pos = ol.xml.createElementNS(node.namespaceURI, 'pos'); + node.appendChild(pos); + this.writePos_(pos, geometry, objectStack); +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GML3.ENVELOPE_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'lowerCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'upperCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.Extent} extent Extent. + * @param {Array.<*>} objectStack Node stack. + */ +ol.format.GML3.prototype.writeEnvelope = function(node, extent, objectStack) { + ol.DEBUG && console.assert(extent.length == 4, 'extent should have 4 items'); + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + if (srsName) { + node.setAttribute('srsName', srsName); + } + var keys = ['lowerCorner', 'upperCorner']; + var values = [extent[0] + ' ' + extent[1], extent[2] + ' ' + extent[3]]; + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ + ({node: node}), ol.format.GML3.ENVELOPE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, + objectStack, keys, this); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LinearRing} geometry LinearRing geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeLinearRing_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + if (srsName) { + node.setAttribute('srsName', srsName); + } + var posList = ol.xml.createElementNS(node.namespaceURI, 'posList'); + node.appendChild(posList); + this.writePosList_(posList, geometry, objectStack); +}; + + +/** + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node} Node. + * @private + */ +ol.format.GML3.prototype.RING_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + var context = objectStack[objectStack.length - 1]; + var parentNode = context.node; + var exteriorWritten = context['exteriorWritten']; + if (exteriorWritten === undefined) { + context['exteriorWritten'] = true; + } + return ol.xml.createElementNS(parentNode.namespaceURI, + exteriorWritten !== undefined ? 'interior' : 'exterior'); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Polygon} geometry Polygon geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeSurfaceOrPolygon_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + if (node.nodeName !== 'PolygonPatch' && srsName) { + node.setAttribute('srsName', srsName); + } + if (node.nodeName === 'Polygon' || node.nodeName === 'PolygonPatch') { + var rings = geometry.getLinearRings(); + ol.xml.pushSerializeAndPop( + {node: node, srsName: srsName}, + ol.format.GML3.RING_SERIALIZERS_, + this.RING_NODE_FACTORY_, + rings, objectStack, undefined, this); + } else if (node.nodeName === 'Surface') { + var patches = ol.xml.createElementNS(node.namespaceURI, 'patches'); + node.appendChild(patches); + this.writeSurfacePatches_( + patches, geometry, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LineString} geometry LineString geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeCurveOrLineString_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + if (node.nodeName !== 'LineStringSegment' && srsName) { + node.setAttribute('srsName', srsName); + } + if (node.nodeName === 'LineString' || + node.nodeName === 'LineStringSegment') { + var posList = ol.xml.createElementNS(node.namespaceURI, 'posList'); + node.appendChild(posList); + this.writePosList_(posList, geometry, objectStack); + } else if (node.nodeName === 'Curve') { + var segments = ol.xml.createElementNS(node.namespaceURI, 'segments'); + node.appendChild(segments); + this.writeCurveSegments_(segments, + geometry, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + var surface = context['surface']; + if (srsName) { + node.setAttribute('srsName', srsName); + } + var polygons = geometry.getPolygons(); + ol.xml.pushSerializeAndPop({node: node, srsName: srsName, surface: surface}, + ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_, + this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, polygons, + objectStack, undefined, this); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.MultiPoint} geometry MultiPoint geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeMultiPoint_ = function(node, geometry, + objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + if (srsName) { + node.setAttribute('srsName', srsName); + } + var points = geometry.getPoints(); + ol.xml.pushSerializeAndPop({node: node, srsName: srsName}, + ol.format.GML3.POINTMEMBER_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('pointMember'), points, + objectStack, undefined, this); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.MultiLineString} geometry MultiLineString geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeMultiCurveOrLineString_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + var curve = context['curve']; + if (srsName) { + node.setAttribute('srsName', srsName); + } + var lines = geometry.getLineStrings(); + ol.xml.pushSerializeAndPop({node: node, srsName: srsName, curve: curve}, + ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_, + this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, lines, + objectStack, undefined, this); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LinearRing} ring LinearRing geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeRing_ = function(node, ring, objectStack) { + var linearRing = ol.xml.createElementNS(node.namespaceURI, 'LinearRing'); + node.appendChild(linearRing); + this.writeLinearRing_(linearRing, ring, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Polygon} polygon Polygon geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeSurfaceOrPolygonMember_ = function(node, polygon, objectStack) { + var child = this.GEOMETRY_NODE_FACTORY_( + polygon, objectStack); + if (child) { + node.appendChild(child); + this.writeSurfaceOrPolygon_(child, polygon, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Point} point Point geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writePointMember_ = function(node, point, objectStack) { + var child = ol.xml.createElementNS(node.namespaceURI, 'Point'); + node.appendChild(child); + this.writePoint_(child, point, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LineString} line LineString geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeLineStringOrCurveMember_ = function(node, line, objectStack) { + var child = this.GEOMETRY_NODE_FACTORY_(line, objectStack); + if (child) { + node.appendChild(child); + this.writeCurveOrLineString_(child, line, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Polygon} polygon Polygon geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeSurfacePatches_ = function(node, polygon, objectStack) { + var child = ol.xml.createElementNS(node.namespaceURI, 'PolygonPatch'); + node.appendChild(child); + this.writeSurfaceOrPolygon_(child, polygon, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LineString} line LineString geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeCurveSegments_ = function(node, line, objectStack) { + var child = ol.xml.createElementNS(node.namespaceURI, + 'LineStringSegment'); + node.appendChild(child); + this.writeCurveOrLineString_(child, line, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Geometry|ol.Extent} geometry Geometry. + * @param {Array.<*>} objectStack Node stack. + */ +ol.format.GML3.prototype.writeGeometryElement = function(node, geometry, objectStack) { + var context = /** @type {olx.format.WriteOptions} */ (objectStack[objectStack.length - 1]); + var item = ol.obj.assign({}, context); + item.node = node; + var value; + if (Array.isArray(geometry)) { + if (context.dataProjection) { + value = ol.proj.transformExtent( + geometry, context.featureProjection, context.dataProjection); + } else { + value = geometry; + } + } else { + value = + ol.format.Feature.transformWithOptions(/** @type {ol.geom.Geometry} */ (geometry), true, context); + } + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ + (item), ol.format.GML3.GEOMETRY_SERIALIZERS_, + this.GEOMETRY_NODE_FACTORY_, [value], + objectStack, undefined, this); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + */ +ol.format.GML3.prototype.writeFeatureElement = function(node, feature, objectStack) { + var fid = feature.getId(); + if (fid) { + node.setAttribute('fid', fid); + } + var context = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var featureNS = context['featureNS']; + var geometryName = feature.getGeometryName(); + if (!context.serializers) { + context.serializers = {}; + context.serializers[featureNS] = {}; + } + var properties = feature.getProperties(); + var keys = [], values = []; + for (var key in properties) { + var value = properties[key]; + if (value !== null) { + keys.push(key); + values.push(value); + if (key == geometryName || value instanceof ol.geom.Geometry) { + if (!(key in context.serializers[featureNS])) { + context.serializers[featureNS][key] = ol.xml.makeChildAppender( + this.writeGeometryElement, this); + } + } else { + if (!(key in context.serializers[featureNS])) { + context.serializers[featureNS][key] = ol.xml.makeChildAppender( + ol.format.XSD.writeStringTextNode); + } + } + } + } + var item = ol.obj.assign({}, context); + item.node = node; + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ + (item), context.serializers, + ol.xml.makeSimpleNodeFactory(undefined, featureNS), + values, + objectStack, keys); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<ol.Feature>} features Features. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeFeatureMembers_ = function(node, features, objectStack) { + var context = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var featureType = context['featureType']; + var featureNS = context['featureNS']; + var serializers = {}; + serializers[featureNS] = {}; + serializers[featureNS][featureType] = ol.xml.makeChildAppender( + this.writeFeatureElement, this); + var item = ol.obj.assign({}, context); + item.node = node; + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ + (item), + serializers, + ol.xml.makeSimpleNodeFactory(featureType, featureNS), features, + objectStack); +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'surfaceMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeSurfaceOrPolygonMember_), + 'polygonMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeSurfaceOrPolygonMember_) + } +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GML3.POINTMEMBER_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'pointMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writePointMember_) + } +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'lineStringMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeLineStringOrCurveMember_), + 'curveMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeLineStringOrCurveMember_) + } +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GML3.RING_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'exterior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_), + 'interior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_) + } +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GML3.GEOMETRY_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'Curve': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeCurveOrLineString_), + 'MultiCurve': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiCurveOrLineString_), + 'Point': ol.xml.makeChildAppender(ol.format.GML3.prototype.writePoint_), + 'MultiPoint': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiPoint_), + 'LineString': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeCurveOrLineString_), + 'MultiLineString': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiCurveOrLineString_), + 'LinearRing': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeLinearRing_), + 'Polygon': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeSurfaceOrPolygon_), + 'MultiPolygon': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_), + 'Surface': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeSurfaceOrPolygon_), + 'MultiSurface': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_), + 'Envelope': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeEnvelope) + } +}; + + +/** + * @const + * @type {Object.<string, string>} + * @private + */ +ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_ = { + 'MultiLineString': 'lineStringMember', + 'MultiCurve': 'curveMember', + 'MultiPolygon': 'polygonMember', + 'MultiSurface': 'surfaceMember' +}; + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private + */ +ol.format.GML3.prototype.MULTIGEOMETRY_MEMBER_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + var parentNode = objectStack[objectStack.length - 1].node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode), + 'parentNode should be a node'); + return ol.xml.createElementNS('http://www.opengis.net/gml', + ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_[parentNode.nodeName]); +}; + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private + */ +ol.format.GML3.prototype.GEOMETRY_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + var context = objectStack[objectStack.length - 1]; + var multiSurface = context['multiSurface']; + var surface = context['surface']; + var curve = context['curve']; + var multiCurve = context['multiCurve']; + var parentNode = objectStack[objectStack.length - 1].node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode), + 'parentNode should be a node'); + var nodeName; + if (!Array.isArray(value)) { + nodeName = /** @type {ol.geom.Geometry} */ (value).getType(); + if (nodeName === 'MultiPolygon' && multiSurface === true) { + nodeName = 'MultiSurface'; + } else if (nodeName === 'Polygon' && surface === true) { + nodeName = 'Surface'; + } else if (nodeName === 'LineString' && curve === true) { + nodeName = 'Curve'; + } else if (nodeName === 'MultiLineString' && multiCurve === true) { + nodeName = 'MultiCurve'; + } + } else { + nodeName = 'Envelope'; + } + return ol.xml.createElementNS('http://www.opengis.net/gml', + nodeName); +}; + + +/** + * Encode a geometry in GML 3.1.1 Simple Features. + * + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api + */ +ol.format.GML3.prototype.writeGeometryNode = function(geometry, opt_options) { + opt_options = this.adaptOptions(opt_options); + var geom = ol.xml.createElementNS('http://www.opengis.net/gml', 'geom'); + var context = {node: geom, srsName: this.srsName, + curve: this.curve_, surface: this.surface_, + multiSurface: this.multiSurface_, multiCurve: this.multiCurve_}; + if (opt_options) { + ol.obj.assign(context, opt_options); + } + this.writeGeometryElement(geom, geometry, [context]); + return geom; +}; + + +/** + * Encode an array of features in GML 3.1.1 Simple Features. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {string} Result. + * @api stable + */ +ol.format.GML3.prototype.writeFeatures; + + +/** + * Encode an array of features in the GML 3.1.1 format as an XML node. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api + */ +ol.format.GML3.prototype.writeFeaturesNode = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + var node = ol.xml.createElementNS('http://www.opengis.net/gml', + 'featureMembers'); + ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation); + var context = { + srsName: this.srsName, + curve: this.curve_, + surface: this.surface_, + multiSurface: this.multiSurface_, + multiCurve: this.multiCurve_, + featureNS: this.featureNS, + featureType: this.featureType + }; + if (opt_options) { + ol.obj.assign(context, opt_options); + } + this.writeFeatureMembers_(node, features, [context]); + return node; +}; + +goog.provide('ol.format.GML'); + +goog.require('ol.format.GML3'); + + +/** + * @classdesc + * Feature format for reading and writing data in the GML format + * version 3.1.1. + * Currently only supports GML 3.1.1 Simple Features profile. + * + * @constructor + * @param {olx.format.GMLOptions=} opt_options + * Optional configuration object. + * @extends {ol.format.GMLBase} + * @api stable + */ +ol.format.GML = ol.format.GML3; + + +/** + * Encode an array of features in GML 3.1.1 Simple Features. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {string} Result. + * @api stable + */ +ol.format.GML.prototype.writeFeatures; + + +/** + * Encode an array of features in the GML 3.1.1 format as an XML node. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api + */ +ol.format.GML.prototype.writeFeaturesNode; + +goog.provide('ol.format.GML2'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.format.GMLBase'); +goog.require('ol.format.XSD'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Feature format for reading and writing data in the GML format, + * version 2.1.2. + * + * @constructor + * @param {olx.format.GMLOptions=} opt_options Optional configuration object. + * @extends {ol.format.GMLBase} + * @api + */ +ol.format.GML2 = function(opt_options) { + var options = /** @type {olx.format.GMLOptions} */ + (opt_options ? opt_options : {}); + + ol.format.GMLBase.call(this, options); + + this.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS][ + 'featureMember'] = + ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readFeaturesInternal); + + /** + * @inheritDoc + */ + this.schemaLocation = options.schemaLocation ? + options.schemaLocation : ol.format.GML2.schemaLocation_; + +}; +ol.inherits(ol.format.GML2, ol.format.GMLBase); + + +/** + * @const + * @type {string} + * @private + */ +ol.format.GML2.schemaLocation_ = ol.format.GMLBase.GMLNS + + ' http://schemas.opengis.net/gml/2.1.2/feature.xsd'; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} Flat coordinates. + */ +ol.format.GML2.prototype.readFlatCoordinates_ = function(node, objectStack) { + var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, ''); + var context = /** @type {ol.XmlNodeStackItem} */ (objectStack[0]); + var containerSrs = context['srsName']; + var containerDimension = node.parentNode.getAttribute('srsDimension'); + var axisOrientation = 'enu'; + if (containerSrs) { + var proj = ol.proj.get(containerSrs); + if (proj) { + axisOrientation = proj.getAxisOrientation(); + } + } + var coords = s.split(/[\s,]+/); + // The "dimension" attribute is from the GML 3.0.1 spec. + var dim = 2; + if (node.getAttribute('srsDimension')) { + dim = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('srsDimension')); + } else if (node.getAttribute('dimension')) { + dim = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('dimension')); + } else if (containerDimension) { + dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension); + } + var x, y, z; + var flatCoordinates = []; + for (var i = 0, ii = coords.length; i < ii; i += dim) { + x = parseFloat(coords[i]); + y = parseFloat(coords[i + 1]); + z = (dim === 3) ? parseFloat(coords[i + 2]) : 0; + if (axisOrientation.substr(0, 2) === 'en') { + flatCoordinates.push(x, y, z); + } else { + flatCoordinates.push(y, x, z); + } + } + return flatCoordinates; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Extent|undefined} Envelope. + */ +ol.format.GML2.prototype.readBox_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Box', 'localName should be Box'); + /** @type {Array.<number>} */ + var flatCoordinates = ol.xml.pushParseAndPop([null], + this.BOX_PARSERS_, node, objectStack, this); + return ol.extent.createOrUpdate(flatCoordinates[1][0], + flatCoordinates[1][1], flatCoordinates[1][3], + flatCoordinates[1][4]); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GML2.prototype.innerBoundaryIsParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'innerBoundaryIs', + 'localName should be innerBoundaryIs'); + /** @type {Array.<number>|undefined} */ + var flatLinearRing = ol.xml.pushParseAndPop(undefined, + this.RING_PARSERS, node, objectStack, this); + if (flatLinearRing) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + ol.DEBUG && console.assert(flatLinearRings.length > 0, + 'flatLinearRings should have an array length larger than 0'); + flatLinearRings.push(flatLinearRing); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GML2.prototype.outerBoundaryIsParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'outerBoundaryIs', + 'localName should be outerBoundaryIs'); + /** @type {Array.<number>|undefined} */ + var flatLinearRing = ol.xml.pushParseAndPop(undefined, + this.RING_PARSERS, node, objectStack, this); + if (flatLinearRing) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + ol.DEBUG && console.assert(flatLinearRings.length > 0, + 'flatLinearRings should have an array length larger than 0'); + flatLinearRings[0] = flatLinearRing; + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML2.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'coordinates': ol.xml.makeReplacer( + ol.format.GML2.prototype.readFlatCoordinates_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML2.prototype.FLAT_LINEAR_RINGS_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'innerBoundaryIs': ol.format.GML2.prototype.innerBoundaryIsParser_, + 'outerBoundaryIs': ol.format.GML2.prototype.outerBoundaryIsParser_ + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML2.prototype.BOX_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'coordinates': ol.xml.makeArrayPusher( + ol.format.GML2.prototype.readFlatCoordinates_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML2.prototype.GEOMETRY_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint), + 'MultiPoint': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiPoint), + 'LineString': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readLineString), + 'MultiLineString': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiLineString), + 'LinearRing' : ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readLinearRing), + 'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon), + 'MultiPolygon': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiPolygon), + 'Box': ol.xml.makeReplacer(ol.format.GML2.prototype.readBox_) + } +}; + +goog.provide('ol.format.GPX'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.array'); +goog.require('ol.format.Feature'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.format.XSD'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.Point'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Feature format for reading and writing data in the GPX format. + * + * @constructor + * @extends {ol.format.XMLFeature} + * @param {olx.format.GPXOptions=} opt_options Options. + * @api stable + */ +ol.format.GPX = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.XMLFeature.call(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); + + /** + * @type {function(ol.Feature, Node)|undefined} + * @private + */ + this.readExtensions_ = options.readExtensions; +}; +ol.inherits(ol.format.GPX, ol.format.XMLFeature); + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.format.GPX.NAMESPACE_URIS_ = [ + null, + 'http://www.topografix.com/GPX/1/0', + 'http://www.topografix.com/GPX/1/1' +]; + + +/** + * @const + * @type {string} + * @private + */ +ol.format.GPX.SCHEMA_LOCATION_ = 'http://www.topografix.com/GPX/1/1 ' + + 'http://www.topografix.com/GPX/1/1/gpx.xsd'; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {ol.LayoutOptions} layoutOptions Layout options. + * @param {Node} node Node. + * @param {Object} values Values. + * @private + * @return {Array.<number>} Flat coordinates. + */ +ol.format.GPX.appendCoordinate_ = function(flatCoordinates, layoutOptions, node, values) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + flatCoordinates.push( + parseFloat(node.getAttribute('lon')), + parseFloat(node.getAttribute('lat'))); + if ('ele' in values) { + flatCoordinates.push(/** @type {number} */ (values['ele'])); + delete values['ele']; + layoutOptions.hasZ = true; + } else { + flatCoordinates.push(0); + } + if ('time' in values) { + flatCoordinates.push(/** @type {number} */ (values['time'])); + delete values['time']; + layoutOptions.hasM = true; + } else { + flatCoordinates.push(0); + } + return flatCoordinates; +}; + + +/** + * Choose GeometryLayout based on flags in layoutOptions and adjust flatCoordinates + * and ends arrays by shrinking them accordingly (removing unused zero entries). + * + * @param {ol.LayoutOptions} layoutOptions Layout options. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<number>=} ends Ends. + * @return {ol.geom.GeometryLayout} Layout. + */ +ol.format.GPX.applyLayoutOptions_ = function(layoutOptions, flatCoordinates, ends) { + var layout = ol.geom.GeometryLayout.XY; + var stride = 2; + if (layoutOptions.hasZ && layoutOptions.hasM) { + layout = ol.geom.GeometryLayout.XYZM; + stride = 4; + } else if (layoutOptions.hasZ) { + layout = ol.geom.GeometryLayout.XYZ; + stride = 3; + } else if (layoutOptions.hasM) { + layout = ol.geom.GeometryLayout.XYM; + stride = 3; + } + if (stride !== 4) { + var i, ii; + for (i = 0, ii = flatCoordinates.length / 4; i < ii; i++) { + flatCoordinates[i * stride] = flatCoordinates[i * 4]; + flatCoordinates[i * stride + 1] = flatCoordinates[i * 4 + 1]; + if (layoutOptions.hasZ) { + flatCoordinates[i * stride + 2] = flatCoordinates[i * 4 + 2]; + } + if (layoutOptions.hasM) { + flatCoordinates[i * stride + 2] = flatCoordinates[i * 4 + 3]; + } + } + flatCoordinates.length = flatCoordinates.length / 4 * stride; + if (ends) { + for (i = 0, ii = ends.length; i < ii; i++) { + ends[i] = ends[i] / 4 * stride; + } + } + } + return layout; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.parseLink_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'link', 'localName should be link'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var href = node.getAttribute('href'); + if (href !== null) { + values['link'] = href; + } + ol.xml.parseNode(ol.format.GPX.LINK_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.parseExtensions_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'extensions', + 'localName should be extensions'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + values['extensionsNode_'] = node; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.parseRtePt_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'rtept', 'localName should be rtept'); + var values = ol.xml.pushParseAndPop( + {}, ol.format.GPX.RTEPT_PARSERS_, node, objectStack); + if (values) { + var rteValues = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var flatCoordinates = /** @type {Array.<number>} */ + (rteValues['flatCoordinates']); + var layoutOptions = /** @type {ol.LayoutOptions} */ + (rteValues['layoutOptions']); + ol.format.GPX.appendCoordinate_(flatCoordinates, layoutOptions, node, values); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.parseTrkPt_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'trkpt', 'localName should be trkpt'); + var values = ol.xml.pushParseAndPop( + {}, ol.format.GPX.TRKPT_PARSERS_, node, objectStack); + if (values) { + var trkValues = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var flatCoordinates = /** @type {Array.<number>} */ + (trkValues['flatCoordinates']); + var layoutOptions = /** @type {ol.LayoutOptions} */ + (trkValues['layoutOptions']); + ol.format.GPX.appendCoordinate_(flatCoordinates, layoutOptions, node, values); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.parseTrkSeg_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'trkseg', + 'localName should be trkseg'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + ol.xml.parseNode(ol.format.GPX.TRKSEG_PARSERS_, node, objectStack); + var flatCoordinates = /** @type {Array.<number>} */ + (values['flatCoordinates']); + var ends = /** @type {Array.<number>} */ (values['ends']); + ends.push(flatCoordinates.length); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Feature|undefined} Track. + */ +ol.format.GPX.readRte_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'rte', 'localName should be rte'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var values = ol.xml.pushParseAndPop({ + 'flatCoordinates': [], + 'layoutOptions': {} + }, ol.format.GPX.RTE_PARSERS_, node, objectStack); + if (!values) { + return undefined; + } + var flatCoordinates = /** @type {Array.<number>} */ + (values['flatCoordinates']); + delete values['flatCoordinates']; + var layoutOptions = /** @type {ol.LayoutOptions} */ (values['layoutOptions']); + delete values['layoutOptions']; + var layout = ol.format.GPX.applyLayoutOptions_(layoutOptions, flatCoordinates); + var geometry = new ol.geom.LineString(null); + geometry.setFlatCoordinates(layout, flatCoordinates); + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setProperties(values); + return feature; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Feature|undefined} Track. + */ +ol.format.GPX.readTrk_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'trk', 'localName should be trk'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var values = ol.xml.pushParseAndPop({ + 'flatCoordinates': [], + 'ends': [], + 'layoutOptions': {} + }, ol.format.GPX.TRK_PARSERS_, node, objectStack); + if (!values) { + return undefined; + } + var flatCoordinates = /** @type {Array.<number>} */ + (values['flatCoordinates']); + delete values['flatCoordinates']; + var ends = /** @type {Array.<number>} */ (values['ends']); + delete values['ends']; + var layoutOptions = /** @type {ol.LayoutOptions} */ (values['layoutOptions']); + delete values['layoutOptions']; + var layout = ol.format.GPX.applyLayoutOptions_(layoutOptions, flatCoordinates, ends); + var geometry = new ol.geom.MultiLineString(null); + geometry.setFlatCoordinates(layout, flatCoordinates, ends); + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setProperties(values); + return feature; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Feature|undefined} Waypoint. + */ +ol.format.GPX.readWpt_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'wpt', 'localName should be wpt'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var values = ol.xml.pushParseAndPop( + {}, ol.format.GPX.WPT_PARSERS_, node, objectStack); + if (!values) { + return undefined; + } + var layoutOptions = /** @type {ol.LayoutOptions} */ ({}); + var coordinates = ol.format.GPX.appendCoordinate_([], layoutOptions, node, values); + var layout = ol.format.GPX.applyLayoutOptions_(layoutOptions, coordinates); + var geometry = new ol.geom.Point(coordinates, layout); + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setProperties(values); + return feature; +}; + + +/** + * @const + * @type {Object.<string, function(Node, Array.<*>): (ol.Feature|undefined)>} + * @private + */ +ol.format.GPX.FEATURE_READER_ = { + 'rte': ol.format.GPX.readRte_, + 'trk': ol.format.GPX.readTrk_, + 'wpt': ol.format.GPX.readWpt_ +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.GPX_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'rte': ol.xml.makeArrayPusher(ol.format.GPX.readRte_), + 'trk': ol.xml.makeArrayPusher(ol.format.GPX.readTrk_), + 'wpt': ol.xml.makeArrayPusher(ol.format.GPX.readWpt_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.LINK_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'text': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkText'), + 'type': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkType') + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.RTE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'link': ol.format.GPX.parseLink_, + 'number': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), + 'extensions': ol.format.GPX.parseExtensions_, + 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'rtept': ol.format.GPX.parseRtePt_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.RTEPT_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.TRK_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'link': ol.format.GPX.parseLink_, + 'number': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), + 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'extensions': ol.format.GPX.parseExtensions_, + 'trkseg': ol.format.GPX.parseTrkSeg_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.TRKSEG_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'trkpt': ol.format.GPX.parseTrkPt_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.TRKPT_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.WPT_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime), + 'magvar': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'geoidheight': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'link': ol.format.GPX.parseLink_, + 'sym': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'fix': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'sat': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'hdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'vdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'pdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'ageofdgpsdata': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'dgpsid': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), + 'extensions': ol.format.GPX.parseExtensions_ + }); + + +/** + * @param {Array.<ol.Feature>} features List of features. + * @private + */ +ol.format.GPX.prototype.handleReadExtensions_ = function(features) { + if (!features) { + features = []; + } + for (var i = 0, ii = features.length; i < ii; ++i) { + var feature = features[i]; + if (this.readExtensions_) { + var extensionsNode = feature.get('extensionsNode_') || null; + this.readExtensions_(feature, extensionsNode); + } + feature.set('extensionsNode_', undefined); + } +}; + + +/** + * Read the first feature from a GPX source. + * Routes (`<rte>`) are converted into LineString geometries, and tracks (`<trk>`) + * into MultiLineString. Any properties on route and track waypoints are ignored. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable + */ +ol.format.GPX.prototype.readFeature; + + +/** + * @inheritDoc + */ +ol.format.GPX.prototype.readFeatureFromNode = function(node, opt_options) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + if (!ol.array.includes(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) { + return null; + } + var featureReader = ol.format.GPX.FEATURE_READER_[node.localName]; + if (!featureReader) { + return null; + } + var feature = featureReader(node, [this.getReadOptions(node, opt_options)]); + if (!feature) { + return null; + } + this.handleReadExtensions_([feature]); + return feature; +}; + + +/** + * Read all features from a GPX source. + * Routes (`<rte>`) are converted into LineString geometries, and tracks (`<trk>`) + * into MultiLineString. Any properties on route and track waypoints are ignored. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.GPX.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.GPX.prototype.readFeaturesFromNode = function(node, opt_options) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + if (!ol.array.includes(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) { + return []; + } + if (node.localName == 'gpx') { + /** @type {Array.<ol.Feature>} */ + var features = ol.xml.pushParseAndPop([], ol.format.GPX.GPX_PARSERS_, + node, [this.getReadOptions(node, opt_options)]); + if (features) { + this.handleReadExtensions_(features); + return features; + } else { + return []; + } + } + return []; +}; + + +/** + * Read the projection from a GPX source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.GPX.prototype.readProjection; + + +/** + * @param {Node} node Node. + * @param {string} value Value for the link's `href` attribute. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GPX.writeLink_ = function(node, value, objectStack) { + node.setAttribute('href', value); + var context = objectStack[objectStack.length - 1]; + var properties = context['properties']; + var link = [ + properties['linkText'], + properties['linkType'] + ]; + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ ({node: node}), + ol.format.GPX.LINK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + link, objectStack, ol.format.GPX.LINK_SEQUENCE_); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) { + var context = objectStack[objectStack.length - 1]; + var parentNode = context.node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode), + 'parentNode should be an XML node'); + var namespaceURI = parentNode.namespaceURI; + var properties = context['properties']; + //FIXME Projection handling + ol.xml.setAttributeNS(node, null, 'lat', coordinate[1]); + ol.xml.setAttributeNS(node, null, 'lon', coordinate[0]); + var geometryLayout = context['geometryLayout']; + switch (geometryLayout) { + case ol.geom.GeometryLayout.XYZM: + if (coordinate[3] !== 0) { + properties['time'] = coordinate[3]; + } + // fall through + case ol.geom.GeometryLayout.XYZ: + if (coordinate[2] !== 0) { + properties['ele'] = coordinate[2]; + } + break; + case ol.geom.GeometryLayout.XYM: + if (coordinate[2] !== 0) { + properties['time'] = coordinate[2]; + } + break; + default: + // pass + } + var orderedKeys = (node.nodeName == 'rtept') ? + ol.format.GPX.RTEPT_TYPE_SEQUENCE_[namespaceURI] : + ol.format.GPX.WPT_TYPE_SEQUENCE_[namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ + ({node: node, 'properties': properties}), + ol.format.GPX.WPT_TYPE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.writeRte_ = function(node, feature, objectStack) { + var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); + var properties = feature.getProperties(); + var context = {node: node, 'properties': properties}; + var geometry = feature.getGeometry(); + if (geometry) { + geometry = /** @type {ol.geom.LineString} */ + (ol.format.Feature.transformWithOptions(geometry, true, options)); + context['geometryLayout'] = geometry.getLayout(); + properties['rtept'] = geometry.getCoordinates(); + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.GPX.RTE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, + ol.format.GPX.RTE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.writeTrk_ = function(node, feature, objectStack) { + var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); + var properties = feature.getProperties(); + /** @type {ol.XmlNodeStackItem} */ + var context = {node: node, 'properties': properties}; + var geometry = feature.getGeometry(); + if (geometry) { + geometry = /** @type {ol.geom.MultiLineString} */ + (ol.format.Feature.transformWithOptions(geometry, true, options)); + properties['trkseg'] = geometry.getLineStrings(); + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.GPX.TRK_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, + ol.format.GPX.TRK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LineString} lineString LineString. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.writeTrkSeg_ = function(node, lineString, objectStack) { + /** @type {ol.XmlNodeStackItem} */ + var context = {node: node, 'geometryLayout': lineString.getLayout(), + 'properties': {}}; + ol.xml.pushSerializeAndPop(context, + ol.format.GPX.TRKSEG_SERIALIZERS_, ol.format.GPX.TRKSEG_NODE_FACTORY_, + lineString.getCoordinates(), objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.writeWpt_ = function(node, feature, objectStack) { + var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); + var context = objectStack[objectStack.length - 1]; + context['properties'] = feature.getProperties(); + var geometry = feature.getGeometry(); + if (geometry) { + geometry = /** @type {ol.geom.Point} */ + (ol.format.Feature.transformWithOptions(geometry, true, options)); + context['geometryLayout'] = geometry.getLayout(); + ol.format.GPX.writeWptType_(node, geometry.getCoordinates(), objectStack); + } +}; + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type']; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GPX.LINK_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'text': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'rtept' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GPX.RTE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), + 'number': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'rtept': ol.xml.makeArraySerializer(ol.xml.makeChildAppender( + ol.format.GPX.writeWptType_)) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.GPX.RTEPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'ele', 'time' + ]); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'trkseg' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GPX.TRK_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), + 'number': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'trkseg': ol.xml.makeArraySerializer(ol.xml.makeChildAppender( + ol.format.GPX.writeTrkSeg_)) + }); + + +/** + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt'); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GPX.TRKSEG_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'trkpt': ol.xml.makeChildAppender(ol.format.GPX.writeWptType_) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.GPX.WPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'ele', 'time', 'magvar', 'geoidheight', 'name', 'cmt', 'desc', 'src', + 'link', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop', 'pdop', + 'ageofdgpsdata', 'dgpsid' + ]); + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GPX.WPT_TYPE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'time': ol.xml.makeChildAppender(ol.format.XSD.writeDateTimeTextNode), + 'magvar': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'geoidheight': ol.xml.makeChildAppender( + ol.format.XSD.writeDecimalTextNode), + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), + 'sym': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'fix': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'sat': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode), + 'hdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'vdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'pdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'ageofdgpsdata': ol.xml.makeChildAppender( + ol.format.XSD.writeDecimalTextNode), + 'dgpsid': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode) + }); + + +/** + * @const + * @type {Object.<string, string>} + * @private + */ +ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = { + 'Point': 'wpt', + 'LineString': 'rte', + 'MultiLineString': 'trk' +}; + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private + */ +ol.format.GPX.GPX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + var geometry = /** @type {ol.Feature} */ (value).getGeometry(); + if (geometry) { + var nodeName = ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_[geometry.getType()]; + if (nodeName) { + var parentNode = objectStack[objectStack.length - 1].node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode), + 'parentNode should be an XML node'); + return ol.xml.createElementNS(parentNode.namespaceURI, nodeName); + } + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GPX.GPX_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'rte': ol.xml.makeChildAppender(ol.format.GPX.writeRte_), + 'trk': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_), + 'wpt': ol.xml.makeChildAppender(ol.format.GPX.writeWpt_) + }); + + +/** + * Encode an array of features in the GPX format. + * LineString geometries are output as routes (`<rte>`), and MultiLineString + * as tracks (`<trk>`). + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Result. + * @api stable + */ +ol.format.GPX.prototype.writeFeatures; + + +/** + * Encode an array of features in the GPX format as an XML node. + * LineString geometries are output as routes (`<rte>`), and MultiLineString + * as tracks (`<trk>`). + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api + */ +ol.format.GPX.prototype.writeFeaturesNode = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + //FIXME Serialize metadata + var gpx = ol.xml.createElementNS('http://www.topografix.com/GPX/1/1', 'gpx'); + var xmlnsUri = 'http://www.w3.org/2000/xmlns/'; + var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance'; + ol.xml.setAttributeNS(gpx, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri); + ol.xml.setAttributeNS(gpx, xmlSchemaInstanceUri, 'xsi:schemaLocation', + ol.format.GPX.SCHEMA_LOCATION_); + gpx.setAttribute('version', '1.1'); + gpx.setAttribute('creator', 'OpenLayers 3'); + + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ + ({node: gpx}), ol.format.GPX.GPX_SERIALIZERS_, + ol.format.GPX.GPX_NODE_FACTORY_, features, [opt_options]); + return gpx; +}; + +goog.provide('ol.format.TextFeature'); + +goog.require('ol'); +goog.require('ol.format.Feature'); +goog.require('ol.format.FormatType'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for text feature formats. + * + * @constructor + * @extends {ol.format.Feature} + */ +ol.format.TextFeature = function() { + ol.format.Feature.call(this); +}; +ol.inherits(ol.format.TextFeature, ol.format.Feature); + + +/** + * @param {Document|Node|Object|string} source Source. + * @private + * @return {string} Text. + */ +ol.format.TextFeature.prototype.getText_ = function(source) { + if (typeof source === 'string') { + return source; + } else { + return ''; + } +}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.getType = function() { + return ol.format.FormatType.TEXT; +}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.readFeature = function(source, opt_options) { + return this.readFeatureFromText( + this.getText_(source), this.adaptOptions(opt_options)); +}; + + +/** + * @abstract + * @param {string} text Text. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {ol.Feature} Feature. + */ +ol.format.TextFeature.prototype.readFeatureFromText = function(text, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.readFeatures = function(source, opt_options) { + return this.readFeaturesFromText( + this.getText_(source), this.adaptOptions(opt_options)); +}; + + +/** + * @abstract + * @param {string} text Text. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {Array.<ol.Feature>} Features. + */ +ol.format.TextFeature.prototype.readFeaturesFromText = function(text, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.readGeometry = function(source, opt_options) { + return this.readGeometryFromText( + this.getText_(source), this.adaptOptions(opt_options)); +}; + + +/** + * @abstract + * @param {string} text Text. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.TextFeature.prototype.readGeometryFromText = function(text, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.readProjection = function(source) { + return this.readProjectionFromText(this.getText_(source)); +}; + + +/** + * @param {string} text Text. + * @protected + * @return {ol.proj.Projection} Projection. + */ +ol.format.TextFeature.prototype.readProjectionFromText = function(text) { + return this.defaultDataProjection; +}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.writeFeature = function(feature, opt_options) { + return this.writeFeatureText(feature, this.adaptOptions(opt_options)); +}; + + +/** + * @abstract + * @param {ol.Feature} feature Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @protected + * @return {string} Text. + */ +ol.format.TextFeature.prototype.writeFeatureText = function(feature, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.writeFeatures = function( + features, opt_options) { + return this.writeFeaturesText(features, this.adaptOptions(opt_options)); +}; + + +/** + * @abstract + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @protected + * @return {string} Text. + */ +ol.format.TextFeature.prototype.writeFeaturesText = function(features, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.writeGeometry = function( + geometry, opt_options) { + return this.writeGeometryText(geometry, this.adaptOptions(opt_options)); +}; + + +/** + * @abstract + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @protected + * @return {string} Text. + */ +ol.format.TextFeature.prototype.writeGeometryText = function(geometry, opt_options) {}; + +goog.provide('ol.format.IGC'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.TextFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Feature format for `*.igc` flight recording files. + * + * @constructor + * @extends {ol.format.TextFeature} + * @param {olx.format.IGCOptions=} opt_options Options. + * @api + */ +ol.format.IGC = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.TextFeature.call(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); + + /** + * @private + * @type {ol.format.IGC.Z} + */ + this.altitudeMode_ = options.altitudeMode ? + options.altitudeMode : ol.format.IGC.Z.NONE; + +}; +ol.inherits(ol.format.IGC, ol.format.TextFeature); + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.IGC.EXTENSIONS_ = ['.igc']; + + +/** + * @const + * @type {RegExp} + * @private + */ +ol.format.IGC.B_RECORD_RE_ = + /^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/; + + +/** + * @const + * @type {RegExp} + * @private + */ +ol.format.IGC.H_RECORD_RE_ = /^H.([A-Z]{3}).*?:(.*)/; + + +/** + * @const + * @type {RegExp} + * @private + */ +ol.format.IGC.HFDTE_RECORD_RE_ = /^HFDTE(\d{2})(\d{2})(\d{2})/; + + +/** + * A regular expression matching the newline characters `\r\n`, `\r` and `\n`. + * + * @const + * @type {RegExp} + * @private + */ +ol.format.IGC.NEWLINE_RE_ = /\r\n|\r|\n/; + + +/** + * @inheritDoc + */ +ol.format.IGC.prototype.getExtensions = function() { + return ol.format.IGC.EXTENSIONS_; +}; + + +/** + * Read the feature from the IGC source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api + */ +ol.format.IGC.prototype.readFeature; + + +/** + * @inheritDoc + */ +ol.format.IGC.prototype.readFeatureFromText = function(text, opt_options) { + var altitudeMode = this.altitudeMode_; + var lines = text.split(ol.format.IGC.NEWLINE_RE_); + /** @type {Object.<string, string>} */ + var properties = {}; + var flatCoordinates = []; + var year = 2000; + var month = 0; + var day = 1; + var lastDateTime = -1; + var i, ii; + for (i = 0, ii = lines.length; i < ii; ++i) { + var line = lines[i]; + var m; + if (line.charAt(0) == 'B') { + m = ol.format.IGC.B_RECORD_RE_.exec(line); + if (m) { + var hour = parseInt(m[1], 10); + var minute = parseInt(m[2], 10); + var second = parseInt(m[3], 10); + var y = parseInt(m[4], 10) + parseInt(m[5], 10) / 60000; + if (m[6] == 'S') { + y = -y; + } + var x = parseInt(m[7], 10) + parseInt(m[8], 10) / 60000; + if (m[9] == 'W') { + x = -x; + } + flatCoordinates.push(x, y); + if (altitudeMode != ol.format.IGC.Z.NONE) { + var z; + if (altitudeMode == ol.format.IGC.Z.GPS) { + z = parseInt(m[11], 10); + } else if (altitudeMode == ol.format.IGC.Z.BAROMETRIC) { + z = parseInt(m[12], 10); + } else { + ol.DEBUG && console.assert(false, 'Unknown altitude mode.'); + z = 0; + } + flatCoordinates.push(z); + } + var dateTime = Date.UTC(year, month, day, hour, minute, second); + // Detect UTC midnight wrap around. + if (dateTime < lastDateTime) { + dateTime = Date.UTC(year, month, day + 1, hour, minute, second); + } + flatCoordinates.push(dateTime / 1000); + lastDateTime = dateTime; + } + } else if (line.charAt(0) == 'H') { + m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line); + if (m) { + day = parseInt(m[1], 10); + month = parseInt(m[2], 10) - 1; + year = 2000 + parseInt(m[3], 10); + } else { + m = ol.format.IGC.H_RECORD_RE_.exec(line); + if (m) { + properties[m[1]] = m[2].trim(); + } + } + } + } + if (flatCoordinates.length === 0) { + return null; + } + var lineString = new ol.geom.LineString(null); + var layout = altitudeMode == ol.format.IGC.Z.NONE ? + ol.geom.GeometryLayout.XYM : ol.geom.GeometryLayout.XYZM; + lineString.setFlatCoordinates(layout, flatCoordinates); + var feature = new ol.Feature(ol.format.Feature.transformWithOptions( + lineString, false, opt_options)); + feature.setProperties(properties); + return feature; +}; + + +/** + * Read the feature from the source. As IGC sources contain a single + * feature, this will return the feature in an array. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api + */ +ol.format.IGC.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.IGC.prototype.readFeaturesFromText = function(text, opt_options) { + var feature = this.readFeatureFromText(text, opt_options); + if (feature) { + return [feature]; + } else { + return []; + } +}; + + +/** + * Read the projection from the IGC source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api + */ +ol.format.IGC.prototype.readProjection; + + +/** + * IGC altitude/z. One of 'barometric', 'gps', 'none'. + * @enum {string} + */ +ol.format.IGC.Z = { + BAROMETRIC: 'barometric', + GPS: 'gps', + NONE: 'none' +}; + +goog.provide('ol.style.IconImage'); + +goog.require('ol'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); +goog.require('ol.events.EventType'); +goog.require('ol.Image'); +goog.require('ol.style'); + + +/** + * @constructor + * @param {Image|HTMLCanvasElement} image Image. + * @param {string|undefined} src Src. + * @param {ol.Size} size Size. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Image.State} imageState Image state. + * @param {ol.Color} color Color. + * @extends {ol.events.EventTarget} + */ +ol.style.IconImage = function(image, src, size, crossOrigin, imageState, + color) { + + ol.events.EventTarget.call(this); + + /** + * @private + * @type {Image|HTMLCanvasElement} + */ + this.hitDetectionImage_ = null; + + /** + * @private + * @type {Image|HTMLCanvasElement} + */ + this.image_ = !image ? new Image() : image; + + if (crossOrigin !== null) { + this.image_.crossOrigin = crossOrigin; + } + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = color ? + /** @type {HTMLCanvasElement} */ (document.createElement('CANVAS')) : + null; + + /** + * @private + * @type {ol.Color} + */ + this.color_ = color; + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.imageListenerKeys_ = null; + + /** + * @private + * @type {ol.Image.State} + */ + this.imageState_ = imageState; + + /** + * @private + * @type {ol.Size} + */ + this.size_ = size; + + /** + * @private + * @type {string|undefined} + */ + this.src_ = src; + + /** + * @private + * @type {boolean} + */ + this.tainting_ = false; + if (this.imageState_ == ol.Image.State.LOADED) { + this.determineTainting_(); + } + +}; +ol.inherits(ol.style.IconImage, ol.events.EventTarget); + + +/** + * @param {Image|HTMLCanvasElement} image Image. + * @param {string} src Src. + * @param {ol.Size} size Size. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Image.State} imageState Image state. + * @param {ol.Color} color Color. + * @return {ol.style.IconImage} Icon image. + */ +ol.style.IconImage.get = function(image, src, size, crossOrigin, imageState, + color) { + var iconImageCache = ol.style.iconImageCache; + var iconImage = iconImageCache.get(src, crossOrigin, color); + if (!iconImage) { + iconImage = new ol.style.IconImage( + image, src, size, crossOrigin, imageState, color); + iconImageCache.set(src, crossOrigin, color, iconImage); + } + return iconImage; +}; + + +/** + * @private + */ +ol.style.IconImage.prototype.determineTainting_ = function() { + var context = ol.dom.createCanvasContext2D(1, 1); + try { + context.drawImage(this.image_, 0, 0); + context.getImageData(0, 0, 1, 1); + } catch (e) { + this.tainting_ = true; + } +}; + + +/** + * @private + */ +ol.style.IconImage.prototype.dispatchChangeEvent_ = function() { + this.dispatchEvent(ol.events.EventType.CHANGE); +}; + + +/** + * @private + */ +ol.style.IconImage.prototype.handleImageError_ = function() { + this.imageState_ = ol.Image.State.ERROR; + this.unlistenImage_(); + this.dispatchChangeEvent_(); +}; + + +/** + * @private + */ +ol.style.IconImage.prototype.handleImageLoad_ = function() { + this.imageState_ = ol.Image.State.LOADED; + if (this.size_) { + this.image_.width = this.size_[0]; + this.image_.height = this.size_[1]; + } + this.size_ = [this.image_.width, this.image_.height]; + this.unlistenImage_(); + this.determineTainting_(); + this.replaceColor_(); + this.dispatchChangeEvent_(); +}; + + +/** + * @param {number} pixelRatio Pixel ratio. + * @return {Image|HTMLCanvasElement} Image or Canvas element. + */ +ol.style.IconImage.prototype.getImage = function(pixelRatio) { + return this.canvas_ ? this.canvas_ : this.image_; +}; + + +/** + * @return {ol.Image.State} Image state. + */ +ol.style.IconImage.prototype.getImageState = function() { + return this.imageState_; +}; + + +/** + * @param {number} pixelRatio Pixel ratio. + * @return {Image|HTMLCanvasElement} Image element. + */ +ol.style.IconImage.prototype.getHitDetectionImage = function(pixelRatio) { + if (!this.hitDetectionImage_) { + if (this.tainting_) { + var width = this.size_[0]; + var height = this.size_[1]; + var context = ol.dom.createCanvasContext2D(width, height); + context.fillRect(0, 0, width, height); + this.hitDetectionImage_ = context.canvas; + } else { + this.hitDetectionImage_ = this.image_; + } + } + return this.hitDetectionImage_; +}; + + +/** + * @return {ol.Size} Image size. + */ +ol.style.IconImage.prototype.getSize = function() { + return this.size_; +}; + + +/** + * @return {string|undefined} Image src. + */ +ol.style.IconImage.prototype.getSrc = function() { + return this.src_; +}; + + +/** + * Load not yet loaded URI. + */ +ol.style.IconImage.prototype.load = function() { + if (this.imageState_ == ol.Image.State.IDLE) { + ol.DEBUG && console.assert(this.src_ !== undefined, + 'this.src_ must not be undefined'); + ol.DEBUG && console.assert(!this.imageListenerKeys_, + 'no listener keys existing'); + this.imageState_ = ol.Image.State.LOADING; + this.imageListenerKeys_ = [ + ol.events.listenOnce(this.image_, ol.events.EventType.ERROR, + this.handleImageError_, this), + ol.events.listenOnce(this.image_, ol.events.EventType.LOAD, + this.handleImageLoad_, this) + ]; + try { + this.image_.src = this.src_; + } catch (e) { + this.handleImageError_(); + } + } +}; + + +/** + * @private + */ +ol.style.IconImage.prototype.replaceColor_ = function() { + if (this.tainting_ || this.color_ === null) { + return; + } + + this.canvas_.width = this.image_.width; + this.canvas_.height = this.image_.height; + + var ctx = this.canvas_.getContext('2d'); + ctx.drawImage(this.image_, 0, 0); + + var imgData = ctx.getImageData(0, 0, this.image_.width, this.image_.height); + var data = imgData.data; + var r = this.color_[0] / 255.0; + var g = this.color_[1] / 255.0; + var b = this.color_[2] / 255.0; + + for (var i = 0, ii = data.length; i < ii; i += 4) { + data[i] *= r; + data[i + 1] *= g; + data[i + 2] *= b; + } + ctx.putImageData(imgData, 0, 0); +}; + + +/** + * Discards event handlers which listen for load completion or errors. + * + * @private + */ +ol.style.IconImage.prototype.unlistenImage_ = function() { + this.imageListenerKeys_.forEach(ol.events.unlistenByKey); + this.imageListenerKeys_ = null; +}; + +goog.provide('ol.style.Icon'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.color'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.Image'); +goog.require('ol.style.IconImage'); +goog.require('ol.style.Image'); + + +/** + * @classdesc + * Set icon style for vector features. + * + * @constructor + * @param {olx.style.IconOptions=} opt_options Options. + * @extends {ol.style.Image} + * @api + */ +ol.style.Icon = function(opt_options) { + + var options = opt_options || {}; + + /** + * @private + * @type {Array.<number>} + */ + this.anchor_ = options.anchor !== undefined ? options.anchor : [0.5, 0.5]; + + /** + * @private + * @type {Array.<number>} + */ + this.normalizedAnchor_ = null; + + /** + * @private + * @type {ol.style.Icon.Origin} + */ + this.anchorOrigin_ = options.anchorOrigin !== undefined ? + options.anchorOrigin : ol.style.Icon.Origin.TOP_LEFT; + + /** + * @private + * @type {ol.style.Icon.AnchorUnits} + */ + this.anchorXUnits_ = options.anchorXUnits !== undefined ? + options.anchorXUnits : ol.style.Icon.AnchorUnits.FRACTION; + + /** + * @private + * @type {ol.style.Icon.AnchorUnits} + */ + this.anchorYUnits_ = options.anchorYUnits !== undefined ? + options.anchorYUnits : ol.style.Icon.AnchorUnits.FRACTION; + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + options.crossOrigin !== undefined ? options.crossOrigin : null; + + /** + * @type {Image|HTMLCanvasElement} + */ + var image = options.img !== undefined ? options.img : null; + + /** + * @type {ol.Size} + */ + var imgSize = options.imgSize !== undefined ? options.imgSize : null; + + /** + * @type {string|undefined} + */ + var src = options.src; + + ol.asserts.assert(!(src !== undefined && image), + 4); // `image` and `src` cannot be provided at the same time + ol.asserts.assert(!image || (image && imgSize), + 5); // `imgSize` must be set when `image` is provided + + if ((src === undefined || src.length === 0) && image) { + src = image.src || ol.getUid(image).toString(); + } + ol.asserts.assert(src !== undefined && src.length > 0, + 6); // A defined and non-empty `src` or `image` must be provided + + /** + * @type {ol.Image.State} + */ + var imageState = options.src !== undefined ? + ol.Image.State.IDLE : ol.Image.State.LOADED; + + /** + * @private + * @type {ol.Color} + */ + this.color_ = options.color !== undefined ? ol.color.asArray(options.color) : + null; + + /** + * @private + * @type {ol.style.IconImage} + */ + this.iconImage_ = ol.style.IconImage.get( + image, /** @type {string} */ (src), imgSize, this.crossOrigin_, imageState, this.color_); + + /** + * @private + * @type {Array.<number>} + */ + this.offset_ = options.offset !== undefined ? options.offset : [0, 0]; + + /** + * @private + * @type {ol.style.Icon.Origin} + */ + this.offsetOrigin_ = options.offsetOrigin !== undefined ? + options.offsetOrigin : ol.style.Icon.Origin.TOP_LEFT; + + /** + * @private + * @type {Array.<number>} + */ + this.origin_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.size_ = options.size !== undefined ? options.size : null; + + /** + * @type {number} + */ + var opacity = options.opacity !== undefined ? options.opacity : 1; + + /** + * @type {boolean} + */ + var rotateWithView = options.rotateWithView !== undefined ? + options.rotateWithView : false; + + /** + * @type {number} + */ + var rotation = options.rotation !== undefined ? options.rotation : 0; + + /** + * @type {number} + */ + var scale = options.scale !== undefined ? options.scale : 1; + + /** + * @type {boolean} + */ + var snapToPixel = options.snapToPixel !== undefined ? + options.snapToPixel : true; + + ol.style.Image.call(this, { + opacity: opacity, + rotation: rotation, + scale: scale, + snapToPixel: snapToPixel, + rotateWithView: rotateWithView + }); + +}; +ol.inherits(ol.style.Icon, ol.style.Image); + + +/** + * Clones the style. + * @return {ol.style.Icon} The cloned style. + * @api + */ +ol.style.Icon.prototype.clone = function() { + var oldImage = this.getImage(1); + var newImage; + if (this.iconImage_.getImageState() === ol.Image.State.LOADED) { + if (oldImage.tagName.toUpperCase() === 'IMG') { + newImage = /** @type {Image} */ (oldImage.cloneNode(true)); + } else { + newImage = /** @type {HTMLCanvasElement} */ (document.createElement('canvas')); + var context = newImage.getContext('2d'); + newImage.width = oldImage.width; + newImage.height = oldImage.height; + context.drawImage(oldImage, 0, 0); + } + } + return new ol.style.Icon({ + anchor: this.anchor_.slice(), + anchorOrigin: this.anchorOrigin_, + anchorXUnits: this.anchorXUnits_, + anchorYUnits: this.anchorYUnits_, + crossOrigin: this.crossOrigin_, + color: (this.color_ && this.color_.slice) ? this.color_.slice() : this.color_ || undefined, + img: newImage ? newImage : undefined, + imgSize: newImage ? this.iconImage_.getSize().slice() : undefined, + src: newImage ? undefined : this.getSrc(), + offset: this.offset_.slice(), + offsetOrigin: this.offsetOrigin_, + size: this.size_ !== null ? this.size_.slice() : undefined, + opacity: this.getOpacity(), + scale: this.getScale(), + snapToPixel: this.getSnapToPixel(), + rotation: this.getRotation(), + rotateWithView: this.getRotateWithView() + }); +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.Icon.prototype.getAnchor = function() { + if (this.normalizedAnchor_) { + return this.normalizedAnchor_; + } + var anchor = this.anchor_; + var size = this.getSize(); + if (this.anchorXUnits_ == ol.style.Icon.AnchorUnits.FRACTION || + this.anchorYUnits_ == ol.style.Icon.AnchorUnits.FRACTION) { + if (!size) { + return null; + } + anchor = this.anchor_.slice(); + if (this.anchorXUnits_ == ol.style.Icon.AnchorUnits.FRACTION) { + anchor[0] *= size[0]; + } + if (this.anchorYUnits_ == ol.style.Icon.AnchorUnits.FRACTION) { + anchor[1] *= size[1]; + } + } + + if (this.anchorOrigin_ != ol.style.Icon.Origin.TOP_LEFT) { + if (!size) { + return null; + } + if (anchor === this.anchor_) { + anchor = this.anchor_.slice(); + } + if (this.anchorOrigin_ == ol.style.Icon.Origin.TOP_RIGHT || + this.anchorOrigin_ == ol.style.Icon.Origin.BOTTOM_RIGHT) { + anchor[0] = -anchor[0] + size[0]; + } + if (this.anchorOrigin_ == ol.style.Icon.Origin.BOTTOM_LEFT || + this.anchorOrigin_ == ol.style.Icon.Origin.BOTTOM_RIGHT) { + anchor[1] = -anchor[1] + size[1]; + } + } + this.normalizedAnchor_ = anchor; + return this.normalizedAnchor_; +}; + + +/** + * Get the icon color. + * @return {ol.Color} Color. + * @api + */ +ol.style.Icon.prototype.getColor = function() { + return this.color_; +}; + + +/** + * Get the image icon. + * @param {number} pixelRatio Pixel ratio. + * @return {Image|HTMLCanvasElement} Image or Canvas element. + * @api + */ +ol.style.Icon.prototype.getImage = function(pixelRatio) { + return this.iconImage_.getImage(pixelRatio); +}; + + +/** + * Real Image size used. + * @return {ol.Size} Size. + */ +ol.style.Icon.prototype.getImageSize = function() { + return this.iconImage_.getSize(); +}; + + +/** + * @inheritDoc + */ +ol.style.Icon.prototype.getHitDetectionImageSize = function() { + return this.getImageSize(); +}; + + +/** + * @inheritDoc + */ +ol.style.Icon.prototype.getImageState = function() { + return this.iconImage_.getImageState(); +}; + + +/** + * @inheritDoc + */ +ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) { + return this.iconImage_.getHitDetectionImage(pixelRatio); +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.Icon.prototype.getOrigin = function() { + if (this.origin_) { + return this.origin_; + } + var offset = this.offset_; + + if (this.offsetOrigin_ != ol.style.Icon.Origin.TOP_LEFT) { + var size = this.getSize(); + var iconImageSize = this.iconImage_.getSize(); + if (!size || !iconImageSize) { + return null; + } + offset = offset.slice(); + if (this.offsetOrigin_ == ol.style.Icon.Origin.TOP_RIGHT || + this.offsetOrigin_ == ol.style.Icon.Origin.BOTTOM_RIGHT) { + offset[0] = iconImageSize[0] - size[0] - offset[0]; + } + if (this.offsetOrigin_ == ol.style.Icon.Origin.BOTTOM_LEFT || + this.offsetOrigin_ == ol.style.Icon.Origin.BOTTOM_RIGHT) { + offset[1] = iconImageSize[1] - size[1] - offset[1]; + } + } + this.origin_ = offset; + return this.origin_; +}; + + +/** + * Get the image URL. + * @return {string|undefined} Image src. + * @api + */ +ol.style.Icon.prototype.getSrc = function() { + return this.iconImage_.getSrc(); +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.Icon.prototype.getSize = function() { + return !this.size_ ? this.iconImage_.getSize() : this.size_; +}; + + +/** + * @inheritDoc + */ +ol.style.Icon.prototype.listenImageChange = function(listener, thisArg) { + return ol.events.listen(this.iconImage_, ol.events.EventType.CHANGE, + listener, thisArg); +}; + + +/** + * Load not yet loaded URI. + * When rendering a feature with an icon style, the vector renderer will + * automatically call this method. However, you might want to call this + * method yourself for preloading or other purposes. + * @api + */ +ol.style.Icon.prototype.load = function() { + this.iconImage_.load(); +}; + + +/** + * @inheritDoc + */ +ol.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) { + ol.events.unlisten(this.iconImage_, ol.events.EventType.CHANGE, + listener, thisArg); +}; + + +/** + * Icon anchor units. One of 'fraction', 'pixels'. + * @enum {string} + */ +ol.style.Icon.AnchorUnits = { + FRACTION: 'fraction', + PIXELS: 'pixels' +}; + + +/** + * Icon origin. One of 'bottom-left', 'bottom-right', 'top-left', 'top-right'. + * @enum {string} + */ +ol.style.Icon.Origin = { + BOTTOM_LEFT: 'bottom-left', + BOTTOM_RIGHT: 'bottom-right', + TOP_LEFT: 'top-left', + TOP_RIGHT: 'top-right' +}; + +goog.provide('ol.style.Text'); + + +goog.require('ol.style.Fill'); + + +/** + * @classdesc + * Set text style for vector features. + * + * @constructor + * @param {olx.style.TextOptions=} opt_options Options. + * @api + */ +ol.style.Text = function(opt_options) { + + var options = opt_options || {}; + + /** + * @private + * @type {string|undefined} + */ + this.font_ = options.font; + + /** + * @private + * @type {number|undefined} + */ + this.rotation_ = options.rotation; + + /** + * @private + * @type {boolean|undefined} + */ + this.rotateWithView_ = options.rotateWithView; + + /** + * @private + * @type {number|undefined} + */ + this.scale_ = options.scale; + + /** + * @private + * @type {string|undefined} + */ + this.text_ = options.text; + + /** + * @private + * @type {string|undefined} + */ + this.textAlign_ = options.textAlign; + + /** + * @private + * @type {string|undefined} + */ + this.textBaseline_ = options.textBaseline; + + /** + * @private + * @type {ol.style.Fill} + */ + this.fill_ = options.fill !== undefined ? options.fill : + new ol.style.Fill({color: ol.style.Text.DEFAULT_FILL_COLOR_}); + + /** + * @private + * @type {ol.style.Stroke} + */ + this.stroke_ = options.stroke !== undefined ? options.stroke : null; + + /** + * @private + * @type {number} + */ + this.offsetX_ = options.offsetX !== undefined ? options.offsetX : 0; + + /** + * @private + * @type {number} + */ + this.offsetY_ = options.offsetY !== undefined ? options.offsetY : 0; +}; + + +/** + * The default fill color to use if no fill was set at construction time; a + * blackish `#333`. + * + * @const {string} + * @private + */ +ol.style.Text.DEFAULT_FILL_COLOR_ = '#333'; + + +/** + * Clones the style. + * @return {ol.style.Text} The cloned style. + * @api + */ +ol.style.Text.prototype.clone = function() { + return new ol.style.Text({ + font: this.getFont(), + rotation: this.getRotation(), + rotateWithView: this.getRotateWithView(), + scale: this.getScale(), + text: this.getText(), + textAlign: this.getTextAlign(), + textBaseline: this.getTextBaseline(), + fill: this.getFill() ? this.getFill().clone() : undefined, + stroke: this.getStroke() ? this.getStroke().clone() : undefined, + offsetX: this.getOffsetX(), + offsetY: this.getOffsetY() + }); +}; + + +/** + * Get the font name. + * @return {string|undefined} Font. + * @api + */ +ol.style.Text.prototype.getFont = function() { + return this.font_; +}; + + +/** + * Get the x-offset for the text. + * @return {number} Horizontal text offset. + * @api + */ +ol.style.Text.prototype.getOffsetX = function() { + return this.offsetX_; +}; + + +/** + * Get the y-offset for the text. + * @return {number} Vertical text offset. + * @api + */ +ol.style.Text.prototype.getOffsetY = function() { + return this.offsetY_; +}; + + +/** + * Get the fill style for the text. + * @return {ol.style.Fill} Fill style. + * @api + */ +ol.style.Text.prototype.getFill = function() { + return this.fill_; +}; + + +/** + * Determine whether the text rotates with the map. + * @return {boolean|undefined} Rotate with map. + * @api + */ +ol.style.Text.prototype.getRotateWithView = function() { + return this.rotateWithView_; +}; + + +/** + * Get the text rotation. + * @return {number|undefined} Rotation. + * @api + */ +ol.style.Text.prototype.getRotation = function() { + return this.rotation_; +}; + + +/** + * Get the text scale. + * @return {number|undefined} Scale. + * @api + */ +ol.style.Text.prototype.getScale = function() { + return this.scale_; +}; + + +/** + * Get the stroke style for the text. + * @return {ol.style.Stroke} Stroke style. + * @api + */ +ol.style.Text.prototype.getStroke = function() { + return this.stroke_; +}; + + +/** + * Get the text to be rendered. + * @return {string|undefined} Text. + * @api + */ +ol.style.Text.prototype.getText = function() { + return this.text_; +}; + + +/** + * Get the text alignment. + * @return {string|undefined} Text align. + * @api + */ +ol.style.Text.prototype.getTextAlign = function() { + return this.textAlign_; +}; + + +/** + * Get the text baseline. + * @return {string|undefined} Text baseline. + * @api + */ +ol.style.Text.prototype.getTextBaseline = function() { + return this.textBaseline_; +}; + + +/** + * Set the font. + * + * @param {string|undefined} font Font. + * @api + */ +ol.style.Text.prototype.setFont = function(font) { + this.font_ = font; +}; + + +/** + * Set the x offset. + * + * @param {number} offsetX Horizontal text offset. + * @api + */ +ol.style.Text.prototype.setOffsetX = function(offsetX) { + this.offsetX_ = offsetX; +}; + + +/** + * Set the y offset. + * + * @param {number} offsetY Vertical text offset. + * @api + */ +ol.style.Text.prototype.setOffsetY = function(offsetY) { + this.offsetY_ = offsetY; +}; + + +/** + * Set the fill. + * + * @param {ol.style.Fill} fill Fill style. + * @api + */ +ol.style.Text.prototype.setFill = function(fill) { + this.fill_ = fill; +}; + + +/** + * Set the rotation. + * + * @param {number|undefined} rotation Rotation. + * @api + */ +ol.style.Text.prototype.setRotation = function(rotation) { + this.rotation_ = rotation; +}; + + +/** + * Set the scale. + * + * @param {number|undefined} scale Scale. + * @api + */ +ol.style.Text.prototype.setScale = function(scale) { + this.scale_ = scale; +}; + + +/** + * Set the stroke. + * + * @param {ol.style.Stroke} stroke Stroke style. + * @api + */ +ol.style.Text.prototype.setStroke = function(stroke) { + this.stroke_ = stroke; +}; + + +/** + * Set the text. + * + * @param {string|undefined} text Text. + * @api + */ +ol.style.Text.prototype.setText = function(text) { + this.text_ = text; +}; + + +/** + * Set the text alignment. + * + * @param {string|undefined} textAlign Text align. + * @api + */ +ol.style.Text.prototype.setTextAlign = function(textAlign) { + this.textAlign_ = textAlign; +}; + + +/** + * Set the text baseline. + * + * @param {string|undefined} textBaseline Text baseline. + * @api + */ +ol.style.Text.prototype.setTextBaseline = function(textBaseline) { + this.textBaseline_ = textBaseline; +}; + +// FIXME http://earth.google.com/kml/1.0 namespace? +// FIXME why does node.getAttribute return an unknown type? +// FIXME serialize arbitrary feature properties +// FIXME don't parse style if extractStyles is false + +goog.provide('ol.format.KML'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.array'); +goog.require('ol.asserts'); +goog.require('ol.color'); +goog.require('ol.format.Feature'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.format.XSD'); +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.math'); +goog.require('ol.proj'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Icon'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Style'); +goog.require('ol.style.Text'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Feature format for reading and writing data in the KML format. + * + * Note that the KML format uses the URL() constructor. Older browsers such as IE + * which do not support this will need a URL polyfill to be loaded before use. + * + * @constructor + * @extends {ol.format.XMLFeature} + * @param {olx.format.KMLOptions=} opt_options Options. + * @api stable + */ +ol.format.KML = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.XMLFeature.call(this); + + if (!ol.format.KML.DEFAULT_STYLE_ARRAY_) { + ol.format.KML.createStyleDefaults_(); + } + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); + + /** + * @private + * @type {Array.<ol.style.Style>} + */ + this.defaultStyle_ = options.defaultStyle ? + options.defaultStyle : ol.format.KML.DEFAULT_STYLE_ARRAY_; + + /** + * @private + * @type {boolean} + */ + this.extractStyles_ = options.extractStyles !== undefined ? + options.extractStyles : true; + + /** + * @private + * @type {boolean} + */ + this.writeStyles_ = options.writeStyles !== undefined ? + options.writeStyles : true; + + /** + * @private + * @type {Object.<string, (Array.<ol.style.Style>|string)>} + */ + this.sharedStyles_ = {}; + + /** + * @private + * @type {boolean} + */ + this.showPointNames_ = options.showPointNames !== undefined ? + options.showPointNames : true; + +}; +ol.inherits(ol.format.KML, ol.format.XMLFeature); + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.KML.EXTENSIONS_ = ['.kml']; + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.KML.GX_NAMESPACE_URIS_ = [ + 'http://www.google.com/kml/ext/2.2' +]; + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.KML.NAMESPACE_URIS_ = [ + null, + 'http://earth.google.com/kml/2.0', + 'http://earth.google.com/kml/2.1', + 'http://earth.google.com/kml/2.2', + 'http://www.opengis.net/kml/2.2' +]; + + +/** + * @const + * @type {string} + * @private + */ +ol.format.KML.SCHEMA_LOCATION_ = 'http://www.opengis.net/kml/2.2 ' + + 'https://developers.google.com/kml/schema/kml22gx.xsd'; + + +/** + * @return {Array.<ol.style.Style>} Default style. + * @private + */ +ol.format.KML.createStyleDefaults_ = function() { + /** + * @const + * @type {ol.Color} + * @private + */ + ol.format.KML.DEFAULT_COLOR_ = [255, 255, 255, 1]; + + /** + * @const + * @type {ol.style.Fill} + * @private + */ + ol.format.KML.DEFAULT_FILL_STYLE_ = new ol.style.Fill({ + color: ol.format.KML.DEFAULT_COLOR_ + }); + + /** + * @const + * @type {ol.Size} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_ = [20, 2]; // FIXME maybe [8, 32] ? + + /** + * @const + * @type {ol.style.Icon.AnchorUnits} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_ = + ol.style.Icon.AnchorUnits.PIXELS; + + /** + * @const + * @type {ol.style.Icon.AnchorUnits} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_ = + ol.style.Icon.AnchorUnits.PIXELS; + + /** + * @const + * @type {ol.Size} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_ = [64, 64]; + + /** + * @const + * @type {string} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ = + 'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png'; + + /** + * @const + * @type {number} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_ = 0.5; + + /** + * @const + * @type {ol.style.Image} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_STYLE_ = new ol.style.Icon({ + anchor: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_, + anchorOrigin: ol.style.Icon.Origin.BOTTOM_LEFT, + anchorXUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_, + anchorYUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_, + crossOrigin: 'anonymous', + rotation: 0, + scale: ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_, + size: ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_, + src: ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ + }); + + /** + * @const + * @type {string} + * @private + */ + ol.format.KML.DEFAULT_NO_IMAGE_STYLE_ = 'NO_IMAGE'; + + /** + * @const + * @type {ol.style.Stroke} + * @private + */ + ol.format.KML.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({ + color: ol.format.KML.DEFAULT_COLOR_, + width: 1 + }); + + /** + * @const + * @type {ol.style.Stroke} + * @private + */ + ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_ = new ol.style.Stroke({ + color: [51, 51, 51, 1], + width: 2 + }); + + /** + * @const + * @type {ol.style.Text} + * @private + */ + ol.format.KML.DEFAULT_TEXT_STYLE_ = new ol.style.Text({ + font: 'bold 16px Helvetica', + fill: ol.format.KML.DEFAULT_FILL_STYLE_, + stroke: ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_, + scale: 0.8 + }); + + /** + * @const + * @type {ol.style.Style} + * @private + */ + ol.format.KML.DEFAULT_STYLE_ = new ol.style.Style({ + fill: ol.format.KML.DEFAULT_FILL_STYLE_, + image: ol.format.KML.DEFAULT_IMAGE_STYLE_, + text: ol.format.KML.DEFAULT_TEXT_STYLE_, + stroke: ol.format.KML.DEFAULT_STROKE_STYLE_, + zIndex: 0 + }); + + /** + * @const + * @type {Array.<ol.style.Style>} + * @private + */ + ol.format.KML.DEFAULT_STYLE_ARRAY_ = [ol.format.KML.DEFAULT_STYLE_]; + + return ol.format.KML.DEFAULT_STYLE_ARRAY_; +}; + + +/** + * @const + * @type {Object.<string, ol.style.Icon.AnchorUnits>} + * @private + */ +ol.format.KML.ICON_ANCHOR_UNITS_MAP_ = { + 'fraction': ol.style.Icon.AnchorUnits.FRACTION, + 'pixels': ol.style.Icon.AnchorUnits.PIXELS +}; + + +/** + * @param {ol.style.Style|undefined} foundStyle Style. + * @param {string} name Name. + * @return {ol.style.Style} style Style. + * @private + */ +ol.format.KML.createNameStyleFunction_ = function(foundStyle, name) { + var textStyle = null; + var textOffset = [0, 0]; + var textAlign = 'start'; + if (foundStyle.getImage()) { + var imageSize = foundStyle.getImage().getImageSize(); + if (imageSize === null) { + imageSize = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_; + } + if (imageSize.length == 2) { + var imageScale = foundStyle.getImage().getScale(); + // Offset the label to be centered to the right of the icon, if there is + // one. + textOffset[0] = imageScale * imageSize[0] / 2; + textOffset[1] = -imageScale * imageSize[1] / 2; + textAlign = 'left'; + } + } + if (foundStyle.getText() !== null) { + // clone the text style, customizing it with name, alignments and offset. + // Note that kml does not support many text options that OpenLayers does (rotation, textBaseline). + var foundText = foundStyle.getText(); + textStyle = foundText.clone(); + textStyle.setFont(foundText.getFont() || ol.format.KML.DEFAULT_TEXT_STYLE_.getFont()); + textStyle.setScale(foundText.getScale() || ol.format.KML.DEFAULT_TEXT_STYLE_.getScale()); + textStyle.setFill(foundText.getFill() || ol.format.KML.DEFAULT_TEXT_STYLE_.getFill()); + textStyle.setStroke(foundText.getStroke() || ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_); + } else { + textStyle = ol.format.KML.DEFAULT_TEXT_STYLE_.clone(); + } + textStyle.setText(name); + textStyle.setOffsetX(textOffset[0]); + textStyle.setOffsetY(textOffset[1]); + textStyle.setTextAlign(textAlign); + + var nameStyle = new ol.style.Style({ + text: textStyle + }); + return nameStyle; +}; + + +/** + * @param {Array.<ol.style.Style>|undefined} style Style. + * @param {string} styleUrl Style URL. + * @param {Array.<ol.style.Style>} defaultStyle Default style. + * @param {Object.<string, (Array.<ol.style.Style>|string)>} sharedStyles Shared + * styles. + * @param {boolean|undefined} showPointNames true to show names for point + * placemarks. + * @return {ol.FeatureStyleFunction} Feature style function. + * @private + */ +ol.format.KML.createFeatureStyleFunction_ = function(style, styleUrl, + defaultStyle, sharedStyles, showPointNames) { + + return ( + /** + * @param {number} resolution Resolution. + * @return {Array.<ol.style.Style>} Style. + * @this {ol.Feature} + */ + function(resolution) { + var drawName = showPointNames; + /** @type {ol.style.Style|undefined} */ + var nameStyle; + var name = ''; + if (drawName) { + if (this.getGeometry()) { + drawName = (this.getGeometry().getType() === + ol.geom.GeometryType.POINT); + } + } + + if (drawName) { + name = /** @type {string} */ (this.get('name')); + drawName = drawName && name; + } + + if (style) { + if (drawName) { + nameStyle = ol.format.KML.createNameStyleFunction_(style[0], + name); + return style.concat(nameStyle); + } + return style; + } + if (styleUrl) { + var foundStyle = ol.format.KML.findStyle_(styleUrl, defaultStyle, + sharedStyles); + if (drawName) { + nameStyle = ol.format.KML.createNameStyleFunction_(foundStyle[0], + name); + return foundStyle.concat(nameStyle); + } + return foundStyle; + } + if (drawName) { + nameStyle = ol.format.KML.createNameStyleFunction_(defaultStyle[0], + name); + return defaultStyle.concat(nameStyle); + } + return defaultStyle; + }); +}; + + +/** + * @param {Array.<ol.style.Style>|string|undefined} styleValue Style value. + * @param {Array.<ol.style.Style>} defaultStyle Default style. + * @param {Object.<string, (Array.<ol.style.Style>|string)>} sharedStyles + * Shared styles. + * @return {Array.<ol.style.Style>} Style. + * @private + */ +ol.format.KML.findStyle_ = function(styleValue, defaultStyle, sharedStyles) { + if (Array.isArray(styleValue)) { + return styleValue; + } else if (typeof styleValue === 'string') { + // KML files in the wild occasionally forget the leading `#` on styleUrls + // defined in the same document. Add a leading `#` if it enables to find + // a style. + if (!(styleValue in sharedStyles) && ('#' + styleValue in sharedStyles)) { + styleValue = '#' + styleValue; + } + return ol.format.KML.findStyle_( + sharedStyles[styleValue], defaultStyle, sharedStyles); + } else { + return defaultStyle; + } +}; + + +/** + * @param {Node} node Node. + * @private + * @return {ol.Color|undefined} Color. + */ +ol.format.KML.readColor_ = function(node) { + var s = ol.xml.getAllTextContent(node, false); + // The KML specification states that colors should not include a leading `#` + // but we tolerate them. + var m = /^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(s); + if (m) { + var hexColor = m[1]; + return [ + parseInt(hexColor.substr(6, 2), 16), + parseInt(hexColor.substr(4, 2), 16), + parseInt(hexColor.substr(2, 2), 16), + parseInt(hexColor.substr(0, 2), 16) / 255 + ]; + + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @private + * @return {Array.<number>|undefined} Flat coordinates. + */ +ol.format.KML.readFlatCoordinates_ = function(node) { + var s = ol.xml.getAllTextContent(node, false); + var flatCoordinates = []; + // The KML specification states that coordinate tuples should not include + // spaces, but we tolerate them. + var re = + /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i; + var m; + while ((m = re.exec(s))) { + var x = parseFloat(m[1]); + var y = parseFloat(m[2]); + var z = m[3] ? parseFloat(m[3]) : 0; + flatCoordinates.push(x, y, z); + s = s.substr(m[0].length); + } + if (s !== '') { + return undefined; + } + return flatCoordinates; +}; + + +/** + * @param {Node} node Node. + * @private + * @return {string} URI. + */ +ol.format.KML.readURI_ = function(node) { + var s = ol.xml.getAllTextContent(node, false).trim(); + if (node.baseURI) { + var url = new URL(s, node.baseURI); + return url.href; + } else { + return s; + } +}; + + +/** + * @param {Node} node Node. + * @private + * @return {ol.KMLVec2_} Vec2. + */ +ol.format.KML.readVec2_ = function(node) { + var xunits = node.getAttribute('xunits'); + var yunits = node.getAttribute('yunits'); + return { + x: parseFloat(node.getAttribute('x')), + xunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[xunits], + y: parseFloat(node.getAttribute('y')), + yunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[yunits] + }; +}; + + +/** + * @param {Node} node Node. + * @private + * @return {number|undefined} Scale. + */ +ol.format.KML.readScale_ = function(node) { + return ol.format.XSD.readDecimal(node); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<ol.style.Style>|string|undefined} StyleMap. + */ +ol.format.KML.readStyleMapValue_ = function(node, objectStack) { + return ol.xml.pushParseAndPop(undefined, + ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack); +}; + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.IconStyleParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be an ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'IconStyle', + 'localName should be IconStyle'); + // FIXME refreshMode + // FIXME refreshInterval + // FIXME viewRefreshTime + // FIXME viewBoundScale + // FIXME viewFormat + // FIXME httpQuery + var object = ol.xml.pushParseAndPop( + {}, ol.format.KML.ICON_STYLE_PARSERS_, node, objectStack); + if (!object) { + return; + } + var styleObject = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var IconObject = 'Icon' in object ? object['Icon'] : {}; + var drawIcon = (!('Icon' in object) || Object.keys(IconObject).length > 0); + var src; + var href = /** @type {string|undefined} */ + (IconObject['href']); + if (href) { + src = href; + } else if (drawIcon) { + src = ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_; + } + var anchor, anchorXUnits, anchorYUnits; + var hotSpot = /** @type {ol.KMLVec2_|undefined} */ + (object['hotSpot']); + if (hotSpot) { + anchor = [hotSpot.x, hotSpot.y]; + anchorXUnits = hotSpot.xunits; + anchorYUnits = hotSpot.yunits; + } else if (src === ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) { + anchor = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_; + anchorXUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_; + anchorYUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_; + } else if (/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(src)) { + anchor = [0.5, 0]; + anchorXUnits = ol.style.Icon.AnchorUnits.FRACTION; + anchorYUnits = ol.style.Icon.AnchorUnits.FRACTION; + } + + var offset; + var x = /** @type {number|undefined} */ + (IconObject['x']); + var y = /** @type {number|undefined} */ + (IconObject['y']); + if (x !== undefined && y !== undefined) { + offset = [x, y]; + } + + var size; + var w = /** @type {number|undefined} */ + (IconObject['w']); + var h = /** @type {number|undefined} */ + (IconObject['h']); + if (w !== undefined && h !== undefined) { + size = [w, h]; + } + + var rotation; + var heading = /** @type {number} */ + (object['heading']); + if (heading !== undefined) { + rotation = ol.math.toRadians(heading); + } + + var scale = /** @type {number|undefined} */ + (object['scale']); + + if (drawIcon) { + if (src == ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) { + size = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_; + if (scale === undefined) { + scale = ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_; + } + } + + var imageStyle = new ol.style.Icon({ + anchor: anchor, + anchorOrigin: ol.style.Icon.Origin.BOTTOM_LEFT, + anchorXUnits: anchorXUnits, + anchorYUnits: anchorYUnits, + crossOrigin: 'anonymous', // FIXME should this be configurable? + offset: offset, + offsetOrigin: ol.style.Icon.Origin.BOTTOM_LEFT, + rotation: rotation, + scale: scale, + size: size, + src: src + }); + styleObject['imageStyle'] = imageStyle; + } else { + // handle the case when we explicitly want to draw no icon. + styleObject['imageStyle'] = ol.format.KML.DEFAULT_NO_IMAGE_STYLE_; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.LabelStyleParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LabelStyle', + 'localName should be LabelStyle'); + // FIXME colorMode + var object = ol.xml.pushParseAndPop( + {}, ol.format.KML.LABEL_STYLE_PARSERS_, node, objectStack); + if (!object) { + return; + } + var styleObject = objectStack[objectStack.length - 1]; + var textStyle = new ol.style.Text({ + fill: new ol.style.Fill({ + color: /** @type {ol.Color} */ + ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_) + }), + scale: /** @type {number|undefined} */ + (object['scale']) + }); + styleObject['textStyle'] = textStyle; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.LineStyleParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LineStyle', + 'localName should be LineStyle'); + // FIXME colorMode + // FIXME gx:outerColor + // FIXME gx:outerWidth + // FIXME gx:physicalWidth + // FIXME gx:labelVisibility + var object = ol.xml.pushParseAndPop( + {}, ol.format.KML.LINE_STYLE_PARSERS_, node, objectStack); + if (!object) { + return; + } + var styleObject = objectStack[objectStack.length - 1]; + var strokeStyle = new ol.style.Stroke({ + color: /** @type {ol.Color} */ + ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_), + width: /** @type {number} */ ('width' in object ? object['width'] : 1) + }); + styleObject['strokeStyle'] = strokeStyle; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.PolyStyleParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'PolyStyle', + 'localName should be PolyStyle'); + // FIXME colorMode + var object = ol.xml.pushParseAndPop( + {}, ol.format.KML.POLY_STYLE_PARSERS_, node, objectStack); + if (!object) { + return; + } + var styleObject = objectStack[objectStack.length - 1]; + var fillStyle = new ol.style.Fill({ + color: /** @type {ol.Color} */ + ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_) + }); + styleObject['fillStyle'] = fillStyle; + var fill = /** @type {boolean|undefined} */ (object['fill']); + if (fill !== undefined) { + styleObject['fill'] = fill; + } + var outline = + /** @type {boolean|undefined} */ (object['outline']); + if (outline !== undefined) { + styleObject['outline'] = outline; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>} LinearRing flat coordinates. + */ +ol.format.KML.readFlatLinearRing_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LinearRing', + 'localName should be LinearRing'); + return ol.xml.pushParseAndPop(null, + ol.format.KML.FLAT_LINEAR_RING_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.gxCoordParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(ol.array.includes( + ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI), + 'namespaceURI of the node should be known to the KML parser'); + ol.DEBUG && console.assert(node.localName == 'coord', 'localName should be coord'); + var gxTrackObject = /** @type {ol.KMLGxTrackObject_} */ + (objectStack[objectStack.length - 1]); + var flatCoordinates = gxTrackObject.flatCoordinates; + var s = ol.xml.getAllTextContent(node, false); + var re = + /^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i; + var m = re.exec(s); + if (m) { + var x = parseFloat(m[1]); + var y = parseFloat(m[2]); + var z = parseFloat(m[3]); + flatCoordinates.push(x, y, z, 0); + } else { + flatCoordinates.push(0, 0, 0, 0); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.MultiLineString|undefined} MultiLineString. + */ +ol.format.KML.readGxMultiTrack_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(ol.array.includes( + ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI), + 'namespaceURI of the node should be known to the KML parser'); + ol.DEBUG && console.assert(node.localName == 'MultiTrack', + 'localName should be MultiTrack'); + var lineStrings = ol.xml.pushParseAndPop([], + ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_, node, objectStack); + if (!lineStrings) { + return undefined; + } + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setLineStrings(lineStrings); + return multiLineString; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.LineString|undefined} LineString. + */ +ol.format.KML.readGxTrack_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(ol.array.includes( + ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI), + 'namespaceURI of the node should be known to the KML parser'); + ol.DEBUG && console.assert(node.localName == 'Track', 'localName should be Track'); + var gxTrackObject = ol.xml.pushParseAndPop( + /** @type {ol.KMLGxTrackObject_} */ ({ + flatCoordinates: [], + whens: [] + }), ol.format.KML.GX_TRACK_PARSERS_, node, objectStack); + if (!gxTrackObject) { + return undefined; + } + var flatCoordinates = gxTrackObject.flatCoordinates; + var whens = gxTrackObject.whens; + ol.DEBUG && console.assert(flatCoordinates.length / 4 == whens.length, + 'the length of the flatCoordinates array divided by 4 should be the ' + + 'length of the whens array'); + var i, ii; + for (i = 0, ii = Math.min(flatCoordinates.length, whens.length); i < ii; + ++i) { + flatCoordinates[4 * i + 3] = whens[i]; + } + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates); + return lineString; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object} Icon object. + */ +ol.format.KML.readIcon_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Icon', 'localName should be Icon'); + var iconObject = ol.xml.pushParseAndPop( + {}, ol.format.KML.ICON_PARSERS_, node, objectStack); + if (iconObject) { + return iconObject; + } else { + return null; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>} Flat coordinates. + */ +ol.format.KML.readFlatCoordinatesFromNode_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return ol.xml.pushParseAndPop(null, + ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.LineString|undefined} LineString. + */ +ol.format.KML.readLineString_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LineString', + 'localName should be LineString'); + var properties = ol.xml.pushParseAndPop({}, + ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node, + objectStack); + var flatCoordinates = + ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack); + if (flatCoordinates) { + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + lineString.setProperties(properties); + return lineString; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Polygon|undefined} Polygon. + */ +ol.format.KML.readLinearRing_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LinearRing', + 'localName should be LinearRing'); + var properties = ol.xml.pushParseAndPop({}, + ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node, + objectStack); + var flatCoordinates = + ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack); + if (flatCoordinates) { + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates, + [flatCoordinates.length]); + polygon.setProperties(properties); + return polygon; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.KML.readMultiGeometry_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MultiGeometry', + 'localName should be MultiGeometry'); + var geometries = ol.xml.pushParseAndPop([], + ol.format.KML.MULTI_GEOMETRY_PARSERS_, node, objectStack); + if (!geometries) { + return null; + } + if (geometries.length === 0) { + return new ol.geom.GeometryCollection(geometries); + } + /** @type {ol.geom.Geometry} */ + var multiGeometry; + var homogeneous = true; + var type = geometries[0].getType(); + var geometry, i, ii; + for (i = 1, ii = geometries.length; i < ii; ++i) { + geometry = geometries[i]; + if (geometry.getType() != type) { + homogeneous = false; + break; + } + } + if (homogeneous) { + var layout; + var flatCoordinates; + if (type == ol.geom.GeometryType.POINT) { + var point = geometries[0]; + layout = point.getLayout(); + flatCoordinates = point.getFlatCoordinates(); + for (i = 1, ii = geometries.length; i < ii; ++i) { + geometry = geometries[i]; + ol.DEBUG && console.assert(geometry.getLayout() == layout, + 'geometry layout should be consistent'); + ol.array.extend(flatCoordinates, geometry.getFlatCoordinates()); + } + multiGeometry = new ol.geom.MultiPoint(null); + multiGeometry.setFlatCoordinates(layout, flatCoordinates); + ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries); + } else if (type == ol.geom.GeometryType.LINE_STRING) { + multiGeometry = new ol.geom.MultiLineString(null); + multiGeometry.setLineStrings(geometries); + ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries); + } else if (type == ol.geom.GeometryType.POLYGON) { + multiGeometry = new ol.geom.MultiPolygon(null); + multiGeometry.setPolygons(geometries); + ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries); + } else if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) { + multiGeometry = new ol.geom.GeometryCollection(geometries); + } else { + ol.asserts.assert(false, 37); // Unknown geometry type found + } + } else { + multiGeometry = new ol.geom.GeometryCollection(geometries); + } + return /** @type {ol.geom.Geometry} */ (multiGeometry); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Point|undefined} Point. + */ +ol.format.KML.readPoint_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Point', 'localName should be Point'); + var properties = ol.xml.pushParseAndPop({}, + ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node, + objectStack); + var flatCoordinates = + ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack); + if (flatCoordinates) { + var point = new ol.geom.Point(null); + ol.DEBUG && console.assert(flatCoordinates.length == 3, + 'flatCoordinates should have a length of 3'); + point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + point.setProperties(properties); + return point; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Polygon|undefined} Polygon. + */ +ol.format.KML.readPolygon_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Polygon', + 'localName should be Polygon'); + var properties = ol.xml.pushParseAndPop(/** @type {Object<string,*>} */ ({}), + ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node, + objectStack); + var flatLinearRings = ol.xml.pushParseAndPop([null], + ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack); + if (flatLinearRings && flatLinearRings[0]) { + var polygon = new ol.geom.Polygon(null); + var flatCoordinates = flatLinearRings[0]; + var ends = [flatCoordinates.length]; + var i, ii; + for (i = 1, ii = flatLinearRings.length; i < ii; ++i) { + ol.array.extend(flatCoordinates, flatLinearRings[i]); + ends.push(flatCoordinates.length); + } + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XYZ, flatCoordinates, ends); + polygon.setProperties(properties); + return polygon; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<ol.style.Style>} Style. + */ +ol.format.KML.readStyle_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Style', 'localName should be Style'); + var styleObject = ol.xml.pushParseAndPop( + {}, ol.format.KML.STYLE_PARSERS_, node, objectStack); + if (!styleObject) { + return null; + } + var fillStyle = /** @type {ol.style.Fill} */ + ('fillStyle' in styleObject ? + styleObject['fillStyle'] : ol.format.KML.DEFAULT_FILL_STYLE_); + var fill = /** @type {boolean|undefined} */ (styleObject['fill']); + if (fill !== undefined && !fill) { + fillStyle = null; + } + var imageStyle = /** @type {ol.style.Image} */ + ('imageStyle' in styleObject ? + styleObject['imageStyle'] : ol.format.KML.DEFAULT_IMAGE_STYLE_); + if (imageStyle == ol.format.KML.DEFAULT_NO_IMAGE_STYLE_) { + imageStyle = undefined; + } + var textStyle = /** @type {ol.style.Text} */ + ('textStyle' in styleObject ? + styleObject['textStyle'] : ol.format.KML.DEFAULT_TEXT_STYLE_); + var strokeStyle = /** @type {ol.style.Stroke} */ + ('strokeStyle' in styleObject ? + styleObject['strokeStyle'] : ol.format.KML.DEFAULT_STROKE_STYLE_); + var outline = /** @type {boolean|undefined} */ + (styleObject['outline']); + if (outline !== undefined && !outline) { + strokeStyle = null; + } + return [new ol.style.Style({ + fill: fillStyle, + image: imageStyle, + stroke: strokeStyle, + text: textStyle, + zIndex: undefined // FIXME + })]; +}; + + +/** + * Reads an array of geometries and creates arrays for common geometry + * properties. Then sets them to the multi geometry. + * @param {ol.geom.MultiPoint|ol.geom.MultiLineString|ol.geom.MultiPolygon} + * multiGeometry A multi-geometry. + * @param {Array.<ol.geom.Geometry>} geometries List of geometries. + * @private + */ +ol.format.KML.setCommonGeometryProperties_ = function(multiGeometry, + geometries) { + var ii = geometries.length; + var extrudes = new Array(geometries.length); + var altitudeModes = new Array(geometries.length); + var geometry, i, hasExtrude, hasAltitudeMode; + hasExtrude = hasAltitudeMode = false; + for (i = 0; i < ii; ++i) { + geometry = geometries[i]; + extrudes[i] = geometry.get('extrude'); + altitudeModes[i] = geometry.get('altitudeMode'); + hasExtrude = hasExtrude || extrudes[i] !== undefined; + hasAltitudeMode = hasAltitudeMode || altitudeModes[i]; + } + if (hasExtrude) { + multiGeometry.set('extrude', extrudes); + } + if (hasAltitudeMode) { + multiGeometry.set('altitudeMode', altitudeModes); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.DataParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Data', 'localName should be Data'); + var name = node.getAttribute('name'); + ol.xml.parseNode(ol.format.KML.DATA_PARSERS_, node, objectStack); + var featureObject = + /** @type {Object} */ (objectStack[objectStack.length - 1]); + if (name !== null) { + featureObject[name] = featureObject.value; + } else if (featureObject.displayName !== null) { + featureObject[featureObject.displayName] = featureObject.value; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.ExtendedDataParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ExtendedData', + 'localName should be ExtendedData'); + ol.xml.parseNode(ol.format.KML.EXTENDED_DATA_PARSERS_, node, objectStack); +}; + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.RegionParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Region', + 'localName should be Region'); + ol.xml.parseNode(ol.format.KML.REGION_PARSERS_, node, objectStack); +}; + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.PairDataParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Pair', 'localName should be Pair'); + var pairObject = ol.xml.pushParseAndPop( + {}, ol.format.KML.PAIR_PARSERS_, node, objectStack); + if (!pairObject) { + return; + } + var key = /** @type {string|undefined} */ + (pairObject['key']); + if (key && key == 'normal') { + var styleUrl = /** @type {string|undefined} */ + (pairObject['styleUrl']); + if (styleUrl) { + objectStack[objectStack.length - 1] = styleUrl; + } + var Style = /** @type {ol.style.Style} */ + (pairObject['Style']); + if (Style) { + objectStack[objectStack.length - 1] = Style; + } + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.PlacemarkStyleMapParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'StyleMap', + 'localName should be StyleMap'); + var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack); + if (!styleMapValue) { + return; + } + var placemarkObject = objectStack[objectStack.length - 1]; + if (Array.isArray(styleMapValue)) { + placemarkObject['Style'] = styleMapValue; + } else if (typeof styleMapValue === 'string') { + placemarkObject['styleUrl'] = styleMapValue; + } else { + ol.asserts.assert(false, 38); // `styleMapValue` has an unknown type + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.SchemaDataParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'SchemaData', + 'localName should be SchemaData'); + ol.xml.parseNode(ol.format.KML.SCHEMA_DATA_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.SimpleDataParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'SimpleData', + 'localName should be SimpleData'); + var name = node.getAttribute('name'); + if (name !== null) { + var data = ol.format.XSD.readString(node); + var featureObject = + /** @type {Object} */ (objectStack[objectStack.length - 1]); + featureObject[name] = data; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.LatLonAltBoxParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LatLonAltBox', + 'localName should be LatLonAltBox'); + var object = ol.xml.pushParseAndPop({}, ol.format.KML.LAT_LON_ALT_BOX_PARSERS_, node, objectStack); + if (!object) { + return; + } + var regionObject = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var extent = [ + parseFloat(object['west']), + parseFloat(object['south']), + parseFloat(object['east']), + parseFloat(object['north']) + ]; + regionObject['extent'] = extent; + regionObject['altitudeMode'] = object['altitudeMode']; + regionObject['minAltitude'] = parseFloat(object['minAltitude']); + regionObject['maxAltitude'] = parseFloat(object['maxAltitude']); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.LodParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Lod', + 'localName should be Lod'); + var object = ol.xml.pushParseAndPop({}, ol.format.KML.LOD_PARSERS_, node, objectStack); + if (!object) { + return; + } + var lodObject = /** @type {Object} */ (objectStack[objectStack.length - 1]); + lodObject['minLodPixels'] = parseFloat(object['minLodPixels']); + lodObject['maxLodPixels'] = parseFloat(object['maxLodPixels']); + lodObject['minFadeExtent'] = parseFloat(object['minFadeExtent']); + lodObject['maxFadeExtent'] = parseFloat(object['maxFadeExtent']); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.innerBoundaryIsParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'innerBoundaryIs', + 'localName should be innerBoundaryIs'); + /** @type {Array.<number>|undefined} */ + var flatLinearRing = ol.xml.pushParseAndPop(undefined, + ol.format.KML.INNER_BOUNDARY_IS_PARSERS_, node, objectStack); + if (flatLinearRing) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + ol.DEBUG && console.assert(flatLinearRings.length > 0, + 'flatLinearRings array should not be empty'); + flatLinearRings.push(flatLinearRing); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.outerBoundaryIsParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'outerBoundaryIs', + 'localName should be outerBoundaryIs'); + /** @type {Array.<number>|undefined} */ + var flatLinearRing = ol.xml.pushParseAndPop(undefined, + ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_, node, objectStack); + if (flatLinearRing) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + ol.DEBUG && console.assert(flatLinearRings.length > 0, + 'flatLinearRings array should not be empty'); + flatLinearRings[0] = flatLinearRing; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.LinkParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Link', 'localName should be Link'); + ol.xml.parseNode(ol.format.KML.LINK_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.whenParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'when', 'localName should be when'); + var gxTrackObject = /** @type {ol.KMLGxTrackObject_} */ + (objectStack[objectStack.length - 1]); + var whens = gxTrackObject.whens; + var s = ol.xml.getAllTextContent(node, false); + var when = Date.parse(s); + whens.push(isNaN(when) ? 0 : when); +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.DATA_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'displayName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'value': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Data': ol.format.KML.DataParser_, + 'SchemaData': ol.format.KML.SchemaDataParser_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.REGION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LatLonAltBox': ol.format.KML.LatLonAltBoxParser_, + 'Lod': ol.format.KML.LodParser_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.LAT_LON_ALT_BOX_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'altitudeMode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'minAltitude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'maxAltitude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'north': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'south': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'east': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'west': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.LOD_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'minLodPixels': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'maxLodPixels': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'minFadeExtent': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'maxFadeExtent': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'extrude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), + 'altitudeMode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.FLAT_LINEAR_RING_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'innerBoundaryIs': ol.format.KML.innerBoundaryIsParser_, + 'outerBoundaryIs': ol.format.KML.outerBoundaryIsParser_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.GX_TRACK_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'when': ol.format.KML.whenParser_ + }, ol.xml.makeStructureNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'coord': ol.format.KML.gxCoordParser_ + })); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.ICON_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_) + }, ol.xml.makeStructureNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'x': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'y': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'w': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'h': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + })); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.ICON_STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Icon': ol.xml.makeObjectPropertySetter(ol.format.KML.readIcon_), + 'heading': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'hotSpot': ol.xml.makeObjectPropertySetter(ol.format.KML.readVec2_), + 'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.INNER_BOUNDARY_IS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.LABEL_STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_), + 'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.LINE_STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_), + 'width': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.MULTI_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LineString': ol.xml.makeArrayPusher(ol.format.KML.readLineString_), + 'LinearRing': ol.xml.makeArrayPusher(ol.format.KML.readLinearRing_), + 'MultiGeometry': ol.xml.makeArrayPusher(ol.format.KML.readMultiGeometry_), + 'Point': ol.xml.makeArrayPusher(ol.format.KML.readPoint_), + 'Polygon': ol.xml.makeArrayPusher(ol.format.KML.readPolygon_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'Track': ol.xml.makeArrayPusher(ol.format.KML.readGxTrack_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.NETWORK_LINK_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'ExtendedData': ol.format.KML.ExtendedDataParser_, + 'Region': ol.format.KML.RegionParser_, + 'Link': ol.format.KML.LinkParser_, + 'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'open': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), + 'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'visibility': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.LINK_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.PAIR_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_), + 'key': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'ExtendedData': ol.format.KML.ExtendedDataParser_, + 'Region': ol.format.KML.RegionParser_, + 'MultiGeometry': ol.xml.makeObjectPropertySetter( + ol.format.KML.readMultiGeometry_, 'geometry'), + 'LineString': ol.xml.makeObjectPropertySetter( + ol.format.KML.readLineString_, 'geometry'), + 'LinearRing': ol.xml.makeObjectPropertySetter( + ol.format.KML.readLinearRing_, 'geometry'), + 'Point': ol.xml.makeObjectPropertySetter( + ol.format.KML.readPoint_, 'geometry'), + 'Polygon': ol.xml.makeObjectPropertySetter( + ol.format.KML.readPolygon_, 'geometry'), + 'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_), + 'StyleMap': ol.format.KML.PlacemarkStyleMapParser_, + 'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'open': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), + 'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_), + 'visibility': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean) + }, ol.xml.makeStructureNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'MultiTrack': ol.xml.makeObjectPropertySetter( + ol.format.KML.readGxMultiTrack_, 'geometry'), + 'Track': ol.xml.makeObjectPropertySetter( + ol.format.KML.readGxTrack_, 'geometry') + } + )); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.POLY_STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_), + 'fill': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), + 'outline': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.SCHEMA_DATA_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'SimpleData': ol.format.KML.SimpleDataParser_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'IconStyle': ol.format.KML.IconStyleParser_, + 'LabelStyle': ol.format.KML.LabelStyleParser_, + 'LineStyle': ol.format.KML.LineStyleParser_, + 'PolyStyle': ol.format.KML.PolyStyleParser_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.STYLE_MAP_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Pair': ol.format.KML.PairDataParser_ + }); + + +/** + * @inheritDoc + */ +ol.format.KML.prototype.getExtensions = function() { + return ol.format.KML.EXTENSIONS_; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<ol.Feature>|undefined} Features. + */ +ol.format.KML.prototype.readDocumentOrFolder_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + var localName = node.localName; + ol.DEBUG && console.assert(localName == 'Document' || localName == 'Folder', + 'localName should be Document or Folder'); + // FIXME use scope somehow + var parsersNS = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Document': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this), + 'Folder': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this), + 'Placemark': ol.xml.makeArrayPusher(this.readPlacemark_, this), + 'Style': this.readSharedStyle_.bind(this), + 'StyleMap': this.readSharedStyleMap_.bind(this) + }); + /** @type {Array.<ol.Feature>} */ + var features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack, this); + if (features) { + return features; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Feature|undefined} Feature. + */ +ol.format.KML.prototype.readPlacemark_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Placemark', + 'localName should be Placemark'); + var object = ol.xml.pushParseAndPop({'geometry': null}, + ol.format.KML.PLACEMARK_PARSERS_, node, objectStack); + if (!object) { + return undefined; + } + var feature = new ol.Feature(); + var id = node.getAttribute('id'); + if (id !== null) { + feature.setId(id); + } + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + + var geometry = object['geometry']; + if (geometry) { + ol.format.Feature.transformWithOptions(geometry, false, options); + } + feature.setGeometry(geometry); + delete object['geometry']; + + if (this.extractStyles_) { + var style = object['Style']; + var styleUrl = object['styleUrl']; + var styleFunction = ol.format.KML.createFeatureStyleFunction_( + style, styleUrl, this.defaultStyle_, this.sharedStyles_, + this.showPointNames_); + feature.setStyle(styleFunction); + } + delete object['Style']; + // we do not remove the styleUrl property from the object, so it + // gets stored on feature when setProperties is called + + feature.setProperties(object); + + return feature; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.prototype.readSharedStyle_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Style', 'localName should be Style'); + var id = node.getAttribute('id'); + if (id !== null) { + var style = ol.format.KML.readStyle_(node, objectStack); + if (style) { + var styleUri; + if (node.baseURI) { + var url = new URL('#' + id, node.baseURI); + styleUri = url.href; + } else { + styleUri = '#' + id; + } + this.sharedStyles_[styleUri] = style; + } + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.prototype.readSharedStyleMap_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'StyleMap', + 'localName should be StyleMap'); + var id = node.getAttribute('id'); + if (id === null) { + return; + } + var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack); + if (!styleMapValue) { + return; + } + var styleUri; + if (node.baseURI) { + var url = new URL('#' + id, node.baseURI); + styleUri = url.href; + } else { + styleUri = '#' + id; + } + this.sharedStyles_[styleUri] = styleMapValue; +}; + + +/** + * Read the first feature from a KML source. MultiGeometries are converted into + * GeometryCollections if they are a mix of geometry types, and into MultiPoint/ + * MultiLineString/MultiPolygon if they are all of the same type. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable + */ +ol.format.KML.prototype.readFeature; + + +/** + * @inheritDoc + */ +ol.format.KML.prototype.readFeatureFromNode = function(node, opt_options) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + if (!ol.array.includes(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) { + return null; + } + ol.DEBUG && console.assert(node.localName == 'Placemark', + 'localName should be Placemark'); + var feature = this.readPlacemark_( + node, [this.getReadOptions(node, opt_options)]); + if (feature) { + return feature; + } else { + return null; + } +}; + + +/** + * Read all features from a KML source. MultiGeometries are converted into + * GeometryCollections if they are a mix of geometry types, and into MultiPoint/ + * MultiLineString/MultiPolygon if they are all of the same type. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.KML.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.KML.prototype.readFeaturesFromNode = function(node, opt_options) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + if (!ol.array.includes(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) { + return []; + } + var features; + var localName = node.localName; + if (localName == 'Document' || localName == 'Folder') { + features = this.readDocumentOrFolder_( + node, [this.getReadOptions(node, opt_options)]); + if (features) { + return features; + } else { + return []; + } + } else if (localName == 'Placemark') { + var feature = this.readPlacemark_( + node, [this.getReadOptions(node, opt_options)]); + if (feature) { + return [feature]; + } else { + return []; + } + } else if (localName == 'kml') { + features = []; + var n; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var fs = this.readFeaturesFromNode(n, opt_options); + if (fs) { + ol.array.extend(features, fs); + } + } + return features; + } else { + return []; + } +}; + + +/** + * Read the name of the KML. + * + * @param {Document|Node|string} source Souce. + * @return {string|undefined} Name. + * @api stable + */ +ol.format.KML.prototype.readName = function(source) { + if (ol.xml.isDocument(source)) { + return this.readNameFromDocument(/** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readNameFromNode(/** @type {Node} */ (source)); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readNameFromDocument(doc); + } else { + return undefined; + } +}; + + +/** + * @param {Document} doc Document. + * @return {string|undefined} Name. + */ +ol.format.KML.prototype.readNameFromDocument = function(doc) { + var n; + for (n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + var name = this.readNameFromNode(n); + if (name) { + return name; + } + } + } + return undefined; +}; + + +/** + * @param {Node} node Node. + * @return {string|undefined} Name. + */ +ol.format.KML.prototype.readNameFromNode = function(node) { + var n; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + n.localName == 'name') { + return ol.format.XSD.readString(n); + } + } + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var localName = n.localName; + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + (localName == 'Document' || + localName == 'Folder' || + localName == 'Placemark' || + localName == 'kml')) { + var name = this.readNameFromNode(n); + if (name) { + return name; + } + } + } + return undefined; +}; + + +/** + * Read the network links of the KML. + * + * @param {Document|Node|string} source Source. + * @return {Array.<Object>} Network links. + * @api + */ +ol.format.KML.prototype.readNetworkLinks = function(source) { + var networkLinks = []; + if (ol.xml.isDocument(source)) { + ol.array.extend(networkLinks, this.readNetworkLinksFromDocument( + /** @type {Document} */ (source))); + } else if (ol.xml.isNode(source)) { + ol.array.extend(networkLinks, this.readNetworkLinksFromNode( + /** @type {Node} */ (source))); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + ol.array.extend(networkLinks, this.readNetworkLinksFromDocument(doc)); + } + return networkLinks; +}; + + +/** + * @param {Document} doc Document. + * @return {Array.<Object>} Network links. + */ +ol.format.KML.prototype.readNetworkLinksFromDocument = function(doc) { + var n, networkLinks = []; + for (n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + ol.array.extend(networkLinks, this.readNetworkLinksFromNode(n)); + } + } + return networkLinks; +}; + + +/** + * @param {Node} node Node. + * @return {Array.<Object>} Network links. + */ +ol.format.KML.prototype.readNetworkLinksFromNode = function(node) { + var n, networkLinks = []; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + n.localName == 'NetworkLink') { + var obj = ol.xml.pushParseAndPop({}, ol.format.KML.NETWORK_LINK_PARSERS_, + n, []); + networkLinks.push(obj); + } + } + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var localName = n.localName; + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + (localName == 'Document' || + localName == 'Folder' || + localName == 'kml')) { + ol.array.extend(networkLinks, this.readNetworkLinksFromNode(n)); + } + } + return networkLinks; +}; + + +/** + * Read the regions of the KML. + * + * @param {Document|Node|string} source Source. + * @return {Array.<Object>} Regions. + * @api + */ +ol.format.KML.prototype.readRegion = function(source) { + var regions = []; + if (ol.xml.isDocument(source)) { + ol.array.extend(regions, this.readRegionFromDocument( + /** @type {Document} */ (source))); + } else if (ol.xml.isNode(source)) { + ol.array.extend(regions, this.readRegionFromNode( + /** @type {Node} */ (source))); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + ol.array.extend(regions, this.readRegionFromDocument(doc)); + } + return regions; +}; + + +/** + * @param {Document} doc Document. + * @return {Array.<Object>} Region. + */ +ol.format.KML.prototype.readRegionFromDocument = function(doc) { + var n, regions = []; + for (n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + ol.array.extend(regions, this.readRegionFromNode(n)); + } + } + return regions; +}; + + +/** + * @param {Node} node Node. + * @return {Array.<Object>} Region. + * @api + */ +ol.format.KML.prototype.readRegionFromNode = function(node) { + var n, regions = []; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + n.localName == 'Region') { + var obj = ol.xml.pushParseAndPop({}, ol.format.KML.REGION_PARSERS_, + n, []); + regions.push(obj); + } + } + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var localName = n.localName; + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + (localName == 'Document' || + localName == 'Folder' || + localName == 'kml')) { + ol.array.extend(regions, this.readRegionFromNode(n)); + } + } + return regions; +}; + + +/** + * Read the projection from a KML source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.KML.prototype.readProjection; + + +/** + * @param {Node} node Node to append a TextNode with the color to. + * @param {ol.Color|string} color Color. + * @private + */ +ol.format.KML.writeColorTextNode_ = function(node, color) { + var rgba = ol.color.asArray(color); + var opacity = (rgba.length == 4) ? rgba[3] : 1; + var abgr = [opacity * 255, rgba[2], rgba[1], rgba[0]]; + var i; + for (i = 0; i < 4; ++i) { + var hex = parseInt(abgr[i], 10).toString(16); + abgr[i] = (hex.length == 1) ? '0' + hex : hex; + } + ol.format.XSD.writeStringTextNode(node, abgr.join('')); +}; + + +/** + * @param {Node} node Node to append a TextNode with the coordinates to. + * @param {Array.<number>} coordinates Coordinates. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeCoordinatesTextNode_ = function(node, coordinates, objectStack) { + var context = objectStack[objectStack.length - 1]; + + var layout = context['layout']; + var stride = context['stride']; + + var dimension; + if (layout == ol.geom.GeometryLayout.XY || + layout == ol.geom.GeometryLayout.XYM) { + dimension = 2; + } else if (layout == ol.geom.GeometryLayout.XYZ || + layout == ol.geom.GeometryLayout.XYZM) { + dimension = 3; + } else { + ol.asserts.assert(false, 34); // Invalid geometry layout + } + + var d, i; + var ii = coordinates.length; + var text = ''; + if (ii > 0) { + text += coordinates[0]; + for (d = 1; d < dimension; ++d) { + text += ',' + coordinates[d]; + } + for (i = stride; i < ii; i += stride) { + text += ' ' + coordinates[i]; + for (d = 1; d < dimension; ++d) { + text += ',' + coordinates[i + d]; + } + } + } + ol.format.XSD.writeStringTextNode(node, text); +}; + + +/** + * @param {Node} node Node. + * @param {{name: *, value: *}} pair Name value pair. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeDataNode_ = function(node, pair, objectStack) { + node.setAttribute('name', pair.name); + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var value = pair.value; + + if (typeof value == 'object') { + if (value !== null && value.displayName) { + ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value.displayName], objectStack, ['displayName']); + } + + if (value !== null && value.value) { + ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value.value], objectStack, ['value']); + } + } else { + ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value], objectStack, ['value']); + } +}; + + +/** + * @param {Node} node Node to append a TextNode with the name to. + * @param {string} name DisplayName. + * @private + */ +ol.format.KML.writeDataNodeName_ = function(node, name) { + ol.format.XSD.writeCDATASection(node, name); +}; + + +/** + * @param {Node} node Node to append a CDATA Section with the value to. + * @param {string} value Value. + * @private + */ +ol.format.KML.writeDataNodeValue_ = function(node, value) { + ol.format.XSD.writeStringTextNode(node, value); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<ol.Feature>} features Features. + * @param {Array.<*>} objectStack Object stack. + * @this {ol.format.KML} + * @private + */ +ol.format.KML.writeDocument_ = function(node, features, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + ol.xml.pushSerializeAndPop(context, ol.format.KML.DOCUMENT_SERIALIZERS_, + ol.format.KML.DOCUMENT_NODE_FACTORY_, features, objectStack, undefined, + this); +}; + + +/** + * @param {Node} node Node. + * @param {{names: Array<string>, values: (Array<*>)}} namesAndValues Names and values. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeExtendedData_ = function(node, namesAndValues, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var names = namesAndValues.names, values = namesAndValues.values; + var length = names.length; + + for (var i = 0; i < length; i++) { + ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_, + ol.format.KML.DATA_NODE_FACTORY_, [{name: names[i], value: values[i]}], objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {Object} icon Icon object. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeIcon_ = function(node, icon, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.ICON_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(icon, orderedKeys); + ol.xml.pushSerializeAndPop(context, + ol.format.KML.ICON_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); + orderedKeys = + ol.format.KML.ICON_SEQUENCE_[ol.format.KML.GX_NAMESPACE_URIS_[0]]; + values = ol.xml.makeSequence(icon, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_SERIALIZERS_, + ol.format.KML.GX_NODE_FACTORY_, values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.style.Icon} style Icon style. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeIconStyle_ = function(node, style, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var properties = {}; + var src = style.getSrc(); + var size = style.getSize(); + var iconImageSize = style.getImageSize(); + var iconProperties = { + 'href': src + }; + + if (size) { + iconProperties['w'] = size[0]; + iconProperties['h'] = size[1]; + var anchor = style.getAnchor(); // top-left + var origin = style.getOrigin(); // top-left + + if (origin && iconImageSize && origin[0] !== 0 && origin[1] !== size[1]) { + iconProperties['x'] = origin[0]; + iconProperties['y'] = iconImageSize[1] - (origin[1] + size[1]); + } + + if (anchor && anchor[0] !== 0 && anchor[1] !== size[1]) { + var /** @type {ol.KMLVec2_} */ hotSpot = { + x: anchor[0], + xunits: ol.style.Icon.AnchorUnits.PIXELS, + y: size[1] - anchor[1], + yunits: ol.style.Icon.AnchorUnits.PIXELS + }; + properties['hotSpot'] = hotSpot; + } + } + + properties['Icon'] = iconProperties; + + var scale = style.getScale(); + if (scale !== 1) { + properties['scale'] = scale; + } + + var rotation = style.getRotation(); + if (rotation !== 0) { + properties['heading'] = rotation; // 0-360 + } + + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.ICON_STYLE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_STYLE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.style.Text} style style. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeLabelStyle_ = function(node, style, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var properties = {}; + var fill = style.getFill(); + if (fill) { + properties['color'] = fill.getColor(); + } + var scale = style.getScale(); + if (scale && scale !== 1) { + properties['scale'] = scale; + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = + ol.format.KML.LABEL_STYLE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.LABEL_STYLE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.style.Stroke} style style. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeLineStyle_ = function(node, style, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var properties = { + 'color': style.getColor(), + 'width': style.getWidth() + }; + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.LINE_STYLE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.LINE_STYLE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeMultiGeometry_ = function(node, geometry, objectStack) { + /** @type {ol.XmlNodeStackItem} */ + var context = {node: node}; + var type = geometry.getType(); + /** @type {Array.<ol.geom.Geometry>} */ + var geometries; + /** @type {function(*, Array.<*>, string=): (Node|undefined)} */ + var factory; + if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) { + geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries(); + factory = ol.format.KML.GEOMETRY_NODE_FACTORY_; + } else if (type == ol.geom.GeometryType.MULTI_POINT) { + geometries = /** @type {ol.geom.MultiPoint} */ (geometry).getPoints(); + factory = ol.format.KML.POINT_NODE_FACTORY_; + } else if (type == ol.geom.GeometryType.MULTI_LINE_STRING) { + geometries = + (/** @type {ol.geom.MultiLineString} */ (geometry)).getLineStrings(); + factory = ol.format.KML.LINE_STRING_NODE_FACTORY_; + } else if (type == ol.geom.GeometryType.MULTI_POLYGON) { + geometries = + (/** @type {ol.geom.MultiPolygon} */ (geometry)).getPolygons(); + factory = ol.format.KML.POLYGON_NODE_FACTORY_; + } else { + ol.asserts.assert(false, 39); // Unknown geometry type + } + ol.xml.pushSerializeAndPop(context, + ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_, factory, + geometries, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LinearRing} linearRing Linear ring. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeBoundaryIs_ = function(node, linearRing, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + ol.xml.pushSerializeAndPop(context, + ol.format.KML.BOUNDARY_IS_SERIALIZERS_, + ol.format.KML.LINEAR_RING_NODE_FACTORY_, [linearRing], objectStack); +}; + + +/** + * FIXME currently we do serialize arbitrary/custom feature properties + * (ExtendedData). + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @this {ol.format.KML} + * @private + */ +ol.format.KML.writePlacemark_ = function(node, feature, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + + // set id + if (feature.getId()) { + node.setAttribute('id', feature.getId()); + } + + // serialize properties (properties unknown to KML are not serialized) + var properties = feature.getProperties(); + + // don't export these to ExtendedData + var filter = {'address': 1, 'description': 1, 'name': 1, 'open': 1, + 'phoneNumber': 1, 'styleUrl': 1, 'visibility': 1}; + filter[feature.getGeometryName()] = 1; + var keys = Object.keys(properties || {}).sort().filter(function(v) { + return !filter[v]; + }); + + if (keys.length > 0) { + var sequence = ol.xml.makeSequence(properties, keys); + var namesAndValues = {names: keys, values: sequence}; + ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_, + ol.format.KML.EXTENDEDDATA_NODE_FACTORY_, [namesAndValues], objectStack); + } + + var styleFunction = feature.getStyleFunction(); + if (styleFunction) { + // FIXME the styles returned by the style function are supposed to be + // resolution-independent here + var styles = styleFunction.call(feature, 0); + if (styles) { + var style = Array.isArray(styles) ? styles[0] : styles; + if (this.writeStyles_) { + properties['Style'] = style; + } + var textStyle = style.getText(); + if (textStyle) { + properties['name'] = textStyle.getText(); + } + } + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.PLACEMARK_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); + + // serialize geometry + var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); + var geometry = feature.getGeometry(); + if (geometry) { + geometry = + ol.format.Feature.transformWithOptions(geometry, true, options); + } + ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_, + ol.format.KML.GEOMETRY_NODE_FACTORY_, [geometry], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.SimpleGeometry} geometry Geometry. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writePrimitiveGeometry_ = function(node, geometry, objectStack) { + ol.DEBUG && console.assert( + (geometry instanceof ol.geom.Point) || + (geometry instanceof ol.geom.LineString) || + (geometry instanceof ol.geom.LinearRing), + 'geometry should be one of ol.geom.Point, ol.geom.LineString ' + + 'or ol.geom.LinearRing'); + var flatCoordinates = geometry.getFlatCoordinates(); + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + context['layout'] = geometry.getLayout(); + context['stride'] = geometry.getStride(); + ol.xml.pushSerializeAndPop(context, + ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_, + ol.format.KML.COORDINATES_NODE_FACTORY_, + [flatCoordinates], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Polygon} polygon Polygon. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writePolygon_ = function(node, polygon, objectStack) { + var linearRings = polygon.getLinearRings(); + ol.DEBUG && console.assert(linearRings.length > 0, + 'linearRings should not be empty'); + var outerRing = linearRings.shift(); + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + // inner rings + ol.xml.pushSerializeAndPop(context, + ol.format.KML.POLYGON_SERIALIZERS_, + ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_, + linearRings, objectStack); + // outer ring + ol.xml.pushSerializeAndPop(context, + ol.format.KML.POLYGON_SERIALIZERS_, + ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_, + [outerRing], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.style.Fill} style Style. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writePolyStyle_ = function(node, style, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + ol.xml.pushSerializeAndPop(context, ol.format.KML.POLY_STYLE_SERIALIZERS_, + ol.format.KML.COLOR_NODE_FACTORY_, [style.getColor()], objectStack); +}; + + +/** + * @param {Node} node Node to append a TextNode with the scale to. + * @param {number|undefined} scale Scale. + * @private + */ +ol.format.KML.writeScaleTextNode_ = function(node, scale) { + // the Math is to remove any excess decimals created by float arithmetic + ol.format.XSD.writeDecimalTextNode(node, + Math.round(scale * 1e6) / 1e6); +}; + + +/** + * @param {Node} node Node. + * @param {ol.style.Style} style Style. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeStyle_ = function(node, style, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var properties = {}; + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + var imageStyle = style.getImage(); + var textStyle = style.getText(); + if (imageStyle instanceof ol.style.Icon) { + properties['IconStyle'] = imageStyle; + } + if (textStyle) { + properties['LabelStyle'] = textStyle; + } + if (strokeStyle) { + properties['LineStyle'] = strokeStyle; + } + if (fillStyle) { + properties['PolyStyle'] = fillStyle; + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.STYLE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.STYLE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node to append a TextNode with the Vec2 to. + * @param {ol.KMLVec2_} vec2 Vec2. + * @private + */ +ol.format.KML.writeVec2_ = function(node, vec2) { + node.setAttribute('x', vec2.x); + node.setAttribute('y', vec2.y); + node.setAttribute('xunits', vec2.xunits); + node.setAttribute('yunits', vec2.yunits); +}; + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.KML_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'Document', 'Placemark' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.KML_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Document': ol.xml.makeChildAppender(ol.format.KML.writeDocument_), + 'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Data': ol.xml.makeChildAppender(ol.format.KML.writeDataNode_), + 'value': ol.xml.makeChildAppender(ol.format.KML.writeDataNodeValue_), + 'displayName': ol.xml.makeChildAppender(ol.format.KML.writeDataNodeName_) + }); + + +/** + * @const + * @type {Object.<string, string>} + * @private + */ +ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_ = { + 'Point': 'Point', + 'LineString': 'LineString', + 'LinearRing': 'LinearRing', + 'Polygon': 'Polygon', + 'MultiPoint': 'MultiGeometry', + 'MultiLineString': 'MultiGeometry', + 'MultiPolygon': 'MultiGeometry', + 'GeometryCollection': 'MultiGeometry' +}; + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.ICON_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'href' + ], + ol.xml.makeStructureNS(ol.format.KML.GX_NAMESPACE_URIS_, [ + 'x', 'y', 'w', 'h' + ])); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.ICON_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'href': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) + }, ol.xml.makeStructureNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'x': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'y': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'w': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'h': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode) + })); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.ICON_STYLE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'scale', 'heading', 'Icon', 'hotSpot' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.ICON_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Icon': ol.xml.makeChildAppender(ol.format.KML.writeIcon_), + 'heading': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'hotSpot': ol.xml.makeChildAppender(ol.format.KML.writeVec2_), + 'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.LABEL_STYLE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'color', 'scale' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.LABEL_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_), + 'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.LINE_STYLE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'color', 'width' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.LINE_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_), + 'width': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.BOUNDARY_IS_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LinearRing': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LineString': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'Point': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_), + 'GeometryCollection': ol.xml.makeChildAppender( + ol.format.KML.writeMultiGeometry_) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.PLACEMARK_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'name', 'open', 'visibility', 'address', 'phoneNumber', 'description', + 'styleUrl', 'Style' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'ExtendedData': ol.xml.makeChildAppender( + ol.format.KML.writeExtendedData_), + 'MultiGeometry': ol.xml.makeChildAppender( + ol.format.KML.writeMultiGeometry_), + 'LineString': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'LinearRing': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'Point': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_), + 'Style': ol.xml.makeChildAppender(ol.format.KML.writeStyle_), + 'address': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'description': ol.xml.makeChildAppender( + ol.format.XSD.writeStringTextNode), + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'open': ol.xml.makeChildAppender(ol.format.XSD.writeBooleanTextNode), + 'phoneNumber': ol.xml.makeChildAppender( + ol.format.XSD.writeStringTextNode), + 'styleUrl': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'visibility': ol.xml.makeChildAppender( + ol.format.XSD.writeBooleanTextNode) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'coordinates': ol.xml.makeChildAppender( + ol.format.KML.writeCoordinatesTextNode_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.POLYGON_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'outerBoundaryIs': ol.xml.makeChildAppender( + ol.format.KML.writeBoundaryIs_), + 'innerBoundaryIs': ol.xml.makeChildAppender( + ol.format.KML.writeBoundaryIs_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.POLY_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.STYLE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'IconStyle', 'LabelStyle', 'LineStyle', 'PolyStyle' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'IconStyle': ol.xml.makeChildAppender(ol.format.KML.writeIconStyle_), + 'LabelStyle': ol.xml.makeChildAppender(ol.format.KML.writeLabelStyle_), + 'LineStyle': ol.xml.makeChildAppender(ol.format.KML.writeLineStyle_), + 'PolyStyle': ol.xml.makeChildAppender(ol.format.KML.writePolyStyle_) + }); + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private + */ +ol.format.KML.GX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + return ol.xml.createElementNS(ol.format.KML.GX_NAMESPACE_URIS_[0], + 'gx:' + opt_nodeName); +}; + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private + */ +ol.format.KML.DOCUMENT_NODE_FACTORY_ = function(value, objectStack, + opt_nodeName) { + var parentNode = objectStack[objectStack.length - 1].node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode), + 'parentNode should be an XML node'); + return ol.xml.createElementNS(parentNode.namespaceURI, 'Placemark'); +}; + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private + */ +ol.format.KML.GEOMETRY_NODE_FACTORY_ = function(value, objectStack, + opt_nodeName) { + if (value) { + var parentNode = objectStack[objectStack.length - 1].node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode), + 'parentNode should be an XML node'); + return ol.xml.createElementNS(parentNode.namespaceURI, + ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_[/** @type {ol.geom.Geometry} */ (value).getType()]); + } +}; + + +/** + * A factory for creating coordinates nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.COLOR_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('color'); + + +/** + * A factory for creating coordinates nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.COORDINATES_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('coordinates'); + + +/** + * A factory for creating Data nodes. + * @const + * @type {function(*, Array.<*>): (Node|undefined)} + * @private + */ +ol.format.KML.DATA_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('Data'); + + +/** + * A factory for creating ExtendedData nodes. + * @const + * @type {function(*, Array.<*>): (Node|undefined)} + * @private + */ +ol.format.KML.EXTENDEDDATA_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('ExtendedData'); + + +/** + * A factory for creating innerBoundaryIs nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('innerBoundaryIs'); + + +/** + * A factory for creating Point nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.POINT_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('Point'); + + +/** + * A factory for creating LineString nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.LINE_STRING_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('LineString'); + + +/** + * A factory for creating LinearRing nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.LINEAR_RING_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('LinearRing'); + + +/** + * A factory for creating Polygon nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.POLYGON_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('Polygon'); + + +/** + * A factory for creating outerBoundaryIs nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('outerBoundaryIs'); + + +/** + * Encode an array of features in the KML format. GeometryCollections, MultiPoints, + * MultiLineStrings, and MultiPolygons are output as MultiGeometries. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {string} Result. + * @api stable + */ +ol.format.KML.prototype.writeFeatures; + + +/** + * Encode an array of features in the KML format as an XML node. GeometryCollections, + * MultiPoints, MultiLineStrings, and MultiPolygons are output as MultiGeometries. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api + */ +ol.format.KML.prototype.writeFeaturesNode = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + var kml = ol.xml.createElementNS(ol.format.KML.NAMESPACE_URIS_[4], 'kml'); + var xmlnsUri = 'http://www.w3.org/2000/xmlns/'; + var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance'; + ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:gx', + ol.format.KML.GX_NAMESPACE_URIS_[0]); + ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri); + ol.xml.setAttributeNS(kml, xmlSchemaInstanceUri, 'xsi:schemaLocation', + ol.format.KML.SCHEMA_LOCATION_); + + var /** @type {ol.XmlNodeStackItem} */ context = {node: kml}; + var properties = {}; + if (features.length > 1) { + properties['Document'] = features; + } else if (features.length == 1) { + properties['Placemark'] = features[0]; + } + var orderedKeys = ol.format.KML.KML_SEQUENCE_[kml.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.KML_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, [opt_options], orderedKeys, + this); + return kml; +}; + +goog.provide('ol.ext.pbf'); +/** @typedef {function(*)} */ +ol.ext.pbf; +(function() { +var exports = {}; +var module = {exports: exports}; +var define; +/** + * @fileoverview + * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility} + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pbf = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) +} + +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 +} + +},{}],2:[function(_dereq_,module,exports){ +'use strict'; + +module.exports = Pbf; + +var ieee754 = _dereq_('ieee754'); + +function Pbf(buf) { + this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0); + this.pos = 0; + this.type = 0; + this.length = this.buf.length; +} + +Pbf.Varint = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum +Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64 +Pbf.Bytes = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields +Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32 + +var SHIFT_LEFT_32 = (1 << 16) * (1 << 16), + SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32; + +Pbf.prototype = { + + destroy: function() { + this.buf = null; + }, + + // === READING ================================================================= + + readFields: function(readField, result, end) { + end = end || this.length; + + while (this.pos < end) { + var val = this.readVarint(), + tag = val >> 3, + startPos = this.pos; + + this.type = val & 0x7; + readField(tag, result, this); + + if (this.pos === startPos) this.skip(val); + } + return result; + }, + + readMessage: function(readField, result) { + return this.readFields(readField, result, this.readVarint() + this.pos); + }, + + readFixed32: function() { + var val = readUInt32(this.buf, this.pos); + this.pos += 4; + return val; + }, + + readSFixed32: function() { + var val = readInt32(this.buf, this.pos); + this.pos += 4; + return val; + }, + + // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed) + + readFixed64: function() { + var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32; + this.pos += 8; + return val; + }, + + readSFixed64: function() { + var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32; + this.pos += 8; + return val; + }, + + readFloat: function() { + var val = ieee754.read(this.buf, this.pos, true, 23, 4); + this.pos += 4; + return val; + }, + + readDouble: function() { + var val = ieee754.read(this.buf, this.pos, true, 52, 8); + this.pos += 8; + return val; + }, + + readVarint: function(isSigned) { + var buf = this.buf, + val, b; + + b = buf[this.pos++]; val = b & 0x7f; if (b < 0x80) return val; + b = buf[this.pos++]; val |= (b & 0x7f) << 7; if (b < 0x80) return val; + b = buf[this.pos++]; val |= (b & 0x7f) << 14; if (b < 0x80) return val; + b = buf[this.pos++]; val |= (b & 0x7f) << 21; if (b < 0x80) return val; + b = buf[this.pos]; val |= (b & 0x0f) << 28; + + return readVarintRemainder(val, isSigned, this); + }, + + readVarint64: function() { // for compatibility with v2.0.1 + return this.readVarint(true); + }, + + readSVarint: function() { + var num = this.readVarint(); + return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding + }, + + readBoolean: function() { + return Boolean(this.readVarint()); + }, + + readString: function() { + var end = this.readVarint() + this.pos, + str = readUtf8(this.buf, this.pos, end); + this.pos = end; + return str; + }, + + readBytes: function() { + var end = this.readVarint() + this.pos, + buffer = this.buf.subarray(this.pos, end); + this.pos = end; + return buffer; + }, + + // verbose for performance reasons; doesn't affect gzipped size + + readPackedVarint: function(arr, isSigned) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readVarint(isSigned)); + return arr; + }, + readPackedSVarint: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readSVarint()); + return arr; + }, + readPackedBoolean: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readBoolean()); + return arr; + }, + readPackedFloat: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readFloat()); + return arr; + }, + readPackedDouble: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readDouble()); + return arr; + }, + readPackedFixed32: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readFixed32()); + return arr; + }, + readPackedSFixed32: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readSFixed32()); + return arr; + }, + readPackedFixed64: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readFixed64()); + return arr; + }, + readPackedSFixed64: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readSFixed64()); + return arr; + }, + + skip: function(val) { + var type = val & 0x7; + if (type === Pbf.Varint) while (this.buf[this.pos++] > 0x7f) {} + else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos; + else if (type === Pbf.Fixed32) this.pos += 4; + else if (type === Pbf.Fixed64) this.pos += 8; + else throw new Error('Unimplemented type: ' + type); + }, + + // === WRITING ================================================================= + + writeTag: function(tag, type) { + this.writeVarint((tag << 3) | type); + }, + + realloc: function(min) { + var length = this.length || 16; + + while (length < this.pos + min) length *= 2; + + if (length !== this.length) { + var buf = new Uint8Array(length); + buf.set(this.buf); + this.buf = buf; + this.length = length; + } + }, + + finish: function() { + this.length = this.pos; + this.pos = 0; + return this.buf.subarray(0, this.length); + }, + + writeFixed32: function(val) { + this.realloc(4); + writeInt32(this.buf, val, this.pos); + this.pos += 4; + }, + + writeSFixed32: function(val) { + this.realloc(4); + writeInt32(this.buf, val, this.pos); + this.pos += 4; + }, + + writeFixed64: function(val) { + this.realloc(8); + writeInt32(this.buf, val & -1, this.pos); + writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4); + this.pos += 8; + }, + + writeSFixed64: function(val) { + this.realloc(8); + writeInt32(this.buf, val & -1, this.pos); + writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4); + this.pos += 8; + }, + + writeVarint: function(val) { + val = +val || 0; + + if (val > 0xfffffff || val < 0) { + writeBigVarint(val, this); + return; + } + + this.realloc(4); + + this.buf[this.pos++] = val & 0x7f | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return; + this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return; + this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return; + this.buf[this.pos++] = (val >>> 7) & 0x7f; + }, + + writeSVarint: function(val) { + this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2); + }, + + writeBoolean: function(val) { + this.writeVarint(Boolean(val)); + }, + + writeString: function(str) { + str = String(str); + this.realloc(str.length * 4); + + this.pos++; // reserve 1 byte for short string length + + var startPos = this.pos; + // write the string directly to the buffer and see how much was written + this.pos = writeUtf8(this.buf, str, this.pos); + var len = this.pos - startPos; + + if (len >= 0x80) makeRoomForExtraLength(startPos, len, this); + + // finally, write the message length in the reserved place and restore the position + this.pos = startPos - 1; + this.writeVarint(len); + this.pos += len; + }, + + writeFloat: function(val) { + this.realloc(4); + ieee754.write(this.buf, val, this.pos, true, 23, 4); + this.pos += 4; + }, + + writeDouble: function(val) { + this.realloc(8); + ieee754.write(this.buf, val, this.pos, true, 52, 8); + this.pos += 8; + }, + + writeBytes: function(buffer) { + var len = buffer.length; + this.writeVarint(len); + this.realloc(len); + for (var i = 0; i < len; i++) this.buf[this.pos++] = buffer[i]; + }, + + writeRawMessage: function(fn, obj) { + this.pos++; // reserve 1 byte for short message length + + // write the message directly to the buffer and see how much was written + var startPos = this.pos; + fn(obj, this); + var len = this.pos - startPos; + + if (len >= 0x80) makeRoomForExtraLength(startPos, len, this); + + // finally, write the message length in the reserved place and restore the position + this.pos = startPos - 1; + this.writeVarint(len); + this.pos += len; + }, + + writeMessage: function(tag, fn, obj) { + this.writeTag(tag, Pbf.Bytes); + this.writeRawMessage(fn, obj); + }, + + writePackedVarint: function(tag, arr) { this.writeMessage(tag, writePackedVarint, arr); }, + writePackedSVarint: function(tag, arr) { this.writeMessage(tag, writePackedSVarint, arr); }, + writePackedBoolean: function(tag, arr) { this.writeMessage(tag, writePackedBoolean, arr); }, + writePackedFloat: function(tag, arr) { this.writeMessage(tag, writePackedFloat, arr); }, + writePackedDouble: function(tag, arr) { this.writeMessage(tag, writePackedDouble, arr); }, + writePackedFixed32: function(tag, arr) { this.writeMessage(tag, writePackedFixed32, arr); }, + writePackedSFixed32: function(tag, arr) { this.writeMessage(tag, writePackedSFixed32, arr); }, + writePackedFixed64: function(tag, arr) { this.writeMessage(tag, writePackedFixed64, arr); }, + writePackedSFixed64: function(tag, arr) { this.writeMessage(tag, writePackedSFixed64, arr); }, + + writeBytesField: function(tag, buffer) { + this.writeTag(tag, Pbf.Bytes); + this.writeBytes(buffer); + }, + writeFixed32Field: function(tag, val) { + this.writeTag(tag, Pbf.Fixed32); + this.writeFixed32(val); + }, + writeSFixed32Field: function(tag, val) { + this.writeTag(tag, Pbf.Fixed32); + this.writeSFixed32(val); + }, + writeFixed64Field: function(tag, val) { + this.writeTag(tag, Pbf.Fixed64); + this.writeFixed64(val); + }, + writeSFixed64Field: function(tag, val) { + this.writeTag(tag, Pbf.Fixed64); + this.writeSFixed64(val); + }, + writeVarintField: function(tag, val) { + this.writeTag(tag, Pbf.Varint); + this.writeVarint(val); + }, + writeSVarintField: function(tag, val) { + this.writeTag(tag, Pbf.Varint); + this.writeSVarint(val); + }, + writeStringField: function(tag, str) { + this.writeTag(tag, Pbf.Bytes); + this.writeString(str); + }, + writeFloatField: function(tag, val) { + this.writeTag(tag, Pbf.Fixed32); + this.writeFloat(val); + }, + writeDoubleField: function(tag, val) { + this.writeTag(tag, Pbf.Fixed64); + this.writeDouble(val); + }, + writeBooleanField: function(tag, val) { + this.writeVarintField(tag, Boolean(val)); + } +}; + +function readVarintRemainder(l, s, p) { + var buf = p.buf, + h, b; + + b = buf[p.pos++]; h = (b & 0x70) >> 4; if (b < 0x80) return toNum(l, h, s); + b = buf[p.pos++]; h |= (b & 0x7f) << 3; if (b < 0x80) return toNum(l, h, s); + b = buf[p.pos++]; h |= (b & 0x7f) << 10; if (b < 0x80) return toNum(l, h, s); + b = buf[p.pos++]; h |= (b & 0x7f) << 17; if (b < 0x80) return toNum(l, h, s); + b = buf[p.pos++]; h |= (b & 0x7f) << 24; if (b < 0x80) return toNum(l, h, s); + b = buf[p.pos++]; h |= (b & 0x01) << 31; if (b < 0x80) return toNum(l, h, s); + + throw new Error('Expected varint not more than 10 bytes'); +} + +function readPackedEnd(pbf) { + return pbf.type === Pbf.Bytes ? + pbf.readVarint() + pbf.pos : pbf.pos + 1; +} + +function toNum(low, high, isSigned) { + if (isSigned) { + return high * 0x100000000 + (low >>> 0); + } + + return ((high >>> 0) * 0x100000000) + (low >>> 0); +} + +function writeBigVarint(val, pbf) { + var low, high; + + if (val >= 0) { + low = (val % 0x100000000) | 0; + high = (val / 0x100000000) | 0; + } else { + low = ~(-val % 0x100000000); + high = ~(-val / 0x100000000); + + if (low ^ 0xffffffff) { + low = (low + 1) | 0; + } else { + low = 0; + high = (high + 1) | 0; + } + } + + if (val >= 0x10000000000000000 || val < -0x10000000000000000) { + throw new Error('Given varint doesn\'t fit into 10 bytes'); + } + + pbf.realloc(10); + + writeBigVarintLow(low, high, pbf); + writeBigVarintHigh(high, pbf); +} + +function writeBigVarintLow(low, high, pbf) { + pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; + pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; + pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; + pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; + pbf.buf[pbf.pos] = low & 0x7f; +} + +function writeBigVarintHigh(high, pbf) { + var lsb = (high & 0x07) << 4; + + pbf.buf[pbf.pos++] |= lsb | ((high >>>= 3) ? 0x80 : 0); if (!high) return; + pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; + pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; + pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; + pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; + pbf.buf[pbf.pos++] = high & 0x7f; +} + +function makeRoomForExtraLength(startPos, len, pbf) { + var extraLen = + len <= 0x3fff ? 1 : + len <= 0x1fffff ? 2 : + len <= 0xfffffff ? 3 : Math.ceil(Math.log(len) / (Math.LN2 * 7)); + + // if 1 byte isn't enough for encoding message length, shift the data to the right + pbf.realloc(extraLen); + for (var i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i]; +} + +function writePackedVarint(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeVarint(arr[i]); } +function writePackedSVarint(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSVarint(arr[i]); } +function writePackedFloat(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFloat(arr[i]); } +function writePackedDouble(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeDouble(arr[i]); } +function writePackedBoolean(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeBoolean(arr[i]); } +function writePackedFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFixed32(arr[i]); } +function writePackedSFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed32(arr[i]); } +function writePackedFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFixed64(arr[i]); } +function writePackedSFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed64(arr[i]); } + +// Buffer code below from https://github.com/feross/buffer, MIT-licensed + +function readUInt32(buf, pos) { + return ((buf[pos]) | + (buf[pos + 1] << 8) | + (buf[pos + 2] << 16)) + + (buf[pos + 3] * 0x1000000); +} + +function writeInt32(buf, val, pos) { + buf[pos] = val; + buf[pos + 1] = (val >>> 8); + buf[pos + 2] = (val >>> 16); + buf[pos + 3] = (val >>> 24); +} + +function readInt32(buf, pos) { + return ((buf[pos]) | + (buf[pos + 1] << 8) | + (buf[pos + 2] << 16)) + + (buf[pos + 3] << 24); +} + +function readUtf8(buf, pos, end) { + var str = ''; + var i = pos; + + while (i < end) { + var b0 = buf[i]; + var c = null; // codepoint + var bytesPerSequence = + b0 > 0xEF ? 4 : + b0 > 0xDF ? 3 : + b0 > 0xBF ? 2 : 1; + + if (i + bytesPerSequence > end) break; + + var b1, b2, b3; + + if (bytesPerSequence === 1) { + if (b0 < 0x80) { + c = b0; + } + } else if (bytesPerSequence === 2) { + b1 = buf[i + 1]; + if ((b1 & 0xC0) === 0x80) { + c = (b0 & 0x1F) << 0x6 | (b1 & 0x3F); + if (c <= 0x7F) { + c = null; + } + } + } else if (bytesPerSequence === 3) { + b1 = buf[i + 1]; + b2 = buf[i + 2]; + if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) { + c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | (b2 & 0x3F); + if (c <= 0x7FF || (c >= 0xD800 && c <= 0xDFFF)) { + c = null; + } + } + } else if (bytesPerSequence === 4) { + b1 = buf[i + 1]; + b2 = buf[i + 2]; + b3 = buf[i + 3]; + if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) { + c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | (b3 & 0x3F); + if (c <= 0xFFFF || c >= 0x110000) { + c = null; + } + } + } + + if (c === null) { + c = 0xFFFD; + bytesPerSequence = 1; + + } else if (c > 0xFFFF) { + c -= 0x10000; + str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800); + c = 0xDC00 | c & 0x3FF; + } + + str += String.fromCharCode(c); + i += bytesPerSequence; + } + + return str; +} + +function writeUtf8(buf, str, pos) { + for (var i = 0, c, lead; i < str.length; i++) { + c = str.charCodeAt(i); // code point + + if (c > 0xD7FF && c < 0xE000) { + if (lead) { + if (c < 0xDC00) { + buf[pos++] = 0xEF; + buf[pos++] = 0xBF; + buf[pos++] = 0xBD; + lead = c; + continue; + } else { + c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000; + lead = null; + } + } else { + if (c > 0xDBFF || (i + 1 === str.length)) { + buf[pos++] = 0xEF; + buf[pos++] = 0xBF; + buf[pos++] = 0xBD; + } else { + lead = c; + } + continue; + } + } else if (lead) { + buf[pos++] = 0xEF; + buf[pos++] = 0xBF; + buf[pos++] = 0xBD; + lead = null; + } + + if (c < 0x80) { + buf[pos++] = c; + } else { + if (c < 0x800) { + buf[pos++] = c >> 0x6 | 0xC0; + } else { + if (c < 0x10000) { + buf[pos++] = c >> 0xC | 0xE0; + } else { + buf[pos++] = c >> 0x12 | 0xF0; + buf[pos++] = c >> 0xC & 0x3F | 0x80; + } + buf[pos++] = c >> 0x6 & 0x3F | 0x80; + } + buf[pos++] = c & 0x3F | 0x80; + } + } + return pos; +} + +},{"ieee754":1}]},{},[2])(2) +}); +ol.ext.pbf = module.exports; +})(); + +goog.provide('ol.ext.vectortile'); +/** @typedef {function(*)} */ +ol.ext.vectortile; +(function() { +var exports = {}; +var module = {exports: exports}; +var define; +/** + * @fileoverview + * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility} + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.vectortile = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ +'use strict'; + +module.exports = Point; + +function Point(x, y) { + this.x = x; + this.y = y; +} + +Point.prototype = { + clone: function() { return new Point(this.x, this.y); }, + + add: function(p) { return this.clone()._add(p); }, + sub: function(p) { return this.clone()._sub(p); }, + mult: function(k) { return this.clone()._mult(k); }, + div: function(k) { return this.clone()._div(k); }, + rotate: function(a) { return this.clone()._rotate(a); }, + matMult: function(m) { return this.clone()._matMult(m); }, + unit: function() { return this.clone()._unit(); }, + perp: function() { return this.clone()._perp(); }, + round: function() { return this.clone()._round(); }, + + mag: function() { + return Math.sqrt(this.x * this.x + this.y * this.y); + }, + + equals: function(p) { + return this.x === p.x && + this.y === p.y; + }, + + dist: function(p) { + return Math.sqrt(this.distSqr(p)); + }, + + distSqr: function(p) { + var dx = p.x - this.x, + dy = p.y - this.y; + return dx * dx + dy * dy; + }, + + angle: function() { + return Math.atan2(this.y, this.x); + }, + + angleTo: function(b) { + return Math.atan2(this.y - b.y, this.x - b.x); + }, + + angleWith: function(b) { + return this.angleWithSep(b.x, b.y); + }, + + // Find the angle of the two vectors, solving the formula for the cross product a x b = |a||b|sin(θ) for θ. + angleWithSep: function(x, y) { + return Math.atan2( + this.x * y - this.y * x, + this.x * x + this.y * y); + }, + + _matMult: function(m) { + var x = m[0] * this.x + m[1] * this.y, + y = m[2] * this.x + m[3] * this.y; + this.x = x; + this.y = y; + return this; + }, + + _add: function(p) { + this.x += p.x; + this.y += p.y; + return this; + }, + + _sub: function(p) { + this.x -= p.x; + this.y -= p.y; + return this; + }, + + _mult: function(k) { + this.x *= k; + this.y *= k; + return this; + }, + + _div: function(k) { + this.x /= k; + this.y /= k; + return this; + }, + + _unit: function() { + this._div(this.mag()); + return this; + }, + + _perp: function() { + var y = this.y; + this.y = this.x; + this.x = -y; + return this; + }, + + _rotate: function(angle) { + var cos = Math.cos(angle), + sin = Math.sin(angle), + x = cos * this.x - sin * this.y, + y = sin * this.x + cos * this.y; + this.x = x; + this.y = y; + return this; + }, + + _round: function() { + this.x = Math.round(this.x); + this.y = Math.round(this.y); + return this; + } +}; + +// constructs Point from an array if necessary +Point.convert = function (a) { + if (a instanceof Point) { + return a; + } + if (Array.isArray(a)) { + return new Point(a[0], a[1]); + } + return a; +}; + +},{}],2:[function(_dereq_,module,exports){ +module.exports.VectorTile = _dereq_('./lib/vectortile.js'); +module.exports.VectorTileFeature = _dereq_('./lib/vectortilefeature.js'); +module.exports.VectorTileLayer = _dereq_('./lib/vectortilelayer.js'); + +},{"./lib/vectortile.js":3,"./lib/vectortilefeature.js":4,"./lib/vectortilelayer.js":5}],3:[function(_dereq_,module,exports){ +'use strict'; + +var VectorTileLayer = _dereq_('./vectortilelayer'); + +module.exports = VectorTile; + +function VectorTile(pbf, end) { + this.layers = pbf.readFields(readTile, {}, end); +} + +function readTile(tag, layers, pbf) { + if (tag === 3) { + var layer = new VectorTileLayer(pbf, pbf.readVarint() + pbf.pos); + if (layer.length) layers[layer.name] = layer; + } +} + + +},{"./vectortilelayer":5}],4:[function(_dereq_,module,exports){ +'use strict'; + +var Point = _dereq_('point-geometry'); + +module.exports = VectorTileFeature; + +function VectorTileFeature(pbf, end, extent, keys, values) { + // Public + this.properties = {}; + this.extent = extent; + this.type = 0; + + // Private + this._pbf = pbf; + this._geometry = -1; + this._keys = keys; + this._values = values; + + pbf.readFields(readFeature, this, end); +} + +function readFeature(tag, feature, pbf) { + if (tag == 1) feature.id = pbf.readVarint(); + else if (tag == 2) readTag(pbf, feature); + else if (tag == 3) feature.type = pbf.readVarint(); + else if (tag == 4) feature._geometry = pbf.pos; +} + +function readTag(pbf, feature) { + var end = pbf.readVarint() + pbf.pos; + + while (pbf.pos < end) { + var key = feature._keys[pbf.readVarint()], + value = feature._values[pbf.readVarint()]; + feature.properties[key] = value; + } +} + +VectorTileFeature.types = ['Unknown', 'Point', 'LineString', 'Polygon']; + +VectorTileFeature.prototype.loadGeometry = function() { + var pbf = this._pbf; + pbf.pos = this._geometry; + + var end = pbf.readVarint() + pbf.pos, + cmd = 1, + length = 0, + x = 0, + y = 0, + lines = [], + line; + + while (pbf.pos < end) { + if (!length) { + var cmdLen = pbf.readVarint(); + cmd = cmdLen & 0x7; + length = cmdLen >> 3; + } + + length--; + + if (cmd === 1 || cmd === 2) { + x += pbf.readSVarint(); + y += pbf.readSVarint(); + + if (cmd === 1) { // moveTo + if (line) lines.push(line); + line = []; + } + + line.push(new Point(x, y)); + + } else if (cmd === 7) { + + // Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90 + if (line) { + line.push(line[0].clone()); // closePolygon + } + + } else { + throw new Error('unknown command ' + cmd); + } + } + + if (line) lines.push(line); + + return lines; +}; + +VectorTileFeature.prototype.bbox = function() { + var pbf = this._pbf; + pbf.pos = this._geometry; + + var end = pbf.readVarint() + pbf.pos, + cmd = 1, + length = 0, + x = 0, + y = 0, + x1 = Infinity, + x2 = -Infinity, + y1 = Infinity, + y2 = -Infinity; + + while (pbf.pos < end) { + if (!length) { + var cmdLen = pbf.readVarint(); + cmd = cmdLen & 0x7; + length = cmdLen >> 3; + } + + length--; + + if (cmd === 1 || cmd === 2) { + x += pbf.readSVarint(); + y += pbf.readSVarint(); + if (x < x1) x1 = x; + if (x > x2) x2 = x; + if (y < y1) y1 = y; + if (y > y2) y2 = y; + + } else if (cmd !== 7) { + throw new Error('unknown command ' + cmd); + } + } + + return [x1, y1, x2, y2]; +}; + +VectorTileFeature.prototype.toGeoJSON = function(x, y, z) { + var size = this.extent * Math.pow(2, z), + x0 = this.extent * x, + y0 = this.extent * y, + coords = this.loadGeometry(), + type = VectorTileFeature.types[this.type], + i, j; + + function project(line) { + for (var j = 0; j < line.length; j++) { + var p = line[j], y2 = 180 - (p.y + y0) * 360 / size; + line[j] = [ + (p.x + x0) * 360 / size - 180, + 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90 + ]; + } + } + + switch (this.type) { + case 1: + var points = []; + for (i = 0; i < coords.length; i++) { + points[i] = coords[i][0]; + } + coords = points; + project(coords); + break; + + case 2: + for (i = 0; i < coords.length; i++) { + project(coords[i]); + } + break; + + case 3: + coords = classifyRings(coords); + for (i = 0; i < coords.length; i++) { + for (j = 0; j < coords[i].length; j++) { + project(coords[i][j]); + } + } + break; + } + + if (coords.length === 1) { + coords = coords[0]; + } else { + type = 'Multi' + type; + } + + var result = { + type: "Feature", + geometry: { + type: type, + coordinates: coords + }, + properties: this.properties + }; + + if ('id' in this) { + result.id = this.id; + } + + return result; +}; + +// classifies an array of rings into polygons with outer rings and holes + +function classifyRings(rings) { + var len = rings.length; + + if (len <= 1) return [rings]; + + var polygons = [], + polygon, + ccw; + + for (var i = 0; i < len; i++) { + var area = signedArea(rings[i]); + if (area === 0) continue; + + if (ccw === undefined) ccw = area < 0; + + if (ccw === area < 0) { + if (polygon) polygons.push(polygon); + polygon = [rings[i]]; + + } else { + polygon.push(rings[i]); + } + } + if (polygon) polygons.push(polygon); + + return polygons; +} + +function signedArea(ring) { + var sum = 0; + for (var i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) { + p1 = ring[i]; + p2 = ring[j]; + sum += (p2.x - p1.x) * (p1.y + p2.y); + } + return sum; +} + +},{"point-geometry":1}],5:[function(_dereq_,module,exports){ +'use strict'; + +var VectorTileFeature = _dereq_('./vectortilefeature.js'); + +module.exports = VectorTileLayer; + +function VectorTileLayer(pbf, end) { + // Public + this.version = 1; + this.name = null; + this.extent = 4096; + this.length = 0; + + // Private + this._pbf = pbf; + this._keys = []; + this._values = []; + this._features = []; + + pbf.readFields(readLayer, this, end); + + this.length = this._features.length; +} + +function readLayer(tag, layer, pbf) { + if (tag === 15) layer.version = pbf.readVarint(); + else if (tag === 1) layer.name = pbf.readString(); + else if (tag === 5) layer.extent = pbf.readVarint(); + else if (tag === 2) layer._features.push(pbf.pos); + else if (tag === 3) layer._keys.push(pbf.readString()); + else if (tag === 4) layer._values.push(readValueMessage(pbf)); +} + +function readValueMessage(pbf) { + var value = null, + end = pbf.readVarint() + pbf.pos; + + while (pbf.pos < end) { + var tag = pbf.readVarint() >> 3; + + value = tag === 1 ? pbf.readString() : + tag === 2 ? pbf.readFloat() : + tag === 3 ? pbf.readDouble() : + tag === 4 ? pbf.readVarint64() : + tag === 5 ? pbf.readVarint() : + tag === 6 ? pbf.readSVarint() : + tag === 7 ? pbf.readBoolean() : null; + } + + return value; +} + +// return feature `i` from this layer as a `VectorTileFeature` +VectorTileLayer.prototype.feature = function(i) { + if (i < 0 || i >= this._features.length) throw new Error('feature index out of bounds'); + + this._pbf.pos = this._features[i]; + + var end = this._pbf.readVarint() + this._pbf.pos; + return new VectorTileFeature(this._pbf, end, this.extent, this._keys, this._values); +}; + +},{"./vectortilefeature.js":4}]},{},[2])(2) +}); +ol.ext.vectortile = module.exports; +})(); + +goog.provide('ol.render.Feature'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); + + +/** + * Lightweight, read-only, {@link ol.Feature} and {@link ol.geom.Geometry} like + * structure, optimized for rendering and styling. Geometry access through the + * API is limited to getting the type and extent of the geometry. + * + * @constructor + * @param {ol.geom.GeometryType} type Geometry type. + * @param {Array.<number>} flatCoordinates Flat coordinates. These always need + * to be right-handed for polygons. + * @param {Array.<number>|Array.<Array.<number>>} ends Ends or Endss. + * @param {Object.<string, *>} properties Properties. + */ +ol.render.Feature = function(type, flatCoordinates, ends, properties) { + + /** + * @private + * @type {ol.Extent|undefined} + */ + this.extent_; + + ol.DEBUG && console.assert(type === ol.geom.GeometryType.POINT || + type === ol.geom.GeometryType.MULTI_POINT || + type === ol.geom.GeometryType.LINE_STRING || + type === ol.geom.GeometryType.MULTI_LINE_STRING || + type === ol.geom.GeometryType.POLYGON, + 'Need a Point, MultiPoint, LineString, MultiLineString or Polygon type'); + + /** + * @private + * @type {ol.geom.GeometryType} + */ + this.type_ = type; + + /** + * @private + * @type {Array.<number>} + */ + this.flatCoordinates_ = flatCoordinates; + + /** + * @private + * @type {Array.<number>|Array.<Array.<number>>} + */ + this.ends_ = ends; + + /** + * @private + * @type {Object.<string, *>} + */ + this.properties_ = properties; + +}; + + +/** + * Get a feature property by its key. + * @param {string} key Key + * @return {*} Value for the requested key. + * @api + */ +ol.render.Feature.prototype.get = function(key) { + return this.properties_[key]; +}; + + +/** + * @return {Array.<number>|Array.<Array.<number>>} Ends or endss. + */ +ol.render.Feature.prototype.getEnds = function() { + return this.ends_; +}; + + +/** + * Get the extent of this feature's geometry. + * @return {ol.Extent} Extent. + * @api + */ +ol.render.Feature.prototype.getExtent = function() { + if (!this.extent_) { + this.extent_ = this.type_ === ol.geom.GeometryType.POINT ? + ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates_) : + ol.extent.createOrUpdateFromFlatCoordinates( + this.flatCoordinates_, 0, this.flatCoordinates_.length, 2); + + } + return this.extent_; +}; + + +/** + * @return {Array.<number>} Flat coordinates. + */ +ol.render.Feature.prototype.getOrientedFlatCoordinates = function() { + return this.flatCoordinates_; +}; + + +/** + * @return {Array.<number>} Flat coordinates. + */ +ol.render.Feature.prototype.getFlatCoordinates = + ol.render.Feature.prototype.getOrientedFlatCoordinates; + + +/** + * Get the feature for working with its geometry. + * @return {ol.render.Feature} Feature. + * @api + */ +ol.render.Feature.prototype.getGeometry = function() { + return this; +}; + + +/** + * Get the feature properties. + * @return {Object.<string, *>} Feature properties. + * @api + */ +ol.render.Feature.prototype.getProperties = function() { + return this.properties_; +}; + + +/** + * Get the feature for working with its geometry. + * @return {ol.render.Feature} Feature. + */ +ol.render.Feature.prototype.getSimplifiedGeometry = + ol.render.Feature.prototype.getGeometry; + + +/** + * @return {number} Stride. + */ +ol.render.Feature.prototype.getStride = function() { + return 2; +}; + + +/** + * @return {undefined} + */ +ol.render.Feature.prototype.getStyleFunction = ol.nullFunction; + + +/** + * Get the type of this feature's geometry. + * @return {ol.geom.GeometryType} Geometry type. + * @api + */ +ol.render.Feature.prototype.getType = function() { + return this.type_; +}; + +//FIXME Implement projection handling + +goog.provide('ol.format.MVT'); + +goog.require('ol'); +goog.require('ol.ext.pbf'); +goog.require('ol.ext.vectortile'); +goog.require('ol.format.Feature'); +goog.require('ol.format.FormatType'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); +goog.require('ol.render.Feature'); + + +/** + * @classdesc + * Feature format for reading data in the Mapbox MVT format. + * + * @constructor + * @extends {ol.format.Feature} + * @param {olx.format.MVTOptions=} opt_options Options. + * @api + */ +ol.format.MVT = function(opt_options) { + + ol.format.Feature.call(this); + + var options = opt_options ? opt_options : {}; + + /** + * @type {ol.proj.Projection} + */ + this.defaultDataProjection = new ol.proj.Projection({ + code: '', + units: ol.proj.Units.TILE_PIXELS + }); + + /** + * @private + * @type {function((ol.geom.Geometry|Object.<string, *>)=)| + * function(ol.geom.GeometryType,Array.<number>, + * (Array.<number>|Array.<Array.<number>>),Object.<string, *>)} + */ + this.featureClass_ = options.featureClass ? + options.featureClass : ol.render.Feature; + + /** + * @private + * @type {string} + */ + this.geometryName_ = options.geometryName ? + options.geometryName : 'geometry'; + + /** + * @private + * @type {string} + */ + this.layerName_ = options.layerName ? options.layerName : 'layer'; + + /** + * @private + * @type {Array.<string>} + */ + this.layers_ = options.layers ? options.layers : null; + +}; +ol.inherits(ol.format.MVT, ol.format.Feature); + + +/** + * @inheritDoc + */ +ol.format.MVT.prototype.getType = function() { + return ol.format.FormatType.ARRAY_BUFFER; +}; + + +/** + * @private + * @param {Object} rawFeature Raw Mapbox feature. + * @param {string} layer Layer. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + */ +ol.format.MVT.prototype.readFeature_ = function( + rawFeature, layer, opt_options) { + var feature = new this.featureClass_(); + var id = rawFeature.id; + var values = rawFeature.properties; + values[this.layerName_] = layer; + var geometry = ol.format.Feature.transformWithOptions( + ol.format.MVT.readGeometry_(rawFeature), false, + this.adaptOptions(opt_options)); + if (geometry) { + values[this.geometryName_] = geometry; + } + feature.setId(id); + feature.setProperties(values); + feature.setGeometryName(this.geometryName_); + return feature; +}; + + +/** + * @private + * @param {Object} rawFeature Raw Mapbox feature. + * @param {string} layer Layer. + * @return {ol.render.Feature} Feature. + */ +ol.format.MVT.prototype.readRenderFeature_ = function(rawFeature, layer) { + var coords = rawFeature.loadGeometry(); + var ends = []; + var flatCoordinates = []; + ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends); + + var type = rawFeature.type; + /** @type {ol.geom.GeometryType} */ + var geometryType; + if (type === 1) { + geometryType = coords.length === 1 ? + ol.geom.GeometryType.POINT : ol.geom.GeometryType.MULTI_POINT; + } else if (type === 2) { + if (coords.length === 1) { + geometryType = ol.geom.GeometryType.LINE_STRING; + } else { + geometryType = ol.geom.GeometryType.MULTI_LINE_STRING; + } + } else if (type === 3) { + geometryType = ol.geom.GeometryType.POLYGON; + } + + var values = rawFeature.properties; + values[this.layerName_] = layer; + + return new this.featureClass_(geometryType, flatCoordinates, ends, values); +}; + + +/** + * @inheritDoc + * @api + */ +ol.format.MVT.prototype.readFeatures = function(source, opt_options) { + var layers = this.layers_; + + var pbf = new ol.ext.pbf(/** @type {ArrayBuffer} */ (source)); + var tile = new ol.ext.vectortile.VectorTile(pbf); + var features = []; + var featureClass = this.featureClass_; + var layer, feature; + for (var name in tile.layers) { + if (layers && layers.indexOf(name) == -1) { + continue; + } + layer = tile.layers[name]; + + for (var i = 0, ii = layer.length; i < ii; ++i) { + if (featureClass === ol.render.Feature) { + feature = this.readRenderFeature_(layer.feature(i), name); + } else { + feature = this.readFeature_(layer.feature(i), name, opt_options); + } + features.push(feature); + } + } + + return features; +}; + + +/** + * @inheritDoc + * @api + */ +ol.format.MVT.prototype.readProjection = function(source) { + return this.defaultDataProjection; +}; + + +/** + * Sets the layers that features will be read from. + * @param {Array.<string>} layers Layers. + * @api + */ +ol.format.MVT.prototype.setLayers = function(layers) { + this.layers_ = layers; +}; + + +/** + * @private + * @param {Object} coords Raw feature coordinates. + * @param {Array.<number>} flatCoordinates Flat coordinates to be populated by + * this function. + * @param {Array.<number>} ends Ends to be populated by this function. + */ +ol.format.MVT.calculateFlatCoordinates_ = function( + coords, flatCoordinates, ends) { + var end = 0; + for (var i = 0, ii = coords.length; i < ii; ++i) { + var line = coords[i]; + var j, jj; + for (j = 0, jj = line.length; j < jj; ++j) { + var coord = line[j]; + // Non-tilespace coords can be calculated here when a TileGrid and + // TileCoord are known. + flatCoordinates.push(coord.x, coord.y); + } + end += 2 * j; + ends.push(end); + } +}; + + +/** + * @private + * @param {Object} rawFeature Raw Mapbox feature. + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.MVT.readGeometry_ = function(rawFeature) { + var type = rawFeature.type; + if (type === 0) { + return null; + } + + var coords = rawFeature.loadGeometry(); + var ends = []; + var flatCoordinates = []; + ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends); + + var geom; + if (type === 1) { + geom = coords.length === 1 ? + new ol.geom.Point(null) : new ol.geom.MultiPoint(null); + } else if (type === 2) { + if (coords.length === 1) { + geom = new ol.geom.LineString(null); + } else { + geom = new ol.geom.MultiLineString(null); + } + } else if (type === 3) { + geom = new ol.geom.Polygon(null); + } + + geom.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates, + ends); + + return geom; +}; + +// FIXME add typedef for stack state objects +goog.provide('ol.format.OSMXML'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Feature format for reading data in the + * [OSMXML format](http://wiki.openstreetmap.org/wiki/OSM_XML). + * + * @constructor + * @extends {ol.format.XMLFeature} + * @api stable + */ +ol.format.OSMXML = function() { + ol.format.XMLFeature.call(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); +}; +ol.inherits(ol.format.OSMXML, ol.format.XMLFeature); + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.OSMXML.EXTENSIONS_ = ['.osm']; + + +/** + * @inheritDoc + */ +ol.format.OSMXML.prototype.getExtensions = function() { + return ol.format.OSMXML.EXTENSIONS_; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.OSMXML.readNode_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'node', 'localName should be node'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var state = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var id = node.getAttribute('id'); + /** @type {ol.Coordinate} */ + var coordinates = [ + parseFloat(node.getAttribute('lon')), + parseFloat(node.getAttribute('lat')) + ]; + state.nodes[id] = coordinates; + + var values = ol.xml.pushParseAndPop({ + tags: {} + }, ol.format.OSMXML.NODE_PARSERS_, node, objectStack); + if (!ol.obj.isEmpty(values.tags)) { + var geometry = new ol.geom.Point(coordinates); + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setId(id); + feature.setProperties(values.tags); + state.features.push(feature); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.OSMXML.readWay_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'way', 'localName should be way'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var id = node.getAttribute('id'); + var values = ol.xml.pushParseAndPop({ + ndrefs: [], + tags: {} + }, ol.format.OSMXML.WAY_PARSERS_, node, objectStack); + var state = /** @type {Object} */ (objectStack[objectStack.length - 1]); + /** @type {Array.<number>} */ + var flatCoordinates = []; + for (var i = 0, ii = values.ndrefs.length; i < ii; i++) { + var point = state.nodes[values.ndrefs[i]]; + ol.array.extend(flatCoordinates, point); + } + var geometry; + if (values.ndrefs[0] == values.ndrefs[values.ndrefs.length - 1]) { + // closed way + geometry = new ol.geom.Polygon(null); + geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates, + [flatCoordinates.length]); + } else { + geometry = new ol.geom.LineString(null); + geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates); + } + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setId(id); + feature.setProperties(values.tags); + state.features.push(feature); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.OSMXML.readNd_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'nd', 'localName should be nd'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + values.ndrefs.push(node.getAttribute('ref')); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.OSMXML.readTag_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'tag', 'localName should be tag'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + values.tags[node.getAttribute('k')] = node.getAttribute('v'); +}; + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.format.OSMXML.NAMESPACE_URIS_ = [ + null +]; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OSMXML.NAMESPACE_URIS_, { + 'nd': ol.format.OSMXML.readNd_, + 'tag': ol.format.OSMXML.readTag_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OSMXML.PARSERS_ = ol.xml.makeStructureNS( + ol.format.OSMXML.NAMESPACE_URIS_, { + 'node': ol.format.OSMXML.readNode_, + 'way': ol.format.OSMXML.readWay_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OSMXML.NODE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OSMXML.NAMESPACE_URIS_, { + 'tag': ol.format.OSMXML.readTag_ + }); + + +/** + * Read all features from an OSM source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.OSMXML.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.OSMXML.prototype.readFeaturesFromNode = function(node, opt_options) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + var options = this.getReadOptions(node, opt_options); + if (node.localName == 'osm') { + var state = ol.xml.pushParseAndPop({ + nodes: {}, + features: [] + }, ol.format.OSMXML.PARSERS_, node, [options]); + if (state.features) { + return state.features; + } + } + return []; +}; + + +/** + * Read the projection from an OSM source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.OSMXML.prototype.readProjection; + +goog.provide('ol.format.XLink'); + + +/** + * @const + * @type {string} + */ +ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink'; + + +/** + * @param {Node} node Node. + * @return {boolean|undefined} Boolean. + */ +ol.format.XLink.readHref = function(node) { + return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href'); +}; + +goog.provide('ol.format.XML'); + +goog.require('ol.xml'); + + +/** + * @classdesc + * Generic format for reading non-feature XML data + * + * @constructor + * @struct + */ +ol.format.XML = function() { +}; + + +/** + * @param {Document|Node|string} source Source. + * @return {Object} The parsed result. + */ +ol.format.XML.prototype.read = function(source) { + if (ol.xml.isDocument(source)) { + return this.readFromDocument(/** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readFromNode(/** @type {Node} */ (source)); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readFromDocument(doc); + } else { + return null; + } +}; + + +/** + * @abstract + * @param {Document} doc Document. + * @return {Object} Object + */ +ol.format.XML.prototype.readFromDocument = function(doc) {}; + + +/** + * @abstract + * @param {Node} node Node. + * @return {Object} Object + */ +ol.format.XML.prototype.readFromNode = function(node) {}; + +goog.provide('ol.format.OWS'); + +goog.require('ol'); +goog.require('ol.format.XLink'); +goog.require('ol.format.XML'); +goog.require('ol.format.XSD'); +goog.require('ol.xml'); + + +/** + * @constructor + * @extends {ol.format.XML} + */ +ol.format.OWS = function() { + ol.format.XML.call(this); +}; +ol.inherits(ol.format.OWS, ol.format.XML); + + +/** + * @param {Document} doc Document. + * @return {Object} OWS object. + */ +ol.format.OWS.prototype.readFromDocument = function(doc) { + ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + return this.readFromNode(n); + } + } + return null; +}; + + +/** + * @param {Node} node Node. + * @return {Object} OWS object. + */ +ol.format.OWS.prototype.readFromNode = function(node) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + var owsObject = ol.xml.pushParseAndPop({}, + ol.format.OWS.PARSERS_, node, []); + return owsObject ? owsObject : null; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The address. + */ +ol.format.OWS.readAddress_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Address', + 'localName should be Address'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.ADDRESS_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The values. + */ +ol.format.OWS.readAllowedValues_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'AllowedValues', + 'localName should be AllowedValues'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.ALLOWED_VALUES_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The constraint. + */ +ol.format.OWS.readConstraint_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Constraint', + 'localName should be Constraint'); + var name = node.getAttribute('name'); + if (!name) { + return undefined; + } + return ol.xml.pushParseAndPop({'name': name}, + ol.format.OWS.CONSTRAINT_PARSERS_, node, + objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The contact info. + */ +ol.format.OWS.readContactInfo_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ContactInfo', + 'localName should be ContactInfo'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.CONTACT_INFO_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The DCP. + */ +ol.format.OWS.readDcp_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'DCP', 'localName should be DCP'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.DCP_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The GET object. + */ +ol.format.OWS.readGet_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Get', 'localName should be Get'); + var href = ol.format.XLink.readHref(node); + if (!href) { + return undefined; + } + return ol.xml.pushParseAndPop({'href': href}, + ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The HTTP object. + */ +ol.format.OWS.readHttp_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'HTTP', 'localName should be HTTP'); + return ol.xml.pushParseAndPop({}, ol.format.OWS.HTTP_PARSERS_, + node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The operation. + */ +ol.format.OWS.readOperation_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Operation', + 'localName should be Operation'); + var name = node.getAttribute('name'); + var value = ol.xml.pushParseAndPop({}, + ol.format.OWS.OPERATION_PARSERS_, node, objectStack); + if (!value) { + return undefined; + } + var object = /** @type {Object} */ + (objectStack[objectStack.length - 1]); + object[name] = value; + +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The operations metadata. + */ +ol.format.OWS.readOperationsMetadata_ = function(node, + objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'OperationsMetadata', + 'localName should be OperationsMetadata'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.OPERATIONS_METADATA_PARSERS_, node, + objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The phone. + */ +ol.format.OWS.readPhone_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Phone', 'localName should be Phone'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.PHONE_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The service identification. + */ +ol.format.OWS.readServiceIdentification_ = function(node, + objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ServiceIdentification', + 'localName should be ServiceIdentification'); + return ol.xml.pushParseAndPop( + {}, ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_, node, + objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The service contact. + */ +ol.format.OWS.readServiceContact_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ServiceContact', + 'localName should be ServiceContact'); + return ol.xml.pushParseAndPop( + {}, ol.format.OWS.SERVICE_CONTACT_PARSERS_, node, + objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The service provider. + */ +ol.format.OWS.readServiceProvider_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ServiceProvider', + 'localName should be ServiceProvider'); + return ol.xml.pushParseAndPop( + {}, ol.format.OWS.SERVICE_PROVIDER_PARSERS_, node, + objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {string|undefined} The value. + */ +ol.format.OWS.readValue_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Value', 'localName should be Value'); + return ol.format.XSD.readString(node); +}; + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.OWS.NAMESPACE_URIS_ = [ + null, + 'http://www.opengis.net/ows/1.1' +]; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'ServiceIdentification': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readServiceIdentification_), + 'ServiceProvider': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readServiceProvider_), + 'OperationsMetadata': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readOperationsMetadata_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.ADDRESS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'DeliveryPoint': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'AdministrativeArea': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'PostalCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'ElectronicMailAddress': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Value': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readValue_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'AllowedValues': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readAllowedValues_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.CONTACT_INFO_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Phone': ol.xml.makeObjectPropertySetter(ol.format.OWS.readPhone_), + 'Address': ol.xml.makeObjectPropertySetter(ol.format.OWS.readAddress_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.DCP_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'HTTP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readHttp_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Get': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readGet_), + 'Post': undefined // TODO + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'DCP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readDcp_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Operation': ol.format.OWS.readOperation_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.PHONE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Voice': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Facsimile': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Constraint': ol.xml.makeObjectPropertyPusher( + ol.format.OWS.readConstraint_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.SERVICE_CONTACT_PARSERS_ = + ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'IndividualName': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'PositionName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'ContactInfo': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readContactInfo_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_ = + ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'ServiceTypeVersion': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ServiceType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.SERVICE_PROVIDER_PARSERS_ = + ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'ProviderName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'ProviderSite': ol.xml.makeObjectPropertySetter(ol.format.XLink.readHref), + 'ServiceContact': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readServiceContact_) + }); + +goog.provide('ol.geom.flat.flip'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {Array.<number>=} opt_dest Destination. + * @param {number=} opt_destOffset Destination offset. + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.flat.flip.flipXY = function(flatCoordinates, offset, end, stride, opt_dest, opt_destOffset) { + var dest, destOffset; + if (opt_dest !== undefined) { + dest = opt_dest; + destOffset = opt_destOffset !== undefined ? opt_destOffset : 0; + } else { + dest = []; + destOffset = 0; + } + var j = offset; + while (j < end) { + var x = flatCoordinates[j++]; + dest[destOffset++] = flatCoordinates[j++]; + dest[destOffset++] = x; + for (var k = 2; k < stride; ++k) { + dest[destOffset++] = flatCoordinates[j++]; + } + } + dest.length = destOffset; + return dest; +}; + +goog.provide('ol.format.Polyline'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.TextFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.flip'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Feature format for reading and writing data in the Encoded + * Polyline Algorithm Format. + * + * @constructor + * @extends {ol.format.TextFeature} + * @param {olx.format.PolylineOptions=} opt_options + * Optional configuration object. + * @api stable + */ +ol.format.Polyline = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.TextFeature.call(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); + + /** + * @private + * @type {number} + */ + this.factor_ = options.factor ? options.factor : 1e5; + + /** + * @private + * @type {ol.geom.GeometryLayout} + */ + this.geometryLayout_ = options.geometryLayout ? + options.geometryLayout : ol.geom.GeometryLayout.XY; +}; +ol.inherits(ol.format.Polyline, ol.format.TextFeature); + + +/** + * Encode a list of n-dimensional points and return an encoded string + * + * Attention: This function will modify the passed array! + * + * @param {Array.<number>} numbers A list of n-dimensional points. + * @param {number} stride The number of dimension of the points in the list. + * @param {number=} opt_factor The factor by which the numbers will be + * multiplied. The remaining decimal places will get rounded away. + * Default is `1e5`. + * @return {string} The encoded string. + * @api + */ +ol.format.Polyline.encodeDeltas = function(numbers, stride, opt_factor) { + var factor = opt_factor ? opt_factor : 1e5; + var d; + + var lastNumbers = new Array(stride); + for (d = 0; d < stride; ++d) { + lastNumbers[d] = 0; + } + + var i, ii; + for (i = 0, ii = numbers.length; i < ii;) { + for (d = 0; d < stride; ++d, ++i) { + var num = numbers[i]; + var delta = num - lastNumbers[d]; + lastNumbers[d] = num; + + numbers[i] = delta; + } + } + + return ol.format.Polyline.encodeFloats(numbers, factor); +}; + + +/** + * Decode a list of n-dimensional points from an encoded string + * + * @param {string} encoded An encoded string. + * @param {number} stride The number of dimension of the points in the + * encoded string. + * @param {number=} opt_factor The factor by which the resulting numbers will + * be divided. Default is `1e5`. + * @return {Array.<number>} A list of n-dimensional points. + * @api + */ +ol.format.Polyline.decodeDeltas = function(encoded, stride, opt_factor) { + var factor = opt_factor ? opt_factor : 1e5; + var d; + + /** @type {Array.<number>} */ + var lastNumbers = new Array(stride); + for (d = 0; d < stride; ++d) { + lastNumbers[d] = 0; + } + + var numbers = ol.format.Polyline.decodeFloats(encoded, factor); + + var i, ii; + for (i = 0, ii = numbers.length; i < ii;) { + for (d = 0; d < stride; ++d, ++i) { + lastNumbers[d] += numbers[i]; + + numbers[i] = lastNumbers[d]; + } + } + + return numbers; +}; + + +/** + * Encode a list of floating point numbers and return an encoded string + * + * Attention: This function will modify the passed array! + * + * @param {Array.<number>} numbers A list of floating point numbers. + * @param {number=} opt_factor The factor by which the numbers will be + * multiplied. The remaining decimal places will get rounded away. + * Default is `1e5`. + * @return {string} The encoded string. + * @api + */ +ol.format.Polyline.encodeFloats = function(numbers, opt_factor) { + var factor = opt_factor ? opt_factor : 1e5; + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + numbers[i] = Math.round(numbers[i] * factor); + } + + return ol.format.Polyline.encodeSignedIntegers(numbers); +}; + + +/** + * Decode a list of floating point numbers from an encoded string + * + * @param {string} encoded An encoded string. + * @param {number=} opt_factor The factor by which the result will be divided. + * Default is `1e5`. + * @return {Array.<number>} A list of floating point numbers. + * @api + */ +ol.format.Polyline.decodeFloats = function(encoded, opt_factor) { + var factor = opt_factor ? opt_factor : 1e5; + var numbers = ol.format.Polyline.decodeSignedIntegers(encoded); + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + numbers[i] /= factor; + } + return numbers; +}; + + +/** + * Encode a list of signed integers and return an encoded string + * + * Attention: This function will modify the passed array! + * + * @param {Array.<number>} numbers A list of signed integers. + * @return {string} The encoded string. + */ +ol.format.Polyline.encodeSignedIntegers = function(numbers) { + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + var num = numbers[i]; + numbers[i] = (num < 0) ? ~(num << 1) : (num << 1); + } + return ol.format.Polyline.encodeUnsignedIntegers(numbers); +}; + + +/** + * Decode a list of signed integers from an encoded string + * + * @param {string} encoded An encoded string. + * @return {Array.<number>} A list of signed integers. + */ +ol.format.Polyline.decodeSignedIntegers = function(encoded) { + var numbers = ol.format.Polyline.decodeUnsignedIntegers(encoded); + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + var num = numbers[i]; + numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1); + } + return numbers; +}; + + +/** + * Encode a list of unsigned integers and return an encoded string + * + * @param {Array.<number>} numbers A list of unsigned integers. + * @return {string} The encoded string. + */ +ol.format.Polyline.encodeUnsignedIntegers = function(numbers) { + var encoded = ''; + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + encoded += ol.format.Polyline.encodeUnsignedInteger(numbers[i]); + } + return encoded; +}; + + +/** + * Decode a list of unsigned integers from an encoded string + * + * @param {string} encoded An encoded string. + * @return {Array.<number>} A list of unsigned integers. + */ +ol.format.Polyline.decodeUnsignedIntegers = function(encoded) { + var numbers = []; + var current = 0; + var shift = 0; + var i, ii; + for (i = 0, ii = encoded.length; i < ii; ++i) { + var b = encoded.charCodeAt(i) - 63; + current |= (b & 0x1f) << shift; + if (b < 0x20) { + numbers.push(current); + current = 0; + shift = 0; + } else { + shift += 5; + } + } + return numbers; +}; + + +/** + * Encode one single unsigned integer and return an encoded string + * + * @param {number} num Unsigned integer that should be encoded. + * @return {string} The encoded string. + */ +ol.format.Polyline.encodeUnsignedInteger = function(num) { + var value, encoded = ''; + while (num >= 0x20) { + value = (0x20 | (num & 0x1f)) + 63; + encoded += String.fromCharCode(value); + num >>= 5; + } + value = num + 63; + encoded += String.fromCharCode(value); + return encoded; +}; + + +/** + * Read the feature from the Polyline source. The coordinates are assumed to be + * in two dimensions and in latitude, longitude order. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable + */ +ol.format.Polyline.prototype.readFeature; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.readFeatureFromText = function(text, opt_options) { + var geometry = this.readGeometryFromText(text, opt_options); + return new ol.Feature(geometry); +}; + + +/** + * Read the feature from the source. As Polyline sources contain a single + * feature, this will return the feature in an array. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.Polyline.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.readFeaturesFromText = function(text, opt_options) { + var feature = this.readFeatureFromText(text, opt_options); + return [feature]; +}; + + +/** + * Read the geometry from the source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. + * @api stable + */ +ol.format.Polyline.prototype.readGeometry; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.readGeometryFromText = function(text, opt_options) { + var stride = ol.geom.SimpleGeometry.getStrideForLayout(this.geometryLayout_); + var flatCoordinates = ol.format.Polyline.decodeDeltas( + text, stride, this.factor_); + ol.geom.flat.flip.flipXY( + flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates); + var coordinates = ol.geom.flat.inflate.coordinates( + flatCoordinates, 0, flatCoordinates.length, stride); + + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions( + new ol.geom.LineString(coordinates, this.geometryLayout_), false, + this.adaptOptions(opt_options))); +}; + + +/** + * Read the projection from a Polyline source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.Polyline.prototype.readProjection; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.writeFeatureText = function(feature, opt_options) { + var geometry = feature.getGeometry(); + if (geometry) { + return this.writeGeometryText(geometry, opt_options); + } else { + ol.asserts.assert(false, 40); // Expected `feature` to have a geometry + return ''; + } +}; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.writeFeaturesText = function(features, opt_options) { + ol.DEBUG && console.assert(features.length == 1, + 'features array should have 1 item'); + return this.writeFeatureText(features[0], opt_options); +}; + + +/** + * Write a single geometry in Polyline format. + * + * @function + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Geometry. + * @api stable + */ +ol.format.Polyline.prototype.writeGeometry; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.writeGeometryText = function(geometry, opt_options) { + geometry = /** @type {ol.geom.LineString} */ + (ol.format.Feature.transformWithOptions( + geometry, true, this.adaptOptions(opt_options))); + var flatCoordinates = geometry.getFlatCoordinates(); + var stride = geometry.getStride(); + ol.geom.flat.flip.flipXY( + flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates); + return ol.format.Polyline.encodeDeltas(flatCoordinates, stride, this.factor_); +}; + +goog.provide('ol.format.TopoJSON'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.JSONFeature'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.obj'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Feature format for reading data in the TopoJSON format. + * + * @constructor + * @extends {ol.format.JSONFeature} + * @param {olx.format.TopoJSONOptions=} opt_options Options. + * @api stable + */ +ol.format.TopoJSON = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.JSONFeature.call(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get( + options.defaultDataProjection ? + options.defaultDataProjection : 'EPSG:4326'); + +}; +ol.inherits(ol.format.TopoJSON, ol.format.JSONFeature); + + +/** + * @const {Array.<string>} + * @private + */ +ol.format.TopoJSON.EXTENSIONS_ = ['.topojson']; + + +/** + * Concatenate arcs into a coordinate array. + * @param {Array.<number>} indices Indices of arcs to concatenate. Negative + * values indicate arcs need to be reversed. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs (already + * transformed). + * @return {Array.<ol.Coordinate>} Coordinates array. + * @private + */ +ol.format.TopoJSON.concatenateArcs_ = function(indices, arcs) { + /** @type {Array.<ol.Coordinate>} */ + var coordinates = []; + var index, arc; + var i, ii; + var j, jj; + for (i = 0, ii = indices.length; i < ii; ++i) { + index = indices[i]; + if (i > 0) { + // splicing together arcs, discard last point + coordinates.pop(); + } + if (index >= 0) { + // forward arc + arc = arcs[index]; + } else { + // reverse arc + arc = arcs[~index].slice().reverse(); + } + coordinates.push.apply(coordinates, arc); + } + // provide fresh copies of coordinate arrays + for (j = 0, jj = coordinates.length; j < jj; ++j) { + coordinates[j] = coordinates[j].slice(); + } + return coordinates; +}; + + +/** + * Create a point from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @return {ol.geom.Point} Geometry. + * @private + */ +ol.format.TopoJSON.readPointGeometry_ = function(object, scale, translate) { + var coordinates = object.coordinates; + if (scale && translate) { + ol.format.TopoJSON.transformVertex_(coordinates, scale, translate); + } + return new ol.geom.Point(coordinates); +}; + + +/** + * Create a multi-point from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @return {ol.geom.MultiPoint} Geometry. + * @private + */ +ol.format.TopoJSON.readMultiPointGeometry_ = function(object, scale, + translate) { + var coordinates = object.coordinates; + var i, ii; + if (scale && translate) { + for (i = 0, ii = coordinates.length; i < ii; ++i) { + ol.format.TopoJSON.transformVertex_(coordinates[i], scale, translate); + } + } + return new ol.geom.MultiPoint(coordinates); +}; + + +/** + * Create a linestring from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @return {ol.geom.LineString} Geometry. + * @private + */ +ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) { + var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs); + return new ol.geom.LineString(coordinates); +}; + + +/** + * Create a multi-linestring from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @return {ol.geom.MultiLineString} Geometry. + * @private + */ +ol.format.TopoJSON.readMultiLineStringGeometry_ = function(object, arcs) { + var coordinates = []; + var i, ii; + for (i = 0, ii = object.arcs.length; i < ii; ++i) { + coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs); + } + return new ol.geom.MultiLineString(coordinates); +}; + + +/** + * Create a polygon from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @return {ol.geom.Polygon} Geometry. + * @private + */ +ol.format.TopoJSON.readPolygonGeometry_ = function(object, arcs) { + var coordinates = []; + var i, ii; + for (i = 0, ii = object.arcs.length; i < ii; ++i) { + coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs); + } + return new ol.geom.Polygon(coordinates); +}; + + +/** + * Create a multi-polygon from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @return {ol.geom.MultiPolygon} Geometry. + * @private + */ +ol.format.TopoJSON.readMultiPolygonGeometry_ = function(object, arcs) { + var coordinates = []; + var polyArray, ringCoords, j, jj; + var i, ii; + for (i = 0, ii = object.arcs.length; i < ii; ++i) { + // for each polygon + polyArray = object.arcs[i]; + ringCoords = []; + for (j = 0, jj = polyArray.length; j < jj; ++j) { + // for each ring + ringCoords[j] = ol.format.TopoJSON.concatenateArcs_(polyArray[j], arcs); + } + coordinates[i] = ringCoords; + } + return new ol.geom.MultiPolygon(coordinates); +}; + + +/** + * @inheritDoc + */ +ol.format.TopoJSON.prototype.getExtensions = function() { + return ol.format.TopoJSON.EXTENSIONS_; +}; + + +/** + * Create features from a TopoJSON GeometryCollection object. + * + * @param {TopoJSONGeometryCollection} collection TopoJSON Geometry + * object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Array of features. + * @private + */ +ol.format.TopoJSON.readFeaturesFromGeometryCollection_ = function( + collection, arcs, scale, translate, opt_options) { + var geometries = collection.geometries; + var features = []; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + features[i] = ol.format.TopoJSON.readFeatureFromGeometry_( + geometries[i], arcs, scale, translate, opt_options); + } + return features; +}; + + +/** + * Create a feature from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON geometry object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @private + */ +ol.format.TopoJSON.readFeatureFromGeometry_ = function(object, arcs, + scale, translate, opt_options) { + var geometry; + var type = object.type; + var geometryReader = ol.format.TopoJSON.GEOMETRY_READERS_[type]; + if ((type === 'Point') || (type === 'MultiPoint')) { + geometry = geometryReader(object, scale, translate); + } else { + geometry = geometryReader(object, arcs); + } + var feature = new ol.Feature(); + feature.setGeometry(/** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, false, opt_options))); + if (object.id !== undefined) { + feature.setId(object.id); + } + if (object.properties) { + feature.setProperties(object.properties); + } + return feature; +}; + + +/** + * Read all features from a TopoJSON source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.TopoJSON.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.TopoJSON.prototype.readFeaturesFromObject = function( + object, opt_options) { + if (object.type == 'Topology') { + var topoJSONTopology = /** @type {TopoJSONTopology} */ (object); + var transform, scale = null, translate = null; + if (topoJSONTopology.transform) { + transform = topoJSONTopology.transform; + scale = transform.scale; + translate = transform.translate; + } + var arcs = topoJSONTopology.arcs; + if (transform) { + ol.format.TopoJSON.transformArcs_(arcs, scale, translate); + } + /** @type {Array.<ol.Feature>} */ + var features = []; + var topoJSONFeatures = ol.obj.getValues(topoJSONTopology.objects); + var i, ii; + var feature; + for (i = 0, ii = topoJSONFeatures.length; i < ii; ++i) { + if (topoJSONFeatures[i].type === 'GeometryCollection') { + feature = /** @type {TopoJSONGeometryCollection} */ + (topoJSONFeatures[i]); + features.push.apply(features, + ol.format.TopoJSON.readFeaturesFromGeometryCollection_( + feature, arcs, scale, translate, opt_options)); + } else { + feature = /** @type {TopoJSONGeometry} */ + (topoJSONFeatures[i]); + features.push(ol.format.TopoJSON.readFeatureFromGeometry_( + feature, arcs, scale, translate, opt_options)); + } + } + return features; + } else { + return []; + } +}; + + +/** + * Apply a linear transform to array of arcs. The provided array of arcs is + * modified in place. + * + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @private + */ +ol.format.TopoJSON.transformArcs_ = function(arcs, scale, translate) { + var i, ii; + for (i = 0, ii = arcs.length; i < ii; ++i) { + ol.format.TopoJSON.transformArc_(arcs[i], scale, translate); + } +}; + + +/** + * Apply a linear transform to an arc. The provided arc is modified in place. + * + * @param {Array.<ol.Coordinate>} arc Arc. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @private + */ +ol.format.TopoJSON.transformArc_ = function(arc, scale, translate) { + var x = 0; + var y = 0; + var vertex; + var i, ii; + for (i = 0, ii = arc.length; i < ii; ++i) { + vertex = arc[i]; + x += vertex[0]; + y += vertex[1]; + vertex[0] = x; + vertex[1] = y; + ol.format.TopoJSON.transformVertex_(vertex, scale, translate); + } +}; + + +/** + * Apply a linear transform to a vertex. The provided vertex is modified in + * place. + * + * @param {ol.Coordinate} vertex Vertex. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @private + */ +ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) { + vertex[0] = vertex[0] * scale[0] + translate[0]; + vertex[1] = vertex[1] * scale[1] + translate[1]; +}; + + +/** + * Read the projection from a TopoJSON source. + * + * @function + * @param {Document|Node|Object|string} object Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.TopoJSON.prototype.readProjection = function(object) { + return this.defaultDataProjection; +}; + + +/** + * @const + * @private + * @type {Object.<string, function(TopoJSONGeometry, Array, ...Array): ol.geom.Geometry>} + */ +ol.format.TopoJSON.GEOMETRY_READERS_ = { + 'Point': ol.format.TopoJSON.readPointGeometry_, + 'LineString': ol.format.TopoJSON.readLineStringGeometry_, + 'Polygon': ol.format.TopoJSON.readPolygonGeometry_, + 'MultiPoint': ol.format.TopoJSON.readMultiPointGeometry_, + 'MultiLineString': ol.format.TopoJSON.readMultiLineStringGeometry_, + 'MultiPolygon': ol.format.TopoJSON.readMultiPolygonGeometry_ +}; + +goog.provide('ol.format.WFS'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.format.GML3'); +goog.require('ol.format.GMLBase'); +goog.require('ol.format.filter'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.format.XSD'); +goog.require('ol.geom.Geometry'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Feature format for reading and writing data in the WFS format. + * By default, supports WFS version 1.1.0. You can pass a GML format + * as option if you want to read a WFS that contains GML2 (WFS 1.0.0). + * Also see {@link ol.format.GMLBase} which is used by this format. + * + * @constructor + * @param {olx.format.WFSOptions=} opt_options + * Optional configuration object. + * @extends {ol.format.XMLFeature} + * @api stable + */ +ol.format.WFS = function(opt_options) { + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {Array.<string>|string|undefined} + */ + this.featureType_ = options.featureType; + + /** + * @private + * @type {Object.<string, string>|string|undefined} + */ + this.featureNS_ = options.featureNS; + + /** + * @private + * @type {ol.format.GMLBase} + */ + this.gmlFormat_ = options.gmlFormat ? + options.gmlFormat : new ol.format.GML3(); + + /** + * @private + * @type {string} + */ + this.schemaLocation_ = options.schemaLocation ? + options.schemaLocation : ol.format.WFS.SCHEMA_LOCATION; + + ol.format.XMLFeature.call(this); +}; +ol.inherits(ol.format.WFS, ol.format.XMLFeature); + + +/** + * @const + * @type {string} + */ +ol.format.WFS.FEATURE_PREFIX = 'feature'; + + +/** + * @const + * @type {string} + */ +ol.format.WFS.XMLNS = 'http://www.w3.org/2000/xmlns/'; + + +/** + * @const + * @type {string} + */ +ol.format.WFS.OGCNS = 'http://www.opengis.net/ogc'; + + +/** + * @const + * @type {string} + */ +ol.format.WFS.WFSNS = 'http://www.opengis.net/wfs'; + + +/** + * @const + * @type {string} + */ +ol.format.WFS.SCHEMA_LOCATION = 'http://www.opengis.net/wfs ' + + 'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd'; + + +/** + * Read all features from a WFS FeatureCollection. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.WFS.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.WFS.prototype.readFeaturesFromNode = function(node, opt_options) { + var context = /** @type {ol.XmlNodeStackItem} */ ({ + 'featureType': this.featureType_, + 'featureNS': this.featureNS_ + }); + ol.obj.assign(context, this.getReadOptions(node, + opt_options ? opt_options : {})); + var objectStack = [context]; + this.gmlFormat_.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS][ + 'featureMember'] = + ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readFeaturesInternal); + var features = ol.xml.pushParseAndPop([], + this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node, + objectStack, this.gmlFormat_); + if (!features) { + features = []; + } + return features; +}; + + +/** + * Read transaction response of the source. + * + * @param {Document|Node|Object|string} source Source. + * @return {ol.WFSTransactionResponse|undefined} Transaction response. + * @api stable + */ +ol.format.WFS.prototype.readTransactionResponse = function(source) { + if (ol.xml.isDocument(source)) { + return this.readTransactionResponseFromDocument( + /** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readTransactionResponseFromNode(/** @type {Node} */ (source)); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readTransactionResponseFromDocument(doc); + } else { + return undefined; + } +}; + + +/** + * Read feature collection metadata of the source. + * + * @param {Document|Node|Object|string} source Source. + * @return {ol.WFSFeatureCollectionMetadata|undefined} + * FeatureCollection metadata. + * @api stable + */ +ol.format.WFS.prototype.readFeatureCollectionMetadata = function(source) { + if (ol.xml.isDocument(source)) { + return this.readFeatureCollectionMetadataFromDocument( + /** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readFeatureCollectionMetadataFromNode( + /** @type {Node} */ (source)); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readFeatureCollectionMetadataFromDocument(doc); + } else { + return undefined; + } +}; + + +/** + * @param {Document} doc Document. + * @return {ol.WFSFeatureCollectionMetadata|undefined} + * FeatureCollection metadata. + */ +ol.format.WFS.prototype.readFeatureCollectionMetadataFromDocument = function(doc) { + ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + return this.readFeatureCollectionMetadataFromNode(n); + } + } + return undefined; +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WFS.FEATURE_COLLECTION_PARSERS_ = { + 'http://www.opengis.net/gml': { + 'boundedBy': ol.xml.makeObjectPropertySetter( + ol.format.GMLBase.prototype.readGeometryElement, 'bounds') + } +}; + + +/** + * @param {Node} node Node. + * @return {ol.WFSFeatureCollectionMetadata|undefined} + * FeatureCollection metadata. + */ +ol.format.WFS.prototype.readFeatureCollectionMetadataFromNode = function(node) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'FeatureCollection', + 'localName should be FeatureCollection'); + var result = {}; + var value = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('numberOfFeatures')); + result['numberOfFeatures'] = value; + return ol.xml.pushParseAndPop( + /** @type {ol.WFSFeatureCollectionMetadata} */ (result), + ol.format.WFS.FEATURE_COLLECTION_PARSERS_, node, [], this.gmlFormat_); +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_ = { + 'http://www.opengis.net/wfs': { + 'totalInserted': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'totalUpdated': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'totalDeleted': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger) + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Transaction Summary. + * @private + */ +ol.format.WFS.readTransactionSummary_ = function(node, objectStack) { + return ol.xml.pushParseAndPop( + {}, ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_, node, objectStack); +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WFS.OGC_FID_PARSERS_ = { + 'http://www.opengis.net/ogc': { + 'FeatureId': ol.xml.makeArrayPusher(function(node, objectStack) { + return node.getAttribute('fid'); + }) + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.WFS.fidParser_ = function(node, objectStack) { + ol.xml.parseNode(ol.format.WFS.OGC_FID_PARSERS_, node, objectStack); +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WFS.INSERT_RESULTS_PARSERS_ = { + 'http://www.opengis.net/wfs': { + 'Feature': ol.format.WFS.fidParser_ + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Array.<string>|undefined} Insert results. + * @private + */ +ol.format.WFS.readInsertResults_ = function(node, objectStack) { + return ol.xml.pushParseAndPop( + [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack); +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_ = { + 'http://www.opengis.net/wfs': { + 'TransactionSummary': ol.xml.makeObjectPropertySetter( + ol.format.WFS.readTransactionSummary_, 'transactionSummary'), + 'InsertResults': ol.xml.makeObjectPropertySetter( + ol.format.WFS.readInsertResults_, 'insertIds') + } +}; + + +/** + * @param {Document} doc Document. + * @return {ol.WFSTransactionResponse|undefined} Transaction response. + */ +ol.format.WFS.prototype.readTransactionResponseFromDocument = function(doc) { + ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + return this.readTransactionResponseFromNode(n); + } + } + return undefined; +}; + + +/** + * @param {Node} node Node. + * @return {ol.WFSTransactionResponse|undefined} Transaction response. + */ +ol.format.WFS.prototype.readTransactionResponseFromNode = function(node) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'TransactionResponse', + 'localName should be TransactionResponse'); + return ol.xml.pushParseAndPop( + /** @type {ol.WFSTransactionResponse} */({}), + ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_, node, []); +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.WFS.QUERY_SERIALIZERS_ = { + 'http://www.opengis.net/wfs': { + 'PropertyName': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeFeature_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + var featureType = context['featureType']; + var featureNS = context['featureNS']; + var child = ol.xml.createElementNS(featureNS, featureType); + node.appendChild(child); + ol.format.GML3.prototype.writeFeatureElement(child, feature, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {number|string} fid Feature identifier. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeOgcFidFilter_ = function(node, fid, objectStack) { + var filter = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'Filter'); + var child = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'FeatureId'); + filter.appendChild(child); + child.setAttribute('fid', fid); + node.appendChild(filter); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeDelete_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + ol.asserts.assert(feature.getId() !== undefined, 26); // Features must have an id set + var featureType = context['featureType']; + var featurePrefix = context['featurePrefix']; + featurePrefix = featurePrefix ? featurePrefix : + ol.format.WFS.FEATURE_PREFIX; + var featureNS = context['featureNS']; + node.setAttribute('typeName', featurePrefix + ':' + featureType); + ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix, + featureNS); + var fid = feature.getId(); + if (fid !== undefined) { + ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + ol.asserts.assert(feature.getId() !== undefined, 27); // Features must have an id set + var featureType = context['featureType']; + var featurePrefix = context['featurePrefix']; + featurePrefix = featurePrefix ? featurePrefix : + ol.format.WFS.FEATURE_PREFIX; + var featureNS = context['featureNS']; + node.setAttribute('typeName', featurePrefix + ':' + featureType); + ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix, + featureNS); + var fid = feature.getId(); + if (fid !== undefined) { + var keys = feature.getKeys(); + var values = []; + for (var i = 0, ii = keys.length; i < ii; i++) { + var value = feature.get(keys[i]); + if (value !== undefined) { + values.push({name: keys[i], value: value}); + } + } + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ ( + {node: node, 'srsName': context['srsName']}), + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Property'), values, + objectStack); + ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {Object} pair Property name and value. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeProperty_ = function(node, pair, objectStack) { + var name = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'Name'); + node.appendChild(name); + ol.format.XSD.writeStringTextNode(name, pair.name); + if (pair.value !== undefined && pair.value !== null) { + var value = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'Value'); + node.appendChild(value); + if (pair.value instanceof ol.geom.Geometry) { + ol.format.GML3.prototype.writeGeometryElement(value, + pair.value, objectStack); + } else { + ol.format.XSD.writeStringTextNode(value, pair.value); + } + } +}; + + +/** + * @param {Node} node Node. + * @param {{vendorId: string, safeToIgnore: boolean, value: string}} + * nativeElement The native element. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeNative_ = function(node, nativeElement, objectStack) { + if (nativeElement.vendorId) { + node.setAttribute('vendorId', nativeElement.vendorId); + } + if (nativeElement.safeToIgnore !== undefined) { + node.setAttribute('safeToIgnore', nativeElement.safeToIgnore); + } + if (nativeElement.value !== undefined) { + ol.format.XSD.writeStringTextNode(node, nativeElement.value); + } +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.WFS.TRANSACTION_SERIALIZERS_ = { + 'http://www.opengis.net/wfs': { + 'Insert': ol.xml.makeChildAppender(ol.format.WFS.writeFeature_), + 'Update': ol.xml.makeChildAppender(ol.format.WFS.writeUpdate_), + 'Delete': ol.xml.makeChildAppender(ol.format.WFS.writeDelete_), + 'Property': ol.xml.makeChildAppender(ol.format.WFS.writeProperty_), + 'Native': ol.xml.makeChildAppender(ol.format.WFS.writeNative_) + } +}; + + +/** + * @param {Node} node Node. + * @param {string} featureType Feature type. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeQuery_ = function(node, featureType, objectStack) { + var context = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var featurePrefix = context['featurePrefix']; + var featureNS = context['featureNS']; + var propertyNames = context['propertyNames']; + var srsName = context['srsName']; + var prefix = featurePrefix ? featurePrefix + ':' : ''; + node.setAttribute('typeName', prefix + featureType); + if (srsName) { + node.setAttribute('srsName', srsName); + } + if (featureNS) { + ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix, + featureNS); + } + var item = /** @type {ol.XmlNodeStackItem} */ (ol.obj.assign({}, context)); + item.node = node; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.QUERY_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('PropertyName'), propertyNames, + objectStack); + var filter = context['filter']; + if (filter) { + var child = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'Filter'); + node.appendChild(child); + ol.format.WFS.writeFilterCondition_(child, filter, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.Filter} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeFilterCondition_ = function(node, filter, objectStack) { + /** @type {ol.XmlNodeStackItem} */ + var item = {node: node}; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory(filter.getTagName()), + [filter], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.Bbox} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeBboxFilter_ = function(node, filter, objectStack) { + var context = objectStack[objectStack.length - 1]; + context['srsName'] = filter.srsName; + + ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName); + ol.format.GML3.prototype.writeGeometryElement(node, filter.extent, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.Intersects} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeIntersectsFilter_ = function(node, filter, objectStack) { + var context = objectStack[objectStack.length - 1]; + context['srsName'] = filter.srsName; + + ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName); + ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.Within} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeWithinFilter_ = function(node, filter, objectStack) { + var context = objectStack[objectStack.length - 1]; + context['srsName'] = filter.srsName; + + ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName); + ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.LogicalBinary} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeLogicalFilter_ = function(node, filter, objectStack) { + /** @type {ol.XmlNodeStackItem} */ + var item = {node: node}; + var conditionA = filter.conditionA; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory(conditionA.getTagName()), + [conditionA], objectStack); + var conditionB = filter.conditionB; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory(conditionB.getTagName()), + [conditionB], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.Not} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeNotFilter_ = function(node, filter, objectStack) { + /** @type {ol.XmlNodeStackItem} */ + var item = {node: node}; + var condition = filter.condition; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory(condition.getTagName()), + [condition], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.ComparisonBinary} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeComparisonFilter_ = function(node, filter, objectStack) { + if (filter.matchCase !== undefined) { + node.setAttribute('matchCase', filter.matchCase.toString()); + } + ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName); + ol.format.WFS.writeOgcLiteral_(node, '' + filter.expression); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.IsNull} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeIsNullFilter_ = function(node, filter, objectStack) { + ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.IsBetween} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeIsBetweenFilter_ = function(node, filter, objectStack) { + ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName); + + var lowerBoundary = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'LowerBoundary'); + node.appendChild(lowerBoundary); + ol.format.WFS.writeOgcLiteral_(lowerBoundary, '' + filter.lowerBoundary); + + var upperBoundary = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'UpperBoundary'); + node.appendChild(upperBoundary); + ol.format.WFS.writeOgcLiteral_(upperBoundary, '' + filter.upperBoundary); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.IsLike} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeIsLikeFilter_ = function(node, filter, objectStack) { + node.setAttribute('wildCard', filter.wildCard); + node.setAttribute('singleChar', filter.singleChar); + node.setAttribute('escapeChar', filter.escapeChar); + if (filter.matchCase !== undefined) { + node.setAttribute('matchCase', filter.matchCase.toString()); + } + ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName); + ol.format.WFS.writeOgcLiteral_(node, '' + filter.pattern); +}; + + +/** + * @param {string} tagName Tag name. + * @param {Node} node Node. + * @param {string} value Value. + * @private + */ +ol.format.WFS.writeOgcExpression_ = function(tagName, node, value) { + var property = ol.xml.createElementNS(ol.format.WFS.OGCNS, tagName); + ol.format.XSD.writeStringTextNode(property, value); + node.appendChild(property); +}; + + +/** + * @param {Node} node Node. + * @param {string} value PropertyName value. + * @private + */ +ol.format.WFS.writeOgcPropertyName_ = function(node, value) { + ol.format.WFS.writeOgcExpression_('PropertyName', node, value); +}; + + +/** + * @param {Node} node Node. + * @param {string} value PropertyName value. + * @private + */ +ol.format.WFS.writeOgcLiteral_ = function(node, value) { + ol.format.WFS.writeOgcExpression_('Literal', node, value); +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.WFS.GETFEATURE_SERIALIZERS_ = { + 'http://www.opengis.net/wfs': { + 'Query': ol.xml.makeChildAppender(ol.format.WFS.writeQuery_) + }, + 'http://www.opengis.net/ogc': { + 'And': ol.xml.makeChildAppender(ol.format.WFS.writeLogicalFilter_), + 'Or': ol.xml.makeChildAppender(ol.format.WFS.writeLogicalFilter_), + 'Not': ol.xml.makeChildAppender(ol.format.WFS.writeNotFilter_), + 'BBOX': ol.xml.makeChildAppender(ol.format.WFS.writeBboxFilter_), + 'Intersects': ol.xml.makeChildAppender(ol.format.WFS.writeIntersectsFilter_), + 'Within': ol.xml.makeChildAppender(ol.format.WFS.writeWithinFilter_), + 'PropertyIsEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsNotEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsLessThan': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsLessThanOrEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsGreaterThan': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsGreaterThanOrEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsNull': ol.xml.makeChildAppender(ol.format.WFS.writeIsNullFilter_), + 'PropertyIsBetween': ol.xml.makeChildAppender(ol.format.WFS.writeIsBetweenFilter_), + 'PropertyIsLike': ol.xml.makeChildAppender(ol.format.WFS.writeIsLikeFilter_) + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<string>} featureTypes Feature types. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeGetFeature_ = function(node, featureTypes, objectStack) { + var context = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var item = /** @type {ol.XmlNodeStackItem} */ (ol.obj.assign({}, context)); + item.node = node; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Query'), featureTypes, + objectStack); +}; + + +/** + * Encode format as WFS `GetFeature` and return the Node. + * + * @param {olx.format.WFSWriteGetFeatureOptions} options Options. + * @return {Node} Result. + * @api stable + */ +ol.format.WFS.prototype.writeGetFeature = function(options) { + var node = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'GetFeature'); + node.setAttribute('service', 'WFS'); + node.setAttribute('version', '1.1.0'); + var filter; + if (options) { + if (options.handle) { + node.setAttribute('handle', options.handle); + } + if (options.outputFormat) { + node.setAttribute('outputFormat', options.outputFormat); + } + if (options.maxFeatures !== undefined) { + node.setAttribute('maxFeatures', options.maxFeatures); + } + if (options.resultType) { + node.setAttribute('resultType', options.resultType); + } + if (options.startIndex !== undefined) { + node.setAttribute('startIndex', options.startIndex); + } + if (options.count !== undefined) { + node.setAttribute('count', options.count); + } + filter = options.filter; + if (options.bbox) { + ol.asserts.assert(options.geometryName, + 12); // `options.geometryName` must also be provided when `options.bbox` is set + var bbox = ol.format.filter.bbox( + /** @type {string} */ (options.geometryName), options.bbox, options.srsName); + if (filter) { + // if bbox and filter are both set, combine the two into a single filter + filter = ol.format.filter.and(filter, bbox); + } else { + filter = bbox; + } + } + } + ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation_); + /** @type {ol.XmlNodeStackItem} */ + var context = { + node: node, + 'srsName': options.srsName, + 'featureNS': options.featureNS ? options.featureNS : this.featureNS_, + 'featurePrefix': options.featurePrefix, + 'geometryName': options.geometryName, + 'filter': filter, + 'propertyNames': options.propertyNames ? options.propertyNames : [] + }; + ol.asserts.assert(Array.isArray(options.featureTypes), + 11); // `options.featureTypes` should be an Array + ol.format.WFS.writeGetFeature_(node, /** @type {!Array.<string>} */ (options.featureTypes), [context]); + return node; +}; + + +/** + * Encode format as WFS `Transaction` and return the Node. + * + * @param {Array.<ol.Feature>} inserts The features to insert. + * @param {Array.<ol.Feature>} updates The features to update. + * @param {Array.<ol.Feature>} deletes The features to delete. + * @param {olx.format.WFSWriteTransactionOptions} options Write options. + * @return {Node} Result. + * @api stable + */ +ol.format.WFS.prototype.writeTransaction = function(inserts, updates, deletes, + options) { + var objectStack = []; + var node = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'Transaction'); + node.setAttribute('service', 'WFS'); + node.setAttribute('version', '1.1.0'); + var baseObj; + /** @type {ol.XmlNodeStackItem} */ + var obj; + if (options) { + baseObj = options.gmlOptions ? options.gmlOptions : {}; + if (options.handle) { + node.setAttribute('handle', options.handle); + } + } + ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation_); + if (inserts) { + obj = {node: node, 'featureNS': options.featureNS, + 'featureType': options.featureType, 'featurePrefix': options.featurePrefix, + 'srsName': options.srsName}; + ol.obj.assign(obj, baseObj); + ol.xml.pushSerializeAndPop(obj, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Insert'), inserts, + objectStack); + } + if (updates) { + obj = {node: node, 'featureNS': options.featureNS, + 'featureType': options.featureType, 'featurePrefix': options.featurePrefix, + 'srsName': options.srsName}; + ol.obj.assign(obj, baseObj); + ol.xml.pushSerializeAndPop(obj, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Update'), updates, + objectStack); + } + if (deletes) { + ol.xml.pushSerializeAndPop({node: node, 'featureNS': options.featureNS, + 'featureType': options.featureType, 'featurePrefix': options.featurePrefix, + 'srsName': options.srsName}, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Delete'), deletes, + objectStack); + } + if (options.nativeElements) { + ol.xml.pushSerializeAndPop({node: node, 'featureNS': options.featureNS, + 'featureType': options.featureType, 'featurePrefix': options.featurePrefix, + 'srsName': options.srsName}, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Native'), options.nativeElements, + objectStack); + } + return node; +}; + + +/** + * Read the projection from a WFS source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {?ol.proj.Projection} Projection. + * @api stable + */ +ol.format.WFS.prototype.readProjection; + + +/** + * @inheritDoc + */ +ol.format.WFS.prototype.readProjectionFromDocument = function(doc) { + ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE, + 'doc.nodeType should be a DOCUMENT'); + for (var n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + return this.readProjectionFromNode(n); + } + } + return null; +}; + + +/** + * @inheritDoc + */ +ol.format.WFS.prototype.readProjectionFromNode = function(node) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'FeatureCollection', + 'localName should be FeatureCollection'); + + if (node.firstElementChild && + node.firstElementChild.firstElementChild) { + node = node.firstElementChild.firstElementChild; + for (var n = node.firstElementChild; n; n = n.nextElementSibling) { + if (!(n.childNodes.length === 0 || + (n.childNodes.length === 1 && + n.firstChild.nodeType === 3))) { + var objectStack = [{}]; + this.gmlFormat_.readGeometryElement(n, objectStack); + return ol.proj.get(objectStack.pop().srsName); + } + } + } + + return null; +}; + +goog.provide('ol.format.WKT'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.TextFeature'); +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.SimpleGeometry'); + + +/** + * @classdesc + * Geometry format for reading and writing data in the `WellKnownText` (WKT) + * format. + * + * @constructor + * @extends {ol.format.TextFeature} + * @param {olx.format.WKTOptions=} opt_options Options. + * @api stable + */ +ol.format.WKT = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.TextFeature.call(this); + + /** + * Split GeometryCollection into multiple features. + * @type {boolean} + * @private + */ + this.splitCollection_ = options.splitCollection !== undefined ? + options.splitCollection : false; + +}; +ol.inherits(ol.format.WKT, ol.format.TextFeature); + + +/** + * @const + * @type {string} + */ +ol.format.WKT.EMPTY = 'EMPTY'; + + +/** + * @const + * @type {string} + */ +ol.format.WKT.Z = 'Z'; + + +/** + * @const + * @type {string} + */ +ol.format.WKT.M = 'M'; + + +/** + * @const + * @type {string} + */ +ol.format.WKT.ZM = 'ZM'; + + +/** + * @param {ol.geom.Point} geom Point geometry. + * @return {string} Coordinates part of Point as WKT. + * @private + */ +ol.format.WKT.encodePointGeometry_ = function(geom) { + var coordinates = geom.getCoordinates(); + if (coordinates.length === 0) { + return ''; + } + return coordinates.join(' '); +}; + + +/** + * @param {ol.geom.MultiPoint} geom MultiPoint geometry. + * @return {string} Coordinates part of MultiPoint as WKT. + * @private + */ +ol.format.WKT.encodeMultiPointGeometry_ = function(geom) { + var array = []; + var components = geom.getPoints(); + for (var i = 0, ii = components.length; i < ii; ++i) { + array.push('(' + ol.format.WKT.encodePointGeometry_(components[i]) + ')'); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry. + * @return {string} Coordinates part of GeometryCollection as WKT. + * @private + */ +ol.format.WKT.encodeGeometryCollectionGeometry_ = function(geom) { + var array = []; + var geoms = geom.getGeometries(); + for (var i = 0, ii = geoms.length; i < ii; ++i) { + array.push(ol.format.WKT.encode_(geoms[i])); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry. + * @return {string} Coordinates part of LineString as WKT. + * @private + */ +ol.format.WKT.encodeLineStringGeometry_ = function(geom) { + var coordinates = geom.getCoordinates(); + var array = []; + for (var i = 0, ii = coordinates.length; i < ii; ++i) { + array.push(coordinates[i].join(' ')); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.MultiLineString} geom MultiLineString geometry. + * @return {string} Coordinates part of MultiLineString as WKT. + * @private + */ +ol.format.WKT.encodeMultiLineStringGeometry_ = function(geom) { + var array = []; + var components = geom.getLineStrings(); + for (var i = 0, ii = components.length; i < ii; ++i) { + array.push('(' + ol.format.WKT.encodeLineStringGeometry_( + components[i]) + ')'); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.Polygon} geom Polygon geometry. + * @return {string} Coordinates part of Polygon as WKT. + * @private + */ +ol.format.WKT.encodePolygonGeometry_ = function(geom) { + var array = []; + var rings = geom.getLinearRings(); + for (var i = 0, ii = rings.length; i < ii; ++i) { + array.push('(' + ol.format.WKT.encodeLineStringGeometry_( + rings[i]) + ')'); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry. + * @return {string} Coordinates part of MultiPolygon as WKT. + * @private + */ +ol.format.WKT.encodeMultiPolygonGeometry_ = function(geom) { + var array = []; + var components = geom.getPolygons(); + for (var i = 0, ii = components.length; i < ii; ++i) { + array.push('(' + ol.format.WKT.encodePolygonGeometry_( + components[i]) + ')'); + } + return array.join(','); +}; + +/** + * @param {ol.geom.SimpleGeometry} geom SimpleGeometry geometry. + * @return {string} Potential dimensional information for WKT type. + * @private + */ +ol.format.WKT.encodeGeometryLayout_ = function(geom) { + var layout = geom.getLayout(); + var dimInfo = ''; + if (layout === ol.geom.GeometryLayout.XYZ || layout === ol.geom.GeometryLayout.XYZM) { + dimInfo += ol.format.WKT.Z; + } + if (layout === ol.geom.GeometryLayout.XYM || layout === ol.geom.GeometryLayout.XYZM) { + dimInfo += ol.format.WKT.M; + } + return dimInfo; +}; + + +/** + * Encode a geometry as WKT. + * @param {ol.geom.Geometry} geom The geometry to encode. + * @return {string} WKT string for the geometry. + * @private + */ +ol.format.WKT.encode_ = function(geom) { + var type = geom.getType(); + var geometryEncoder = ol.format.WKT.GeometryEncoder_[type]; + ol.DEBUG && console.assert(geometryEncoder, 'geometryEncoder should be defined'); + var enc = geometryEncoder(geom); + type = type.toUpperCase(); + if (geom instanceof ol.geom.SimpleGeometry) { + var dimInfo = ol.format.WKT.encodeGeometryLayout_(geom); + if (dimInfo.length > 0) { + type += ' ' + dimInfo; + } + } + if (enc.length === 0) { + return type + ' ' + ol.format.WKT.EMPTY; + } + return type + '(' + enc + ')'; +}; + + +/** + * @const + * @type {Object.<string, function(ol.geom.Geometry): string>} + * @private + */ +ol.format.WKT.GeometryEncoder_ = { + 'Point': ol.format.WKT.encodePointGeometry_, + 'LineString': ol.format.WKT.encodeLineStringGeometry_, + 'Polygon': ol.format.WKT.encodePolygonGeometry_, + 'MultiPoint': ol.format.WKT.encodeMultiPointGeometry_, + 'MultiLineString': ol.format.WKT.encodeMultiLineStringGeometry_, + 'MultiPolygon': ol.format.WKT.encodeMultiPolygonGeometry_, + 'GeometryCollection': ol.format.WKT.encodeGeometryCollectionGeometry_ +}; + + +/** + * Parse a WKT string. + * @param {string} wkt WKT string. + * @return {ol.geom.Geometry|undefined} + * The geometry created. + * @private + */ +ol.format.WKT.prototype.parse_ = function(wkt) { + var lexer = new ol.format.WKT.Lexer(wkt); + var parser = new ol.format.WKT.Parser(lexer); + return parser.parse(); +}; + + +/** + * Read a feature from a WKT source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable + */ +ol.format.WKT.prototype.readFeature; + + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.readFeatureFromText = function(text, opt_options) { + var geom = this.readGeometryFromText(text, opt_options); + if (geom) { + var feature = new ol.Feature(); + feature.setGeometry(geom); + return feature; + } + return null; +}; + + +/** + * Read all features from a WKT source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.WKT.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.readFeaturesFromText = function(text, opt_options) { + var geometries = []; + var geometry = this.readGeometryFromText(text, opt_options); + if (this.splitCollection_ && + geometry.getType() == ol.geom.GeometryType.GEOMETRY_COLLECTION) { + geometries = (/** @type {ol.geom.GeometryCollection} */ (geometry)) + .getGeometriesArray(); + } else { + geometries = [geometry]; + } + var feature, features = []; + for (var i = 0, ii = geometries.length; i < ii; ++i) { + feature = new ol.Feature(); + feature.setGeometry(geometries[i]); + features.push(feature); + } + return features; +}; + + +/** + * Read a single geometry from a WKT source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. + * @api stable + */ +ol.format.WKT.prototype.readGeometry; + + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.readGeometryFromText = function(text, opt_options) { + var geometry = this.parse_(text); + if (geometry) { + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, false, opt_options)); + } else { + return null; + } +}; + + +/** + * Encode a feature as a WKT string. + * + * @function + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} WKT string. + * @api stable + */ +ol.format.WKT.prototype.writeFeature; + + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.writeFeatureText = function(feature, opt_options) { + var geometry = feature.getGeometry(); + if (geometry) { + return this.writeGeometryText(geometry, opt_options); + } + return ''; +}; + + +/** + * Encode an array of features as a WKT string. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} WKT string. + * @api stable + */ +ol.format.WKT.prototype.writeFeatures; + + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.writeFeaturesText = function(features, opt_options) { + if (features.length == 1) { + return this.writeFeatureText(features[0], opt_options); + } + var geometries = []; + for (var i = 0, ii = features.length; i < ii; ++i) { + geometries.push(features[i].getGeometry()); + } + var collection = new ol.geom.GeometryCollection(geometries); + return this.writeGeometryText(collection, opt_options); +}; + + +/** + * Write a single geometry as a WKT string. + * + * @function + * @param {ol.geom.Geometry} geometry Geometry. + * @return {string} WKT string. + * @api stable + */ +ol.format.WKT.prototype.writeGeometry; + + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.writeGeometryText = function(geometry, opt_options) { + return ol.format.WKT.encode_(/** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, true, opt_options))); +}; + + +/** + * @const + * @enum {number} + */ +ol.format.WKT.TokenType = { + TEXT: 1, + LEFT_PAREN: 2, + RIGHT_PAREN: 3, + NUMBER: 4, + COMMA: 5, + EOF: 6 +}; + + +/** + * Class to tokenize a WKT string. + * @param {string} wkt WKT string. + * @constructor + * @protected + */ +ol.format.WKT.Lexer = function(wkt) { + + /** + * @type {string} + */ + this.wkt = wkt; + + /** + * @type {number} + * @private + */ + this.index_ = -1; +}; + + +/** + * @param {string} c Character. + * @return {boolean} Whether the character is alphabetic. + * @private + */ +ol.format.WKT.Lexer.prototype.isAlpha_ = function(c) { + return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; +}; + + +/** + * @param {string} c Character. + * @param {boolean=} opt_decimal Whether the string number + * contains a dot, i.e. is a decimal number. + * @return {boolean} Whether the character is numeric. + * @private + */ +ol.format.WKT.Lexer.prototype.isNumeric_ = function(c, opt_decimal) { + var decimal = opt_decimal !== undefined ? opt_decimal : false; + return c >= '0' && c <= '9' || c == '.' && !decimal; +}; + + +/** + * @param {string} c Character. + * @return {boolean} Whether the character is whitespace. + * @private + */ +ol.format.WKT.Lexer.prototype.isWhiteSpace_ = function(c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +}; + + +/** + * @return {string} Next string character. + * @private + */ +ol.format.WKT.Lexer.prototype.nextChar_ = function() { + return this.wkt.charAt(++this.index_); +}; + + +/** + * Fetch and return the next token. + * @return {!ol.WKTToken} Next string token. + */ +ol.format.WKT.Lexer.prototype.nextToken = function() { + var c = this.nextChar_(); + var token = {position: this.index_, value: c}; + + if (c == '(') { + token.type = ol.format.WKT.TokenType.LEFT_PAREN; + } else if (c == ',') { + token.type = ol.format.WKT.TokenType.COMMA; + } else if (c == ')') { + token.type = ol.format.WKT.TokenType.RIGHT_PAREN; + } else if (this.isNumeric_(c) || c == '-') { + token.type = ol.format.WKT.TokenType.NUMBER; + token.value = this.readNumber_(); + } else if (this.isAlpha_(c)) { + token.type = ol.format.WKT.TokenType.TEXT; + token.value = this.readText_(); + } else if (this.isWhiteSpace_(c)) { + return this.nextToken(); + } else if (c === '') { + token.type = ol.format.WKT.TokenType.EOF; + } else { + throw new Error('Unexpected character: ' + c); + } + + return token; +}; + + +/** + * @return {number} Numeric token value. + * @private + */ +ol.format.WKT.Lexer.prototype.readNumber_ = function() { + var c, index = this.index_; + var decimal = false; + var scientificNotation = false; + do { + if (c == '.') { + decimal = true; + } else if (c == 'e' || c == 'E') { + scientificNotation = true; + } + c = this.nextChar_(); + } while ( + this.isNumeric_(c, decimal) || + // if we haven't detected a scientific number before, 'e' or 'E' + // hint that we should continue to read + !scientificNotation && (c == 'e' || c == 'E') || + // once we know that we have a scientific number, both '-' and '+' + // are allowed + scientificNotation && (c == '-' || c == '+') + ); + return parseFloat(this.wkt.substring(index, this.index_--)); +}; + + +/** + * @return {string} String token value. + * @private + */ +ol.format.WKT.Lexer.prototype.readText_ = function() { + var c, index = this.index_; + do { + c = this.nextChar_(); + } while (this.isAlpha_(c)); + return this.wkt.substring(index, this.index_--).toUpperCase(); +}; + + +/** + * Class to parse the tokens from the WKT string. + * @param {ol.format.WKT.Lexer} lexer The lexer. + * @constructor + * @protected + */ +ol.format.WKT.Parser = function(lexer) { + + /** + * @type {ol.format.WKT.Lexer} + * @private + */ + this.lexer_ = lexer; + + /** + * @type {ol.WKTToken} + * @private + */ + this.token_; + + /** + * @type {ol.geom.GeometryLayout} + * @private + */ + this.layout_ = ol.geom.GeometryLayout.XY; +}; + + +/** + * Fetch the next token form the lexer and replace the active token. + * @private + */ +ol.format.WKT.Parser.prototype.consume_ = function() { + this.token_ = this.lexer_.nextToken(); +}; + +/** + * Tests if the given type matches the type of the current token. + * @param {ol.format.WKT.TokenType} type Token type. + * @return {boolean} Whether the token matches the given type. + */ +ol.format.WKT.Parser.prototype.isTokenType = function(type) { + var isMatch = this.token_.type == type; + return isMatch; +}; + + +/** + * If the given type matches the current token, consume it. + * @param {ol.format.WKT.TokenType} type Token type. + * @return {boolean} Whether the token matches the given type. + */ +ol.format.WKT.Parser.prototype.match = function(type) { + var isMatch = this.isTokenType(type); + if (isMatch) { + this.consume_(); + } + return isMatch; +}; + + +/** + * Try to parse the tokens provided by the lexer. + * @return {ol.geom.Geometry} The geometry. + */ +ol.format.WKT.Parser.prototype.parse = function() { + this.consume_(); + var geometry = this.parseGeometry_(); + ol.DEBUG && console.assert(this.token_.type == ol.format.WKT.TokenType.EOF, + 'token type should be end of file'); + return geometry; +}; + + +/** + * Try to parse the dimensional info. + * @return {ol.geom.GeometryLayout} The layout. + * @private + */ +ol.format.WKT.Parser.prototype.parseGeometryLayout_ = function() { + var layout = ol.geom.GeometryLayout.XY; + var dimToken = this.token_; + if (this.isTokenType(ol.format.WKT.TokenType.TEXT)) { + var dimInfo = dimToken.value; + if (dimInfo === ol.format.WKT.Z) { + layout = ol.geom.GeometryLayout.XYZ; + } else if (dimInfo === ol.format.WKT.M) { + layout = ol.geom.GeometryLayout.XYM; + } else if (dimInfo === ol.format.WKT.ZM) { + layout = ol.geom.GeometryLayout.XYZM; + } + if (layout !== ol.geom.GeometryLayout.XY) { + this.consume_(); + } + } + return layout; +}; + + +/** + * @return {!ol.geom.Geometry} The geometry. + * @private + */ +ol.format.WKT.Parser.prototype.parseGeometry_ = function() { + var token = this.token_; + if (this.match(ol.format.WKT.TokenType.TEXT)) { + var geomType = token.value; + this.layout_ = this.parseGeometryLayout_(); + if (geomType == ol.geom.GeometryType.GEOMETRY_COLLECTION.toUpperCase()) { + var geometries = this.parseGeometryCollectionText_(); + return new ol.geom.GeometryCollection(geometries); + } else { + var parser = ol.format.WKT.Parser.GeometryParser_[geomType]; + var ctor = ol.format.WKT.Parser.GeometryConstructor_[geomType]; + if (!parser || !ctor) { + throw new Error('Invalid geometry type: ' + geomType); + } + var coordinates = parser.call(this); + return new ctor(coordinates, this.layout_); + } + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<ol.geom.Geometry>} A collection of geometries. + * @private + */ +ol.format.WKT.Parser.prototype.parseGeometryCollectionText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var geometries = []; + do { + geometries.push(this.parseGeometry_()); + } while (this.match(ol.format.WKT.TokenType.COMMA)); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return geometries; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {Array.<number>} All values in a point. + * @private + */ +ol.format.WKT.Parser.prototype.parsePointText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parsePoint_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return null; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<!Array.<number>>} All points in a linestring. + * @private + */ +ol.format.WKT.Parser.prototype.parseLineStringText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parsePointList_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<!Array.<number>>} All points in a polygon. + * @private + */ +ol.format.WKT.Parser.prototype.parsePolygonText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parseLineStringTextList_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<!Array.<number>>} All points in a multipoint. + * @private + */ +ol.format.WKT.Parser.prototype.parseMultiPointText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates; + if (this.token_.type == ol.format.WKT.TokenType.LEFT_PAREN) { + coordinates = this.parsePointTextList_(); + } else { + coordinates = this.parsePointList_(); + } + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<!Array.<number>>} All linestring points + * in a multilinestring. + * @private + */ +ol.format.WKT.Parser.prototype.parseMultiLineStringText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parseLineStringTextList_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<!Array.<number>>} All polygon points in a multipolygon. + * @private + */ +ol.format.WKT.Parser.prototype.parseMultiPolygonText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parsePolygonTextList_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<number>} A point. + * @private + */ +ol.format.WKT.Parser.prototype.parsePoint_ = function() { + var coordinates = []; + var dimensions = this.layout_.length; + for (var i = 0; i < dimensions; ++i) { + var token = this.token_; + if (this.match(ol.format.WKT.TokenType.NUMBER)) { + coordinates.push(token.value); + } else { + break; + } + } + if (coordinates.length == dimensions) { + return coordinates; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<!Array.<number>>} An array of points. + * @private + */ +ol.format.WKT.Parser.prototype.parsePointList_ = function() { + var coordinates = [this.parsePoint_()]; + while (this.match(ol.format.WKT.TokenType.COMMA)) { + coordinates.push(this.parsePoint_()); + } + return coordinates; +}; + + +/** + * @return {!Array.<!Array.<number>>} An array of points. + * @private + */ +ol.format.WKT.Parser.prototype.parsePointTextList_ = function() { + var coordinates = [this.parsePointText_()]; + while (this.match(ol.format.WKT.TokenType.COMMA)) { + coordinates.push(this.parsePointText_()); + } + return coordinates; +}; + + +/** + * @return {!Array.<!Array.<number>>} An array of points. + * @private + */ +ol.format.WKT.Parser.prototype.parseLineStringTextList_ = function() { + var coordinates = [this.parseLineStringText_()]; + while (this.match(ol.format.WKT.TokenType.COMMA)) { + coordinates.push(this.parseLineStringText_()); + } + return coordinates; +}; + + +/** + * @return {!Array.<!Array.<number>>} An array of points. + * @private + */ +ol.format.WKT.Parser.prototype.parsePolygonTextList_ = function() { + var coordinates = [this.parsePolygonText_()]; + while (this.match(ol.format.WKT.TokenType.COMMA)) { + coordinates.push(this.parsePolygonText_()); + } + return coordinates; +}; + + +/** + * @return {boolean} Whether the token implies an empty geometry. + * @private + */ +ol.format.WKT.Parser.prototype.isEmptyGeometry_ = function() { + var isEmpty = this.isTokenType(ol.format.WKT.TokenType.TEXT) && + this.token_.value == ol.format.WKT.EMPTY; + if (isEmpty) { + this.consume_(); + } + return isEmpty; +}; + + +/** + * Create an error message for an unexpected token error. + * @return {string} Error message. + * @private + */ +ol.format.WKT.Parser.prototype.formatErrorMessage_ = function() { + return 'Unexpected `' + this.token_.value + '` at position ' + + this.token_.position + ' in `' + this.lexer_.wkt + '`'; +}; + + +/** + * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout)} + * @private + */ +ol.format.WKT.Parser.GeometryConstructor_ = { + 'POINT': ol.geom.Point, + 'LINESTRING': ol.geom.LineString, + 'POLYGON': ol.geom.Polygon, + 'MULTIPOINT': ol.geom.MultiPoint, + 'MULTILINESTRING': ol.geom.MultiLineString, + 'MULTIPOLYGON': ol.geom.MultiPolygon +}; + + +/** + * @enum {(function(): Array)} + * @private + */ +ol.format.WKT.Parser.GeometryParser_ = { + 'POINT': ol.format.WKT.Parser.prototype.parsePointText_, + 'LINESTRING': ol.format.WKT.Parser.prototype.parseLineStringText_, + 'POLYGON': ol.format.WKT.Parser.prototype.parsePolygonText_, + 'MULTIPOINT': ol.format.WKT.Parser.prototype.parseMultiPointText_, + 'MULTILINESTRING': ol.format.WKT.Parser.prototype.parseMultiLineStringText_, + 'MULTIPOLYGON': ol.format.WKT.Parser.prototype.parseMultiPolygonText_ +}; + +goog.provide('ol.format.WMSCapabilities'); + +goog.require('ol'); +goog.require('ol.format.XLink'); +goog.require('ol.format.XML'); +goog.require('ol.format.XSD'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Format for reading WMS capabilities data + * + * @constructor + * @extends {ol.format.XML} + * @api + */ +ol.format.WMSCapabilities = function() { + + ol.format.XML.call(this); + + /** + * @type {string|undefined} + */ + this.version = undefined; +}; +ol.inherits(ol.format.WMSCapabilities, ol.format.XML); + + +/** + * Read a WMS capabilities document. + * + * @function + * @param {Document|Node|string} source The XML source. + * @return {Object} An object representing the WMS capabilities. + * @api + */ +ol.format.WMSCapabilities.prototype.read; + + +/** + * @param {Document} doc Document. + * @return {Object} WMS Capability object. + */ +ol.format.WMSCapabilities.prototype.readFromDocument = function(doc) { + ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + return this.readFromNode(n); + } + } + return null; +}; + + +/** + * @param {Node} node Node. + * @return {Object} WMS Capability object. + */ +ol.format.WMSCapabilities.prototype.readFromNode = function(node) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'WMS_Capabilities' || + node.localName == 'WMT_MS_Capabilities', + 'localName should be WMS_Capabilities or WMT_MS_Capabilities'); + this.version = node.getAttribute('version').trim(); + var wmsCapabilityObject = ol.xml.pushParseAndPop({ + 'version': this.version + }, ol.format.WMSCapabilities.PARSERS_, node, []); + return wmsCapabilityObject ? wmsCapabilityObject : null; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Attribution object. + */ +ol.format.WMSCapabilities.readAttribution_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Attribution', + 'localName should be Attribution'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object} Bounding box object. + */ +ol.format.WMSCapabilities.readBoundingBox_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'BoundingBox', + 'localName should be BoundingBox'); + + var extent = [ + ol.format.XSD.readDecimalString(node.getAttribute('minx')), + ol.format.XSD.readDecimalString(node.getAttribute('miny')), + ol.format.XSD.readDecimalString(node.getAttribute('maxx')), + ol.format.XSD.readDecimalString(node.getAttribute('maxy')) + ]; + + var resolutions = [ + ol.format.XSD.readDecimalString(node.getAttribute('resx')), + ol.format.XSD.readDecimalString(node.getAttribute('resy')) + ]; + + return { + 'crs': node.getAttribute('CRS'), + 'extent': extent, + 'res': resolutions + }; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.Extent|undefined} Bounding box object. + */ +ol.format.WMSCapabilities.readEXGeographicBoundingBox_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'EX_GeographicBoundingBox', + 'localName should be EX_GeographicBoundingBox'); + var geographicBoundingBox = ol.xml.pushParseAndPop( + {}, + ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_, + node, objectStack); + if (!geographicBoundingBox) { + return undefined; + } + var westBoundLongitude = /** @type {number|undefined} */ + (geographicBoundingBox['westBoundLongitude']); + var southBoundLatitude = /** @type {number|undefined} */ + (geographicBoundingBox['southBoundLatitude']); + var eastBoundLongitude = /** @type {number|undefined} */ + (geographicBoundingBox['eastBoundLongitude']); + var northBoundLatitude = /** @type {number|undefined} */ + (geographicBoundingBox['northBoundLatitude']); + if (westBoundLongitude === undefined || southBoundLatitude === undefined || + eastBoundLongitude === undefined || northBoundLatitude === undefined) { + return undefined; + } + return [ + westBoundLongitude, southBoundLatitude, + eastBoundLongitude, northBoundLatitude + ]; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Capability object. + */ +ol.format.WMSCapabilities.readCapability_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Capability', + 'localName should be Capability'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.CAPABILITY_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Service object. + */ +ol.format.WMSCapabilities.readService_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Service', + 'localName should be Service'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.SERVICE_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Contact information object. + */ +ol.format.WMSCapabilities.readContactInformation_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType shpuld be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ContactInformation', + 'localName should be ContactInformation'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_, + node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Contact person object. + */ +ol.format.WMSCapabilities.readContactPersonPrimary_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ContactPersonPrimary', + 'localName should be ContactPersonPrimary'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_, + node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Contact address object. + */ +ol.format.WMSCapabilities.readContactAddress_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ContactAddress', + 'localName should be ContactAddress'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_, + node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<string>|undefined} Format array. + */ +ol.format.WMSCapabilities.readException_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Exception', + 'localName should be Exception'); + return ol.xml.pushParseAndPop( + [], ol.format.WMSCapabilities.EXCEPTION_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Layer object. + */ +ol.format.WMSCapabilities.readCapabilityLayer_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Layer', 'localName should be Layer'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Layer object. + */ +ol.format.WMSCapabilities.readLayer_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Layer', 'localName should be Layer'); + var parentLayerObject = /** @type {Object.<string,*>} */ + (objectStack[objectStack.length - 1]); + + var layerObject = ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack); + + if (!layerObject) { + return undefined; + } + var queryable = + ol.format.XSD.readBooleanString(node.getAttribute('queryable')); + if (queryable === undefined) { + queryable = parentLayerObject['queryable']; + } + layerObject['queryable'] = queryable !== undefined ? queryable : false; + + var cascaded = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('cascaded')); + if (cascaded === undefined) { + cascaded = parentLayerObject['cascaded']; + } + layerObject['cascaded'] = cascaded; + + var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque')); + if (opaque === undefined) { + opaque = parentLayerObject['opaque']; + } + layerObject['opaque'] = opaque !== undefined ? opaque : false; + + var noSubsets = + ol.format.XSD.readBooleanString(node.getAttribute('noSubsets')); + if (noSubsets === undefined) { + noSubsets = parentLayerObject['noSubsets']; + } + layerObject['noSubsets'] = noSubsets !== undefined ? noSubsets : false; + + var fixedWidth = + ol.format.XSD.readDecimalString(node.getAttribute('fixedWidth')); + if (!fixedWidth) { + fixedWidth = parentLayerObject['fixedWidth']; + } + layerObject['fixedWidth'] = fixedWidth; + + var fixedHeight = + ol.format.XSD.readDecimalString(node.getAttribute('fixedHeight')); + if (!fixedHeight) { + fixedHeight = parentLayerObject['fixedHeight']; + } + layerObject['fixedHeight'] = fixedHeight; + + // See 7.2.4.8 + var addKeys = ['Style', 'CRS', 'AuthorityURL']; + addKeys.forEach(function(key) { + if (key in parentLayerObject) { + var childValue = layerObject[key] || []; + layerObject[key] = childValue.concat(parentLayerObject[key]); + } + }); + + var replaceKeys = ['EX_GeographicBoundingBox', 'BoundingBox', 'Dimension', + 'Attribution', 'MinScaleDenominator', 'MaxScaleDenominator']; + replaceKeys.forEach(function(key) { + if (!(key in layerObject)) { + var parentValue = parentLayerObject[key]; + layerObject[key] = parentValue; + } + }); + + return layerObject; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object} Dimension object. + */ +ol.format.WMSCapabilities.readDimension_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Dimension', + 'localName should be Dimension'); + var dimensionObject = { + 'name': node.getAttribute('name'), + 'units': node.getAttribute('units'), + 'unitSymbol': node.getAttribute('unitSymbol'), + 'default': node.getAttribute('default'), + 'multipleValues': ol.format.XSD.readBooleanString( + node.getAttribute('multipleValues')), + 'nearestValue': ol.format.XSD.readBooleanString( + node.getAttribute('nearestValue')), + 'current': ol.format.XSD.readBooleanString(node.getAttribute('current')), + 'values': ol.format.XSD.readString(node) + }; + return dimensionObject; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Online resource object. + */ +ol.format.WMSCapabilities.readFormatOnlineresource_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_, + node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Request object. + */ +ol.format.WMSCapabilities.readRequest_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Request', + 'localName should be Request'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.REQUEST_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} DCP type object. + */ +ol.format.WMSCapabilities.readDCPType_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'DCPType', + 'localName should be DCPType'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.DCPTYPE_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} HTTP object. + */ +ol.format.WMSCapabilities.readHTTP_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'HTTP', 'localName should be HTTP'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.HTTP_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Operation type object. + */ +ol.format.WMSCapabilities.readOperationType_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Online resource object. + */ +ol.format.WMSCapabilities.readSizedFormatOnlineresource_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + var formatOnlineresource = + ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack); + if (formatOnlineresource) { + var size = [ + ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('width')), + ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('height')) + ]; + formatOnlineresource['size'] = size; + return formatOnlineresource; + } + return undefined; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Authority URL object. + */ +ol.format.WMSCapabilities.readAuthorityURL_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'AuthorityURL', + 'localName should be AuthorityURL'); + var authorityObject = + ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack); + if (authorityObject) { + authorityObject['name'] = node.getAttribute('name'); + return authorityObject; + } + return undefined; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Metadata URL object. + */ +ol.format.WMSCapabilities.readMetadataURL_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MetadataURL', + 'localName should be MetadataURL'); + var metadataObject = + ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack); + if (metadataObject) { + metadataObject['type'] = node.getAttribute('type'); + return metadataObject; + } + return undefined; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Style object. + */ +ol.format.WMSCapabilities.readStyle_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Style', 'localName should be Style'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.STYLE_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Array.<string>|undefined} Keyword list. + */ +ol.format.WMSCapabilities.readKeywordList_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'KeywordList', + 'localName should be KeywordList'); + return ol.xml.pushParseAndPop( + [], ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_, node, objectStack); +}; + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.format.WMSCapabilities.NAMESPACE_URIS_ = [ + null, + 'http://www.opengis.net/wms' +]; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Service': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readService_), + 'Capability': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readCapability_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.CAPABILITY_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Request': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readRequest_), + 'Exception': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readException_), + 'Layer': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readCapabilityLayer_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.SERVICE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'KeywordList': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readKeywordList_), + 'OnlineResource': ol.xml.makeObjectPropertySetter( + ol.format.XLink.readHref), + 'ContactInformation': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readContactInformation_), + 'Fees': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'AccessConstraints': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'LayerLimit': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MaxWidth': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MaxHeight': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'ContactPersonPrimary': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readContactPersonPrimary_), + 'ContactPosition': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ContactAddress': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readContactAddress_), + 'ContactVoiceTelephone': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ContactFacsimileTelephone': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ContactElectronicMailAddress': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'ContactPerson': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ContactOrganization': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'AddressType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'StateOrProvince': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'PostCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.EXCEPTION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Format': ol.xml.makeArrayPusher(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.LAYER_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'KeywordList': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readKeywordList_), + 'CRS': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString), + 'EX_GeographicBoundingBox': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readEXGeographicBoundingBox_), + 'BoundingBox': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readBoundingBox_), + 'Dimension': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readDimension_), + 'Attribution': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readAttribution_), + 'AuthorityURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readAuthorityURL_), + 'Identifier': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString), + 'MetadataURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readMetadataURL_), + 'DataURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readFormatOnlineresource_), + 'FeatureListURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readFormatOnlineresource_), + 'Style': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readStyle_), + 'MinScaleDenominator': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'MaxScaleDenominator': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'Layer': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readLayer_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'OnlineResource': ol.xml.makeObjectPropertySetter( + ol.format.XLink.readHref), + 'LogoURL': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readSizedFormatOnlineresource_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_ = + ol.xml.makeStructureNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'westBoundLongitude': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'eastBoundLongitude': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'southBoundLatitude': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'northBoundLatitude': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.REQUEST_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'GetCapabilities': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readOperationType_), + 'GetMap': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readOperationType_), + 'GetFeatureInfo': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readOperationType_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Format': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString), + 'DCPType': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readDCPType_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'HTTP': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readHTTP_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.HTTP_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Get': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readFormatOnlineresource_), + 'Post': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readFormatOnlineresource_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'LegendURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readSizedFormatOnlineresource_), + 'StyleSheetURL': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readFormatOnlineresource_), + 'StyleURL': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readFormatOnlineresource_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_ = + ol.xml.makeStructureNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Format': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'OnlineResource': ol.xml.makeObjectPropertySetter( + ol.format.XLink.readHref) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Keyword': ol.xml.makeArrayPusher(ol.format.XSD.readString) + }); + +goog.provide('ol.format.WMSGetFeatureInfo'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.format.GML2'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.obj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Format for reading WMSGetFeatureInfo format. It uses + * {@link ol.format.GML2} to read features. + * + * @constructor + * @extends {ol.format.XMLFeature} + * @param {olx.format.WMSGetFeatureInfoOptions=} opt_options Options. + * @api + */ +ol.format.WMSGetFeatureInfo = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {string} + */ + this.featureNS_ = 'http://mapserver.gis.umn.edu/mapserver'; + + + /** + * @private + * @type {ol.format.GML2} + */ + this.gmlFormat_ = new ol.format.GML2(); + + + /** + * @private + * @type {Array.<string>} + */ + this.layers_ = options.layers ? options.layers : null; + + ol.format.XMLFeature.call(this); +}; +ol.inherits(ol.format.WMSGetFeatureInfo, ol.format.XMLFeature); + + +/** + * @const + * @type {string} + * @private + */ +ol.format.WMSGetFeatureInfo.featureIdentifier_ = '_feature'; + + +/** + * @const + * @type {string} + * @private + */ +ol.format.WMSGetFeatureInfo.layerIdentifier_ = '_layer'; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Array.<ol.Feature>} Features. + * @private + */ +ol.format.WMSGetFeatureInfo.prototype.readFeatures_ = function(node, objectStack) { + + node.setAttribute('namespaceURI', this.featureNS_); + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + var localName = node.localName; + /** @type {Array.<ol.Feature>} */ + var features = []; + if (node.childNodes.length === 0) { + return features; + } + if (localName == 'msGMLOutput') { + for (var i = 0, ii = node.childNodes.length; i < ii; i++) { + var layer = node.childNodes[i]; + if (layer.nodeType !== Node.ELEMENT_NODE) { + continue; + } + var context = objectStack[0]; + + ol.DEBUG && console.assert(layer.localName.indexOf( + ol.format.WMSGetFeatureInfo.layerIdentifier_) >= 0, + 'localName of layer node should match layerIdentifier'); + + var toRemove = ol.format.WMSGetFeatureInfo.layerIdentifier_; + var layerName = layer.localName.replace(toRemove, ''); + + if (this.layers_ && !ol.array.includes(this.layers_, layerName)) { + continue; + } + + var featureType = layerName + + ol.format.WMSGetFeatureInfo.featureIdentifier_; + + context['featureType'] = featureType; + context['featureNS'] = this.featureNS_; + + var parsers = {}; + parsers[featureType] = ol.xml.makeArrayPusher( + this.gmlFormat_.readFeatureElement, this.gmlFormat_); + var parsersNS = ol.xml.makeStructureNS( + [context['featureNS'], null], parsers); + layer.setAttribute('namespaceURI', this.featureNS_); + var layerFeatures = ol.xml.pushParseAndPop( + [], parsersNS, layer, objectStack, this.gmlFormat_); + if (layerFeatures) { + ol.array.extend(features, layerFeatures); + } + } + } + if (localName == 'FeatureCollection') { + var gmlFeatures = ol.xml.pushParseAndPop([], + this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node, + [{}], this.gmlFormat_); + if (gmlFeatures) { + features = gmlFeatures; + } + } + return features; +}; + + +/** + * Read all features from a WMSGetFeatureInfo response. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.WMSGetFeatureInfo.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.WMSGetFeatureInfo.prototype.readFeaturesFromNode = function(node, opt_options) { + var options = {}; + if (opt_options) { + ol.obj.assign(options, this.getReadOptions(node, opt_options)); + } + return this.readFeatures_(node, [options]); +}; + +goog.provide('ol.format.WMTSCapabilities'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.format.OWS'); +goog.require('ol.format.XLink'); +goog.require('ol.format.XML'); +goog.require('ol.format.XSD'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Format for reading WMTS capabilities data. + * + * @constructor + * @extends {ol.format.XML} + * @api + */ +ol.format.WMTSCapabilities = function() { + ol.format.XML.call(this); + + /** + * @type {ol.format.OWS} + * @private + */ + this.owsParser_ = new ol.format.OWS(); +}; +ol.inherits(ol.format.WMTSCapabilities, ol.format.XML); + + +/** + * Read a WMTS capabilities document. + * + * @function + * @param {Document|Node|string} source The XML source. + * @return {Object} An object representing the WMTS capabilities. + * @api + */ +ol.format.WMTSCapabilities.prototype.read; + + +/** + * @param {Document} doc Document. + * @return {Object} WMTS Capability object. + */ +ol.format.WMTSCapabilities.prototype.readFromDocument = function(doc) { + ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + return this.readFromNode(n); + } + } + return null; +}; + + +/** + * @param {Node} node Node. + * @return {Object} WMTS Capability object. + */ +ol.format.WMTSCapabilities.prototype.readFromNode = function(node) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Capabilities', + 'localName should be Capabilities'); + var version = node.getAttribute('version').trim(); + var WMTSCapabilityObject = this.owsParser_.readFromNode(node); + if (!WMTSCapabilityObject) { + return null; + } + WMTSCapabilityObject['version'] = version; + WMTSCapabilityObject = ol.xml.pushParseAndPop(WMTSCapabilityObject, + ol.format.WMTSCapabilities.PARSERS_, node, []); + return WMTSCapabilityObject ? WMTSCapabilityObject : null; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Attribution object. + */ +ol.format.WMTSCapabilities.readContents_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Contents', + 'localName should be Contents'); + + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.CONTENTS_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Layers object. + */ +ol.format.WMTSCapabilities.readLayer_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Layer', 'localName should be Layer'); + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.LAYER_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Tile Matrix Set object. + */ +ol.format.WMTSCapabilities.readTileMatrixSet_ = function(node, objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.TMS_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Style object. + */ +ol.format.WMTSCapabilities.readStyle_ = function(node, objectStack) { + var style = ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.STYLE_PARSERS_, node, objectStack); + if (!style) { + return undefined; + } + var isDefault = node.getAttribute('isDefault') === 'true'; + style['isDefault'] = isDefault; + return style; + +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Tile Matrix Set Link object. + */ +ol.format.WMTSCapabilities.readTileMatrixSetLink_ = function(node, + objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Dimension object. + */ +ol.format.WMTSCapabilities.readDimensions_ = function(node, objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.DIMENSION_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Resource URL object. + */ +ol.format.WMTSCapabilities.readResourceUrl_ = function(node, objectStack) { + var format = node.getAttribute('format'); + var template = node.getAttribute('template'); + var resourceType = node.getAttribute('resourceType'); + var resource = {}; + if (format) { + resource['format'] = format; + } + if (template) { + resource['template'] = template; + } + if (resourceType) { + resource['resourceType'] = resourceType; + } + return resource; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} WGS84 BBox object. + */ +ol.format.WMTSCapabilities.readWgs84BoundingBox_ = function(node, objectStack) { + var coordinates = ol.xml.pushParseAndPop([], + ol.format.WMTSCapabilities.WGS84_BBOX_READERS_, node, objectStack); + if (coordinates.length != 2) { + return undefined; + } + return ol.extent.boundingExtent(coordinates); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Legend object. + */ +ol.format.WMTSCapabilities.readLegendUrl_ = function(node, objectStack) { + var legend = {}; + legend['format'] = node.getAttribute('format'); + legend['href'] = ol.format.XLink.readHref(node); + return legend; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Coordinates object. + */ +ol.format.WMTSCapabilities.readCoordinates_ = function(node, objectStack) { + var coordinates = ol.format.XSD.readString(node).split(' '); + if (!coordinates || coordinates.length != 2) { + return undefined; + } + var x = +coordinates[0]; + var y = +coordinates[1]; + if (isNaN(x) || isNaN(y)) { + return undefined; + } + return [x, y]; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} TileMatrix object. + */ +ol.format.WMTSCapabilities.readTileMatrix_ = function(node, objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.TM_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} TileMatrixSetLimits Object. + */ +ol.format.WMTSCapabilities.readTileMatrixLimitsList_ = function(node, + objectStack) { + return ol.xml.pushParseAndPop([], + ol.format.WMTSCapabilities.TMS_LIMITS_LIST_PARSERS_, node, + objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} TileMatrixLimits Array. + */ +ol.format.WMTSCapabilities.readTileMatrixLimits_ = function(node, objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.TMS_LIMITS_PARSERS_, node, objectStack); +}; + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.format.WMTSCapabilities.NAMESPACE_URIS_ = [ + null, + 'http://www.opengis.net/wmts/1.0' +]; + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_ = [ + null, + 'http://www.opengis.net/ows/1.1' +]; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'Contents': ol.xml.makeObjectPropertySetter( + ol.format.WMTSCapabilities.readContents_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.CONTENTS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'Layer': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readLayer_), + 'TileMatrixSet': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readTileMatrixSet_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.LAYER_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'Style': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readStyle_), + 'Format': ol.xml.makeObjectPropertyPusher( + ol.format.XSD.readString), + 'TileMatrixSetLink': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readTileMatrixSetLink_), + 'Dimension': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readDimensions_), + 'ResourceURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readResourceUrl_) + }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'Title': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'Abstract': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'WGS84BoundingBox': ol.xml.makeObjectPropertySetter( + ol.format.WMTSCapabilities.readWgs84BoundingBox_), + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'LegendURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readLegendUrl_) + }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'Title': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'TileMatrixSet': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'TileMatrixSetLimits': ol.xml.makeObjectPropertySetter( + ol.format.WMTSCapabilities.readTileMatrixLimitsList_) + }); + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.TMS_LIMITS_LIST_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'TileMatrixLimits': ol.xml.makeArrayPusher( + ol.format.WMTSCapabilities.readTileMatrixLimits_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.TMS_LIMITS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'TileMatrix': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'MinTileRow': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MaxTileRow': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MinTileCol': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MaxTileCol': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.DIMENSION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'Default': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'Value': ol.xml.makeObjectPropertyPusher( + ol.format.XSD.readString) + }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.WGS84_BBOX_READERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'LowerCorner': ol.xml.makeArrayPusher( + ol.format.WMTSCapabilities.readCoordinates_), + 'UpperCorner': ol.xml.makeArrayPusher( + ol.format.WMTSCapabilities.readCoordinates_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.TMS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'WellKnownScaleSet': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'TileMatrix': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readTileMatrix_) + }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'SupportedCRS': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.TM_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'TopLeftCorner': ol.xml.makeObjectPropertySetter( + ol.format.WMTSCapabilities.readCoordinates_), + 'ScaleDenominator': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'TileWidth': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'TileHeight': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MatrixWidth': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MatrixHeight': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger) + }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); + +// FIXME handle geolocation not supported + +goog.provide('ol.Geolocation'); + +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.geom.Polygon'); +goog.require('ol.has'); +goog.require('ol.math'); +goog.require('ol.proj'); +goog.require('ol.sphere.WGS84'); + + +/** + * @classdesc + * Helper class for providing HTML5 Geolocation capabilities. + * The [Geolocation API](http://www.w3.org/TR/geolocation-API/) + * is used to locate a user's position. + * + * To get notified of position changes, register a listener for the generic + * `change` event on your instance of `ol.Geolocation`. + * + * Example: + * + * var geolocation = new ol.Geolocation({ + * // take the projection to use from the map's view + * projection: view.getProjection() + * }); + * // listen to changes in position + * geolocation.on('change', function(evt) { + * window.console.log(geolocation.getPosition()); + * }); + * + * @fires error + * @constructor + * @extends {ol.Object} + * @param {olx.GeolocationOptions=} opt_options Options. + * @api stable + */ +ol.Geolocation = function(opt_options) { + + ol.Object.call(this); + + var options = opt_options || {}; + + /** + * The unprojected (EPSG:4326) device position. + * @private + * @type {ol.Coordinate} + */ + this.position_ = null; + + /** + * @private + * @type {ol.TransformFunction} + */ + this.transform_ = ol.proj.identityTransform; + + /** + * @private + * @type {number|undefined} + */ + this.watchId_ = undefined; + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Geolocation.Property.PROJECTION), + this.handleProjectionChanged_, this); + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Geolocation.Property.TRACKING), + this.handleTrackingChanged_, this); + + if (options.projection !== undefined) { + this.setProjection(ol.proj.get(options.projection)); + } + if (options.trackingOptions !== undefined) { + this.setTrackingOptions(options.trackingOptions); + } + + this.setTracking(options.tracking !== undefined ? options.tracking : false); + +}; +ol.inherits(ol.Geolocation, ol.Object); + + +/** + * @inheritDoc + */ +ol.Geolocation.prototype.disposeInternal = function() { + this.setTracking(false); + ol.Object.prototype.disposeInternal.call(this); +}; + + +/** + * @private + */ +ol.Geolocation.prototype.handleProjectionChanged_ = function() { + var projection = this.getProjection(); + if (projection) { + this.transform_ = ol.proj.getTransformFromProjections( + ol.proj.get('EPSG:4326'), projection); + if (this.position_) { + this.set( + ol.Geolocation.Property.POSITION, this.transform_(this.position_)); + } + } +}; + + +/** + * @private + */ +ol.Geolocation.prototype.handleTrackingChanged_ = function() { + if (ol.has.GEOLOCATION) { + var tracking = this.getTracking(); + if (tracking && this.watchId_ === undefined) { + this.watchId_ = navigator.geolocation.watchPosition( + this.positionChange_.bind(this), + this.positionError_.bind(this), + this.getTrackingOptions()); + } else if (!tracking && this.watchId_ !== undefined) { + navigator.geolocation.clearWatch(this.watchId_); + this.watchId_ = undefined; + } + } +}; + + +/** + * @private + * @param {GeolocationPosition} position position event. + */ +ol.Geolocation.prototype.positionChange_ = function(position) { + var coords = position.coords; + this.set(ol.Geolocation.Property.ACCURACY, coords.accuracy); + this.set(ol.Geolocation.Property.ALTITUDE, + coords.altitude === null ? undefined : coords.altitude); + this.set(ol.Geolocation.Property.ALTITUDE_ACCURACY, + coords.altitudeAccuracy === null ? + undefined : coords.altitudeAccuracy); + this.set(ol.Geolocation.Property.HEADING, coords.heading === null ? + undefined : ol.math.toRadians(coords.heading)); + if (!this.position_) { + this.position_ = [coords.longitude, coords.latitude]; + } else { + this.position_[0] = coords.longitude; + this.position_[1] = coords.latitude; + } + var projectedPosition = this.transform_(this.position_); + this.set(ol.Geolocation.Property.POSITION, projectedPosition); + this.set(ol.Geolocation.Property.SPEED, + coords.speed === null ? undefined : coords.speed); + var geometry = ol.geom.Polygon.circular( + ol.sphere.WGS84, this.position_, coords.accuracy); + geometry.applyTransform(this.transform_); + this.set(ol.Geolocation.Property.ACCURACY_GEOMETRY, geometry); + this.changed(); +}; + +/** + * Triggered when the Geolocation returns an error. + * @event error + * @api + */ + +/** + * @private + * @param {GeolocationPositionError} error error object. + */ +ol.Geolocation.prototype.positionError_ = function(error) { + error.type = ol.events.EventType.ERROR; + this.setTracking(false); + this.dispatchEvent(/** @type {{type: string, target: undefined}} */ (error)); +}; + + +/** + * Get the accuracy of the position in meters. + * @return {number|undefined} The accuracy of the position measurement in + * meters. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getAccuracy = function() { + return /** @type {number|undefined} */ ( + this.get(ol.Geolocation.Property.ACCURACY)); +}; + + +/** + * Get a geometry of the position accuracy. + * @return {?ol.geom.Geometry} A geometry of the position accuracy. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getAccuracyGeometry = function() { + return /** @type {?ol.geom.Geometry} */ ( + this.get(ol.Geolocation.Property.ACCURACY_GEOMETRY) || null); +}; + + +/** + * Get the altitude associated with the position. + * @return {number|undefined} The altitude of the position in meters above mean + * sea level. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getAltitude = function() { + return /** @type {number|undefined} */ ( + this.get(ol.Geolocation.Property.ALTITUDE)); +}; + + +/** + * Get the altitude accuracy of the position. + * @return {number|undefined} The accuracy of the altitude measurement in + * meters. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getAltitudeAccuracy = function() { + return /** @type {number|undefined} */ ( + this.get(ol.Geolocation.Property.ALTITUDE_ACCURACY)); +}; + + +/** + * Get the heading as radians clockwise from North. + * @return {number|undefined} The heading of the device in radians from north. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getHeading = function() { + return /** @type {number|undefined} */ ( + this.get(ol.Geolocation.Property.HEADING)); +}; + + +/** + * Get the position of the device. + * @return {ol.Coordinate|undefined} The current position of the device reported + * in the current projection. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getPosition = function() { + return /** @type {ol.Coordinate|undefined} */ ( + this.get(ol.Geolocation.Property.POSITION)); +}; + + +/** + * Get the projection associated with the position. + * @return {ol.proj.Projection|undefined} The projection the position is + * reported in. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getProjection = function() { + return /** @type {ol.proj.Projection|undefined} */ ( + this.get(ol.Geolocation.Property.PROJECTION)); +}; + + +/** + * Get the speed in meters per second. + * @return {number|undefined} The instantaneous speed of the device in meters + * per second. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getSpeed = function() { + return /** @type {number|undefined} */ ( + this.get(ol.Geolocation.Property.SPEED)); +}; + + +/** + * Determine if the device location is being tracked. + * @return {boolean} The device location is being tracked. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getTracking = function() { + return /** @type {boolean} */ ( + this.get(ol.Geolocation.Property.TRACKING)); +}; + + +/** + * Get the tracking options. + * @see http://www.w3.org/TR/geolocation-API/#position-options + * @return {GeolocationPositionOptions|undefined} PositionOptions as defined by + * the [HTML5 Geolocation spec + * ](http://www.w3.org/TR/geolocation-API/#position_options_interface). + * @observable + * @api stable + */ +ol.Geolocation.prototype.getTrackingOptions = function() { + return /** @type {GeolocationPositionOptions|undefined} */ ( + this.get(ol.Geolocation.Property.TRACKING_OPTIONS)); +}; + + +/** + * Set the projection to use for transforming the coordinates. + * @param {ol.proj.Projection} projection The projection the position is + * reported in. + * @observable + * @api stable + */ +ol.Geolocation.prototype.setProjection = function(projection) { + this.set(ol.Geolocation.Property.PROJECTION, projection); +}; + + +/** + * Enable or disable tracking. + * @param {boolean} tracking Enable tracking. + * @observable + * @api stable + */ +ol.Geolocation.prototype.setTracking = function(tracking) { + this.set(ol.Geolocation.Property.TRACKING, tracking); +}; + + +/** + * Set the tracking options. + * @see http://www.w3.org/TR/geolocation-API/#position-options + * @param {GeolocationPositionOptions} options PositionOptions as defined by the + * [HTML5 Geolocation spec + * ](http://www.w3.org/TR/geolocation-API/#position_options_interface). + * @observable + * @api stable + */ +ol.Geolocation.prototype.setTrackingOptions = function(options) { + this.set(ol.Geolocation.Property.TRACKING_OPTIONS, options); +}; + + +/** + * @enum {string} + */ +ol.Geolocation.Property = { + ACCURACY: 'accuracy', + ACCURACY_GEOMETRY: 'accuracyGeometry', + ALTITUDE: 'altitude', + ALTITUDE_ACCURACY: 'altitudeAccuracy', + HEADING: 'heading', + POSITION: 'position', + PROJECTION: 'projection', + SPEED: 'speed', + TRACKING: 'tracking', + TRACKING_OPTIONS: 'trackingOptions' +}; + +goog.provide('ol.geom.Circle'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.deflate'); + + +/** + * @classdesc + * Circle geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {ol.Coordinate} center Center. + * @param {number=} opt_radius Radius. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api + */ +ol.geom.Circle = function(center, opt_radius, opt_layout) { + ol.geom.SimpleGeometry.call(this); + var radius = opt_radius ? opt_radius : 0; + this.setCenterAndRadius(center, radius, opt_layout); +}; +ol.inherits(ol.geom.Circle, ol.geom.SimpleGeometry); + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.Circle} Clone. + * @api + */ +ol.geom.Circle.prototype.clone = function() { + var circle = new ol.geom.Circle(null); + circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return circle; +}; + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + var flatCoordinates = this.flatCoordinates; + var dx = x - flatCoordinates[0]; + var dy = y - flatCoordinates[1]; + var squaredDistance = dx * dx + dy * dy; + if (squaredDistance < minSquaredDistance) { + var i; + if (squaredDistance === 0) { + for (i = 0; i < this.stride; ++i) { + closestPoint[i] = flatCoordinates[i]; + } + } else { + var delta = this.getRadius() / Math.sqrt(squaredDistance); + closestPoint[0] = flatCoordinates[0] + delta * dx; + closestPoint[1] = flatCoordinates[1] + delta * dy; + for (i = 2; i < this.stride; ++i) { + closestPoint[i] = flatCoordinates[i]; + } + } + closestPoint.length = this.stride; + return squaredDistance; + } else { + return minSquaredDistance; + } +}; + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.containsXY = function(x, y) { + var flatCoordinates = this.flatCoordinates; + var dx = x - flatCoordinates[0]; + var dy = y - flatCoordinates[1]; + return dx * dx + dy * dy <= this.getRadiusSquared_(); +}; + + +/** + * Return the center of the circle as {@link ol.Coordinate coordinate}. + * @return {ol.Coordinate} Center. + * @api + */ +ol.geom.Circle.prototype.getCenter = function() { + return this.flatCoordinates.slice(0, this.stride); +}; + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.computeExtent = function(extent) { + var flatCoordinates = this.flatCoordinates; + var radius = flatCoordinates[this.stride] - flatCoordinates[0]; + return ol.extent.createOrUpdate( + flatCoordinates[0] - radius, flatCoordinates[1] - radius, + flatCoordinates[0] + radius, flatCoordinates[1] + radius, + extent); +}; + + +/** + * Return the radius of the circle. + * @return {number} Radius. + * @api + */ +ol.geom.Circle.prototype.getRadius = function() { + return Math.sqrt(this.getRadiusSquared_()); +}; + + +/** + * @private + * @return {number} Radius squared. + */ +ol.geom.Circle.prototype.getRadiusSquared_ = function() { + var dx = this.flatCoordinates[this.stride] - this.flatCoordinates[0]; + var dy = this.flatCoordinates[this.stride + 1] - this.flatCoordinates[1]; + return dx * dx + dy * dy; +}; + + +/** + * @inheritDoc + * @api + */ +ol.geom.Circle.prototype.getType = function() { + return ol.geom.GeometryType.CIRCLE; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.Circle.prototype.intersectsExtent = function(extent) { + var circleExtent = this.getExtent(); + if (ol.extent.intersects(extent, circleExtent)) { + var center = this.getCenter(); + + if (extent[0] <= center[0] && extent[2] >= center[0]) { + return true; + } + if (extent[1] <= center[1] && extent[3] >= center[1]) { + return true; + } + + return ol.extent.forEachCorner(extent, this.intersectsCoordinate, this); + } + return false; + +}; + + +/** + * Set the center of the circle as {@link ol.Coordinate coordinate}. + * @param {ol.Coordinate} center Center. + * @api + */ +ol.geom.Circle.prototype.setCenter = function(center) { + var stride = this.stride; + ol.DEBUG && console.assert(center.length == stride, + 'center array length should match stride'); + var radius = this.flatCoordinates[stride] - this.flatCoordinates[0]; + var flatCoordinates = center.slice(); + flatCoordinates[stride] = flatCoordinates[0] + radius; + var i; + for (i = 1; i < stride; ++i) { + flatCoordinates[stride + i] = center[i]; + } + this.setFlatCoordinates(this.layout, flatCoordinates); +}; + + +/** + * Set the center (as {@link ol.Coordinate coordinate}) and the radius (as + * number) of the circle. + * @param {ol.Coordinate} center Center. + * @param {number} radius Radius. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api + */ +ol.geom.Circle.prototype.setCenterAndRadius = function(center, radius, opt_layout) { + if (!center) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, center, 0); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + /** @type {Array.<number>} */ + var flatCoordinates = this.flatCoordinates; + var offset = ol.geom.flat.deflate.coordinate( + flatCoordinates, 0, center, this.stride); + flatCoordinates[offset++] = flatCoordinates[0] + radius; + var i, ii; + for (i = 1, ii = this.stride; i < ii; ++i) { + flatCoordinates[offset++] = flatCoordinates[i]; + } + flatCoordinates.length = offset; + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + */ +ol.geom.Circle.prototype.setFlatCoordinates = function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; + + +/** + * Set the radius of the circle. The radius is in the units of the projection. + * @param {number} radius Radius. + * @api + */ +ol.geom.Circle.prototype.setRadius = function(radius) { + this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius; + this.changed(); +}; + + +/** + * Transform each coordinate of the circle from one coordinate reference system + * to another. The geometry is modified in place. + * If you do not want the geometry modified in place, first clone() it and + * then use this function on the clone. + * + * Internally a circle is currently represented by two points: the center of + * the circle `[cx, cy]`, and the point to the right of the circle + * `[cx + r, cy]`. This `transform` function just transforms these two points. + * So the resulting geometry is also a circle, and that circle does not + * correspond to the shape that would be obtained by transforming every point + * of the original circle. + * + * @param {ol.ProjectionLike} source The current projection. Can be a + * string identifier or a {@link ol.proj.Projection} object. + * @param {ol.ProjectionLike} destination The desired projection. Can be a + * string identifier or a {@link ol.proj.Projection} object. + * @return {ol.geom.Circle} This geometry. Note that original geometry is + * modified in place. + * @function + * @api stable + */ +ol.geom.Circle.prototype.transform; + +goog.provide('ol.geom.flat.geodesic'); + +goog.require('ol'); +goog.require('ol.math'); +goog.require('ol.proj'); + + +/** + * @private + * @param {function(number): ol.Coordinate} interpolate Interpolate function. + * @param {ol.TransformFunction} transform Transform from longitude/latitude to + * projected coordinates. + * @param {number} squaredTolerance Squared tolerance. + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.flat.geodesic.line_ = function(interpolate, transform, squaredTolerance) { + // FIXME reduce garbage generation + // FIXME optimize stack operations + + /** @type {Array.<number>} */ + var flatCoordinates = []; + + var geoA = interpolate(0); + var geoB = interpolate(1); + + var a = transform(geoA); + var b = transform(geoB); + + /** @type {Array.<ol.Coordinate>} */ + var geoStack = [geoB, geoA]; + /** @type {Array.<ol.Coordinate>} */ + var stack = [b, a]; + /** @type {Array.<number>} */ + var fractionStack = [1, 0]; + + /** @type {Object.<string, boolean>} */ + var fractions = {}; + + var maxIterations = 1e5; + var geoM, m, fracA, fracB, fracM, key; + + while (--maxIterations > 0 && fractionStack.length > 0) { + // Pop the a coordinate off the stack + fracA = fractionStack.pop(); + geoA = geoStack.pop(); + a = stack.pop(); + // Add the a coordinate if it has not been added yet + key = fracA.toString(); + if (!(key in fractions)) { + flatCoordinates.push(a[0], a[1]); + fractions[key] = true; + } + // Pop the b coordinate off the stack + fracB = fractionStack.pop(); + geoB = geoStack.pop(); + b = stack.pop(); + // Find the m point between the a and b coordinates + fracM = (fracA + fracB) / 2; + geoM = interpolate(fracM); + m = transform(geoM); + if (ol.math.squaredSegmentDistance(m[0], m[1], a[0], a[1], + b[0], b[1]) < squaredTolerance) { + // If the m point is sufficiently close to the straight line, then we + // discard it. Just use the b coordinate and move on to the next line + // segment. + flatCoordinates.push(b[0], b[1]); + key = fracB.toString(); + ol.DEBUG && console.assert(!(key in fractions), + 'fractions object should contain key : ' + key); + fractions[key] = true; + } else { + // Otherwise, we need to subdivide the current line segment. Split it + // into two and push the two line segments onto the stack. + fractionStack.push(fracB, fracM, fracM, fracA); + stack.push(b, m, m, a); + geoStack.push(geoB, geoM, geoM, geoA); + } + } + ol.DEBUG && console.assert(maxIterations > 0, + 'maxIterations should be more than 0'); + + return flatCoordinates; +}; + + +/** +* Generate a great-circle arcs between two lat/lon points. +* @param {number} lon1 Longitude 1 in degrees. +* @param {number} lat1 Latitude 1 in degrees. +* @param {number} lon2 Longitude 2 in degrees. +* @param {number} lat2 Latitude 2 in degrees. + * @param {ol.proj.Projection} projection Projection. +* @param {number} squaredTolerance Squared tolerance. +* @return {Array.<number>} Flat coordinates. +*/ +ol.geom.flat.geodesic.greatCircleArc = function( + lon1, lat1, lon2, lat2, projection, squaredTolerance) { + + var geoProjection = ol.proj.get('EPSG:4326'); + + var cosLat1 = Math.cos(ol.math.toRadians(lat1)); + var sinLat1 = Math.sin(ol.math.toRadians(lat1)); + var cosLat2 = Math.cos(ol.math.toRadians(lat2)); + var sinLat2 = Math.sin(ol.math.toRadians(lat2)); + var cosDeltaLon = Math.cos(ol.math.toRadians(lon2 - lon1)); + var sinDeltaLon = Math.sin(ol.math.toRadians(lon2 - lon1)); + var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon; + + return ol.geom.flat.geodesic.line_( + /** + * @param {number} frac Fraction. + * @return {ol.Coordinate} Coordinate. + */ + function(frac) { + if (1 <= d) { + return [lon2, lat2]; + } + var D = frac * Math.acos(d); + var cosD = Math.cos(D); + var sinD = Math.sin(D); + var y = sinDeltaLon * cosLat2; + var x = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosDeltaLon; + var theta = Math.atan2(y, x); + var lat = Math.asin(sinLat1 * cosD + cosLat1 * sinD * Math.cos(theta)); + var lon = ol.math.toRadians(lon1) + + Math.atan2(Math.sin(theta) * sinD * cosLat1, + cosD - sinLat1 * Math.sin(lat)); + return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)]; + }, ol.proj.getTransform(geoProjection, projection), squaredTolerance); +}; + + +/** + * Generate a meridian (line at constant longitude). + * @param {number} lon Longitude. + * @param {number} lat1 Latitude 1. + * @param {number} lat2 Latitude 2. + * @param {ol.proj.Projection} projection Projection. + * @param {number} squaredTolerance Squared tolerance. + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.flat.geodesic.meridian = function(lon, lat1, lat2, projection, squaredTolerance) { + var epsg4326Projection = ol.proj.get('EPSG:4326'); + return ol.geom.flat.geodesic.line_( + /** + * @param {number} frac Fraction. + * @return {ol.Coordinate} Coordinate. + */ + function(frac) { + return [lon, lat1 + ((lat2 - lat1) * frac)]; + }, + ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance); +}; + + +/** + * Generate a parallel (line at constant latitude). + * @param {number} lat Latitude. + * @param {number} lon1 Longitude 1. + * @param {number} lon2 Longitude 2. + * @param {ol.proj.Projection} projection Projection. + * @param {number} squaredTolerance Squared tolerance. + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.flat.geodesic.parallel = function(lat, lon1, lon2, projection, squaredTolerance) { + var epsg4326Projection = ol.proj.get('EPSG:4326'); + return ol.geom.flat.geodesic.line_( + /** + * @param {number} frac Fraction. + * @return {ol.Coordinate} Coordinate. + */ + function(frac) { + return [lon1 + ((lon2 - lon1) * frac), lat]; + }, + ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance); +}; + +goog.provide('ol.Graticule'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.flat.geodesic'); +goog.require('ol.math'); +goog.require('ol.proj'); +goog.require('ol.render.Event'); +goog.require('ol.style.Stroke'); + + +/** + * Render a grid for a coordinate system on a map. + * @constructor + * @param {olx.GraticuleOptions=} opt_options Options. + * @api + */ +ol.Graticule = function(opt_options) { + + var options = opt_options || {}; + + /** + * @type {ol.Map} + * @private + */ + this.map_ = null; + + /** + * @type {ol.proj.Projection} + * @private + */ + this.projection_ = null; + + /** + * @type {number} + * @private + */ + this.maxLat_ = Infinity; + + /** + * @type {number} + * @private + */ + this.maxLon_ = Infinity; + + /** + * @type {number} + * @private + */ + this.minLat_ = -Infinity; + + /** + * @type {number} + * @private + */ + this.minLon_ = -Infinity; + + /** + * @type {number} + * @private + */ + this.maxLatP_ = Infinity; + + /** + * @type {number} + * @private + */ + this.maxLonP_ = Infinity; + + /** + * @type {number} + * @private + */ + this.minLatP_ = -Infinity; + + /** + * @type {number} + * @private + */ + this.minLonP_ = -Infinity; + + /** + * @type {number} + * @private + */ + this.targetSize_ = options.targetSize !== undefined ? + options.targetSize : 100; + + /** + * @type {number} + * @private + */ + this.maxLines_ = options.maxLines !== undefined ? options.maxLines : 100; + ol.DEBUG && console.assert(this.maxLines_ > 0, + 'this.maxLines_ should be more than 0'); + + /** + * @type {Array.<ol.geom.LineString>} + * @private + */ + this.meridians_ = []; + + /** + * @type {Array.<ol.geom.LineString>} + * @private + */ + this.parallels_ = []; + + /** + * @type {ol.style.Stroke} + * @private + */ + this.strokeStyle_ = options.strokeStyle !== undefined ? + options.strokeStyle : ol.Graticule.DEFAULT_STROKE_STYLE_; + + /** + * @type {ol.TransformFunction|undefined} + * @private + */ + this.fromLonLatTransform_ = undefined; + + /** + * @type {ol.TransformFunction|undefined} + * @private + */ + this.toLonLatTransform_ = undefined; + + /** + * @type {ol.Coordinate} + * @private + */ + this.projectionCenterLonLat_ = null; + + this.setMap(options.map !== undefined ? options.map : null); +}; + + +/** + * @type {ol.style.Stroke} + * @private + * @const + */ +ol.Graticule.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({ + color: 'rgba(0,0,0,0.2)' +}); + + +/** + * TODO can be configurable + * @type {Array.<number>} + * @private + */ +ol.Graticule.intervals_ = [90, 45, 30, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, + 0.01, 0.005, 0.002, 0.001]; + + +/** + * @param {number} lon Longitude. + * @param {number} minLat Minimal latitude. + * @param {number} maxLat Maximal latitude. + * @param {number} squaredTolerance Squared tolerance. + * @param {ol.Extent} extent Extent. + * @param {number} index Index. + * @return {number} Index. + * @private + */ +ol.Graticule.prototype.addMeridian_ = function(lon, minLat, maxLat, squaredTolerance, extent, index) { + var lineString = this.getMeridian_(lon, minLat, maxLat, + squaredTolerance, index); + if (ol.extent.intersects(lineString.getExtent(), extent)) { + this.meridians_[index++] = lineString; + } + return index; +}; + + +/** + * @param {number} lat Latitude. + * @param {number} minLon Minimal longitude. + * @param {number} maxLon Maximal longitude. + * @param {number} squaredTolerance Squared tolerance. + * @param {ol.Extent} extent Extent. + * @param {number} index Index. + * @return {number} Index. + * @private + */ +ol.Graticule.prototype.addParallel_ = function(lat, minLon, maxLon, squaredTolerance, extent, index) { + var lineString = this.getParallel_(lat, minLon, maxLon, squaredTolerance, + index); + if (ol.extent.intersects(lineString.getExtent(), extent)) { + this.parallels_[index++] = lineString; + } + return index; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} squaredTolerance Squared tolerance. + * @private + */ +ol.Graticule.prototype.createGraticule_ = function(extent, center, resolution, squaredTolerance) { + + var interval = this.getInterval_(resolution); + if (interval == -1) { + this.meridians_.length = this.parallels_.length = 0; + return; + } + + var centerLonLat = this.toLonLatTransform_(center); + var centerLon = centerLonLat[0]; + var centerLat = centerLonLat[1]; + var maxLines = this.maxLines_; + var cnt, idx, lat, lon; + + var validExtent = [ + Math.max(extent[0], this.minLonP_), + Math.max(extent[1], this.minLatP_), + Math.min(extent[2], this.maxLonP_), + Math.min(extent[3], this.maxLatP_) + ]; + + validExtent = ol.proj.transformExtent(validExtent, this.projection_, + 'EPSG:4326'); + var maxLat = validExtent[3]; + var maxLon = validExtent[2]; + var minLat = validExtent[1]; + var minLon = validExtent[0]; + + // Create meridians + + centerLon = Math.floor(centerLon / interval) * interval; + lon = ol.math.clamp(centerLon, this.minLon_, this.maxLon_); + + idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, 0); + + cnt = 0; + while (lon != this.minLon_ && cnt++ < maxLines) { + lon = Math.max(lon - interval, this.minLon_); + idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx); + } + + lon = ol.math.clamp(centerLon, this.minLon_, this.maxLon_); + + cnt = 0; + while (lon != this.maxLon_ && cnt++ < maxLines) { + lon = Math.min(lon + interval, this.maxLon_); + idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx); + } + + this.meridians_.length = idx; + + // Create parallels + + centerLat = Math.floor(centerLat / interval) * interval; + lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_); + + idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, 0); + + cnt = 0; + while (lat != this.minLat_ && cnt++ < maxLines) { + lat = Math.max(lat - interval, this.minLat_); + idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx); + } + + lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_); + + cnt = 0; + while (lat != this.maxLat_ && cnt++ < maxLines) { + lat = Math.min(lat + interval, this.maxLat_); + idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx); + } + + this.parallels_.length = idx; + +}; + + +/** + * @param {number} resolution Resolution. + * @return {number} The interval in degrees. + * @private + */ +ol.Graticule.prototype.getInterval_ = function(resolution) { + var centerLon = this.projectionCenterLonLat_[0]; + var centerLat = this.projectionCenterLonLat_[1]; + var interval = -1; + var i, ii, delta, dist; + var target = Math.pow(this.targetSize_ * resolution, 2); + /** @type {Array.<number>} **/ + var p1 = []; + /** @type {Array.<number>} **/ + var p2 = []; + for (i = 0, ii = ol.Graticule.intervals_.length; i < ii; ++i) { + delta = ol.Graticule.intervals_[i] / 2; + p1[0] = centerLon - delta; + p1[1] = centerLat - delta; + p2[0] = centerLon + delta; + p2[1] = centerLat + delta; + this.fromLonLatTransform_(p1, p1); + this.fromLonLatTransform_(p2, p2); + dist = Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2); + if (dist <= target) { + break; + } + interval = ol.Graticule.intervals_[i]; + } + return interval; +}; + + +/** + * Get the map associated with this graticule. + * @return {ol.Map} The map. + * @api + */ +ol.Graticule.prototype.getMap = function() { + return this.map_; +}; + + +/** + * @param {number} lon Longitude. + * @param {number} minLat Minimal latitude. + * @param {number} maxLat Maximal latitude. + * @param {number} squaredTolerance Squared tolerance. + * @return {ol.geom.LineString} The meridian line string. + * @param {number} index Index. + * @private + */ +ol.Graticule.prototype.getMeridian_ = function(lon, minLat, maxLat, + squaredTolerance, index) { + ol.DEBUG && console.assert(lon >= this.minLon_, + 'lon should be larger than or equal to this.minLon_'); + ol.DEBUG && console.assert(lon <= this.maxLon_, + 'lon should be smaller than or equal to this.maxLon_'); + var flatCoordinates = ol.geom.flat.geodesic.meridian(lon, + minLat, maxLat, this.projection_, squaredTolerance); + ol.DEBUG && console.assert(flatCoordinates.length > 0, + 'flatCoordinates cannot be empty'); + var lineString = this.meridians_[index] !== undefined ? + this.meridians_[index] : new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates); + return lineString; +}; + + +/** + * Get the list of meridians. Meridians are lines of equal longitude. + * @return {Array.<ol.geom.LineString>} The meridians. + * @api + */ +ol.Graticule.prototype.getMeridians = function() { + return this.meridians_; +}; + + +/** + * @param {number} lat Latitude. + * @param {number} minLon Minimal longitude. + * @param {number} maxLon Maximal longitude. + * @param {number} squaredTolerance Squared tolerance. + * @return {ol.geom.LineString} The parallel line string. + * @param {number} index Index. + * @private + */ +ol.Graticule.prototype.getParallel_ = function(lat, minLon, maxLon, + squaredTolerance, index) { + ol.DEBUG && console.assert(lat >= this.minLat_, + 'lat should be larger than or equal to this.minLat_'); + ol.DEBUG && console.assert(lat <= this.maxLat_, + 'lat should be smaller than or equal to this.maxLat_'); + var flatCoordinates = ol.geom.flat.geodesic.parallel(lat, + this.minLon_, this.maxLon_, this.projection_, squaredTolerance); + ol.DEBUG && console.assert(flatCoordinates.length > 0, + 'flatCoordinates cannot be empty'); + var lineString = this.parallels_[index] !== undefined ? + this.parallels_[index] : new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates); + return lineString; +}; + + +/** + * Get the list of parallels. Pallels are lines of equal latitude. + * @return {Array.<ol.geom.LineString>} The parallels. + * @api + */ +ol.Graticule.prototype.getParallels = function() { + return this.parallels_; +}; + + +/** + * @param {ol.render.Event} e Event. + * @private + */ +ol.Graticule.prototype.handlePostCompose_ = function(e) { + var vectorContext = e.vectorContext; + var frameState = e.frameState; + var extent = frameState.extent; + var viewState = frameState.viewState; + var center = viewState.center; + var projection = viewState.projection; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var squaredTolerance = + resolution * resolution / (4 * pixelRatio * pixelRatio); + + var updateProjectionInfo = !this.projection_ || + !ol.proj.equivalent(this.projection_, projection); + + if (updateProjectionInfo) { + this.updateProjectionInfo_(projection); + } + + //Fix the extent if wrapped. + //(note: this is the same extent as vectorContext.extent_) + var offsetX = 0; + if (projection.canWrapX()) { + var projectionExtent = projection.getExtent(); + var worldWidth = ol.extent.getWidth(projectionExtent); + var x = frameState.focus[0]; + if (x < projectionExtent[0] || x > projectionExtent[2]) { + var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth); + offsetX = worldWidth * worldsAway; + extent = [ + extent[0] + offsetX, extent[1], + extent[2] + offsetX, extent[3] + ]; + } + } + + this.createGraticule_(extent, center, resolution, squaredTolerance); + + // Draw the lines + vectorContext.setFillStrokeStyle(null, this.strokeStyle_); + var i, l, line; + for (i = 0, l = this.meridians_.length; i < l; ++i) { + line = this.meridians_[i]; + vectorContext.drawLineString(line, null); + } + for (i = 0, l = this.parallels_.length; i < l; ++i) { + line = this.parallels_[i]; + vectorContext.drawLineString(line, null); + } +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @private + */ +ol.Graticule.prototype.updateProjectionInfo_ = function(projection) { + var epsg4326Projection = ol.proj.get('EPSG:4326'); + + var extent = projection.getExtent(); + var worldExtent = projection.getWorldExtent(); + var worldExtentP = ol.proj.transformExtent(worldExtent, + epsg4326Projection, projection); + + var maxLat = worldExtent[3]; + var maxLon = worldExtent[2]; + var minLat = worldExtent[1]; + var minLon = worldExtent[0]; + + var maxLatP = worldExtentP[3]; + var maxLonP = worldExtentP[2]; + var minLatP = worldExtentP[1]; + var minLonP = worldExtentP[0]; + + ol.DEBUG && console.assert(maxLat !== undefined, 'maxLat should be defined'); + ol.DEBUG && console.assert(maxLon !== undefined, 'maxLon should be defined'); + ol.DEBUG && console.assert(minLat !== undefined, 'minLat should be defined'); + ol.DEBUG && console.assert(minLon !== undefined, 'minLon should be defined'); + + ol.DEBUG && console.assert(maxLatP !== undefined, + 'projected maxLat should be defined'); + ol.DEBUG && console.assert(maxLonP !== undefined, + 'projected maxLon should be defined'); + ol.DEBUG && console.assert(minLatP !== undefined, + 'projected minLat should be defined'); + ol.DEBUG && console.assert(minLonP !== undefined, + 'projected minLon should be defined'); + + this.maxLat_ = maxLat; + this.maxLon_ = maxLon; + this.minLat_ = minLat; + this.minLon_ = minLon; + + this.maxLatP_ = maxLatP; + this.maxLonP_ = maxLonP; + this.minLatP_ = minLatP; + this.minLonP_ = minLonP; + + + this.fromLonLatTransform_ = ol.proj.getTransform( + epsg4326Projection, projection); + + this.toLonLatTransform_ = ol.proj.getTransform( + projection, epsg4326Projection); + + this.projectionCenterLonLat_ = this.toLonLatTransform_( + ol.extent.getCenter(extent)); + + this.projection_ = projection; +}; + + +/** + * Set the map for this graticule. The graticule will be rendered on the + * provided map. + * @param {ol.Map} map Map. + * @api + */ +ol.Graticule.prototype.setMap = function(map) { + if (this.map_) { + this.map_.un(ol.render.Event.Type.POSTCOMPOSE, + this.handlePostCompose_, this); + this.map_.render(); + } + if (map) { + map.on(ol.render.Event.Type.POSTCOMPOSE, + this.handlePostCompose_, this); + map.render(); + } + this.map_ = map; +}; + +goog.provide('ol.ImageTile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Tile.State} state State. + * @param {string} src Image source URI. + * @param {?string} crossOrigin Cross origin. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. + */ +ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction) { + + ol.Tile.call(this, tileCoord, state); + + /** + * Image URI + * + * @private + * @type {string} + */ + this.src_ = src; + + /** + * @private + * @type {Image} + */ + this.image_ = new Image(); + if (crossOrigin !== null) { + this.image_.crossOrigin = crossOrigin; + } + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.imageListenerKeys_ = null; + + /** + * @private + * @type {ol.TileLoadFunctionType} + */ + this.tileLoadFunction_ = tileLoadFunction; + +}; +ol.inherits(ol.ImageTile, ol.Tile); + + +/** + * @inheritDoc + */ +ol.ImageTile.prototype.disposeInternal = function() { + if (this.state == ol.Tile.State.LOADING) { + this.unlistenImage_(); + } + if (this.interimTile) { + this.interimTile.dispose(); + } + this.state = ol.Tile.State.ABORT; + this.changed(); + ol.Tile.prototype.disposeInternal.call(this); +}; + + +/** + * Get the image element for this tile. + * @inheritDoc + * @api + */ +ol.ImageTile.prototype.getImage = function() { + return this.image_; +}; + + +/** + * @inheritDoc + */ +ol.ImageTile.prototype.getKey = function() { + return this.src_; +}; + + +/** + * Tracks loading or read errors. + * + * @private + */ +ol.ImageTile.prototype.handleImageError_ = function() { + this.state = ol.Tile.State.ERROR; + this.unlistenImage_(); + this.changed(); +}; + + +/** + * Tracks successful image load. + * + * @private + */ +ol.ImageTile.prototype.handleImageLoad_ = function() { + if (this.image_.naturalWidth && this.image_.naturalHeight) { + this.state = ol.Tile.State.LOADED; + } else { + this.state = ol.Tile.State.EMPTY; + } + this.unlistenImage_(); + this.changed(); +}; + + +/** + * Load the image or retry if loading previously failed. + * Loading is taken care of by the tile queue, and calling this method is + * only needed for preloading or for reloading in case of an error. + * @api + */ +ol.ImageTile.prototype.load = function() { + if (this.state == ol.Tile.State.IDLE || this.state == ol.Tile.State.ERROR) { + this.state = ol.Tile.State.LOADING; + this.changed(); + ol.DEBUG && console.assert(!this.imageListenerKeys_, + 'this.imageListenerKeys_ should be null'); + this.imageListenerKeys_ = [ + ol.events.listenOnce(this.image_, ol.events.EventType.ERROR, + this.handleImageError_, this), + ol.events.listenOnce(this.image_, ol.events.EventType.LOAD, + this.handleImageLoad_, this) + ]; + this.tileLoadFunction_(this, this.src_); + } +}; + + +/** + * Discards event handlers which listen for load completion or errors. + * + * @private + */ +ol.ImageTile.prototype.unlistenImage_ = function() { + this.imageListenerKeys_.forEach(ol.events.unlistenByKey); + this.imageListenerKeys_ = null; +}; + +// FIXME should handle all geo-referenced data, not just vector data + +goog.provide('ol.interaction.DragAndDrop'); + +goog.require('ol'); +goog.require('ol.functions'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.EventType'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Handles input of vector data by drag and drop. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @fires ol.interaction.DragAndDrop.Event + * @param {olx.interaction.DragAndDropOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragAndDrop = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.DragAndDrop.handleEvent + }); + + /** + * @private + * @type {Array.<function(new: ol.format.Feature)>} + */ + this.formatConstructors_ = options.formatConstructors ? + options.formatConstructors : []; + + /** + * @private + * @type {ol.proj.Projection} + */ + this.projection_ = options.projection ? + ol.proj.get(options.projection) : null; + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.dropListenKeys_ = null; + + /** + * @private + * @type {Element} + */ + this.target = options.target ? options.target : null; + +}; +ol.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction); + + +/** + * @param {Event} event Event. + * @this {ol.interaction.DragAndDrop} + * @private + */ +ol.interaction.DragAndDrop.handleDrop_ = function(event) { + var files = event.dataTransfer.files; + var i, ii, file; + for (i = 0, ii = files.length; i < ii; ++i) { + file = files.item(i); + var reader = new FileReader(); + reader.addEventListener(ol.events.EventType.LOAD, + this.handleResult_.bind(this, file)); + reader.readAsText(file); + } +}; + + +/** + * @param {Event} event Event. + * @private + */ +ol.interaction.DragAndDrop.handleStop_ = function(event) { + event.stopPropagation(); + event.preventDefault(); + event.dataTransfer.dropEffect = 'copy'; +}; + + +/** + * @param {File} file File. + * @param {Event} event Load event. + * @private + */ +ol.interaction.DragAndDrop.prototype.handleResult_ = function(file, event) { + var result = event.target.result; + var map = this.getMap(); + var projection = this.projection_; + if (!projection) { + var view = map.getView(); + projection = view.getProjection(); + ol.DEBUG && console.assert(projection !== undefined, + 'projection should be defined'); + } + var formatConstructors = this.formatConstructors_; + var features = []; + var i, ii; + for (i = 0, ii = formatConstructors.length; i < ii; ++i) { + var formatConstructor = formatConstructors[i]; + var format = new formatConstructor(); + features = this.tryReadFeatures_(format, result, { + featureProjection: projection + }); + if (features && features.length > 0) { + break; + } + } + this.dispatchEvent( + new ol.interaction.DragAndDrop.Event( + ol.interaction.DragAndDrop.EventType.ADD_FEATURES, file, + features, projection)); +}; + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} unconditionally and + * neither prevents the browser default nor stops event propagation. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.DragAndDrop} + * @api + */ +ol.interaction.DragAndDrop.handleEvent = ol.functions.TRUE; + + +/** + * @inheritDoc + */ +ol.interaction.DragAndDrop.prototype.setMap = function(map) { + if (this.dropListenKeys_) { + this.dropListenKeys_.forEach(ol.events.unlistenByKey); + this.dropListenKeys_ = null; + } + ol.interaction.Interaction.prototype.setMap.call(this, map); + if (map) { + var dropArea = this.target ? this.target : map.getViewport(); + this.dropListenKeys_ = [ + ol.events.listen(dropArea, ol.events.EventType.DROP, + ol.interaction.DragAndDrop.handleDrop_, this), + ol.events.listen(dropArea, ol.events.EventType.DRAGENTER, + ol.interaction.DragAndDrop.handleStop_, this), + ol.events.listen(dropArea, ol.events.EventType.DRAGOVER, + ol.interaction.DragAndDrop.handleStop_, this), + ol.events.listen(dropArea, ol.events.EventType.DROP, + ol.interaction.DragAndDrop.handleStop_, this) + ]; + } +}; + + +/** + * @param {ol.format.Feature} format Format. + * @param {string} text Text. + * @param {olx.format.ReadOptions} options Read options. + * @private + * @return {Array.<ol.Feature>} Features. + */ +ol.interaction.DragAndDrop.prototype.tryReadFeatures_ = function(format, text, options) { + try { + return format.readFeatures(text, options); + } catch (e) { + return null; + } +}; + + +/** + * @enum {string} + */ +ol.interaction.DragAndDrop.EventType = { + /** + * Triggered when features are added + * @event ol.interaction.DragAndDrop.Event#addfeatures + * @api stable + */ + ADD_FEATURES: 'addfeatures' +}; + + +/** + * @classdesc + * Events emitted by {@link ol.interaction.DragAndDrop} instances are instances + * of this type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.interaction.DragAndDropEvent} + * @param {ol.interaction.DragAndDrop.EventType} type Type. + * @param {File} file File. + * @param {Array.<ol.Feature>=} opt_features Features. + * @param {ol.proj.Projection=} opt_projection Projection. + */ +ol.interaction.DragAndDrop.Event = function(type, file, opt_features, opt_projection) { + + ol.events.Event.call(this, type); + + /** + * The features parsed from dropped data. + * @type {Array.<ol.Feature>|undefined} + * @api stable + */ + this.features = opt_features; + + /** + * The dropped file. + * @type {File} + * @api stable + */ + this.file = file; + + /** + * The feature projection. + * @type {ol.proj.Projection|undefined} + * @api + */ + this.projection = opt_projection; + +}; +ol.inherits(ol.interaction.DragAndDrop.Event, ol.events.Event); + +goog.provide('ol.interaction.DragRotateAndZoom'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); + + +/** + * @classdesc + * Allows the user to zoom and rotate the map by clicking and dragging + * on the map. By default, this interaction is limited to when the shift + * key is held down. + * + * This interaction is only supported for mouse devices. + * + * And this interaction is not included in the default interactions. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.DragRotateAndZoomOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragRotateAndZoom = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.DragRotateAndZoom.handleDownEvent_, + handleDragEvent: ol.interaction.DragRotateAndZoom.handleDragEvent_, + handleUpEvent: ol.interaction.DragRotateAndZoom.handleUpEvent_ + }); + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.shiftKeyOnly; + + /** + * @private + * @type {number|undefined} + */ + this.lastAngle_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.lastMagnitude_ = undefined; + + /** + * @private + * @type {number} + */ + this.lastScaleDelta_ = 0; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 400; + +}; +ol.inherits(ol.interaction.DragRotateAndZoom, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.DragRotateAndZoom} + * @private + */ +ol.interaction.DragRotateAndZoom.handleDragEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return; + } + + var map = mapBrowserEvent.map; + var size = map.getSize(); + var offset = mapBrowserEvent.pixel; + var deltaX = offset[0] - size[0] / 2; + var deltaY = size[1] / 2 - offset[1]; + var theta = Math.atan2(deltaY, deltaX); + var magnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + var view = map.getView(); + if (this.lastAngle_ !== undefined) { + var angleDelta = theta - this.lastAngle_; + ol.interaction.Interaction.rotateWithoutConstraints( + map, view, view.getRotation() - angleDelta); + } + this.lastAngle_ = theta; + if (this.lastMagnitude_ !== undefined) { + var resolution = this.lastMagnitude_ * (view.getResolution() / magnitude); + ol.interaction.Interaction.zoomWithoutConstraints(map, view, resolution); + } + if (this.lastMagnitude_ !== undefined) { + this.lastScaleDelta_ = this.lastMagnitude_ / magnitude; + } + this.lastMagnitude_ = magnitude; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.DragRotateAndZoom} + * @private + */ +ol.interaction.DragRotateAndZoom.handleUpEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return true; + } + + var map = mapBrowserEvent.map; + var view = map.getView(); + view.setHint(ol.View.Hint.INTERACTING, -1); + var direction = this.lastScaleDelta_ - 1; + ol.interaction.Interaction.rotate(map, view, view.getRotation()); + ol.interaction.Interaction.zoom(map, view, view.getResolution(), + undefined, this.duration_, direction); + this.lastScaleDelta_ = 0; + return false; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.DragRotateAndZoom} + * @private + */ +ol.interaction.DragRotateAndZoom.handleDownEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return false; + } + + if (this.condition_(mapBrowserEvent)) { + mapBrowserEvent.map.getView().setHint(ol.View.Hint.INTERACTING, 1); + this.lastAngle_ = undefined; + this.lastMagnitude_ = undefined; + return true; + } else { + return false; + } +}; + +goog.provide('ol.loadingstrategy'); + + +/** + * Strategy function for loading all features with a single request. + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @return {Array.<ol.Extent>} Extents. + * @api + */ +ol.loadingstrategy.all = function(extent, resolution) { + return [[-Infinity, -Infinity, Infinity, Infinity]]; +}; + + +/** + * Strategy function for loading features based on the view's extent and + * resolution. + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @return {Array.<ol.Extent>} Extents. + * @api + */ +ol.loadingstrategy.bbox = function(extent, resolution) { + return [extent]; +}; + + +/** + * Creates a strategy function for loading features based on a tile grid. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @return {function(ol.Extent, number): Array.<ol.Extent>} Loading strategy. + * @api + */ +ol.loadingstrategy.tile = function(tileGrid) { + return ( + /** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @return {Array.<ol.Extent>} Extents. + */ + function(extent, resolution) { + var z = tileGrid.getZForResolution(resolution); + var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + /** @type {Array.<ol.Extent>} */ + var extents = []; + /** @type {ol.TileCoord} */ + var tileCoord = [z, 0, 0]; + for (tileCoord[1] = tileRange.minX; tileCoord[1] <= tileRange.maxX; + ++tileCoord[1]) { + for (tileCoord[2] = tileRange.minY; tileCoord[2] <= tileRange.maxY; + ++tileCoord[2]) { + extents.push(tileGrid.getTileCoordExtent(tileCoord)); + } + } + return extents; + }); +}; + +// FIXME bulk feature upload - suppress events +// FIXME make change-detection more refined (notably, geometry hint) + +goog.provide('ol.source.Vector'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.Object'); +goog.require('ol.array'); +goog.require('ol.asserts'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.featureloader'); +goog.require('ol.functions'); +goog.require('ol.loadingstrategy'); +goog.require('ol.obj'); +goog.require('ol.source.Source'); +goog.require('ol.source.State'); +goog.require('ol.structs.RBush'); + + +/** + * @classdesc + * Provides a source of features for vector layers. Vector features provided + * by this source are suitable for editing. See {@link ol.source.VectorTile} for + * vector data that is optimized for rendering. + * + * @constructor + * @extends {ol.source.Source} + * @fires ol.source.Vector.Event + * @param {olx.source.VectorOptions=} opt_options Vector source options. + * @api stable + */ +ol.source.Vector = function(opt_options) { + + var options = opt_options || {}; + + ol.source.Source.call(this, { + attributions: options.attributions, + logo: options.logo, + projection: undefined, + state: ol.source.State.READY, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + + /** + * @private + * @type {ol.FeatureLoader} + */ + this.loader_ = ol.nullFunction; + + /** + * @private + * @type {ol.format.Feature|undefined} + */ + this.format_ = options.format; + + /** + * @private + * @type {boolean} + */ + this.overlaps_ = options.overlaps == undefined ? true : options.overlaps; + + /** + * @private + * @type {string|ol.FeatureUrlFunction|undefined} + */ + this.url_ = options.url; + + if (options.loader !== undefined) { + this.loader_ = options.loader; + } else if (this.url_ !== undefined) { + ol.asserts.assert(this.format_, 7); // `format` must be set when `url` is set + // create a XHR feature loader for "url" and "format" + this.loader_ = ol.featureloader.xhr(this.url_, /** @type {ol.format.Feature} */ (this.format_)); + } + + /** + * @private + * @type {ol.LoadingStrategy} + */ + this.strategy_ = options.strategy !== undefined ? options.strategy : + ol.loadingstrategy.all; + + var useSpatialIndex = + options.useSpatialIndex !== undefined ? options.useSpatialIndex : true; + + /** + * @private + * @type {ol.structs.RBush.<ol.Feature>} + */ + this.featuresRtree_ = useSpatialIndex ? new ol.structs.RBush() : null; + + /** + * @private + * @type {ol.structs.RBush.<{extent: ol.Extent}>} + */ + this.loadedExtentsRtree_ = new ol.structs.RBush(); + + /** + * @private + * @type {Object.<string, ol.Feature>} + */ + this.nullGeometryFeatures_ = {}; + + /** + * A lookup of features by id (the return from feature.getId()). + * @private + * @type {Object.<string, ol.Feature>} + */ + this.idIndex_ = {}; + + /** + * A lookup of features without id (keyed by ol.getUid(feature)). + * @private + * @type {Object.<string, ol.Feature>} + */ + this.undefIdIndex_ = {}; + + /** + * @private + * @type {Object.<string, Array.<ol.EventsKey>>} + */ + this.featureChangeKeys_ = {}; + + /** + * @private + * @type {ol.Collection.<ol.Feature>} + */ + this.featuresCollection_ = null; + + var collection, features; + if (options.features instanceof ol.Collection) { + collection = options.features; + features = collection.getArray(); + } else if (Array.isArray(options.features)) { + features = options.features; + } + if (!useSpatialIndex && collection === undefined) { + collection = new ol.Collection(features); + } + if (features !== undefined) { + this.addFeaturesInternal(features); + } + if (collection !== undefined) { + this.bindFeaturesCollection_(collection); + } + +}; +ol.inherits(ol.source.Vector, ol.source.Source); + + +/** + * Add a single feature to the source. If you want to add a batch of features + * at once, call {@link ol.source.Vector#addFeatures source.addFeatures()} + * instead. + * @param {ol.Feature} feature Feature to add. + * @api stable + */ +ol.source.Vector.prototype.addFeature = function(feature) { + this.addFeatureInternal(feature); + this.changed(); +}; + + +/** + * Add a feature without firing a `change` event. + * @param {ol.Feature} feature Feature. + * @protected + */ +ol.source.Vector.prototype.addFeatureInternal = function(feature) { + var featureKey = ol.getUid(feature).toString(); + + if (!this.addToIndex_(featureKey, feature)) { + return; + } + + this.setupChangeEvents_(featureKey, feature); + + var geometry = feature.getGeometry(); + if (geometry) { + var extent = geometry.getExtent(); + if (this.featuresRtree_) { + this.featuresRtree_.insert(extent, feature); + } + } else { + this.nullGeometryFeatures_[featureKey] = feature; + } + + this.dispatchEvent( + new ol.source.Vector.Event(ol.source.Vector.EventType.ADDFEATURE, feature)); +}; + + +/** + * @param {string} featureKey Unique identifier for the feature. + * @param {ol.Feature} feature The feature. + * @private + */ +ol.source.Vector.prototype.setupChangeEvents_ = function(featureKey, feature) { + ol.DEBUG && console.assert(!(featureKey in this.featureChangeKeys_), + 'key (%s) not yet registered in featureChangeKey', featureKey); + this.featureChangeKeys_[featureKey] = [ + ol.events.listen(feature, ol.events.EventType.CHANGE, + this.handleFeatureChange_, this), + ol.events.listen(feature, ol.Object.EventType.PROPERTYCHANGE, + this.handleFeatureChange_, this) + ]; +}; + + +/** + * @param {string} featureKey Unique identifier for the feature. + * @param {ol.Feature} feature The feature. + * @return {boolean} The feature is "valid", in the sense that it is also a + * candidate for insertion into the Rtree. + * @private + */ +ol.source.Vector.prototype.addToIndex_ = function(featureKey, feature) { + var valid = true; + var id = feature.getId(); + if (id !== undefined) { + if (!(id.toString() in this.idIndex_)) { + this.idIndex_[id.toString()] = feature; + } else { + valid = false; + } + } else { + ol.asserts.assert(!(featureKey in this.undefIdIndex_), + 30); // The passed `feature` was already added to the source + this.undefIdIndex_[featureKey] = feature; + } + return valid; +}; + + +/** + * Add a batch of features to the source. + * @param {Array.<ol.Feature>} features Features to add. + * @api stable + */ +ol.source.Vector.prototype.addFeatures = function(features) { + this.addFeaturesInternal(features); + this.changed(); +}; + + +/** + * Add features without firing a `change` event. + * @param {Array.<ol.Feature>} features Features. + * @protected + */ +ol.source.Vector.prototype.addFeaturesInternal = function(features) { + var featureKey, i, length, feature; + + var extents = []; + var newFeatures = []; + var geometryFeatures = []; + + for (i = 0, length = features.length; i < length; i++) { + feature = features[i]; + featureKey = ol.getUid(feature).toString(); + if (this.addToIndex_(featureKey, feature)) { + newFeatures.push(feature); + } + } + + for (i = 0, length = newFeatures.length; i < length; i++) { + feature = newFeatures[i]; + featureKey = ol.getUid(feature).toString(); + this.setupChangeEvents_(featureKey, feature); + + var geometry = feature.getGeometry(); + if (geometry) { + var extent = geometry.getExtent(); + extents.push(extent); + geometryFeatures.push(feature); + } else { + this.nullGeometryFeatures_[featureKey] = feature; + } + } + if (this.featuresRtree_) { + this.featuresRtree_.load(extents, geometryFeatures); + } + + for (i = 0, length = newFeatures.length; i < length; i++) { + this.dispatchEvent(new ol.source.Vector.Event( + ol.source.Vector.EventType.ADDFEATURE, newFeatures[i])); + } +}; + + +/** + * @param {!ol.Collection.<ol.Feature>} collection Collection. + * @private + */ +ol.source.Vector.prototype.bindFeaturesCollection_ = function(collection) { + ol.DEBUG && console.assert(!this.featuresCollection_, + 'bindFeaturesCollection can only be called once'); + var modifyingCollection = false; + ol.events.listen(this, ol.source.Vector.EventType.ADDFEATURE, + function(evt) { + if (!modifyingCollection) { + modifyingCollection = true; + collection.push(evt.feature); + modifyingCollection = false; + } + }); + ol.events.listen(this, ol.source.Vector.EventType.REMOVEFEATURE, + function(evt) { + if (!modifyingCollection) { + modifyingCollection = true; + collection.remove(evt.feature); + modifyingCollection = false; + } + }); + ol.events.listen(collection, ol.Collection.EventType.ADD, + function(evt) { + if (!modifyingCollection) { + modifyingCollection = true; + this.addFeature(/** @type {ol.Feature} */ (evt.element)); + modifyingCollection = false; + } + }, this); + ol.events.listen(collection, ol.Collection.EventType.REMOVE, + function(evt) { + if (!modifyingCollection) { + modifyingCollection = true; + this.removeFeature(/** @type {ol.Feature} */ (evt.element)); + modifyingCollection = false; + } + }, this); + this.featuresCollection_ = collection; +}; + + +/** + * Remove all features from the source. + * @param {boolean=} opt_fast Skip dispatching of {@link removefeature} events. + * @api stable + */ +ol.source.Vector.prototype.clear = function(opt_fast) { + if (opt_fast) { + for (var featureId in this.featureChangeKeys_) { + var keys = this.featureChangeKeys_[featureId]; + keys.forEach(ol.events.unlistenByKey); + } + if (!this.featuresCollection_) { + this.featureChangeKeys_ = {}; + this.idIndex_ = {}; + this.undefIdIndex_ = {}; + } + } else { + if (this.featuresRtree_) { + this.featuresRtree_.forEach(this.removeFeatureInternal, this); + for (var id in this.nullGeometryFeatures_) { + this.removeFeatureInternal(this.nullGeometryFeatures_[id]); + } + } + } + if (this.featuresCollection_) { + this.featuresCollection_.clear(); + } + ol.DEBUG && console.assert(ol.obj.isEmpty(this.featureChangeKeys_), + 'featureChangeKeys is an empty object now'); + ol.DEBUG && console.assert(ol.obj.isEmpty(this.idIndex_), + 'idIndex is an empty object now'); + ol.DEBUG && console.assert(ol.obj.isEmpty(this.undefIdIndex_), + 'undefIdIndex is an empty object now'); + + if (this.featuresRtree_) { + this.featuresRtree_.clear(); + } + this.loadedExtentsRtree_.clear(); + this.nullGeometryFeatures_ = {}; + + var clearEvent = new ol.source.Vector.Event(ol.source.Vector.EventType.CLEAR); + this.dispatchEvent(clearEvent); + this.changed(); +}; + + +/** + * Iterate through all features on the source, calling the provided callback + * with each one. If the callback returns any "truthy" value, iteration will + * stop and the function will return the same value. + * + * @param {function(this: T, ol.Feature): S} callback Called with each feature + * on the source. Return a truthy value to stop iteration. + * @param {T=} opt_this The object to use as `this` in the callback. + * @return {S|undefined} The return value from the last call to the callback. + * @template T,S + * @api stable + */ +ol.source.Vector.prototype.forEachFeature = function(callback, opt_this) { + if (this.featuresRtree_) { + return this.featuresRtree_.forEach(callback, opt_this); + } else if (this.featuresCollection_) { + return this.featuresCollection_.forEach(callback, opt_this); + } +}; + + +/** + * Iterate through all features whose geometries contain the provided + * coordinate, calling the callback with each feature. If the callback returns + * a "truthy" value, iteration will stop and the function will return the same + * value. + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {function(this: T, ol.Feature): S} callback Called with each feature + * whose goemetry contains the provided coordinate. + * @param {T=} opt_this The object to use as `this` in the callback. + * @return {S|undefined} The return value from the last call to the callback. + * @template T,S + */ +ol.source.Vector.prototype.forEachFeatureAtCoordinateDirect = function(coordinate, callback, opt_this) { + var extent = [coordinate[0], coordinate[1], coordinate[0], coordinate[1]]; + return this.forEachFeatureInExtent(extent, function(feature) { + var geometry = feature.getGeometry(); + ol.DEBUG && console.assert(geometry, 'feature geometry is defined and not null'); + if (geometry.intersectsCoordinate(coordinate)) { + return callback.call(opt_this, feature); + } else { + return undefined; + } + }); +}; + + +/** + * Iterate through all features whose bounding box intersects the provided + * extent (note that the feature's geometry may not intersect the extent), + * calling the callback with each feature. If the callback returns a "truthy" + * value, iteration will stop and the function will return the same value. + * + * If you are interested in features whose geometry intersects an extent, call + * the {@link ol.source.Vector#forEachFeatureIntersectingExtent + * source.forEachFeatureIntersectingExtent()} method instead. + * + * When `useSpatialIndex` is set to false, this method will loop through all + * features, equivalent to {@link ol.source.Vector#forEachFeature}. + * + * @param {ol.Extent} extent Extent. + * @param {function(this: T, ol.Feature): S} callback Called with each feature + * whose bounding box intersects the provided extent. + * @param {T=} opt_this The object to use as `this` in the callback. + * @return {S|undefined} The return value from the last call to the callback. + * @template T,S + * @api + */ +ol.source.Vector.prototype.forEachFeatureInExtent = function(extent, callback, opt_this) { + if (this.featuresRtree_) { + return this.featuresRtree_.forEachInExtent(extent, callback, opt_this); + } else if (this.featuresCollection_) { + return this.featuresCollection_.forEach(callback, opt_this); + } +}; + + +/** + * Iterate through all features whose geometry intersects the provided extent, + * calling the callback with each feature. If the callback returns a "truthy" + * value, iteration will stop and the function will return the same value. + * + * If you only want to test for bounding box intersection, call the + * {@link ol.source.Vector#forEachFeatureInExtent + * source.forEachFeatureInExtent()} method instead. + * + * @param {ol.Extent} extent Extent. + * @param {function(this: T, ol.Feature): S} callback Called with each feature + * whose geometry intersects the provided extent. + * @param {T=} opt_this The object to use as `this` in the callback. + * @return {S|undefined} The return value from the last call to the callback. + * @template T,S + * @api + */ +ol.source.Vector.prototype.forEachFeatureIntersectingExtent = function(extent, callback, opt_this) { + return this.forEachFeatureInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + * @return {S|undefined} The return value from the last call to the callback. + * @template S + */ + function(feature) { + var geometry = feature.getGeometry(); + ol.DEBUG && console.assert(geometry, + 'feature geometry is defined and not null'); + if (geometry.intersectsExtent(extent)) { + var result = callback.call(opt_this, feature); + if (result) { + return result; + } + } + }); +}; + + +/** + * Get the features collection associated with this source. Will be `null` + * unless the source was configured with `useSpatialIndex` set to `false`, or + * with an {@link ol.Collection} as `features`. + * @return {ol.Collection.<ol.Feature>} The collection of features. + * @api + */ +ol.source.Vector.prototype.getFeaturesCollection = function() { + return this.featuresCollection_; +}; + + +/** + * Get all features on the source in random order. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.source.Vector.prototype.getFeatures = function() { + var features; + if (this.featuresCollection_) { + features = this.featuresCollection_.getArray(); + } else if (this.featuresRtree_) { + features = this.featuresRtree_.getAll(); + if (!ol.obj.isEmpty(this.nullGeometryFeatures_)) { + ol.array.extend( + features, ol.obj.getValues(this.nullGeometryFeatures_)); + } + } + return /** @type {Array.<ol.Feature>} */ (features); +}; + + +/** + * Get all features whose geometry intersects the provided coordinate. + * @param {ol.Coordinate} coordinate Coordinate. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) { + var features = []; + this.forEachFeatureAtCoordinateDirect(coordinate, function(feature) { + features.push(feature); + }); + return features; +}; + + +/** + * Get all features in the provided extent. Note that this returns an array of + * all features intersecting the given extent in random order (so it may include + * features whose geometries do not intersect the extent). + * + * This method is not available when the source is configured with + * `useSpatialIndex` set to `false`. + * @param {ol.Extent} extent Extent. + * @return {Array.<ol.Feature>} Features. + * @api + */ +ol.source.Vector.prototype.getFeaturesInExtent = function(extent) { + ol.DEBUG && console.assert(this.featuresRtree_, + 'getFeaturesInExtent does not work when useSpatialIndex is set to false'); + return this.featuresRtree_.getInExtent(extent); +}; + + +/** + * Get the closest feature to the provided coordinate. + * + * This method is not available when the source is configured with + * `useSpatialIndex` set to `false`. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {function(ol.Feature):boolean=} opt_filter Feature filter function. + * The filter function will receive one argument, the {@link ol.Feature feature} + * and it should return a boolean value. By default, no filtering is made. + * @return {ol.Feature} Closest feature. + * @api stable + */ +ol.source.Vector.prototype.getClosestFeatureToCoordinate = function(coordinate, opt_filter) { + // Find the closest feature using branch and bound. We start searching an + // infinite extent, and find the distance from the first feature found. This + // becomes the closest feature. We then compute a smaller extent which any + // closer feature must intersect. We continue searching with this smaller + // extent, trying to find a closer feature. Every time we find a closer + // feature, we update the extent being searched so that any even closer + // feature must intersect it. We continue until we run out of features. + var x = coordinate[0]; + var y = coordinate[1]; + var closestFeature = null; + var closestPoint = [NaN, NaN]; + var minSquaredDistance = Infinity; + var extent = [-Infinity, -Infinity, Infinity, Infinity]; + ol.DEBUG && console.assert(this.featuresRtree_, + 'getClosestFeatureToCoordinate does not work with useSpatialIndex set ' + + 'to false'); + var filter = opt_filter ? opt_filter : ol.functions.TRUE; + this.featuresRtree_.forEachInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + if (filter(feature)) { + var geometry = feature.getGeometry(); + ol.DEBUG && console.assert(geometry, + 'feature geometry is defined and not null'); + var previousMinSquaredDistance = minSquaredDistance; + minSquaredDistance = geometry.closestPointXY( + x, y, closestPoint, minSquaredDistance); + if (minSquaredDistance < previousMinSquaredDistance) { + closestFeature = feature; + // This is sneaky. Reduce the extent that it is currently being + // searched while the R-Tree traversal using this same extent object + // is still in progress. This is safe because the new extent is + // strictly contained by the old extent. + var minDistance = Math.sqrt(minSquaredDistance); + extent[0] = x - minDistance; + extent[1] = y - minDistance; + extent[2] = x + minDistance; + extent[3] = y + minDistance; + } + } + }); + return closestFeature; +}; + + +/** + * Get the extent of the features currently in the source. + * + * This method is not available when the source is configured with + * `useSpatialIndex` set to `false`. + * @return {!ol.Extent} Extent. + * @api stable + */ +ol.source.Vector.prototype.getExtent = function() { + ol.DEBUG && console.assert(this.featuresRtree_, + 'getExtent does not work when useSpatialIndex is set to false'); + return this.featuresRtree_.getExtent(); +}; + + +/** + * Get a feature by its identifier (the value returned by feature.getId()). + * Note that the index treats string and numeric identifiers as the same. So + * `source.getFeatureById(2)` will return a feature with id `'2'` or `2`. + * + * @param {string|number} id Feature identifier. + * @return {ol.Feature} The feature (or `null` if not found). + * @api stable + */ +ol.source.Vector.prototype.getFeatureById = function(id) { + var feature = this.idIndex_[id.toString()]; + return feature !== undefined ? feature : null; +}; + + +/** + * Get the format associated with this source. + * + * @return {ol.format.Feature|undefined} The feature format. + * @api + */ +ol.source.Vector.prototype.getFormat = function() { + return this.format_; +}; + + +/** + * @return {boolean} The source can have overlapping geometries. + */ +ol.source.Vector.prototype.getOverlaps = function() { + return this.overlaps_; +}; + + +/** + * Get the url associated with this source. + * + * @return {string|ol.FeatureUrlFunction|undefined} The url. + * @api + */ +ol.source.Vector.prototype.getUrl = function() { + return this.url_; +}; + + +/** + * @param {ol.events.Event} event Event. + * @private + */ +ol.source.Vector.prototype.handleFeatureChange_ = function(event) { + var feature = /** @type {ol.Feature} */ (event.target); + var featureKey = ol.getUid(feature).toString(); + var geometry = feature.getGeometry(); + if (!geometry) { + if (!(featureKey in this.nullGeometryFeatures_)) { + if (this.featuresRtree_) { + this.featuresRtree_.remove(feature); + } + this.nullGeometryFeatures_[featureKey] = feature; + } + } else { + var extent = geometry.getExtent(); + if (featureKey in this.nullGeometryFeatures_) { + delete this.nullGeometryFeatures_[featureKey]; + if (this.featuresRtree_) { + this.featuresRtree_.insert(extent, feature); + } + } else { + if (this.featuresRtree_) { + this.featuresRtree_.update(extent, feature); + } + } + } + var id = feature.getId(); + var removed; + if (id !== undefined) { + var sid = id.toString(); + if (featureKey in this.undefIdIndex_) { + delete this.undefIdIndex_[featureKey]; + this.idIndex_[sid] = feature; + } else { + if (this.idIndex_[sid] !== feature) { + removed = this.removeFromIdIndex_(feature); + ol.DEBUG && console.assert(removed, + 'Expected feature to be removed from index'); + this.idIndex_[sid] = feature; + } + } + } else { + if (!(featureKey in this.undefIdIndex_)) { + removed = this.removeFromIdIndex_(feature); + ol.DEBUG && console.assert(removed, + 'Expected feature to be removed from index'); + this.undefIdIndex_[featureKey] = feature; + } else { + ol.DEBUG && console.assert(this.undefIdIndex_[featureKey] === feature, + 'feature keyed under %s in undefIdKeys', featureKey); + } + } + this.changed(); + this.dispatchEvent(new ol.source.Vector.Event( + ol.source.Vector.EventType.CHANGEFEATURE, feature)); +}; + + +/** + * @return {boolean} Is empty. + */ +ol.source.Vector.prototype.isEmpty = function() { + return this.featuresRtree_.isEmpty() && + ol.obj.isEmpty(this.nullGeometryFeatures_); +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {ol.proj.Projection} projection Projection. + */ +ol.source.Vector.prototype.loadFeatures = function( + extent, resolution, projection) { + var loadedExtentsRtree = this.loadedExtentsRtree_; + var extentsToLoad = this.strategy_(extent, resolution); + var i, ii; + for (i = 0, ii = extentsToLoad.length; i < ii; ++i) { + var extentToLoad = extentsToLoad[i]; + var alreadyLoaded = loadedExtentsRtree.forEachInExtent(extentToLoad, + /** + * @param {{extent: ol.Extent}} object Object. + * @return {boolean} Contains. + */ + function(object) { + return ol.extent.containsExtent(object.extent, extentToLoad); + }); + if (!alreadyLoaded) { + this.loader_.call(this, extentToLoad, resolution, projection); + loadedExtentsRtree.insert(extentToLoad, {extent: extentToLoad.slice()}); + } + } +}; + + +/** + * Remove a single feature from the source. If you want to remove all features + * at once, use the {@link ol.source.Vector#clear source.clear()} method + * instead. + * @param {ol.Feature} feature Feature to remove. + * @api stable + */ +ol.source.Vector.prototype.removeFeature = function(feature) { + var featureKey = ol.getUid(feature).toString(); + if (featureKey in this.nullGeometryFeatures_) { + delete this.nullGeometryFeatures_[featureKey]; + } else { + if (this.featuresRtree_) { + this.featuresRtree_.remove(feature); + } + } + this.removeFeatureInternal(feature); + this.changed(); +}; + + +/** + * Remove feature without firing a `change` event. + * @param {ol.Feature} feature Feature. + * @protected + */ +ol.source.Vector.prototype.removeFeatureInternal = function(feature) { + var featureKey = ol.getUid(feature).toString(); + ol.DEBUG && console.assert(featureKey in this.featureChangeKeys_, + 'featureKey exists in featureChangeKeys'); + this.featureChangeKeys_[featureKey].forEach(ol.events.unlistenByKey); + delete this.featureChangeKeys_[featureKey]; + var id = feature.getId(); + if (id !== undefined) { + delete this.idIndex_[id.toString()]; + } else { + delete this.undefIdIndex_[featureKey]; + } + this.dispatchEvent(new ol.source.Vector.Event( + ol.source.Vector.EventType.REMOVEFEATURE, feature)); +}; + + +/** + * Remove a feature from the id index. Called internally when the feature id + * may have changed. + * @param {ol.Feature} feature The feature. + * @return {boolean} Removed the feature from the index. + * @private + */ +ol.source.Vector.prototype.removeFromIdIndex_ = function(feature) { + var removed = false; + for (var id in this.idIndex_) { + if (this.idIndex_[id] === feature) { + delete this.idIndex_[id]; + removed = true; + break; + } + } + return removed; +}; + + +/** + * @classdesc + * Events emitted by {@link ol.source.Vector} instances are instances of this + * type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.source.Vector.Event} + * @param {string} type Type. + * @param {ol.Feature=} opt_feature Feature. + */ +ol.source.Vector.Event = function(type, opt_feature) { + + ol.events.Event.call(this, type); + + /** + * The feature being added or removed. + * @type {ol.Feature|undefined} + * @api stable + */ + this.feature = opt_feature; + +}; +ol.inherits(ol.source.Vector.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.source.Vector.EventType = { + /** + * Triggered when a feature is added to the source. + * @event ol.source.Vector.Event#addfeature + * @api stable + */ + ADDFEATURE: 'addfeature', + + /** + * Triggered when a feature is updated. + * @event ol.source.Vector.Event#changefeature + * @api + */ + CHANGEFEATURE: 'changefeature', + + /** + * Triggered when the clear method is called on the source. + * @event ol.source.Vector.Event#clear + * @api + */ + CLEAR: 'clear', + + /** + * Triggered when a feature is removed from the source. + * See {@link ol.source.Vector#clear source.clear()} for exceptions. + * @event ol.source.Vector.Event#removefeature + * @api stable + */ + REMOVEFEATURE: 'removefeature' +}; + +goog.provide('ol.interaction.Draw'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.extent'); +goog.require('ol.events.Event'); +goog.require('ol.Feature'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.Object'); +goog.require('ol.coordinate'); +goog.require('ol.functions'); +goog.require('ol.events.condition'); +goog.require('ol.geom.Circle'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * Interaction for drawing feature geometries. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @fires ol.interaction.Draw.Event + * @param {olx.interaction.DrawOptions} options Options. + * @api stable + */ +ol.interaction.Draw = function(options) { + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.Draw.handleDownEvent_, + handleEvent: ol.interaction.Draw.handleEvent, + handleUpEvent: ol.interaction.Draw.handleUpEvent_ + }); + + /** + * @type {ol.Pixel} + * @private + */ + this.downPx_ = null; + + /** + * @type {boolean} + * @private + */ + this.freehand_ = false; + + /** + * Target source for drawn features. + * @type {ol.source.Vector} + * @private + */ + this.source_ = options.source ? options.source : null; + + /** + * Target collection for drawn features. + * @type {ol.Collection.<ol.Feature>} + * @private + */ + this.features_ = options.features ? options.features : null; + + /** + * Pixel distance for snapping. + * @type {number} + * @private + */ + this.snapTolerance_ = options.snapTolerance ? options.snapTolerance : 12; + + /** + * Geometry type. + * @type {ol.geom.GeometryType} + * @private + */ + this.type_ = options.type; + + /** + * Drawing mode (derived from geometry type. + * @type {ol.interaction.Draw.Mode} + * @private + */ + this.mode_ = ol.interaction.Draw.getMode_(this.type_); + + /** + * The number of points that must be drawn before a polygon ring or line + * string can be finished. The default is 3 for polygon rings and 2 for + * line strings. + * @type {number} + * @private + */ + this.minPoints_ = options.minPoints ? + options.minPoints : + (this.mode_ === ol.interaction.Draw.Mode.POLYGON ? 3 : 2); + + /** + * The number of points that can be drawn before a polygon ring or line string + * is finished. The default is no restriction. + * @type {number} + * @private + */ + this.maxPoints_ = options.maxPoints ? options.maxPoints : Infinity; + + /** + * A function to decide if a potential finish coordinate is permissable + * @private + * @type {ol.EventsConditionType} + */ + this.finishCondition_ = options.finishCondition ? options.finishCondition : ol.functions.TRUE; + + var geometryFunction = options.geometryFunction; + if (!geometryFunction) { + if (this.type_ === ol.geom.GeometryType.CIRCLE) { + /** + * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates + * The coordinates. + * @param {ol.geom.SimpleGeometry=} opt_geometry Optional geometry. + * @return {ol.geom.SimpleGeometry} A geometry. + */ + geometryFunction = function(coordinates, opt_geometry) { + var circle = opt_geometry ? /** @type {ol.geom.Circle} */ (opt_geometry) : + new ol.geom.Circle([NaN, NaN]); + var squaredLength = ol.coordinate.squaredDistance( + coordinates[0], coordinates[1]); + circle.setCenterAndRadius(coordinates[0], Math.sqrt(squaredLength)); + return circle; + }; + } else { + var Constructor; + var mode = this.mode_; + if (mode === ol.interaction.Draw.Mode.POINT) { + Constructor = ol.geom.Point; + } else if (mode === ol.interaction.Draw.Mode.LINE_STRING) { + Constructor = ol.geom.LineString; + } else if (mode === ol.interaction.Draw.Mode.POLYGON) { + Constructor = ol.geom.Polygon; + } + /** + * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates + * The coordinates. + * @param {ol.geom.SimpleGeometry=} opt_geometry Optional geometry. + * @return {ol.geom.SimpleGeometry} A geometry. + */ + geometryFunction = function(coordinates, opt_geometry) { + var geometry = opt_geometry; + if (geometry) { + if (mode === ol.interaction.Draw.Mode.POLYGON) { + geometry.setCoordinates([coordinates[0].concat([coordinates[0][0]])]); + } else { + geometry.setCoordinates(coordinates); + } + } else { + geometry = new Constructor(coordinates); + } + return geometry; + }; + } + } + + /** + * @type {ol.DrawGeometryFunctionType} + * @private + */ + this.geometryFunction_ = geometryFunction; + + /** + * Finish coordinate for the feature (first point for polygons, last point for + * linestrings). + * @type {ol.Coordinate} + * @private + */ + this.finishCoordinate_ = null; + + /** + * Sketch feature. + * @type {ol.Feature} + * @private + */ + this.sketchFeature_ = null; + + /** + * Sketch point. + * @type {ol.Feature} + * @private + */ + this.sketchPoint_ = null; + + /** + * Sketch coordinates. Used when drawing a line or polygon. + * @type {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} + * @private + */ + this.sketchCoords_ = null; + + /** + * Sketch line. Used when drawing polygon. + * @type {ol.Feature} + * @private + */ + this.sketchLine_ = null; + + /** + * Sketch line coordinates. Used when drawing a polygon or circle. + * @type {Array.<ol.Coordinate>} + * @private + */ + this.sketchLineCoords_ = null; + + /** + * Squared tolerance for handling up events. If the squared distance + * between a down and up event is greater than this tolerance, up events + * will not be handled. + * @type {number} + * @private + */ + this.squaredClickTolerance_ = options.clickTolerance ? + options.clickTolerance * options.clickTolerance : 36; + + /** + * Draw overlay where our sketch features are drawn. + * @type {ol.layer.Vector} + * @private + */ + this.overlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: options.wrapX ? options.wrapX : false + }), + style: options.style ? options.style : + ol.interaction.Draw.getDefaultStyleFunction() + }); + + /** + * Name of the geometry attribute for newly created features. + * @type {string|undefined} + * @private + */ + this.geometryName_ = options.geometryName; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.noModifierKeys; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.freehandCondition_; + if (options.freehand) { + this.freehandCondition_ = ol.events.condition.always; + } else { + this.freehandCondition_ = options.freehandCondition ? + options.freehandCondition : ol.events.condition.shiftKeyOnly; + } + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.interaction.Interaction.Property.ACTIVE), + this.updateState_, this); + +}; +ol.inherits(ol.interaction.Draw, ol.interaction.Pointer); + + +/** + * @return {ol.StyleFunction} Styles. + */ +ol.interaction.Draw.getDefaultStyleFunction = function() { + var styles = ol.style.Style.createDefaultEditing(); + return function(feature, resolution) { + return styles[feature.getGeometry().getType()]; + }; +}; + + +/** + * @inheritDoc + */ +ol.interaction.Draw.prototype.setMap = function(map) { + ol.interaction.Pointer.prototype.setMap.call(this, map); + this.updateState_(); +}; + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} and may actually + * draw or finish the drawing. + * @param {ol.MapBrowserEvent} event Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.Draw} + * @api + */ +ol.interaction.Draw.handleEvent = function(event) { + this.freehand_ = this.mode_ !== ol.interaction.Draw.Mode.POINT && this.freehandCondition_(event); + var pass = !this.freehand_; + if (this.freehand_ && + event.type === ol.MapBrowserEvent.EventType.POINTERDRAG && this.sketchFeature_ !== null) { + this.addToDrawing_(event); + pass = false; + } else if (event.type === + ol.MapBrowserEvent.EventType.POINTERMOVE) { + pass = this.handlePointerMove_(event); + } else if (event.type === ol.MapBrowserEvent.EventType.DBLCLICK) { + pass = false; + } + return ol.interaction.Pointer.handleEvent.call(this, event) && pass; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} event Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.Draw} + * @private + */ +ol.interaction.Draw.handleDownEvent_ = function(event) { + if (this.freehand_) { + this.downPx_ = event.pixel; + if (!this.finishCoordinate_) { + this.startDrawing_(event); + } + return true; + } else if (this.condition_(event)) { + this.downPx_ = event.pixel; + return true; + } else { + return false; + } +}; + + +/** + * @param {ol.MapBrowserPointerEvent} event Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Draw} + * @private + */ +ol.interaction.Draw.handleUpEvent_ = function(event) { + var downPx = this.downPx_; + var clickPx = event.pixel; + var dx = downPx[0] - clickPx[0]; + var dy = downPx[1] - clickPx[1]; + var squaredDistance = dx * dx + dy * dy; + var pass = true; + var shouldHandle = this.freehand_ ? + squaredDistance > this.squaredClickTolerance_ : + squaredDistance <= this.squaredClickTolerance_; + var circleMode = this.mode_ === ol.interaction.Draw.Mode.CIRCLE; + if (shouldHandle) { + this.handlePointerMove_(event); + if (!this.finishCoordinate_) { + this.startDrawing_(event); + if (this.mode_ === ol.interaction.Draw.Mode.POINT) { + this.finishDrawing(); + } + } else if (this.freehand_ || circleMode) { + this.finishDrawing(); + } else if (this.atFinish_(event)) { + if (this.finishCondition_(event)) { + this.finishDrawing(); + } + } else { + this.addToDrawing_(event); + } + pass = false; + } else if (circleMode) { + this.finishCoordinate_ = null; + } + return pass; +}; + + +/** + * Handle move events. + * @param {ol.MapBrowserEvent} event A move event. + * @return {boolean} Pass the event to other interactions. + * @private + */ +ol.interaction.Draw.prototype.handlePointerMove_ = function(event) { + if (this.finishCoordinate_) { + this.modifyDrawing_(event); + } else { + this.createOrUpdateSketchPoint_(event); + } + return true; +}; + + +/** + * Determine if an event is within the snapping tolerance of the start coord. + * @param {ol.MapBrowserEvent} event Event. + * @return {boolean} The event is within the snapping tolerance of the start. + * @private + */ +ol.interaction.Draw.prototype.atFinish_ = function(event) { + var at = false; + if (this.sketchFeature_) { + var potentiallyDone = false; + var potentiallyFinishCoordinates = [this.finishCoordinate_]; + if (this.mode_ === ol.interaction.Draw.Mode.LINE_STRING) { + potentiallyDone = this.sketchCoords_.length > this.minPoints_; + } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) { + potentiallyDone = this.sketchCoords_[0].length > + this.minPoints_; + potentiallyFinishCoordinates = [this.sketchCoords_[0][0], + this.sketchCoords_[0][this.sketchCoords_[0].length - 2]]; + } + if (potentiallyDone) { + var map = event.map; + for (var i = 0, ii = potentiallyFinishCoordinates.length; i < ii; i++) { + var finishCoordinate = potentiallyFinishCoordinates[i]; + var finishPixel = map.getPixelFromCoordinate(finishCoordinate); + var pixel = event.pixel; + var dx = pixel[0] - finishPixel[0]; + var dy = pixel[1] - finishPixel[1]; + var snapTolerance = this.freehand_ ? 1 : this.snapTolerance_; + at = Math.sqrt(dx * dx + dy * dy) <= snapTolerance; + if (at) { + this.finishCoordinate_ = finishCoordinate; + break; + } + } + } + } + return at; +}; + + +/** + * @param {ol.MapBrowserEvent} event Event. + * @private + */ +ol.interaction.Draw.prototype.createOrUpdateSketchPoint_ = function(event) { + var coordinates = event.coordinate.slice(); + if (!this.sketchPoint_) { + this.sketchPoint_ = new ol.Feature(new ol.geom.Point(coordinates)); + this.updateSketchFeatures_(); + } else { + var sketchPointGeom = /** @type {ol.geom.Point} */ (this.sketchPoint_.getGeometry()); + sketchPointGeom.setCoordinates(coordinates); + } +}; + + +/** + * Start the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private + */ +ol.interaction.Draw.prototype.startDrawing_ = function(event) { + var start = event.coordinate; + this.finishCoordinate_ = start; + if (this.mode_ === ol.interaction.Draw.Mode.POINT) { + this.sketchCoords_ = start.slice(); + } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) { + this.sketchCoords_ = [[start.slice(), start.slice()]]; + this.sketchLineCoords_ = this.sketchCoords_[0]; + } else { + this.sketchCoords_ = [start.slice(), start.slice()]; + if (this.mode_ === ol.interaction.Draw.Mode.CIRCLE) { + this.sketchLineCoords_ = this.sketchCoords_; + } + } + if (this.sketchLineCoords_) { + this.sketchLine_ = new ol.Feature( + new ol.geom.LineString(this.sketchLineCoords_)); + } + var geometry = this.geometryFunction_(this.sketchCoords_); + ol.DEBUG && console.assert(geometry !== undefined, 'geometry should be defined'); + this.sketchFeature_ = new ol.Feature(); + if (this.geometryName_) { + this.sketchFeature_.setGeometryName(this.geometryName_); + } + this.sketchFeature_.setGeometry(geometry); + this.updateSketchFeatures_(); + this.dispatchEvent(new ol.interaction.Draw.Event( + ol.interaction.Draw.EventType.DRAWSTART, this.sketchFeature_)); +}; + + +/** + * Modify the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private + */ +ol.interaction.Draw.prototype.modifyDrawing_ = function(event) { + var coordinate = event.coordinate; + var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry()); + var coordinates, last; + if (this.mode_ === ol.interaction.Draw.Mode.POINT) { + last = this.sketchCoords_; + } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) { + coordinates = this.sketchCoords_[0]; + last = coordinates[coordinates.length - 1]; + if (this.atFinish_(event)) { + // snap to finish + coordinate = this.finishCoordinate_.slice(); + } + } else { + coordinates = this.sketchCoords_; + last = coordinates[coordinates.length - 1]; + } + last[0] = coordinate[0]; + last[1] = coordinate[1]; + ol.DEBUG && console.assert(this.sketchCoords_, 'sketchCoords_ expected'); + this.geometryFunction_( + /** @type {!ol.Coordinate|!Array.<ol.Coordinate>|!Array.<Array.<ol.Coordinate>>} */ (this.sketchCoords_), + geometry); + if (this.sketchPoint_) { + var sketchPointGeom = /** @type {ol.geom.Point} */ (this.sketchPoint_.getGeometry()); + sketchPointGeom.setCoordinates(coordinate); + } + var sketchLineGeom; + if (geometry instanceof ol.geom.Polygon && + this.mode_ !== ol.interaction.Draw.Mode.POLYGON) { + if (!this.sketchLine_) { + this.sketchLine_ = new ol.Feature(new ol.geom.LineString(null)); + } + var ring = geometry.getLinearRing(0); + sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry()); + sketchLineGeom.setFlatCoordinates( + ring.getLayout(), ring.getFlatCoordinates()); + } else if (this.sketchLineCoords_) { + sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry()); + sketchLineGeom.setCoordinates(this.sketchLineCoords_); + } + this.updateSketchFeatures_(); +}; + + +/** + * Add a new coordinate to the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private + */ +ol.interaction.Draw.prototype.addToDrawing_ = function(event) { + var coordinate = event.coordinate; + var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry()); + var done; + var coordinates; + if (this.mode_ === ol.interaction.Draw.Mode.LINE_STRING) { + this.finishCoordinate_ = coordinate.slice(); + coordinates = this.sketchCoords_; + if (coordinates.length >= this.maxPoints_) { + if (this.freehand_) { + coordinates.pop(); + } else { + done = true; + } + } + coordinates.push(coordinate.slice()); + this.geometryFunction_(coordinates, geometry); + } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) { + coordinates = this.sketchCoords_[0]; + if (coordinates.length >= this.maxPoints_) { + if (this.freehand_) { + coordinates.pop(); + } else { + done = true; + } + } + coordinates.push(coordinate.slice()); + if (done) { + this.finishCoordinate_ = coordinates[0]; + } + this.geometryFunction_(this.sketchCoords_, geometry); + } + this.updateSketchFeatures_(); + if (done) { + this.finishDrawing(); + } +}; + + +/** + * Remove last point of the feature currently being drawn. + * @api + */ +ol.interaction.Draw.prototype.removeLastPoint = function() { + var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry()); + var coordinates, sketchLineGeom; + if (this.mode_ === ol.interaction.Draw.Mode.LINE_STRING) { + coordinates = this.sketchCoords_; + coordinates.splice(-2, 1); + this.geometryFunction_(coordinates, geometry); + } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) { + coordinates = this.sketchCoords_[0]; + coordinates.splice(-2, 1); + sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry()); + sketchLineGeom.setCoordinates(coordinates); + this.geometryFunction_(this.sketchCoords_, geometry); + } + + if (coordinates.length === 0) { + this.finishCoordinate_ = null; + } + + this.updateSketchFeatures_(); +}; + + +/** + * Stop drawing and add the sketch feature to the target layer. + * The {@link ol.interaction.Draw.EventType.DRAWEND} event is dispatched before + * inserting the feature. + * @api + */ +ol.interaction.Draw.prototype.finishDrawing = function() { + var sketchFeature = this.abortDrawing_(); + var coordinates = this.sketchCoords_; + var geometry = /** @type {ol.geom.SimpleGeometry} */ (sketchFeature.getGeometry()); + if (this.mode_ === ol.interaction.Draw.Mode.LINE_STRING) { + // remove the redundant last point + coordinates.pop(); + this.geometryFunction_(coordinates, geometry); + } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) { + // remove the redundant last point in ring + coordinates[0].pop(); + this.geometryFunction_(coordinates, geometry); + coordinates = geometry.getCoordinates(); + } + + // cast multi-part geometries + if (this.type_ === ol.geom.GeometryType.MULTI_POINT) { + sketchFeature.setGeometry(new ol.geom.MultiPoint([coordinates])); + } else if (this.type_ === ol.geom.GeometryType.MULTI_LINE_STRING) { + sketchFeature.setGeometry(new ol.geom.MultiLineString([coordinates])); + } else if (this.type_ === ol.geom.GeometryType.MULTI_POLYGON) { + sketchFeature.setGeometry(new ol.geom.MultiPolygon([coordinates])); + } + + // First dispatch event to allow full set up of feature + this.dispatchEvent(new ol.interaction.Draw.Event( + ol.interaction.Draw.EventType.DRAWEND, sketchFeature)); + + // Then insert feature + if (this.features_) { + this.features_.push(sketchFeature); + } + if (this.source_) { + this.source_.addFeature(sketchFeature); + } +}; + + +/** + * Stop drawing without adding the sketch feature to the target layer. + * @return {ol.Feature} The sketch feature (or null if none). + * @private + */ +ol.interaction.Draw.prototype.abortDrawing_ = function() { + this.finishCoordinate_ = null; + var sketchFeature = this.sketchFeature_; + if (sketchFeature) { + this.sketchFeature_ = null; + this.sketchPoint_ = null; + this.sketchLine_ = null; + this.overlay_.getSource().clear(true); + } + return sketchFeature; +}; + + +/** + * Extend an existing geometry by adding additional points. This only works + * on features with `LineString` geometries, where the interaction will + * extend lines by adding points to the end of the coordinates array. + * @param {!ol.Feature} feature Feature to be extended. + * @api + */ +ol.interaction.Draw.prototype.extend = function(feature) { + var geometry = feature.getGeometry(); + ol.DEBUG && console.assert(this.mode_ == ol.interaction.Draw.Mode.LINE_STRING, + 'interaction mode must be "line"'); + ol.DEBUG && console.assert(geometry.getType() == ol.geom.GeometryType.LINE_STRING, + 'feature geometry must be a line string'); + var lineString = /** @type {ol.geom.LineString} */ (geometry); + this.sketchFeature_ = feature; + this.sketchCoords_ = lineString.getCoordinates(); + var last = this.sketchCoords_[this.sketchCoords_.length - 1]; + this.finishCoordinate_ = last.slice(); + this.sketchCoords_.push(last.slice()); + this.updateSketchFeatures_(); + this.dispatchEvent(new ol.interaction.Draw.Event( + ol.interaction.Draw.EventType.DRAWSTART, this.sketchFeature_)); +}; + + +/** + * @inheritDoc + */ +ol.interaction.Draw.prototype.shouldStopEvent = ol.functions.FALSE; + + +/** + * Redraw the sketch features. + * @private + */ +ol.interaction.Draw.prototype.updateSketchFeatures_ = function() { + var sketchFeatures = []; + if (this.sketchFeature_) { + sketchFeatures.push(this.sketchFeature_); + } + if (this.sketchLine_) { + sketchFeatures.push(this.sketchLine_); + } + if (this.sketchPoint_) { + sketchFeatures.push(this.sketchPoint_); + } + var overlaySource = this.overlay_.getSource(); + overlaySource.clear(true); + overlaySource.addFeatures(sketchFeatures); +}; + + +/** + * @private + */ +ol.interaction.Draw.prototype.updateState_ = function() { + var map = this.getMap(); + var active = this.getActive(); + if (!map || !active) { + this.abortDrawing_(); + } + this.overlay_.setMap(active ? map : null); +}; + + +/** + * Create a `geometryFunction` for `type: 'Circle'` that will create a regular + * polygon with a user specified number of sides and start angle instead of an + * `ol.geom.Circle` geometry. + * @param {number=} opt_sides Number of sides of the regular polygon. Default is + * 32. + * @param {number=} opt_angle Angle of the first point in radians. 0 means East. + * Default is the angle defined by the heading from the center of the + * regular polygon to the current pointer position. + * @return {ol.DrawGeometryFunctionType} Function that draws a + * polygon. + * @api + */ +ol.interaction.Draw.createRegularPolygon = function(opt_sides, opt_angle) { + return ( + /** + * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates + * @param {ol.geom.SimpleGeometry=} opt_geometry + * @return {ol.geom.SimpleGeometry} + */ + function(coordinates, opt_geometry) { + var center = coordinates[0]; + var end = coordinates[1]; + var radius = Math.sqrt( + ol.coordinate.squaredDistance(center, end)); + var geometry = opt_geometry ? /** @type {ol.geom.Polygon} */ (opt_geometry) : + ol.geom.Polygon.fromCircle(new ol.geom.Circle(center), opt_sides); + var angle = opt_angle ? opt_angle : + Math.atan((end[1] - center[1]) / (end[0] - center[0])); + ol.geom.Polygon.makeRegular(geometry, center, radius, angle); + return geometry; + } + ); +}; + + +/** + * Create a `geometryFunction` that will create a box-shaped polygon (aligned + * with the coordinate system axes). Use this with the draw interaction and + * `type: 'Circle'` to return a box instead of a circle geometry. + * @return {ol.DrawGeometryFunctionType} Function that draws a box-shaped polygon. + * @api + */ +ol.interaction.Draw.createBox = function() { + return ( + /** + * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates + * @param {ol.geom.SimpleGeometry=} opt_geometry + * @return {ol.geom.SimpleGeometry} + */ + function(coordinates, opt_geometry) { + var extent = ol.extent.boundingExtent(coordinates); + var geometry = opt_geometry || new ol.geom.Polygon(null); + geometry.setCoordinates([[ + ol.extent.getBottomLeft(extent), + ol.extent.getBottomRight(extent), + ol.extent.getTopRight(extent), + ol.extent.getTopLeft(extent), + ol.extent.getBottomLeft(extent) + ]]); + return geometry; + } + ); +}; + + +/** + * Get the drawing mode. The mode for mult-part geometries is the same as for + * their single-part cousins. + * @param {ol.geom.GeometryType} type Geometry type. + * @return {ol.interaction.Draw.Mode} Drawing mode. + * @private + */ +ol.interaction.Draw.getMode_ = function(type) { + var mode; + if (type === ol.geom.GeometryType.POINT || + type === ol.geom.GeometryType.MULTI_POINT) { + mode = ol.interaction.Draw.Mode.POINT; + } else if (type === ol.geom.GeometryType.LINE_STRING || + type === ol.geom.GeometryType.MULTI_LINE_STRING) { + mode = ol.interaction.Draw.Mode.LINE_STRING; + } else if (type === ol.geom.GeometryType.POLYGON || + type === ol.geom.GeometryType.MULTI_POLYGON) { + mode = ol.interaction.Draw.Mode.POLYGON; + } else if (type === ol.geom.GeometryType.CIRCLE) { + mode = ol.interaction.Draw.Mode.CIRCLE; + } + return /** @type {!ol.interaction.Draw.Mode} */ (mode); +}; + + +/** + * Draw mode. This collapses multi-part geometry types with their single-part + * cousins. + * @enum {string} + */ +ol.interaction.Draw.Mode = { + POINT: 'Point', + LINE_STRING: 'LineString', + POLYGON: 'Polygon', + CIRCLE: 'Circle' +}; + +/** + * @classdesc + * Events emitted by {@link ol.interaction.Draw} instances are instances of + * this type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.DrawEvent} + * @param {ol.interaction.Draw.EventType} type Type. + * @param {ol.Feature} feature The feature drawn. + */ +ol.interaction.Draw.Event = function(type, feature) { + + ol.events.Event.call(this, type); + + /** + * The feature being drawn. + * @type {ol.Feature} + * @api stable + */ + this.feature = feature; + +}; +ol.inherits(ol.interaction.Draw.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.interaction.Draw.EventType = { + /** + * Triggered upon feature draw start + * @event ol.interaction.Draw.Event#drawstart + * @api stable + */ + DRAWSTART: 'drawstart', + /** + * Triggered upon feature draw end + * @event ol.interaction.Draw.Event#drawend + * @api stable + */ + DRAWEND: 'drawend' +}; + +goog.provide('ol.interaction.Extent'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserPointerEvent'); +goog.require('ol.coordinate'); +goog.require('ol.events.Event'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * Allows the user to draw a vector box by clicking and dragging on the map. + * Once drawn, the vector box can be modified by dragging its vertices or edges. + * This interaction is only supported for mouse devices. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @fires ol.interaction.Extent.Event + * @param {olx.interaction.ExtentOptions=} opt_options Options. + * @api + */ +ol.interaction.Extent = function(opt_options) { + + /** + * Extent of the drawn box + * @type {ol.Extent} + * @private + */ + this.extent_ = null; + + /** + * Handler for pointer move events + * @type {function (ol.Coordinate): ol.Extent|null} + * @private + */ + this.pointerHandler_ = null; + + /** + * Pixel threshold to snap to extent + * @type {number} + * @private + */ + this.pixelTolerance_ = 10; + + /** + * Is the pointer snapped to an extent vertex + * @type {boolean} + * @private + */ + this.snappedToVertex_ = false; + + /** + * Feature for displaying the visible extent + * @type {ol.Feature} + * @private + */ + this.extentFeature_ = null; + + /** + * Feature for displaying the visible pointer + * @type {ol.Feature} + * @private + */ + this.vertexFeature_ = null; + + if (!opt_options) { + opt_options = {}; + } + + if (opt_options.extent) { + this.setExtent(opt_options.extent); + } + + /* Inherit ol.interaction.Pointer */ + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.Extent.handleDownEvent_, + handleDragEvent: ol.interaction.Extent.handleDragEvent_, + handleEvent: ol.interaction.Extent.handleEvent_, + handleUpEvent: ol.interaction.Extent.handleUpEvent_ + }); + + /** + * Layer for the extentFeature + * @type {ol.layer.Vector} + * @private + */ + this.extentOverlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: !!opt_options.wrapX + }), + style: opt_options.boxStyle ? opt_options.boxStyle : ol.interaction.Extent.getDefaultExtentStyleFunction_(), + updateWhileAnimating: true, + updateWhileInteracting: true + }); + + /** + * Layer for the vertexFeature + * @type {ol.layer.Vector} + * @private + */ + this.vertexOverlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: !!opt_options.wrapX + }), + style: opt_options.pointerStyle ? opt_options.pointerStyle : ol.interaction.Extent.getDefaultPointerStyleFunction_(), + updateWhileAnimating: true, + updateWhileInteracting: true + }); +}; + +ol.inherits(ol.interaction.Extent, ol.interaction.Pointer); + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent Event. + * @return {boolean} Propagate event? + * @this {ol.interaction.Extent} + * @private + */ +ol.interaction.Extent.handleEvent_ = function(mapBrowserEvent) { + if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) { + return true; + } + //display pointer (if not dragging) + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE && !this.handlingDownUpSequence) { + this.handlePointerMove_(mapBrowserEvent); + } + //call pointer to determine up/down/drag + ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent); + //return false to stop propagation + return false; +}; + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Event handled? + * @this {ol.interaction.Extent} + * @private + */ +ol.interaction.Extent.handleDownEvent_ = function(mapBrowserEvent) { + var pixel = mapBrowserEvent.pixel; + var map = mapBrowserEvent.map; + + var extent = this.getExtent(); + var vertex = this.snapToVertex_(pixel, map); + + //find the extent corner opposite the passed corner + var getOpposingPoint = function(point) { + var x_ = null; + var y_ = null; + if (point[0] == extent[0]) { + x_ = extent[2]; + } else if (point[0] == extent[2]) { + x_ = extent[0]; + } + if (point[1] == extent[1]) { + y_ = extent[3]; + } else if (point[1] == extent[3]) { + y_ = extent[1]; + } + if (x_ !== null && y_ !== null) { + return [x_, y_]; + } + return null; + }; + if (vertex && extent) { + var x = (vertex[0] == extent[0] || vertex[0] == extent[2]) ? vertex[0] : null; + var y = (vertex[1] == extent[1] || vertex[1] == extent[3]) ? vertex[1] : null; + + //snap to point + if (x !== null && y !== null) { + this.pointerHandler_ = ol.interaction.Extent.getPointHandler_(getOpposingPoint(vertex)); + //snap to edge + } else if (x !== null) { + this.pointerHandler_ = ol.interaction.Extent.getEdgeHandler_( + getOpposingPoint([x, extent[1]]), + getOpposingPoint([x, extent[3]]) + ); + } else if (y !== null) { + this.pointerHandler_ = ol.interaction.Extent.getEdgeHandler_( + getOpposingPoint([extent[0], y]), + getOpposingPoint([extent[2], y]) + ); + } + //no snap - new bbox + } else { + vertex = map.getCoordinateFromPixel(pixel); + this.setExtent([vertex[0], vertex[1], vertex[0], vertex[1]]); + this.pointerHandler_ = ol.interaction.Extent.getPointHandler_(vertex); + } + return true; //event handled; start downup sequence +}; + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Event handled? + * @this {ol.interaction.Extent} + * @private + */ +ol.interaction.Extent.handleDragEvent_ = function(mapBrowserEvent) { + if (this.pointerHandler_) { + var pixelCoordinate = mapBrowserEvent.coordinate; + this.setExtent(this.pointerHandler_(pixelCoordinate)); + this.createOrUpdatePointerFeature_(pixelCoordinate); + } + return true; +}; + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Extent} + * @private + */ +ol.interaction.Extent.handleUpEvent_ = function(mapBrowserEvent) { + this.pointerHandler_ = null; + //If bbox is zero area, set to null; + var extent = this.getExtent(); + if (!extent || ol.extent.getArea(extent) === 0) { + this.setExtent(null); + } + return false; //Stop handling downup sequence +}; + +/** + * Returns the default style for the drawn bbox + * + * @return {ol.StyleFunction} Default Extent style + * @private + */ +ol.interaction.Extent.getDefaultExtentStyleFunction_ = function() { + var style = ol.style.Style.createDefaultEditing(); + return function(feature, resolution) { + return style[ol.geom.GeometryType.POLYGON]; + }; +}; + +/** + * Returns the default style for the pointer + * + * @return {ol.StyleFunction} Default pointer style + * @private + */ +ol.interaction.Extent.getDefaultPointerStyleFunction_ = function() { + var style = ol.style.Style.createDefaultEditing(); + return function(feature, resolution) { + return style[ol.geom.GeometryType.POINT]; + }; +}; + +/** + * @param {ol.Coordinate} fixedPoint corner that will be unchanged in the new extent + * @returns {function (ol.Coordinate): ol.Extent} event handler + * @private + */ +ol.interaction.Extent.getPointHandler_ = function(fixedPoint) { + return function(point) { + return ol.extent.boundingExtent([fixedPoint, point]); + }; +}; + +/** + * @param {ol.Coordinate} fixedP1 first corner that will be unchanged in the new extent + * @param {ol.Coordinate} fixedP2 second corner that will be unchanged in the new extent + * @returns {function (ol.Coordinate): ol.Extent|null} event handler + * @private + */ +ol.interaction.Extent.getEdgeHandler_ = function(fixedP1, fixedP2) { + if (fixedP1[0] == fixedP2[0]) { + return function(point) { + return ol.extent.boundingExtent([fixedP1, [point[0], fixedP2[1]]]); + }; + } else if (fixedP1[1] == fixedP2[1]) { + return function(point) { + return ol.extent.boundingExtent([fixedP1, [fixedP2[0], point[1]]]); + }; + } else { + return null; + } +}; + +/** + * @param {ol.Extent} extent extent + * @returns {Array<Array<ol.Coordinate>>} extent line segments + * @private + */ +ol.interaction.Extent.getSegments_ = function(extent) { + return [ + [[extent[0], extent[1]], [extent[0], extent[3]]], + [[extent[0], extent[3]], [extent[2], extent[3]]], + [[extent[2], extent[3]], [extent[2], extent[1]]], + [[extent[2], extent[1]], [extent[0], extent[1]]] + ]; +}; + +/** + * @param {ol.Pixel} pixel cursor location + * @param {ol.Map} map map + * @returns {ol.Coordinate|null} snapped vertex on extent + * @private + */ +ol.interaction.Extent.prototype.snapToVertex_ = function(pixel, map) { + var pixelCoordinate = map.getCoordinateFromPixel(pixel); + var sortByDistance = function(a, b) { + return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a) - + ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b); + }; + var extent = this.getExtent(); + if (extent) { + //convert extents to line segments and find the segment closest to pixelCoordinate + var segments = ol.interaction.Extent.getSegments_(extent); + segments.sort(sortByDistance); + var closestSegment = segments[0]; + + var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate, + closestSegment)); + var vertexPixel = map.getPixelFromCoordinate(vertex); + + //if the distance is within tolerance, snap to the segment + if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <= + this.pixelTolerance_) { + + //test if we should further snap to a vertex + var pixel1 = map.getPixelFromCoordinate(closestSegment[0]); + var pixel2 = map.getPixelFromCoordinate(closestSegment[1]); + var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1); + var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2); + var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); + this.snappedToVertex_ = dist <= this.pixelTolerance_; + if (this.snappedToVertex_) { + vertex = squaredDist1 > squaredDist2 ? + closestSegment[1] : closestSegment[0]; + } + return vertex; + } + } + return null; +}; + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent pointer move event + * @private + */ +ol.interaction.Extent.prototype.handlePointerMove_ = function(mapBrowserEvent) { + var pixel = mapBrowserEvent.pixel; + var map = mapBrowserEvent.map; + + var vertex = this.snapToVertex_(pixel, map); + if (!vertex) { + vertex = map.getCoordinateFromPixel(pixel); + } + this.createOrUpdatePointerFeature_(vertex); +}; + +/** + * @param {ol.Extent} extent extent + * @returns {ol.Feature} extent as featrue + * @private + */ +ol.interaction.Extent.prototype.createOrUpdateExtentFeature_ = function(extent) { + var extentFeature = this.extentFeature_; + + if (!extentFeature) { + if (!extent) { + extentFeature = new ol.Feature({}); + } else { + extentFeature = new ol.Feature(ol.geom.Polygon.fromExtent(extent)); + } + this.extentFeature_ = extentFeature; + this.extentOverlay_.getSource().addFeature(extentFeature); + } else { + if (!extent) { + extentFeature.setGeometry(undefined); + } else { + extentFeature.setGeometry(ol.geom.Polygon.fromExtent(extent)); + } + } + return extentFeature; +}; + + +/** + * @param {ol.Coordinate} vertex location of feature + * @returns {ol.Feature} vertex as feature + * @private + */ +ol.interaction.Extent.prototype.createOrUpdatePointerFeature_ = function(vertex) { + var vertexFeature = this.vertexFeature_; + if (!vertexFeature) { + vertexFeature = new ol.Feature(new ol.geom.Point(vertex)); + this.vertexFeature_ = vertexFeature; + this.vertexOverlay_.getSource().addFeature(vertexFeature); + } else { + var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry()); + geometry.setCoordinates(vertex); + } + return vertexFeature; +}; + + +/** + * @inheritDoc + */ +ol.interaction.Extent.prototype.setMap = function(map) { + this.extentOverlay_.setMap(map); + this.vertexOverlay_.setMap(map); + ol.interaction.Pointer.prototype.setMap.call(this, map); +}; + +/** + * Returns the current drawn extent in the view projection + * + * @return {ol.Extent} Drawn extent in the view projection. + * @api + */ +ol.interaction.Extent.prototype.getExtent = function() { + return this.extent_; +}; + +/** + * Manually sets the drawn extent, using the view projection. + * + * @param {ol.Extent} extent Extent + * @api + */ +ol.interaction.Extent.prototype.setExtent = function(extent) { + //Null extent means no bbox + this.extent_ = extent ? extent : null; + this.createOrUpdateExtentFeature_(extent); + this.dispatchEvent(new ol.interaction.Extent.Event(this.extent_)); +}; + + +/** + * @classdesc + * Events emitted by {@link ol.interaction.Extent} instances are instances of + * this type. + * + * @constructor + * @param {ol.Extent} extent the new extent + * @extends {ol.events.Event} + */ +ol.interaction.Extent.Event = function(extent) { + ol.events.Event.call(this, ol.interaction.Extent.EventType.EXTENTCHANGED); + + /** + * The current extent. + * @type {ol.Extent} + * @api + */ + this.extent_ = extent; +}; +ol.inherits(ol.interaction.Extent.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.interaction.Extent.EventType = { + /** + * Triggered after the extent is changed + * @event ol.interaction.Extent.Event + * @api + */ + EXTENTCHANGED: 'extentchanged' +}; + +goog.provide('ol.interaction.Modify'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.Feature'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserPointerEvent'); +goog.require('ol.View'); +goog.require('ol.array'); +goog.require('ol.coordinate'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.EventType'); +goog.require('ol.events.condition'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.Point'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); +goog.require('ol.structs.RBush'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * Interaction for modifying feature geometries. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.ModifyOptions} options Options. + * @fires ol.interaction.Modify.Event + * @api + */ +ol.interaction.Modify = function(options) { + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.Modify.handleDownEvent_, + handleDragEvent: ol.interaction.Modify.handleDragEvent_, + handleEvent: ol.interaction.Modify.handleEvent, + handleUpEvent: ol.interaction.Modify.handleUpEvent_ + }); + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.primaryAction; + + + /** + * @private + * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event. + * @return {boolean} Combined condition result. + */ + this.defaultDeleteCondition_ = function(mapBrowserEvent) { + return ol.events.condition.noModifierKeys(mapBrowserEvent) && + ol.events.condition.singleClick(mapBrowserEvent); + }; + + /** + * @type {ol.EventsConditionType} + * @private + */ + this.deleteCondition_ = options.deleteCondition ? + options.deleteCondition : this.defaultDeleteCondition_; + + /** + * Editing vertex. + * @type {ol.Feature} + * @private + */ + this.vertexFeature_ = null; + + /** + * Segments intersecting {@link this.vertexFeature_} by segment uid. + * @type {Object.<string, boolean>} + * @private + */ + this.vertexSegments_ = null; + + /** + * @type {ol.Pixel} + * @private + */ + this.lastPixel_ = [0, 0]; + + /** + * Tracks if the next `singleclick` event should be ignored to prevent + * accidental deletion right after vertex creation. + * @type {boolean} + * @private + */ + this.ignoreNextSingleClick_ = false; + + /** + * @type {boolean} + * @private + */ + this.modified_ = false; + + /** + * Segment RTree for each layer + * @type {ol.structs.RBush.<ol.ModifySegmentDataType>} + * @private + */ + this.rBush_ = new ol.structs.RBush(); + + /** + * @type {number} + * @private + */ + this.pixelTolerance_ = options.pixelTolerance !== undefined ? + options.pixelTolerance : 10; + + /** + * @type {boolean} + * @private + */ + this.snappedToVertex_ = false; + + /** + * Indicate whether the interaction is currently changing a feature's + * coordinates. + * @type {boolean} + * @private + */ + this.changingFeature_ = false; + + /** + * @type {Array} + * @private + */ + this.dragSegments_ = []; + + /** + * Draw overlay where sketch features are drawn. + * @type {ol.layer.Vector} + * @private + */ + this.overlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: !!options.wrapX + }), + style: options.style ? options.style : + ol.interaction.Modify.getDefaultStyleFunction(), + updateWhileAnimating: true, + updateWhileInteracting: true + }); + + /** + * @const + * @private + * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)>} + */ + this.SEGMENT_WRITERS_ = { + 'Point': this.writePointGeometry_, + 'LineString': this.writeLineStringGeometry_, + 'LinearRing': this.writeLineStringGeometry_, + 'Polygon': this.writePolygonGeometry_, + 'MultiPoint': this.writeMultiPointGeometry_, + 'MultiLineString': this.writeMultiLineStringGeometry_, + 'MultiPolygon': this.writeMultiPolygonGeometry_, + 'GeometryCollection': this.writeGeometryCollectionGeometry_ + }; + + /** + * @type {ol.Collection.<ol.Feature>} + * @private + */ + this.features_ = options.features; + + this.features_.forEach(this.addFeature_, this); + ol.events.listen(this.features_, ol.Collection.EventType.ADD, + this.handleFeatureAdd_, this); + ol.events.listen(this.features_, ol.Collection.EventType.REMOVE, + this.handleFeatureRemove_, this); + + /** + * @type {ol.MapBrowserPointerEvent} + * @private + */ + this.lastPointerEvent_ = null; + +}; +ol.inherits(ol.interaction.Modify, ol.interaction.Pointer); + + +/** + * @param {ol.Feature} feature Feature. + * @private + */ +ol.interaction.Modify.prototype.addFeature_ = function(feature) { + var geometry = feature.getGeometry(); + if (geometry && geometry.getType() in this.SEGMENT_WRITERS_) { + this.SEGMENT_WRITERS_[geometry.getType()].call(this, feature, geometry); + } + var map = this.getMap(); + if (map && map.isRendered()) { + this.handlePointerAtPixel_(this.lastPixel_, map); + } + ol.events.listen(feature, ol.events.EventType.CHANGE, + this.handleFeatureChange_, this); +}; + + +/** + * @param {ol.MapBrowserPointerEvent} evt Map browser event + * @private + */ +ol.interaction.Modify.prototype.willModifyFeatures_ = function(evt) { + if (!this.modified_) { + this.modified_ = true; + this.dispatchEvent(new ol.interaction.Modify.Event( + ol.interaction.Modify.EventType.MODIFYSTART, this.features_, evt)); + } +}; + + +/** + * @param {ol.Feature} feature Feature. + * @private + */ +ol.interaction.Modify.prototype.removeFeature_ = function(feature) { + this.removeFeatureSegmentData_(feature); + // Remove the vertex feature if the collection of canditate features + // is empty. + if (this.vertexFeature_ && this.features_.getLength() === 0) { + this.overlay_.getSource().removeFeature(this.vertexFeature_); + this.vertexFeature_ = null; + } + ol.events.unlisten(feature, ol.events.EventType.CHANGE, + this.handleFeatureChange_, this); +}; + + +/** + * @param {ol.Feature} feature Feature. + * @private + */ +ol.interaction.Modify.prototype.removeFeatureSegmentData_ = function(feature) { + var rBush = this.rBush_; + var /** @type {Array.<ol.ModifySegmentDataType>} */ nodesToRemove = []; + rBush.forEach( + /** + * @param {ol.ModifySegmentDataType} node RTree node. + */ + function(node) { + if (feature === node.feature) { + nodesToRemove.push(node); + } + }); + for (var i = nodesToRemove.length - 1; i >= 0; --i) { + rBush.remove(nodesToRemove[i]); + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.Modify.prototype.setActive = function(active) { + if (this.vertexFeature_ && !active) { + this.overlay_.getSource().removeFeature(this.vertexFeature_); + this.vertexFeature_ = null; + } + ol.interaction.Pointer.prototype.setActive.call(this, active); +}; + + +/** + * @inheritDoc + */ +ol.interaction.Modify.prototype.setMap = function(map) { + this.overlay_.setMap(map); + ol.interaction.Pointer.prototype.setMap.call(this, map); +}; + + +/** + * @param {ol.Collection.Event} evt Event. + * @private + */ +ol.interaction.Modify.prototype.handleFeatureAdd_ = function(evt) { + this.addFeature_(/** @type {ol.Feature} */ (evt.element)); +}; + + +/** + * @param {ol.events.Event} evt Event. + * @private + */ +ol.interaction.Modify.prototype.handleFeatureChange_ = function(evt) { + if (!this.changingFeature_) { + var feature = /** @type {ol.Feature} */ (evt.target); + this.removeFeature_(feature); + this.addFeature_(feature); + } +}; + + +/** + * @param {ol.Collection.Event} evt Event. + * @private + */ +ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) { + var feature = /** @type {ol.Feature} */ (evt.element); + this.removeFeature_(feature); +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.Point} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writePointGeometry_ = function(feature, geometry) { + var coordinates = geometry.getCoordinates(); + var segmentData = /** @type {ol.ModifySegmentDataType} */ ({ + feature: feature, + geometry: geometry, + segment: [coordinates, coordinates] + }); + this.rBush_.insert(geometry.getExtent(), segmentData); +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiPoint} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writeMultiPointGeometry_ = function(feature, geometry) { + var points = geometry.getCoordinates(); + var coordinates, i, ii, segmentData; + for (i = 0, ii = points.length; i < ii; ++i) { + coordinates = points[i]; + segmentData = /** @type {ol.ModifySegmentDataType} */ ({ + feature: feature, + geometry: geometry, + depth: [i], + index: i, + segment: [coordinates, coordinates] + }); + this.rBush_.insert(geometry.getExtent(), segmentData); + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.LineString} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writeLineStringGeometry_ = function(feature, geometry) { + var coordinates = geometry.getCoordinates(); + var i, ii, segment, segmentData; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.ModifySegmentDataType} */ ({ + feature: feature, + geometry: geometry, + index: i, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiLineString} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writeMultiLineStringGeometry_ = function(feature, geometry) { + var lines = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, segment, segmentData; + for (j = 0, jj = lines.length; j < jj; ++j) { + coordinates = lines[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.ModifySegmentDataType} */ ({ + feature: feature, + geometry: geometry, + depth: [j], + index: i, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.Polygon} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writePolygonGeometry_ = function(feature, geometry) { + var rings = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, segment, segmentData; + for (j = 0, jj = rings.length; j < jj; ++j) { + coordinates = rings[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.ModifySegmentDataType} */ ({ + feature: feature, + geometry: geometry, + depth: [j], + index: i, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiPolygon} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writeMultiPolygonGeometry_ = function(feature, geometry) { + var polygons = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData; + for (k = 0, kk = polygons.length; k < kk; ++k) { + rings = polygons[k]; + for (j = 0, jj = rings.length; j < jj; ++j) { + coordinates = rings[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.ModifySegmentDataType} */ ({ + feature: feature, + geometry: geometry, + depth: [j, k], + index: i, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.GeometryCollection} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writeGeometryCollectionGeometry_ = function(feature, geometry) { + var i, geometries = geometry.getGeometriesArray(); + for (i = 0; i < geometries.length; ++i) { + this.SEGMENT_WRITERS_[geometries[i].getType()].call( + this, feature, geometries[i]); + } +}; + + +/** + * @param {ol.Coordinate} coordinates Coordinates. + * @return {ol.Feature} Vertex feature. + * @private + */ +ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ = function(coordinates) { + var vertexFeature = this.vertexFeature_; + if (!vertexFeature) { + vertexFeature = new ol.Feature(new ol.geom.Point(coordinates)); + this.vertexFeature_ = vertexFeature; + this.overlay_.getSource().addFeature(vertexFeature); + } else { + var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry()); + geometry.setCoordinates(coordinates); + } + return vertexFeature; +}; + + +/** + * @param {ol.ModifySegmentDataType} a The first segment data. + * @param {ol.ModifySegmentDataType} b The second segment data. + * @return {number} The difference in indexes. + * @private + */ +ol.interaction.Modify.compareIndexes_ = function(a, b) { + return a.index - b.index; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} evt Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.Modify} + * @private + */ +ol.interaction.Modify.handleDownEvent_ = function(evt) { + if (!this.condition_(evt)) { + return false; + } + this.handlePointerAtPixel_(evt.pixel, evt.map); + this.dragSegments_.length = 0; + this.modified_ = false; + var vertexFeature = this.vertexFeature_; + if (vertexFeature) { + var insertVertices = []; + var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry()); + var vertex = geometry.getCoordinates(); + var vertexExtent = ol.extent.boundingExtent([vertex]); + var segmentDataMatches = this.rBush_.getInExtent(vertexExtent); + var componentSegments = {}; + segmentDataMatches.sort(ol.interaction.Modify.compareIndexes_); + for (var i = 0, ii = segmentDataMatches.length; i < ii; ++i) { + var segmentDataMatch = segmentDataMatches[i]; + var segment = segmentDataMatch.segment; + var uid = ol.getUid(segmentDataMatch.feature); + var depth = segmentDataMatch.depth; + if (depth) { + uid += '-' + depth.join('-'); // separate feature components + } + if (!componentSegments[uid]) { + componentSegments[uid] = new Array(2); + } + if (ol.coordinate.equals(segment[0], vertex) && + !componentSegments[uid][0]) { + this.dragSegments_.push([segmentDataMatch, 0]); + componentSegments[uid][0] = segmentDataMatch; + } else if (ol.coordinate.equals(segment[1], vertex) && + !componentSegments[uid][1]) { + + // prevent dragging closed linestrings by the connecting node + if ((segmentDataMatch.geometry.getType() === + ol.geom.GeometryType.LINE_STRING || + segmentDataMatch.geometry.getType() === + ol.geom.GeometryType.MULTI_LINE_STRING) && + componentSegments[uid][0] && + componentSegments[uid][0].index === 0) { + continue; + } + + this.dragSegments_.push([segmentDataMatch, 1]); + componentSegments[uid][1] = segmentDataMatch; + } else if (ol.getUid(segment) in this.vertexSegments_ && + (!componentSegments[uid][0] && !componentSegments[uid][1])) { + insertVertices.push([segmentDataMatch, vertex]); + } + } + if (insertVertices.length) { + this.willModifyFeatures_(evt); + } + for (var j = insertVertices.length - 1; j >= 0; --j) { + this.insertVertex_.apply(this, insertVertices[j]); + } + } + return !!this.vertexFeature_; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} evt Event. + * @this {ol.interaction.Modify} + * @private + */ +ol.interaction.Modify.handleDragEvent_ = function(evt) { + this.ignoreNextSingleClick_ = false; + this.willModifyFeatures_(evt); + + var vertex = evt.coordinate; + for (var i = 0, ii = this.dragSegments_.length; i < ii; ++i) { + var dragSegment = this.dragSegments_[i]; + var segmentData = dragSegment[0]; + var depth = segmentData.depth; + var geometry = segmentData.geometry; + var coordinates = geometry.getCoordinates(); + var segment = segmentData.segment; + var index = dragSegment[1]; + + while (vertex.length < geometry.getStride()) { + vertex.push(segment[index][vertex.length]); + } + + switch (geometry.getType()) { + case ol.geom.GeometryType.POINT: + coordinates = vertex; + segment[0] = segment[1] = vertex; + break; + case ol.geom.GeometryType.MULTI_POINT: + coordinates[segmentData.index] = vertex; + segment[0] = segment[1] = vertex; + break; + case ol.geom.GeometryType.LINE_STRING: + coordinates[segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case ol.geom.GeometryType.MULTI_LINE_STRING: + coordinates[depth[0]][segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case ol.geom.GeometryType.POLYGON: + coordinates[depth[0]][segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case ol.geom.GeometryType.MULTI_POLYGON: + coordinates[depth[1]][depth[0]][segmentData.index + index] = vertex; + segment[index] = vertex; + break; + default: + // pass + } + + this.setGeometryCoordinates_(geometry, coordinates); + } + this.createOrUpdateVertexFeature_(vertex); +}; + + +/** + * @param {ol.MapBrowserPointerEvent} evt Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Modify} + * @private + */ +ol.interaction.Modify.handleUpEvent_ = function(evt) { + var segmentData; + for (var i = this.dragSegments_.length - 1; i >= 0; --i) { + segmentData = this.dragSegments_[i][0]; + this.rBush_.update(ol.extent.boundingExtent(segmentData.segment), + segmentData); + } + if (this.modified_) { + this.dispatchEvent(new ol.interaction.Modify.Event( + ol.interaction.Modify.EventType.MODIFYEND, this.features_, evt)); + this.modified_ = false; + } + return false; +}; + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} and may modify the + * geometry. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.Modify} + * @api + */ +ol.interaction.Modify.handleEvent = function(mapBrowserEvent) { + if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) { + return true; + } + this.lastPointerEvent_ = mapBrowserEvent; + + var handled; + if (!mapBrowserEvent.map.getView().getHints()[ol.View.Hint.INTERACTING] && + mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE && + !this.handlingDownUpSequence) { + this.handlePointerMove_(mapBrowserEvent); + } + if (this.vertexFeature_ && this.deleteCondition_(mapBrowserEvent)) { + if (mapBrowserEvent.type != ol.MapBrowserEvent.EventType.SINGLECLICK || + !this.ignoreNextSingleClick_) { + handled = this.removePoint(); + } else { + handled = true; + } + } + + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK) { + this.ignoreNextSingleClick_ = false; + } + + return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) && + !handled; +}; + + +/** + * @param {ol.MapBrowserEvent} evt Event. + * @private + */ +ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) { + this.lastPixel_ = evt.pixel; + this.handlePointerAtPixel_(evt.pixel, evt.map); +}; + + +/** + * @param {ol.Pixel} pixel Pixel + * @param {ol.Map} map Map. + * @private + */ +ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) { + var pixelCoordinate = map.getCoordinateFromPixel(pixel); + var sortByDistance = function(a, b) { + return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a.segment) - + ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b.segment); + }; + + var box = ol.extent.buffer( + ol.extent.createOrUpdateFromCoordinate(pixelCoordinate), + map.getView().getResolution() * this.pixelTolerance_); + + var rBush = this.rBush_; + var nodes = rBush.getInExtent(box); + if (nodes.length > 0) { + nodes.sort(sortByDistance); + var node = nodes[0]; + var closestSegment = node.segment; + var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate, + closestSegment)); + var vertexPixel = map.getPixelFromCoordinate(vertex); + if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <= + this.pixelTolerance_) { + var pixel1 = map.getPixelFromCoordinate(closestSegment[0]); + var pixel2 = map.getPixelFromCoordinate(closestSegment[1]); + var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1); + var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2); + var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); + this.snappedToVertex_ = dist <= this.pixelTolerance_; + if (this.snappedToVertex_) { + vertex = squaredDist1 > squaredDist2 ? + closestSegment[1] : closestSegment[0]; + } + this.createOrUpdateVertexFeature_(vertex); + var vertexSegments = {}; + vertexSegments[ol.getUid(closestSegment)] = true; + var segment; + for (var i = 1, ii = nodes.length; i < ii; ++i) { + segment = nodes[i].segment; + if ((ol.coordinate.equals(closestSegment[0], segment[0]) && + ol.coordinate.equals(closestSegment[1], segment[1]) || + (ol.coordinate.equals(closestSegment[0], segment[1]) && + ol.coordinate.equals(closestSegment[1], segment[0])))) { + vertexSegments[ol.getUid(segment)] = true; + } else { + break; + } + } + this.vertexSegments_ = vertexSegments; + return; + } + } + if (this.vertexFeature_) { + this.overlay_.getSource().removeFeature(this.vertexFeature_); + this.vertexFeature_ = null; + } +}; + + +/** + * @param {ol.ModifySegmentDataType} segmentData Segment data. + * @param {ol.Coordinate} vertex Vertex. + * @private + */ +ol.interaction.Modify.prototype.insertVertex_ = function(segmentData, vertex) { + var segment = segmentData.segment; + var feature = segmentData.feature; + var geometry = segmentData.geometry; + var depth = segmentData.depth; + var index = /** @type {number} */ (segmentData.index); + var coordinates; + + while (vertex.length < geometry.getStride()) { + vertex.push(0); + } + + switch (geometry.getType()) { + case ol.geom.GeometryType.MULTI_LINE_STRING: + coordinates = geometry.getCoordinates(); + coordinates[depth[0]].splice(index + 1, 0, vertex); + break; + case ol.geom.GeometryType.POLYGON: + coordinates = geometry.getCoordinates(); + coordinates[depth[0]].splice(index + 1, 0, vertex); + break; + case ol.geom.GeometryType.MULTI_POLYGON: + coordinates = geometry.getCoordinates(); + coordinates[depth[1]][depth[0]].splice(index + 1, 0, vertex); + break; + case ol.geom.GeometryType.LINE_STRING: + coordinates = geometry.getCoordinates(); + coordinates.splice(index + 1, 0, vertex); + break; + default: + return; + } + + this.setGeometryCoordinates_(geometry, coordinates); + var rTree = this.rBush_; + rTree.remove(segmentData); + this.updateSegmentIndices_(geometry, index, depth, 1); + var newSegmentData = /** @type {ol.ModifySegmentDataType} */ ({ + segment: [segment[0], vertex], + feature: feature, + geometry: geometry, + depth: depth, + index: index + }); + rTree.insert(ol.extent.boundingExtent(newSegmentData.segment), + newSegmentData); + this.dragSegments_.push([newSegmentData, 1]); + + var newSegmentData2 = /** @type {ol.ModifySegmentDataType} */ ({ + segment: [vertex, segment[1]], + feature: feature, + geometry: geometry, + depth: depth, + index: index + 1 + }); + rTree.insert(ol.extent.boundingExtent(newSegmentData2.segment), + newSegmentData2); + this.dragSegments_.push([newSegmentData2, 0]); + this.ignoreNextSingleClick_ = true; +}; + +/** + * Removes the vertex currently being pointed. + * @return {boolean} True when a vertex was removed. + * @api + */ +ol.interaction.Modify.prototype.removePoint = function() { + if (this.lastPointerEvent_ && this.lastPointerEvent_.type != ol.MapBrowserEvent.EventType.POINTERDRAG) { + var evt = this.lastPointerEvent_; + this.willModifyFeatures_(evt); + this.removeVertex_(); + this.dispatchEvent(new ol.interaction.Modify.Event( + ol.interaction.Modify.EventType.MODIFYEND, this.features_, evt)); + this.modified_ = false; + return true; + } + return false; +}; + +/** + * Removes a vertex from all matching features. + * @return {boolean} True when a vertex was removed. + * @private + */ +ol.interaction.Modify.prototype.removeVertex_ = function() { + var dragSegments = this.dragSegments_; + var segmentsByFeature = {}; + var deleted = false; + var component, coordinates, dragSegment, geometry, i, index, left; + var newIndex, right, segmentData, uid; + for (i = dragSegments.length - 1; i >= 0; --i) { + dragSegment = dragSegments[i]; + segmentData = dragSegment[0]; + uid = ol.getUid(segmentData.feature); + if (segmentData.depth) { + // separate feature components + uid += '-' + segmentData.depth.join('-'); + } + if (!(uid in segmentsByFeature)) { + segmentsByFeature[uid] = {}; + } + if (dragSegment[1] === 0) { + segmentsByFeature[uid].right = segmentData; + segmentsByFeature[uid].index = segmentData.index; + } else if (dragSegment[1] == 1) { + segmentsByFeature[uid].left = segmentData; + segmentsByFeature[uid].index = segmentData.index + 1; + } + + } + for (uid in segmentsByFeature) { + right = segmentsByFeature[uid].right; + left = segmentsByFeature[uid].left; + index = segmentsByFeature[uid].index; + newIndex = index - 1; + if (left !== undefined) { + segmentData = left; + } else { + segmentData = right; + } + if (newIndex < 0) { + newIndex = 0; + } + geometry = segmentData.geometry; + coordinates = geometry.getCoordinates(); + component = coordinates; + deleted = false; + switch (geometry.getType()) { + case ol.geom.GeometryType.MULTI_LINE_STRING: + if (coordinates[segmentData.depth[0]].length > 2) { + coordinates[segmentData.depth[0]].splice(index, 1); + deleted = true; + } + break; + case ol.geom.GeometryType.LINE_STRING: + if (coordinates.length > 2) { + coordinates.splice(index, 1); + deleted = true; + } + break; + case ol.geom.GeometryType.MULTI_POLYGON: + component = component[segmentData.depth[1]]; + /* falls through */ + case ol.geom.GeometryType.POLYGON: + component = component[segmentData.depth[0]]; + if (component.length > 4) { + if (index == component.length - 1) { + index = 0; + } + component.splice(index, 1); + deleted = true; + if (index === 0) { + // close the ring again + component.pop(); + component.push(component[0]); + newIndex = component.length - 1; + } + } + break; + default: + // pass + } + + if (deleted) { + this.setGeometryCoordinates_(geometry, coordinates); + var segments = []; + if (left !== undefined) { + this.rBush_.remove(left); + segments.push(left.segment[0]); + } + if (right !== undefined) { + this.rBush_.remove(right); + segments.push(right.segment[1]); + } + if (left !== undefined && right !== undefined) { + ol.DEBUG && console.assert(newIndex >= 0, 'newIndex should be larger than 0'); + + var newSegmentData = /** @type {ol.ModifySegmentDataType} */ ({ + depth: segmentData.depth, + feature: segmentData.feature, + geometry: segmentData.geometry, + index: newIndex, + segment: segments + }); + this.rBush_.insert(ol.extent.boundingExtent(newSegmentData.segment), + newSegmentData); + } + this.updateSegmentIndices_(geometry, index, segmentData.depth, -1); + if (this.vertexFeature_) { + this.overlay_.getSource().removeFeature(this.vertexFeature_); + this.vertexFeature_ = null; + } + } + + } + return deleted; +}; + + +/** + * @param {ol.geom.SimpleGeometry} geometry Geometry. + * @param {Array} coordinates Coordinates. + * @private + */ +ol.interaction.Modify.prototype.setGeometryCoordinates_ = function(geometry, coordinates) { + this.changingFeature_ = true; + geometry.setCoordinates(coordinates); + this.changingFeature_ = false; +}; + + +/** + * @param {ol.geom.SimpleGeometry} geometry Geometry. + * @param {number} index Index. + * @param {Array.<number>|undefined} depth Depth. + * @param {number} delta Delta (1 or -1). + * @private + */ +ol.interaction.Modify.prototype.updateSegmentIndices_ = function( + geometry, index, depth, delta) { + this.rBush_.forEachInExtent(geometry.getExtent(), function(segmentDataMatch) { + if (segmentDataMatch.geometry === geometry && + (depth === undefined || segmentDataMatch.depth === undefined || + ol.array.equals(segmentDataMatch.depth, depth)) && + segmentDataMatch.index > index) { + segmentDataMatch.index += delta; + } + }); +}; + + +/** + * @return {ol.StyleFunction} Styles. + */ +ol.interaction.Modify.getDefaultStyleFunction = function() { + var style = ol.style.Style.createDefaultEditing(); + return function(feature, resolution) { + return style[ol.geom.GeometryType.POINT]; + }; +}; + + +/** + * @classdesc + * Events emitted by {@link ol.interaction.Modify} instances are instances of + * this type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.ModifyEvent} + * @param {ol.interaction.Modify.EventType} type Type. + * @param {ol.Collection.<ol.Feature>} features The features modified. + * @param {ol.MapBrowserPointerEvent} mapBrowserPointerEvent Associated + * {@link ol.MapBrowserPointerEvent}. + */ +ol.interaction.Modify.Event = function(type, features, mapBrowserPointerEvent) { + + ol.events.Event.call(this, type); + + /** + * The features being modified. + * @type {ol.Collection.<ol.Feature>} + * @api + */ + this.features = features; + + /** + * Associated {@link ol.MapBrowserEvent}. + * @type {ol.MapBrowserEvent} + * @api + */ + this.mapBrowserEvent = mapBrowserPointerEvent; +}; +ol.inherits(ol.interaction.Modify.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.interaction.Modify.EventType = { + /** + * Triggered upon feature modification start + * @event ol.interaction.Modify.Event#modifystart + * @api + */ + MODIFYSTART: 'modifystart', + /** + * Triggered upon feature modification end + * @event ol.interaction.Modify.Event#modifyend + * @api + */ + MODIFYEND: 'modifyend' +}; + +goog.provide('ol.interaction.Select'); + +goog.require('ol'); +goog.require('ol.functions'); +goog.require('ol.Collection'); +goog.require('ol.array'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.condition'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.layer.Vector'); +goog.require('ol.obj'); +goog.require('ol.source.Vector'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * Interaction for selecting vector features. By default, selected features are + * styled differently, so this interaction can be used for visual highlighting, + * as well as selecting features for other actions, such as modification or + * output. There are three ways of controlling which features are selected: + * using the browser event as defined by the `condition` and optionally the + * `toggle`, `add`/`remove`, and `multi` options; a `layers` filter; and a + * further feature filter using the `filter` option. + * + * Selected features are added to an internal unmanaged layer. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.SelectOptions=} opt_options Options. + * @fires ol.interaction.Select.Event + * @api stable + */ +ol.interaction.Select = function(opt_options) { + + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.Select.handleEvent + }); + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.singleClick; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.addCondition_ = options.addCondition ? + options.addCondition : ol.events.condition.never; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.removeCondition_ = options.removeCondition ? + options.removeCondition : ol.events.condition.never; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.toggleCondition_ = options.toggleCondition ? + options.toggleCondition : ol.events.condition.shiftKeyOnly; + + /** + * @private + * @type {boolean} + */ + this.multi_ = options.multi ? options.multi : false; + + /** + * @private + * @type {ol.SelectFilterFunction} + */ + this.filter_ = options.filter ? options.filter : + ol.functions.TRUE; + + /** + * @private + * @type {number} + */ + this.hitTolerance_ = options.hitTolerance ? options.hitTolerance : 0; + + var featureOverlay = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + features: options.features, + wrapX: options.wrapX + }), + style: options.style ? options.style : + ol.interaction.Select.getDefaultStyleFunction(), + updateWhileAnimating: true, + updateWhileInteracting: true + }); + + /** + * @private + * @type {ol.layer.Vector} + */ + this.featureOverlay_ = featureOverlay; + + /** @type {function(ol.layer.Layer): boolean} */ + var layerFilter; + if (options.layers) { + if (typeof options.layers === 'function') { + layerFilter = options.layers; + } else { + var layers = options.layers; + layerFilter = function(layer) { + return ol.array.includes(layers, layer); + }; + } + } else { + layerFilter = ol.functions.TRUE; + } + + /** + * @private + * @type {function(ol.layer.Layer): boolean} + */ + this.layerFilter_ = layerFilter; + + /** + * An association between selected feature (key) + * and layer (value) + * @private + * @type {Object.<number, ol.layer.Layer>} + */ + this.featureLayerAssociation_ = {}; + + var features = this.featureOverlay_.getSource().getFeaturesCollection(); + ol.events.listen(features, ol.Collection.EventType.ADD, + this.addFeature_, this); + ol.events.listen(features, ol.Collection.EventType.REMOVE, + this.removeFeature_, this); + +}; +ol.inherits(ol.interaction.Select, ol.interaction.Interaction); + + +/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.layer.Layer} layer Layer. + * @private + */ +ol.interaction.Select.prototype.addFeatureLayerAssociation_ = function(feature, layer) { + var key = ol.getUid(feature); + this.featureLayerAssociation_[key] = layer; +}; + + +/** + * Get the selected features. + * @return {ol.Collection.<ol.Feature>} Features collection. + * @api stable + */ +ol.interaction.Select.prototype.getFeatures = function() { + return this.featureOverlay_.getSource().getFeaturesCollection(); +}; + + +/** + * Returns the Hit-detection tolerance. + * @returns {number} Hit tolerance in pixels. + * @api + */ +ol.interaction.Select.prototype.getHitTolerance = function() { + return this.hitTolerance_; +}; + + +/** + * Returns the associated {@link ol.layer.Vector vectorlayer} of + * the (last) selected feature. Note that this will not work with any + * programmatic method like pushing features to + * {@link ol.interaction.Select#getFeatures collection}. + * @param {ol.Feature|ol.render.Feature} feature Feature + * @return {ol.layer.Vector} Layer. + * @api + */ +ol.interaction.Select.prototype.getLayer = function(feature) { + var key = ol.getUid(feature); + return /** @type {ol.layer.Vector} */ (this.featureLayerAssociation_[key]); +}; + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} and may change the + * selected state of features. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.Select} + * @api + */ +ol.interaction.Select.handleEvent = function(mapBrowserEvent) { + if (!this.condition_(mapBrowserEvent)) { + return true; + } + var add = this.addCondition_(mapBrowserEvent); + var remove = this.removeCondition_(mapBrowserEvent); + var toggle = this.toggleCondition_(mapBrowserEvent); + var set = !add && !remove && !toggle; + var map = mapBrowserEvent.map; + var features = this.featureOverlay_.getSource().getFeaturesCollection(); + var deselected = []; + var selected = []; + if (set) { + // Replace the currently selected feature(s) with the feature(s) at the + // pixel, or clear the selected feature(s) if there is no feature at + // the pixel. + ol.obj.clear(this.featureLayerAssociation_); + map.forEachFeatureAtPixel(mapBrowserEvent.pixel, + (/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.layer.Layer} layer Layer. + * @return {boolean|undefined} Continue to iterate over the features. + */ + function(feature, layer) { + if (this.filter_(feature, layer)) { + selected.push(feature); + this.addFeatureLayerAssociation_(feature, layer); + return !this.multi_; + } + }).bind(this), { + layerFilter: this.layerFilter_, + hitTolerance: this.hitTolerance_ + }); + var i; + for (i = features.getLength() - 1; i >= 0; --i) { + var feature = features.item(i); + var index = selected.indexOf(feature); + if (index > -1) { + // feature is already selected + selected.splice(index, 1); + } else { + features.remove(feature); + deselected.push(feature); + } + } + if (selected.length !== 0) { + features.extend(selected); + } + } else { + // Modify the currently selected feature(s). + map.forEachFeatureAtPixel(mapBrowserEvent.pixel, + (/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.layer.Layer} layer Layer. + * @return {boolean|undefined} Continue to iterate over the features. + */ + function(feature, layer) { + if (this.filter_(feature, layer)) { + if ((add || toggle) && + !ol.array.includes(features.getArray(), feature)) { + selected.push(feature); + this.addFeatureLayerAssociation_(feature, layer); + } else if ((remove || toggle) && + ol.array.includes(features.getArray(), feature)) { + deselected.push(feature); + this.removeFeatureLayerAssociation_(feature); + } + return !this.multi_; + } + }).bind(this), { + layerFilter: this.layerFilter_, + hitTolerance: this.hitTolerance_ + }); + var j; + for (j = deselected.length - 1; j >= 0; --j) { + features.remove(deselected[j]); + } + features.extend(selected); + } + if (selected.length > 0 || deselected.length > 0) { + this.dispatchEvent( + new ol.interaction.Select.Event(ol.interaction.Select.EventType.SELECT, + selected, deselected, mapBrowserEvent)); + } + return ol.events.condition.pointerMove(mapBrowserEvent); +}; + + +/** + * Hit-detection tolerance. Pixels inside the radius around the given position + * will be checked for features. This only works for the canvas renderer and + * not for WebGL. + * @param {number} hitTolerance Hit tolerance in pixels. + * @api + */ +ol.interaction.Select.prototype.setHitTolerance = function(hitTolerance) { + this.hitTolerance_ = hitTolerance; +}; + + +/** + * Remove the interaction from its current map, if any, and attach it to a new + * map, if any. Pass `null` to just remove the interaction from the current map. + * @param {ol.Map} map Map. + * @api stable + */ +ol.interaction.Select.prototype.setMap = function(map) { + var currentMap = this.getMap(); + var selectedFeatures = + this.featureOverlay_.getSource().getFeaturesCollection(); + if (currentMap) { + selectedFeatures.forEach(currentMap.unskipFeature, currentMap); + } + ol.interaction.Interaction.prototype.setMap.call(this, map); + this.featureOverlay_.setMap(map); + if (map) { + selectedFeatures.forEach(map.skipFeature, map); + } +}; + + +/** + * @return {ol.StyleFunction} Styles. + */ +ol.interaction.Select.getDefaultStyleFunction = function() { + var styles = ol.style.Style.createDefaultEditing(); + ol.array.extend(styles[ol.geom.GeometryType.POLYGON], + styles[ol.geom.GeometryType.LINE_STRING]); + ol.array.extend(styles[ol.geom.GeometryType.GEOMETRY_COLLECTION], + styles[ol.geom.GeometryType.LINE_STRING]); + + return function(feature, resolution) { + if (!feature.getGeometry()) { + return null; + } + return styles[feature.getGeometry().getType()]; + }; +}; + + +/** + * @param {ol.Collection.Event} evt Event. + * @private + */ +ol.interaction.Select.prototype.addFeature_ = function(evt) { + var map = this.getMap(); + if (map) { + map.skipFeature(/** @type {ol.Feature} */ (evt.element)); + } +}; + + +/** + * @param {ol.Collection.Event} evt Event. + * @private + */ +ol.interaction.Select.prototype.removeFeature_ = function(evt) { + var map = this.getMap(); + if (map) { + map.unskipFeature(/** @type {ol.Feature} */ (evt.element)); + } +}; + + +/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @private + */ +ol.interaction.Select.prototype.removeFeatureLayerAssociation_ = function(feature) { + var key = ol.getUid(feature); + delete this.featureLayerAssociation_[key]; +}; + + +/** + * @classdesc + * Events emitted by {@link ol.interaction.Select} instances are instances of + * this type. + * + * @param {ol.interaction.Select.EventType} type The event type. + * @param {Array.<ol.Feature>} selected Selected features. + * @param {Array.<ol.Feature>} deselected Deselected features. + * @param {ol.MapBrowserEvent} mapBrowserEvent Associated + * {@link ol.MapBrowserEvent}. + * @implements {oli.SelectEvent} + * @extends {ol.events.Event} + * @constructor + */ +ol.interaction.Select.Event = function(type, selected, deselected, mapBrowserEvent) { + ol.events.Event.call(this, type); + + /** + * Selected features array. + * @type {Array.<ol.Feature>} + * @api + */ + this.selected = selected; + + /** + * Deselected features array. + * @type {Array.<ol.Feature>} + * @api + */ + this.deselected = deselected; + + /** + * Associated {@link ol.MapBrowserEvent}. + * @type {ol.MapBrowserEvent} + * @api + */ + this.mapBrowserEvent = mapBrowserEvent; +}; +ol.inherits(ol.interaction.Select.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.interaction.Select.EventType = { + /** + * Triggered when feature(s) has been (de)selected. + * @event ol.interaction.Select.Event#select + * @api + */ + SELECT: 'select' +}; + +goog.provide('ol.interaction.Snap'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.Object'); +goog.require('ol.Observable'); +goog.require('ol.coordinate'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.functions'); +goog.require('ol.obj'); +goog.require('ol.source.Vector'); +goog.require('ol.structs.RBush'); + + +/** + * @classdesc + * Handles snapping of vector features while modifying or drawing them. The + * features can come from a {@link ol.source.Vector} or {@link ol.Collection} + * Any interaction object that allows the user to interact + * with the features using the mouse can benefit from the snapping, as long + * as it is added before. + * + * The snap interaction modifies map browser event `coordinate` and `pixel` + * properties to force the snap to occur to any interaction that them. + * + * Example: + * + * var snap = new ol.interaction.Snap({ + * source: source + * }); + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.SnapOptions=} opt_options Options. + * @api + */ +ol.interaction.Snap = function(opt_options) { + + ol.interaction.Pointer.call(this, { + handleEvent: ol.interaction.Snap.handleEvent_, + handleDownEvent: ol.functions.TRUE, + handleUpEvent: ol.interaction.Snap.handleUpEvent_ + }); + + var options = opt_options ? opt_options : {}; + + /** + * @type {ol.source.Vector} + * @private + */ + this.source_ = options.source ? options.source : null; + + /** + * @private + * @type {boolean} + */ + this.vertex_ = options.vertex !== undefined ? options.vertex : true; + + /** + * @private + * @type {boolean} + */ + this.edge_ = options.edge !== undefined ? options.edge : true; + + /** + * @type {ol.Collection.<ol.Feature>} + * @private + */ + this.features_ = options.features ? options.features : null; + + /** + * @type {Array.<ol.EventsKey>} + * @private + */ + this.featuresListenerKeys_ = []; + + /** + * @type {Object.<number, ol.EventsKey>} + * @private + */ + this.geometryChangeListenerKeys_ = {}; + + /** + * @type {Object.<number, ol.EventsKey>} + * @private + */ + this.geometryModifyListenerKeys_ = {}; + + /** + * Extents are preserved so indexed segment can be quickly removed + * when its feature geometry changes + * @type {Object.<number, ol.Extent>} + * @private + */ + this.indexedFeaturesExtents_ = {}; + + /** + * If a feature geometry changes while a pointer drag|move event occurs, the + * feature doesn't get updated right away. It will be at the next 'pointerup' + * event fired. + * @type {Object.<number, ol.Feature>} + * @private + */ + this.pendingFeatures_ = {}; + + /** + * Used for distance sorting in sortByDistance_ + * @type {ol.Coordinate} + * @private + */ + this.pixelCoordinate_ = null; + + /** + * @type {number} + * @private + */ + this.pixelTolerance_ = options.pixelTolerance !== undefined ? + options.pixelTolerance : 10; + + /** + * @type {function(ol.SnapSegmentDataType, ol.SnapSegmentDataType): number} + * @private + */ + this.sortByDistance_ = ol.interaction.Snap.sortByDistance.bind(this); + + + /** + * Segment RTree for each layer + * @type {ol.structs.RBush.<ol.SnapSegmentDataType>} + * @private + */ + this.rBush_ = new ol.structs.RBush(); + + + /** + * @const + * @private + * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)>} + */ + this.SEGMENT_WRITERS_ = { + 'Point': this.writePointGeometry_, + 'LineString': this.writeLineStringGeometry_, + 'LinearRing': this.writeLineStringGeometry_, + 'Polygon': this.writePolygonGeometry_, + 'MultiPoint': this.writeMultiPointGeometry_, + 'MultiLineString': this.writeMultiLineStringGeometry_, + 'MultiPolygon': this.writeMultiPolygonGeometry_, + 'GeometryCollection': this.writeGeometryCollectionGeometry_ + }; +}; +ol.inherits(ol.interaction.Snap, ol.interaction.Pointer); + + +/** + * Add a feature to the collection of features that we may snap to. + * @param {ol.Feature} feature Feature. + * @param {boolean=} opt_listen Whether to listen to the geometry change or not + * Defaults to `true`. + * @api + */ +ol.interaction.Snap.prototype.addFeature = function(feature, opt_listen) { + var listen = opt_listen !== undefined ? opt_listen : true; + var feature_uid = ol.getUid(feature); + var geometry = feature.getGeometry(); + if (geometry) { + var segmentWriter = this.SEGMENT_WRITERS_[geometry.getType()]; + if (segmentWriter) { + this.indexedFeaturesExtents_[feature_uid] = geometry.getExtent( + ol.extent.createEmpty()); + segmentWriter.call(this, feature, geometry); + + if (listen) { + this.geometryModifyListenerKeys_[feature_uid] = ol.events.listen( + geometry, + ol.events.EventType.CHANGE, + this.handleGeometryModify_.bind(this, feature), + this); + } + } + } + + if (listen) { + this.geometryChangeListenerKeys_[feature_uid] = ol.events.listen( + feature, + ol.Object.getChangeEventType(feature.getGeometryName()), + this.handleGeometryChange_, this); + } +}; + + +/** + * @param {ol.Feature} feature Feature. + * @private + */ +ol.interaction.Snap.prototype.forEachFeatureAdd_ = function(feature) { + this.addFeature(feature); +}; + + +/** + * @param {ol.Feature} feature Feature. + * @private + */ +ol.interaction.Snap.prototype.forEachFeatureRemove_ = function(feature) { + this.removeFeature(feature); +}; + + +/** + * @return {ol.Collection.<ol.Feature>|Array.<ol.Feature>} Features. + * @private + */ +ol.interaction.Snap.prototype.getFeatures_ = function() { + var features; + if (this.features_) { + features = this.features_; + } else if (this.source_) { + features = this.source_.getFeatures(); + } + return /** @type {!Array.<ol.Feature>|!ol.Collection.<ol.Feature>} */ (features); +}; + + +/** + * @param {ol.source.Vector.Event|ol.Collection.Event} evt Event. + * @private + */ +ol.interaction.Snap.prototype.handleFeatureAdd_ = function(evt) { + var feature; + if (evt instanceof ol.source.Vector.Event) { + feature = evt.feature; + } else if (evt instanceof ol.Collection.Event) { + feature = evt.element; + } + this.addFeature(/** @type {ol.Feature} */ (feature)); +}; + + +/** + * @param {ol.source.Vector.Event|ol.Collection.Event} evt Event. + * @private + */ +ol.interaction.Snap.prototype.handleFeatureRemove_ = function(evt) { + var feature; + if (evt instanceof ol.source.Vector.Event) { + feature = evt.feature; + } else if (evt instanceof ol.Collection.Event) { + feature = evt.element; + } + this.removeFeature(/** @type {ol.Feature} */ (feature)); +}; + + +/** + * @param {ol.events.Event} evt Event. + * @private + */ +ol.interaction.Snap.prototype.handleGeometryChange_ = function(evt) { + var feature = /** @type {ol.Feature} */ (evt.target); + this.removeFeature(feature, true); + this.addFeature(feature, true); +}; + + +/** + * @param {ol.Feature} feature Feature which geometry was modified. + * @param {ol.events.Event} evt Event. + * @private + */ +ol.interaction.Snap.prototype.handleGeometryModify_ = function(feature, evt) { + if (this.handlingDownUpSequence) { + var uid = ol.getUid(feature); + if (!(uid in this.pendingFeatures_)) { + this.pendingFeatures_[uid] = feature; + } + } else { + this.updateFeature_(feature); + } +}; + + +/** + * Remove a feature from the collection of features that we may snap to. + * @param {ol.Feature} feature Feature + * @param {boolean=} opt_unlisten Whether to unlisten to the geometry change + * or not. Defaults to `true`. + * @api + */ +ol.interaction.Snap.prototype.removeFeature = function(feature, opt_unlisten) { + var unlisten = opt_unlisten !== undefined ? opt_unlisten : true; + var feature_uid = ol.getUid(feature); + var extent = this.indexedFeaturesExtents_[feature_uid]; + if (extent) { + var rBush = this.rBush_; + var i, nodesToRemove = []; + rBush.forEachInExtent(extent, function(node) { + if (feature === node.feature) { + nodesToRemove.push(node); + } + }); + for (i = nodesToRemove.length - 1; i >= 0; --i) { + rBush.remove(nodesToRemove[i]); + } + if (unlisten) { + ol.Observable.unByKey(this.geometryModifyListenerKeys_[feature_uid]); + delete this.geometryModifyListenerKeys_[feature_uid]; + } + } + + if (unlisten) { + ol.Observable.unByKey(this.geometryChangeListenerKeys_[feature_uid]); + delete this.geometryChangeListenerKeys_[feature_uid]; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.Snap.prototype.setMap = function(map) { + var currentMap = this.getMap(); + var keys = this.featuresListenerKeys_; + var features = this.getFeatures_(); + + if (currentMap) { + keys.forEach(ol.Observable.unByKey); + keys.length = 0; + features.forEach(this.forEachFeatureRemove_, this); + } + ol.interaction.Pointer.prototype.setMap.call(this, map); + + if (map) { + if (this.features_) { + keys.push( + ol.events.listen(this.features_, ol.Collection.EventType.ADD, + this.handleFeatureAdd_, this), + ol.events.listen(this.features_, ol.Collection.EventType.REMOVE, + this.handleFeatureRemove_, this) + ); + } else if (this.source_) { + keys.push( + ol.events.listen(this.source_, ol.source.Vector.EventType.ADDFEATURE, + this.handleFeatureAdd_, this), + ol.events.listen(this.source_, ol.source.Vector.EventType.REMOVEFEATURE, + this.handleFeatureRemove_, this) + ); + } + features.forEach(this.forEachFeatureAdd_, this); + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.Snap.prototype.shouldStopEvent = ol.functions.FALSE; + + +/** + * @param {ol.Pixel} pixel Pixel + * @param {ol.Coordinate} pixelCoordinate Coordinate + * @param {ol.Map} map Map. + * @return {ol.SnapResultType} Snap result + */ +ol.interaction.Snap.prototype.snapTo = function(pixel, pixelCoordinate, map) { + + var lowerLeft = map.getCoordinateFromPixel( + [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]); + var upperRight = map.getCoordinateFromPixel( + [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]); + var box = ol.extent.boundingExtent([lowerLeft, upperRight]); + + var segments = this.rBush_.getInExtent(box); + var snappedToVertex = false; + var snapped = false; + var vertex = null; + var vertexPixel = null; + var dist, pixel1, pixel2, squaredDist1, squaredDist2; + if (segments.length > 0) { + this.pixelCoordinate_ = pixelCoordinate; + segments.sort(this.sortByDistance_); + var closestSegment = segments[0].segment; + if (this.vertex_ && !this.edge_) { + pixel1 = map.getPixelFromCoordinate(closestSegment[0]); + pixel2 = map.getPixelFromCoordinate(closestSegment[1]); + squaredDist1 = ol.coordinate.squaredDistance(pixel, pixel1); + squaredDist2 = ol.coordinate.squaredDistance(pixel, pixel2); + dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); + snappedToVertex = dist <= this.pixelTolerance_; + if (snappedToVertex) { + snapped = true; + vertex = squaredDist1 > squaredDist2 ? + closestSegment[1] : closestSegment[0]; + vertexPixel = map.getPixelFromCoordinate(vertex); + } + } else if (this.edge_) { + vertex = (ol.coordinate.closestOnSegment(pixelCoordinate, + closestSegment)); + vertexPixel = map.getPixelFromCoordinate(vertex); + if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <= + this.pixelTolerance_) { + snapped = true; + if (this.vertex_) { + pixel1 = map.getPixelFromCoordinate(closestSegment[0]); + pixel2 = map.getPixelFromCoordinate(closestSegment[1]); + squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1); + squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2); + dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); + snappedToVertex = dist <= this.pixelTolerance_; + if (snappedToVertex) { + vertex = squaredDist1 > squaredDist2 ? + closestSegment[1] : closestSegment[0]; + vertexPixel = map.getPixelFromCoordinate(vertex); + } + } + } + } + if (snapped) { + vertexPixel = [Math.round(vertexPixel[0]), Math.round(vertexPixel[1])]; + } + } + return /** @type {ol.SnapResultType} */ ({ + snapped: snapped, + vertex: vertex, + vertexPixel: vertexPixel + }); +}; + + +/** + * @param {ol.Feature} feature Feature + * @private + */ +ol.interaction.Snap.prototype.updateFeature_ = function(feature) { + this.removeFeature(feature, false); + this.addFeature(feature, false); +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.GeometryCollection} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeGeometryCollectionGeometry_ = function(feature, geometry) { + var i, geometries = geometry.getGeometriesArray(); + for (i = 0; i < geometries.length; ++i) { + this.SEGMENT_WRITERS_[geometries[i].getType()].call( + this, feature, geometries[i]); + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.LineString} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeLineStringGeometry_ = function(feature, geometry) { + var coordinates = geometry.getCoordinates(); + var i, ii, segment, segmentData; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiLineString} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeMultiLineStringGeometry_ = function(feature, geometry) { + var lines = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, segment, segmentData; + for (j = 0, jj = lines.length; j < jj; ++j) { + coordinates = lines[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiPoint} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeMultiPointGeometry_ = function(feature, geometry) { + var points = geometry.getCoordinates(); + var coordinates, i, ii, segmentData; + for (i = 0, ii = points.length; i < ii; ++i) { + coordinates = points[i]; + segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: [coordinates, coordinates] + }); + this.rBush_.insert(geometry.getExtent(), segmentData); + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiPolygon} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeMultiPolygonGeometry_ = function(feature, geometry) { + var polygons = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData; + for (k = 0, kk = polygons.length; k < kk; ++k) { + rings = polygons[k]; + for (j = 0, jj = rings.length; j < jj; ++j) { + coordinates = rings[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.Point} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writePointGeometry_ = function(feature, geometry) { + var coordinates = geometry.getCoordinates(); + var segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: [coordinates, coordinates] + }); + this.rBush_.insert(geometry.getExtent(), segmentData); +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.Polygon} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writePolygonGeometry_ = function(feature, geometry) { + var rings = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, segment, segmentData; + for (j = 0, jj = rings.length; j < jj; ++j) { + coordinates = rings[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } +}; + + +/** + * Handle all pointer events events. + * @param {ol.MapBrowserEvent} evt A move event. + * @return {boolean} Pass the event to other interactions. + * @this {ol.interaction.Snap} + * @private + */ +ol.interaction.Snap.handleEvent_ = function(evt) { + var result = this.snapTo(evt.pixel, evt.coordinate, evt.map); + if (result.snapped) { + evt.coordinate = result.vertex.slice(0, 2); + evt.pixel = result.vertexPixel; + } + return ol.interaction.Pointer.handleEvent.call(this, evt); +}; + + +/** + * @param {ol.MapBrowserPointerEvent} evt Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Snap} + * @private + */ +ol.interaction.Snap.handleUpEvent_ = function(evt) { + var featuresToUpdate = ol.obj.getValues(this.pendingFeatures_); + if (featuresToUpdate.length) { + featuresToUpdate.forEach(this.updateFeature_, this); + this.pendingFeatures_ = {}; + } + return false; +}; + + +/** + * Sort segments by distance, helper function + * @param {ol.SnapSegmentDataType} a The first segment data. + * @param {ol.SnapSegmentDataType} b The second segment data. + * @return {number} The difference in distance. + * @this {ol.interaction.Snap} + */ +ol.interaction.Snap.sortByDistance = function(a, b) { + return ol.coordinate.squaredDistanceToSegment( + this.pixelCoordinate_, a.segment) - + ol.coordinate.squaredDistanceToSegment( + this.pixelCoordinate_, b.segment); +}; + +goog.provide('ol.interaction.Translate'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.events.Event'); +goog.require('ol.functions'); +goog.require('ol.array'); +goog.require('ol.interaction.Pointer'); + + +/** + * @classdesc + * Interaction for translating (moving) features. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @fires ol.interaction.Translate.Event + * @param {olx.interaction.TranslateOptions=} opt_options Options. + * @api + */ +ol.interaction.Translate = function(opt_options) { + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.Translate.handleDownEvent_, + handleDragEvent: ol.interaction.Translate.handleDragEvent_, + handleMoveEvent: ol.interaction.Translate.handleMoveEvent_, + handleUpEvent: ol.interaction.Translate.handleUpEvent_ + }); + + var options = opt_options ? opt_options : {}; + + /** + * @type {string|undefined} + * @private + */ + this.previousCursor_ = undefined; + + + /** + * The last position we translated to. + * @type {ol.Coordinate} + * @private + */ + this.lastCoordinate_ = null; + + + /** + * @type {ol.Collection.<ol.Feature>} + * @private + */ + this.features_ = options.features !== undefined ? options.features : null; + + /** @type {function(ol.layer.Layer): boolean} */ + var layerFilter; + if (options.layers) { + if (typeof options.layers === 'function') { + layerFilter = options.layers; + } else { + var layers = options.layers; + layerFilter = function(layer) { + return ol.array.includes(layers, layer); + }; + } + } else { + layerFilter = ol.functions.TRUE; + } + + /** + * @private + * @type {function(ol.layer.Layer): boolean} + */ + this.layerFilter_ = layerFilter; + + /** + * @private + * @type {number} + */ + this.hitTolerance_ = options.hitTolerance ? options.hitTolerance : 0; + + /** + * @type {ol.Feature} + * @private + */ + this.lastFeature_ = null; +}; +ol.inherits(ol.interaction.Translate, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} event Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.Translate} + * @private + */ +ol.interaction.Translate.handleDownEvent_ = function(event) { + this.lastFeature_ = this.featuresAtPixel_(event.pixel, event.map); + if (!this.lastCoordinate_ && this.lastFeature_) { + this.lastCoordinate_ = event.coordinate; + ol.interaction.Translate.handleMoveEvent_.call(this, event); + + var features = this.features_ || new ol.Collection([this.lastFeature_]); + + this.dispatchEvent( + new ol.interaction.Translate.Event( + ol.interaction.Translate.EventType.TRANSLATESTART, features, + event.coordinate)); + return true; + } + return false; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} event Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Translate} + * @private + */ +ol.interaction.Translate.handleUpEvent_ = function(event) { + if (this.lastCoordinate_) { + this.lastCoordinate_ = null; + ol.interaction.Translate.handleMoveEvent_.call(this, event); + + var features = this.features_ || new ol.Collection([this.lastFeature_]); + + this.dispatchEvent( + new ol.interaction.Translate.Event( + ol.interaction.Translate.EventType.TRANSLATEEND, features, + event.coordinate)); + return true; + } + return false; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} event Event. + * @this {ol.interaction.Translate} + * @private + */ +ol.interaction.Translate.handleDragEvent_ = function(event) { + if (this.lastCoordinate_) { + var newCoordinate = event.coordinate; + var deltaX = newCoordinate[0] - this.lastCoordinate_[0]; + var deltaY = newCoordinate[1] - this.lastCoordinate_[1]; + + var features = this.features_ || new ol.Collection([this.lastFeature_]); + + features.forEach(function(feature) { + var geom = feature.getGeometry(); + geom.translate(deltaX, deltaY); + feature.setGeometry(geom); + }); + + this.lastCoordinate_ = newCoordinate; + this.dispatchEvent( + new ol.interaction.Translate.Event( + ol.interaction.Translate.EventType.TRANSLATING, features, + newCoordinate)); + } +}; + + +/** + * @param {ol.MapBrowserEvent} event Event. + * @this {ol.interaction.Translate} + * @private + */ +ol.interaction.Translate.handleMoveEvent_ = function(event) { + var elem = event.map.getTargetElement(); + + // Change the cursor to grab/grabbing if hovering any of the features managed + // by the interaction + if (this.featuresAtPixel_(event.pixel, event.map)) { + this.previousCursor_ = elem.style.cursor; + // WebKit browsers don't support the grab icons without a prefix + elem.style.cursor = this.lastCoordinate_ ? + '-webkit-grabbing' : '-webkit-grab'; + + // Thankfully, attempting to set the standard ones will silently fail, + // keeping the prefixed icons + elem.style.cursor = this.lastCoordinate_ ? 'grabbing' : 'grab'; + } else { + elem.style.cursor = this.previousCursor_ !== undefined ? + this.previousCursor_ : ''; + this.previousCursor_ = undefined; + } +}; + + +/** + * Tests to see if the given coordinates intersects any of our selected + * features. + * @param {ol.Pixel} pixel Pixel coordinate to test for intersection. + * @param {ol.Map} map Map to test the intersection on. + * @return {ol.Feature} Returns the feature found at the specified pixel + * coordinates. + * @private + */ +ol.interaction.Translate.prototype.featuresAtPixel_ = function(pixel, map) { + return map.forEachFeatureAtPixel(pixel, + function(feature) { + if (!this.features_ || + ol.array.includes(this.features_.getArray(), feature)) { + return feature; + } + }.bind(this), { + layerFilter: this.layerFilter_, + hitTolerance: this.hitTolerance_ + }); +}; + + +/** + * Returns the Hit-detection tolerance. + * @returns {number} Hit tolerance in pixels. + * @api + */ +ol.interaction.Translate.prototype.getHitTolerance = function() { + return this.hitTolerance_; +}; + + +/** + * Hit-detection tolerance. Pixels inside the radius around the given position + * will be checked for features. This only works for the canvas renderer and + * not for WebGL. + * @param {number} hitTolerance Hit tolerance in pixels. + * @api + */ +ol.interaction.Translate.prototype.setHitTolerance = function(hitTolerance) { + this.hitTolerance_ = hitTolerance; +}; + + +/** + * @classdesc + * Events emitted by {@link ol.interaction.Translate} instances are instances of + * this type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.interaction.TranslateEvent} + * @param {ol.interaction.Translate.EventType} type Type. + * @param {ol.Collection.<ol.Feature>} features The features translated. + * @param {ol.Coordinate} coordinate The event coordinate. + */ +ol.interaction.Translate.Event = function(type, features, coordinate) { + + ol.events.Event.call(this, type); + + /** + * The features being translated. + * @type {ol.Collection.<ol.Feature>} + * @api + */ + this.features = features; + + /** + * The coordinate of the drag event. + * @const + * @type {ol.Coordinate} + * @api + */ + this.coordinate = coordinate; +}; +ol.inherits(ol.interaction.Translate.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.interaction.Translate.EventType = { + /** + * Triggered upon feature translation start. + * @event ol.interaction.Translate.Event#translatestart + * @api + */ + TRANSLATESTART: 'translatestart', + /** + * Triggered upon feature translation. + * @event ol.interaction.Translate.Event#translating + * @api + */ + TRANSLATING: 'translating', + /** + * Triggered upon feature translation end. + * @event ol.interaction.Translate.Event#translateend + * @api + */ + TRANSLATEEND: 'translateend' +}; + +goog.provide('ol.layer.Heatmap'); + +goog.require('ol.events'); +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.dom'); +goog.require('ol.layer.Vector'); +goog.require('ol.math'); +goog.require('ol.obj'); +goog.require('ol.render.Event'); +goog.require('ol.style.Icon'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * Layer for rendering vector data as a heatmap. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Vector} + * @fires ol.render.Event + * @param {olx.layer.HeatmapOptions=} opt_options Options. + * @api + */ +ol.layer.Heatmap = function(opt_options) { + var options = opt_options ? opt_options : {}; + + var baseOptions = ol.obj.assign({}, options); + + delete baseOptions.gradient; + delete baseOptions.radius; + delete baseOptions.blur; + delete baseOptions.shadow; + delete baseOptions.weight; + ol.layer.Vector.call(this, /** @type {olx.layer.VectorOptions} */ (baseOptions)); + + /** + * @private + * @type {Uint8ClampedArray} + */ + this.gradient_ = null; + + /** + * @private + * @type {number} + */ + this.shadow_ = options.shadow !== undefined ? options.shadow : 250; + + /** + * @private + * @type {string|undefined} + */ + this.circleImage_ = undefined; + + /** + * @private + * @type {Array.<Array.<ol.style.Style>>} + */ + this.styleCache_ = null; + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.Heatmap.Property.GRADIENT), + this.handleGradientChanged_, this); + + this.setGradient(options.gradient ? + options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT); + + this.setBlur(options.blur !== undefined ? options.blur : 15); + + this.setRadius(options.radius !== undefined ? options.radius : 8); + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.Heatmap.Property.BLUR), + this.handleStyleChanged_, this); + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.Heatmap.Property.RADIUS), + this.handleStyleChanged_, this); + + this.handleStyleChanged_(); + + var weight = options.weight ? options.weight : 'weight'; + var weightFunction; + if (typeof weight === 'string') { + weightFunction = function(feature) { + return feature.get(weight); + }; + } else { + weightFunction = weight; + } + ol.DEBUG && console.assert(typeof weightFunction === 'function', + 'weightFunction should be a function'); + + this.setStyle(function(feature, resolution) { + ol.DEBUG && console.assert(this.styleCache_, 'this.styleCache_ expected'); + ol.DEBUG && console.assert(this.circleImage_ !== undefined, + 'this.circleImage_ should be defined'); + var weight = weightFunction(feature); + var opacity = weight !== undefined ? ol.math.clamp(weight, 0, 1) : 1; + // cast to 8 bits + var index = (255 * opacity) | 0; + var style = this.styleCache_[index]; + if (!style) { + style = [ + new ol.style.Style({ + image: new ol.style.Icon({ + opacity: opacity, + src: this.circleImage_ + }) + }) + ]; + this.styleCache_[index] = style; + } + return style; + }.bind(this)); + + // For performance reasons, don't sort the features before rendering. + // The render order is not relevant for a heatmap representation. + this.setRenderOrder(null); + + ol.events.listen(this, ol.render.Event.Type.RENDER, this.handleRender_, this); + +}; +ol.inherits(ol.layer.Heatmap, ol.layer.Vector); + + +/** + * @const + * @type {Array.<string>} + */ +ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00']; + + +/** + * @param {Array.<string>} colors A list of colored. + * @return {Uint8ClampedArray} An array. + * @private + */ +ol.layer.Heatmap.createGradient_ = function(colors) { + var width = 1; + var height = 256; + var context = ol.dom.createCanvasContext2D(width, height); + + var gradient = context.createLinearGradient(0, 0, width, height); + var step = 1 / (colors.length - 1); + for (var i = 0, ii = colors.length; i < ii; ++i) { + gradient.addColorStop(i * step, colors[i]); + } + + context.fillStyle = gradient; + context.fillRect(0, 0, width, height); + + return context.getImageData(0, 0, width, height).data; +}; + + +/** + * @return {string} Data URL for a circle. + * @private + */ +ol.layer.Heatmap.prototype.createCircle_ = function() { + var radius = this.getRadius(); + var blur = this.getBlur(); + ol.DEBUG && console.assert(radius !== undefined && blur !== undefined, + 'radius and blur should be defined'); + var halfSize = radius + blur + 1; + var size = 2 * halfSize; + var context = ol.dom.createCanvasContext2D(size, size); + context.shadowOffsetX = context.shadowOffsetY = this.shadow_; + context.shadowBlur = blur; + context.shadowColor = '#000'; + context.beginPath(); + var center = halfSize - this.shadow_; + context.arc(center, center, radius, 0, Math.PI * 2, true); + context.fill(); + return context.canvas.toDataURL(); +}; + + +/** + * Return the blur size in pixels. + * @return {number} Blur size in pixels. + * @api + * @observable + */ +ol.layer.Heatmap.prototype.getBlur = function() { + return /** @type {number} */ (this.get(ol.layer.Heatmap.Property.BLUR)); +}; + + +/** + * Return the gradient colors as array of strings. + * @return {Array.<string>} Colors. + * @api + * @observable + */ +ol.layer.Heatmap.prototype.getGradient = function() { + return /** @type {Array.<string>} */ ( + this.get(ol.layer.Heatmap.Property.GRADIENT)); +}; + + +/** + * Return the size of the radius in pixels. + * @return {number} Radius size in pixel. + * @api + * @observable + */ +ol.layer.Heatmap.prototype.getRadius = function() { + return /** @type {number} */ (this.get(ol.layer.Heatmap.Property.RADIUS)); +}; + + +/** + * @private + */ +ol.layer.Heatmap.prototype.handleGradientChanged_ = function() { + this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient()); +}; + + +/** + * @private + */ +ol.layer.Heatmap.prototype.handleStyleChanged_ = function() { + this.circleImage_ = this.createCircle_(); + this.styleCache_ = new Array(256); + this.changed(); +}; + + +/** + * @param {ol.render.Event} event Post compose event + * @private + */ +ol.layer.Heatmap.prototype.handleRender_ = function(event) { + ol.DEBUG && console.assert(event.type == ol.render.Event.Type.RENDER, + 'event.type should be RENDER'); + ol.DEBUG && console.assert(this.gradient_, 'this.gradient_ expected'); + var context = event.context; + var canvas = context.canvas; + var image = context.getImageData(0, 0, canvas.width, canvas.height); + var view8 = image.data; + var i, ii, alpha; + for (i = 0, ii = view8.length; i < ii; i += 4) { + alpha = view8[i + 3] * 4; + if (alpha) { + view8[i] = this.gradient_[alpha]; + view8[i + 1] = this.gradient_[alpha + 1]; + view8[i + 2] = this.gradient_[alpha + 2]; + } + } + context.putImageData(image, 0, 0); +}; + + +/** + * Set the blur size in pixels. + * @param {number} blur Blur size in pixels. + * @api + * @observable + */ +ol.layer.Heatmap.prototype.setBlur = function(blur) { + this.set(ol.layer.Heatmap.Property.BLUR, blur); +}; + + +/** + * Set the gradient colors as array of strings. + * @param {Array.<string>} colors Gradient. + * @api + * @observable + */ +ol.layer.Heatmap.prototype.setGradient = function(colors) { + this.set(ol.layer.Heatmap.Property.GRADIENT, colors); +}; + + +/** + * Set the size of the radius in pixels. + * @param {number} radius Radius size in pixel. + * @api + * @observable + */ +ol.layer.Heatmap.prototype.setRadius = function(radius) { + this.set(ol.layer.Heatmap.Property.RADIUS, radius); +}; + + +/** + * @enum {string} + */ +ol.layer.Heatmap.Property = { + BLUR: 'blur', + GRADIENT: 'gradient', + RADIUS: 'radius' +}; + +goog.provide('ol.net'); + +goog.require('ol'); + + +/** + * Simple JSONP helper. Supports error callbacks and a custom callback param. + * The error callback will be called when no JSONP is executed after 10 seconds. + * + * @param {string} url Request url. A 'callback' query parameter will be + * appended. + * @param {Function} callback Callback on success. + * @param {function()=} opt_errback Callback on error. + * @param {string=} opt_callbackParam Custom query parameter for the JSONP + * callback. Default is 'callback'. + */ +ol.net.jsonp = function(url, callback, opt_errback, opt_callbackParam) { + var script = document.createElement('script'); + var key = 'olc_' + ol.getUid(callback); + function cleanup() { + delete window[key]; + script.parentNode.removeChild(script); + } + script.async = true; + script.src = url + (url.indexOf('?') == -1 ? '?' : '&') + + (opt_callbackParam || 'callback') + '=' + key; + var timer = setTimeout(function() { + cleanup(); + if (opt_errback) { + opt_errback(); + } + }, 10000); + window[key] = function(data) { + clearTimeout(timer); + cleanup(); + callback(data); + }; + document.getElementsByTagName('head')[0].appendChild(script); +}; + +goog.provide('ol.render'); + +goog.require('ol.has'); +goog.require('ol.transform'); +goog.require('ol.render.canvas.Immediate'); + + +/** + * Binds a Canvas Immediate API to a canvas context, to allow drawing geometries + * to the context's canvas. + * + * The units for geometry coordinates are css pixels relative to the top left + * corner of the canvas element. + * ```js + * var canvas = document.createElement('canvas'); + * var render = ol.render.toContext(canvas.getContext('2d'), + * { size: [100, 100] }); + * render.setFillStrokeStyle(new ol.style.Fill({ color: blue })); + * render.drawPolygon( + * new ol.geom.Polygon([[[0, 0], [100, 100], [100, 0], [0, 0]]])); + * ``` + * + * @param {CanvasRenderingContext2D} context Canvas context. + * @param {olx.render.ToContextOptions=} opt_options Options. + * @return {ol.render.canvas.Immediate} Canvas Immediate. + * @api + */ +ol.render.toContext = function(context, opt_options) { + var canvas = context.canvas; + var options = opt_options ? opt_options : {}; + var pixelRatio = options.pixelRatio || ol.has.DEVICE_PIXEL_RATIO; + var size = options.size; + if (size) { + canvas.width = size[0] * pixelRatio; + canvas.height = size[1] * pixelRatio; + canvas.style.width = size[0] + 'px'; + canvas.style.height = size[1] + 'px'; + } + var extent = [0, 0, canvas.width, canvas.height]; + var transform = ol.transform.scale(ol.transform.create(), pixelRatio, pixelRatio); + return new ol.render.canvas.Immediate(context, pixelRatio, extent, transform, + 0); +}; + +goog.provide('ol.reproj.Tile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.math'); +goog.require('ol.reproj'); +goog.require('ol.reproj.Triangulation'); + + +/** + * @classdesc + * Class encapsulating single reprojected tile. + * See {@link ol.source.TileImage}. + * + * @constructor + * @extends {ol.Tile} + * @param {ol.proj.Projection} sourceProj Source projection. + * @param {ol.tilegrid.TileGrid} sourceTileGrid Source tile grid. + * @param {ol.proj.Projection} targetProj Target projection. + * @param {ol.tilegrid.TileGrid} targetTileGrid Target tile grid. + * @param {ol.TileCoord} tileCoord Coordinate of the tile. + * @param {ol.TileCoord} wrappedTileCoord Coordinate of the tile wrapped in X. + * @param {number} pixelRatio Pixel ratio. + * @param {number} gutter Gutter of the source tiles. + * @param {ol.ReprojTileFunctionType} getTileFunction + * Function returning source tiles (z, x, y, pixelRatio). + * @param {number=} opt_errorThreshold Acceptable reprojection error (in px). + * @param {boolean=} opt_renderEdges Render reprojection edges. + */ +ol.reproj.Tile = function(sourceProj, sourceTileGrid, + targetProj, targetTileGrid, tileCoord, wrappedTileCoord, + pixelRatio, gutter, getTileFunction, + opt_errorThreshold, + opt_renderEdges) { + ol.Tile.call(this, tileCoord, ol.Tile.State.IDLE); + + /** + * @private + * @type {boolean} + */ + this.renderEdges_ = opt_renderEdges !== undefined ? opt_renderEdges : false; + + /** + * @private + * @type {number} + */ + this.pixelRatio_ = pixelRatio; + + /** + * @private + * @type {number} + */ + this.gutter_ = gutter; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = null; + + /** + * @private + * @type {ol.tilegrid.TileGrid} + */ + this.sourceTileGrid_ = sourceTileGrid; + + /** + * @private + * @type {ol.tilegrid.TileGrid} + */ + this.targetTileGrid_ = targetTileGrid; + + /** + * @private + * @type {ol.TileCoord} + */ + this.wrappedTileCoord_ = wrappedTileCoord ? wrappedTileCoord : tileCoord; + + /** + * @private + * @type {!Array.<ol.Tile>} + */ + this.sourceTiles_ = []; + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.sourcesListenerKeys_ = null; + + /** + * @private + * @type {number} + */ + this.sourceZ_ = 0; + + var targetExtent = targetTileGrid.getTileCoordExtent(this.wrappedTileCoord_); + var maxTargetExtent = this.targetTileGrid_.getExtent(); + var maxSourceExtent = this.sourceTileGrid_.getExtent(); + + var limitedTargetExtent = maxTargetExtent ? + ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent; + + if (ol.extent.getArea(limitedTargetExtent) === 0) { + // Tile is completely outside range -> EMPTY + // TODO: is it actually correct that the source even creates the tile ? + this.state = ol.Tile.State.EMPTY; + return; + } + + var sourceProjExtent = sourceProj.getExtent(); + if (sourceProjExtent) { + if (!maxSourceExtent) { + maxSourceExtent = sourceProjExtent; + } else { + maxSourceExtent = ol.extent.getIntersection( + maxSourceExtent, sourceProjExtent); + } + } + + var targetResolution = targetTileGrid.getResolution( + this.wrappedTileCoord_[0]); + + var targetCenter = ol.extent.getCenter(limitedTargetExtent); + var sourceResolution = ol.reproj.calculateSourceResolution( + sourceProj, targetProj, targetCenter, targetResolution); + + if (!isFinite(sourceResolution) || sourceResolution <= 0) { + // invalid sourceResolution -> EMPTY + // probably edges of the projections when no extent is defined + this.state = ol.Tile.State.EMPTY; + return; + } + + var errorThresholdInPixels = opt_errorThreshold !== undefined ? + opt_errorThreshold : ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD; + + /** + * @private + * @type {!ol.reproj.Triangulation} + */ + this.triangulation_ = new ol.reproj.Triangulation( + sourceProj, targetProj, limitedTargetExtent, maxSourceExtent, + sourceResolution * errorThresholdInPixels); + + if (this.triangulation_.getTriangles().length === 0) { + // no valid triangles -> EMPTY + this.state = ol.Tile.State.EMPTY; + return; + } + + this.sourceZ_ = sourceTileGrid.getZForResolution(sourceResolution); + var sourceExtent = this.triangulation_.calculateSourceExtent(); + + if (maxSourceExtent) { + if (sourceProj.canWrapX()) { + sourceExtent[1] = ol.math.clamp( + sourceExtent[1], maxSourceExtent[1], maxSourceExtent[3]); + sourceExtent[3] = ol.math.clamp( + sourceExtent[3], maxSourceExtent[1], maxSourceExtent[3]); + } else { + sourceExtent = ol.extent.getIntersection(sourceExtent, maxSourceExtent); + } + } + + if (!ol.extent.getArea(sourceExtent)) { + this.state = ol.Tile.State.EMPTY; + } else { + var sourceRange = sourceTileGrid.getTileRangeForExtentAndZ( + sourceExtent, this.sourceZ_); + + var tilesRequired = sourceRange.getWidth() * sourceRange.getHeight(); + if (ol.DEBUG && !(tilesRequired < ol.RASTER_REPROJECTION_MAX_SOURCE_TILES)) { + console.assert(false, 'reasonable number of tiles is required'); + this.state = ol.Tile.State.ERROR; + return; + } + for (var srcX = sourceRange.minX; srcX <= sourceRange.maxX; srcX++) { + for (var srcY = sourceRange.minY; srcY <= sourceRange.maxY; srcY++) { + var tile = getTileFunction(this.sourceZ_, srcX, srcY, pixelRatio); + if (tile) { + this.sourceTiles_.push(tile); + } + } + } + + if (this.sourceTiles_.length === 0) { + this.state = ol.Tile.State.EMPTY; + } + } +}; +ol.inherits(ol.reproj.Tile, ol.Tile); + + +/** + * @inheritDoc + */ +ol.reproj.Tile.prototype.disposeInternal = function() { + if (this.state == ol.Tile.State.LOADING) { + this.unlistenSources_(); + } + ol.Tile.prototype.disposeInternal.call(this); +}; + + +/** + * @inheritDoc + */ +ol.reproj.Tile.prototype.getImage = function() { + return this.canvas_; +}; + + +/** + * @private + */ +ol.reproj.Tile.prototype.reproject_ = function() { + var sources = []; + this.sourceTiles_.forEach(function(tile, i, arr) { + if (tile && tile.getState() == ol.Tile.State.LOADED) { + sources.push({ + extent: this.sourceTileGrid_.getTileCoordExtent(tile.tileCoord), + image: tile.getImage() + }); + } + }, this); + this.sourceTiles_.length = 0; + + if (sources.length === 0) { + this.state = ol.Tile.State.ERROR; + } else { + var z = this.wrappedTileCoord_[0]; + var size = this.targetTileGrid_.getTileSize(z); + var width = typeof size === 'number' ? size : size[0]; + var height = typeof size === 'number' ? size : size[1]; + var targetResolution = this.targetTileGrid_.getResolution(z); + var sourceResolution = this.sourceTileGrid_.getResolution(this.sourceZ_); + + var targetExtent = this.targetTileGrid_.getTileCoordExtent( + this.wrappedTileCoord_); + this.canvas_ = ol.reproj.render(width, height, this.pixelRatio_, + sourceResolution, this.sourceTileGrid_.getExtent(), + targetResolution, targetExtent, this.triangulation_, sources, + this.gutter_, this.renderEdges_); + + this.state = ol.Tile.State.LOADED; + } + this.changed(); +}; + + +/** + * @inheritDoc + */ +ol.reproj.Tile.prototype.load = function() { + if (this.state == ol.Tile.State.IDLE) { + this.state = ol.Tile.State.LOADING; + this.changed(); + + var leftToLoad = 0; + + ol.DEBUG && console.assert(!this.sourcesListenerKeys_, + 'this.sourcesListenerKeys_ should be null'); + + this.sourcesListenerKeys_ = []; + this.sourceTiles_.forEach(function(tile, i, arr) { + var state = tile.getState(); + if (state == ol.Tile.State.IDLE || state == ol.Tile.State.LOADING) { + leftToLoad++; + + var sourceListenKey; + sourceListenKey = ol.events.listen(tile, ol.events.EventType.CHANGE, + function(e) { + var state = tile.getState(); + if (state == ol.Tile.State.LOADED || + state == ol.Tile.State.ERROR || + state == ol.Tile.State.EMPTY) { + ol.events.unlistenByKey(sourceListenKey); + leftToLoad--; + ol.DEBUG && console.assert(leftToLoad >= 0, + 'leftToLoad should not be negative'); + if (leftToLoad === 0) { + this.unlistenSources_(); + this.reproject_(); + } + } + }, this); + this.sourcesListenerKeys_.push(sourceListenKey); + } + }, this); + + this.sourceTiles_.forEach(function(tile, i, arr) { + var state = tile.getState(); + if (state == ol.Tile.State.IDLE) { + tile.load(); + } + }); + + if (leftToLoad === 0) { + setTimeout(this.reproject_.bind(this), 0); + } + } +}; + + +/** + * @private + */ +ol.reproj.Tile.prototype.unlistenSources_ = function() { + this.sourcesListenerKeys_.forEach(ol.events.unlistenByKey); + this.sourcesListenerKeys_ = null; +}; + +goog.provide('ol.TileUrlFunction'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.math'); +goog.require('ol.tilecoord'); + + +/** + * @param {string} template Template. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @return {ol.TileUrlFunctionType} Tile URL function. + */ +ol.TileUrlFunction.createFromTemplate = function(template, tileGrid) { + var zRegEx = /\{z\}/g; + var xRegEx = /\{x\}/g; + var yRegEx = /\{y\}/g; + var dashYRegEx = /\{-y\}/g; + return ( + /** + * @param {ol.TileCoord} tileCoord Tile Coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function(tileCoord, pixelRatio, projection) { + if (!tileCoord) { + return undefined; + } else { + return template.replace(zRegEx, tileCoord[0].toString()) + .replace(xRegEx, tileCoord[1].toString()) + .replace(yRegEx, function() { + var y = -tileCoord[2] - 1; + return y.toString(); + }) + .replace(dashYRegEx, function() { + var z = tileCoord[0]; + var range = tileGrid.getFullTileRange(z); + ol.asserts.assert(range, 55); // The {-y} placeholder requires a tile grid with extent + var y = range.getHeight() + tileCoord[2]; + return y.toString(); + }); + } + }); +}; + + +/** + * @param {Array.<string>} templates Templates. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @return {ol.TileUrlFunctionType} Tile URL function. + */ +ol.TileUrlFunction.createFromTemplates = function(templates, tileGrid) { + var len = templates.length; + var tileUrlFunctions = new Array(len); + for (var i = 0; i < len; ++i) { + tileUrlFunctions[i] = ol.TileUrlFunction.createFromTemplate( + templates[i], tileGrid); + } + return ol.TileUrlFunction.createFromTileUrlFunctions(tileUrlFunctions); +}; + + +/** + * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions. + * @return {ol.TileUrlFunctionType} Tile URL function. + */ +ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) { + ol.DEBUG && console.assert(tileUrlFunctions.length > 0, + 'Length of tile url functions should be greater than 0'); + if (tileUrlFunctions.length === 1) { + return tileUrlFunctions[0]; + } + return ( + /** + * @param {ol.TileCoord} tileCoord Tile Coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function(tileCoord, pixelRatio, projection) { + if (!tileCoord) { + return undefined; + } else { + var h = ol.tilecoord.hash(tileCoord); + var index = ol.math.modulo(h, tileUrlFunctions.length); + return tileUrlFunctions[index](tileCoord, pixelRatio, projection); + } + }); +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ +ol.TileUrlFunction.nullTileUrlFunction = function(tileCoord, pixelRatio, projection) { + return undefined; +}; + + +/** + * @param {string} url URL. + * @return {Array.<string>} Array of urls. + */ +ol.TileUrlFunction.expandUrl = function(url) { + var urls = []; + var match = /\{([a-z])-([a-z])\}/.exec(url); + if (match) { + // char range + var startCharCode = match[1].charCodeAt(0); + var stopCharCode = match[2].charCodeAt(0); + var charCode; + for (charCode = startCharCode; charCode <= stopCharCode; ++charCode) { + urls.push(url.replace(match[0], String.fromCharCode(charCode))); + } + return urls; + } + match = match = /\{(\d+)-(\d+)\}/.exec(url); + if (match) { + // number range + var stop = parseInt(match[2], 10); + for (var i = parseInt(match[1], 10); i <= stop; i++) { + urls.push(url.replace(match[0], i.toString())); + } + return urls; + } + urls.push(url); + return urls; +}; + +goog.provide('ol.TileCache'); + +goog.require('ol'); +goog.require('ol.structs.LRUCache'); + + +/** + * @constructor + * @extends {ol.structs.LRUCache.<ol.Tile>} + * @param {number=} opt_highWaterMark High water mark. + * @struct + */ +ol.TileCache = function(opt_highWaterMark) { + + ol.structs.LRUCache.call(this); + + /** + * @private + * @type {number} + */ + this.highWaterMark_ = opt_highWaterMark !== undefined ? opt_highWaterMark : 2048; + +}; +ol.inherits(ol.TileCache, ol.structs.LRUCache); + + +/** + * @return {boolean} Can expire cache. + */ +ol.TileCache.prototype.canExpireCache = function() { + return this.getCount() > this.highWaterMark_; +}; + + +/** + * @param {Object.<string, ol.TileRange>} usedTiles Used tiles. + */ +ol.TileCache.prototype.expireCache = function(usedTiles) { + var tile, zKey; + while (this.canExpireCache()) { + tile = this.peekLast(); + zKey = tile.tileCoord[0].toString(); + if (zKey in usedTiles && usedTiles[zKey].contains(tile.tileCoord)) { + break; + } else { + this.pop().dispose(); + } + } +}; + +goog.provide('ol.source.Tile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.TileCache'); +goog.require('ol.events.Event'); +goog.require('ol.proj'); +goog.require('ol.size'); +goog.require('ol.source.Source'); +goog.require('ol.tilecoord'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for sources providing images divided into a tile grid. + * + * @constructor + * @extends {ol.source.Source} + * @param {ol.SourceTileOptions} options Tile source options. + * @api + */ +ol.source.Tile = function(options) { + + ol.source.Source.call(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + projection: options.projection, + state: options.state, + wrapX: options.wrapX + }); + + /** + * @private + * @type {boolean} + */ + this.opaque_ = options.opaque !== undefined ? options.opaque : false; + + /** + * @private + * @type {number} + */ + this.tilePixelRatio_ = options.tilePixelRatio !== undefined ? + options.tilePixelRatio : 1; + + /** + * @protected + * @type {ol.tilegrid.TileGrid} + */ + this.tileGrid = options.tileGrid !== undefined ? options.tileGrid : null; + + /** + * @protected + * @type {ol.TileCache} + */ + this.tileCache = new ol.TileCache(options.cacheSize); + + /** + * @protected + * @type {ol.Size} + */ + this.tmpSize = [0, 0]; + + /** + * @private + * @type {string} + */ + this.key_ = ''; + +}; +ol.inherits(ol.source.Tile, ol.source.Source); + + +/** + * @return {boolean} Can expire cache. + */ +ol.source.Tile.prototype.canExpireCache = function() { + return this.tileCache.canExpireCache(); +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @param {Object.<string, ol.TileRange>} usedTiles Used tiles. + */ +ol.source.Tile.prototype.expireCache = function(projection, usedTiles) { + var tileCache = this.getTileCacheForProjection(projection); + if (tileCache) { + tileCache.expireCache(usedTiles); + } +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @param {number} z Zoom level. + * @param {ol.TileRange} tileRange Tile range. + * @param {function(ol.Tile):(boolean|undefined)} callback Called with each + * loaded tile. If the callback returns `false`, the tile will not be + * considered loaded. + * @return {boolean} The tile range is fully covered with loaded tiles. + */ +ol.source.Tile.prototype.forEachLoadedTile = function(projection, z, tileRange, callback) { + var tileCache = this.getTileCacheForProjection(projection); + if (!tileCache) { + return false; + } + + var covered = true; + var tile, tileCoordKey, loaded; + for (var x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (var y = tileRange.minY; y <= tileRange.maxY; ++y) { + tileCoordKey = this.getKeyZXY(z, x, y); + loaded = false; + if (tileCache.containsKey(tileCoordKey)) { + tile = /** @type {!ol.Tile} */ (tileCache.get(tileCoordKey)); + loaded = tile.getState() === ol.Tile.State.LOADED; + if (loaded) { + loaded = (callback(tile) !== false); + } + } + if (!loaded) { + covered = false; + } + } + } + return covered; +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @return {number} Gutter. + */ +ol.source.Tile.prototype.getGutter = function(projection) { + return 0; +}; + + +/** + * Return the key to be used for all tiles in the source. + * @return {string} The key for all tiles. + * @protected + */ +ol.source.Tile.prototype.getKey = function() { + return this.key_; +}; + + +/** + * Set the value to be used as the key for all tiles in the source. + * @param {string} key The key for tiles. + * @protected + */ +ol.source.Tile.prototype.setKey = function(key) { + if (this.key_ !== key) { + this.key_ = key; + this.changed(); + } +}; + + +/** + * @param {number} z Z. + * @param {number} x X. + * @param {number} y Y. + * @return {string} Key. + * @protected + */ +ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @return {boolean} Opaque. + */ +ol.source.Tile.prototype.getOpaque = function(projection) { + return this.opaque_; +}; + + +/** + * @inheritDoc + */ +ol.source.Tile.prototype.getResolutions = function() { + return this.tileGrid.getResolutions(); +}; + + +/** + * @abstract + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {!ol.Tile} Tile. + */ +ol.source.Tile.prototype.getTile = function(z, x, y, pixelRatio, projection) {}; + + +/** + * Return the tile grid of the tile source. + * @return {ol.tilegrid.TileGrid} Tile grid. + * @api stable + */ +ol.source.Tile.prototype.getTileGrid = function() { + return this.tileGrid; +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @return {!ol.tilegrid.TileGrid} Tile grid. + */ +ol.source.Tile.prototype.getTileGridForProjection = function(projection) { + if (!this.tileGrid) { + return ol.tilegrid.getForProjection(projection); + } else { + return this.tileGrid; + } +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @return {ol.TileCache} Tile cache. + * @protected + */ +ol.source.Tile.prototype.getTileCacheForProjection = function(projection) { + var thisProj = this.getProjection(); + if (thisProj && !ol.proj.equivalent(thisProj, projection)) { + return null; + } else { + return this.tileCache; + } +}; + + +/** + * Get the tile pixel ratio for this source. Subclasses may override this + * method, which is meant to return a supported pixel ratio that matches the + * provided `opt_pixelRatio` as close as possible. When no `opt_pixelRatio` is + * provided, it is meant to return `this.tilePixelRatio_`. + * @param {number=} opt_pixelRatio Pixel ratio. + * @return {number} Tile pixel ratio. + */ +ol.source.Tile.prototype.getTilePixelRatio = function(opt_pixelRatio) { + return this.tilePixelRatio_; +}; + + +/** + * @param {number} z Z. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.Size} Tile size. + */ +ol.source.Tile.prototype.getTilePixelSize = function(z, pixelRatio, projection) { + var tileGrid = this.getTileGridForProjection(projection); + var tilePixelRatio = this.getTilePixelRatio(pixelRatio); + var tileSize = ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize); + if (tilePixelRatio == 1) { + return tileSize; + } else { + return ol.size.scale(tileSize, tilePixelRatio, this.tmpSize); + } +}; + + +/** + * Returns a tile coordinate wrapped around the x-axis. When the tile coordinate + * is outside the resolution and extent range of the tile grid, `null` will be + * returned. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.proj.Projection=} opt_projection Projection. + * @return {ol.TileCoord} Tile coordinate to be passed to the tileUrlFunction or + * null if no tile URL should be created for the passed `tileCoord`. + */ +ol.source.Tile.prototype.getTileCoordForTileUrlFunction = function(tileCoord, opt_projection) { + var projection = opt_projection !== undefined ? + opt_projection : this.getProjection(); + var tileGrid = this.getTileGridForProjection(projection); + if (this.getWrapX() && projection.isGlobal()) { + tileCoord = ol.tilegrid.wrapX(tileGrid, tileCoord, projection); + } + return ol.tilecoord.withinExtentAndZ(tileCoord, tileGrid) ? tileCoord : null; +}; + + +/** + * @inheritDoc + */ +ol.source.Tile.prototype.refresh = function() { + this.tileCache.clear(); + this.changed(); +}; + + +/** + * Marks a tile coord as being used, without triggering a load. + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @param {ol.proj.Projection} projection Projection. + */ +ol.source.Tile.prototype.useTile = ol.nullFunction; + + +/** + * @classdesc + * Events emitted by {@link ol.source.Tile} instances are instances of this + * type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.source.Tile.Event} + * @param {string} type Type. + * @param {ol.Tile} tile The tile. + */ +ol.source.Tile.Event = function(type, tile) { + + ol.events.Event.call(this, type); + + /** + * The tile related to the event. + * @type {ol.Tile} + * @api + */ + this.tile = tile; + +}; +ol.inherits(ol.source.Tile.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.source.Tile.EventType = { + + /** + * Triggered when a tile starts loading. + * @event ol.source.Tile.Event#tileloadstart + * @api stable + */ + TILELOADSTART: 'tileloadstart', + + /** + * Triggered when a tile finishes loading. + * @event ol.source.Tile.Event#tileloadend + * @api stable + */ + TILELOADEND: 'tileloadend', + + /** + * Triggered if tile loading results in an error. + * @event ol.source.Tile.Event#tileloaderror + * @api stable + */ + TILELOADERROR: 'tileloaderror' + +}; + +goog.provide('ol.source.UrlTile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.source.Tile'); + + +/** + * @classdesc + * Base class for sources providing tiles divided into a tile grid over http. + * + * @constructor + * @fires ol.source.Tile.Event + * @extends {ol.source.Tile} + * @param {ol.SourceUrlTileOptions} options Image tile options. + */ +ol.source.UrlTile = function(options) { + + ol.source.Tile.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + extent: options.extent, + logo: options.logo, + opaque: options.opaque, + projection: options.projection, + state: options.state, + tileGrid: options.tileGrid, + tilePixelRatio: options.tilePixelRatio, + wrapX: options.wrapX + }); + + /** + * @protected + * @type {ol.TileLoadFunctionType} + */ + this.tileLoadFunction = options.tileLoadFunction; + + /** + * @protected + * @type {ol.TileUrlFunctionType} + */ + this.tileUrlFunction = this.fixedTileUrlFunction ? + this.fixedTileUrlFunction.bind(this) : + ol.TileUrlFunction.nullTileUrlFunction; + + /** + * @protected + * @type {!Array.<string>|null} + */ + this.urls = null; + + if (options.urls) { + this.setUrls(options.urls); + } else if (options.url) { + this.setUrl(options.url); + } + if (options.tileUrlFunction) { + this.setTileUrlFunction(options.tileUrlFunction); + } + +}; +ol.inherits(ol.source.UrlTile, ol.source.Tile); + + +/** + * @type {ol.TileUrlFunctionType|undefined} + * @protected + */ +ol.source.UrlTile.prototype.fixedTileUrlFunction; + +/** + * Return the tile load function of the source. + * @return {ol.TileLoadFunctionType} TileLoadFunction + * @api + */ +ol.source.UrlTile.prototype.getTileLoadFunction = function() { + return this.tileLoadFunction; +}; + + +/** + * Return the tile URL function of the source. + * @return {ol.TileUrlFunctionType} TileUrlFunction + * @api + */ +ol.source.UrlTile.prototype.getTileUrlFunction = function() { + return this.tileUrlFunction; +}; + + +/** + * Return the URLs used for this source. + * When a tileUrlFunction is used instead of url or urls, + * null will be returned. + * @return {!Array.<string>|null} URLs. + * @api + */ +ol.source.UrlTile.prototype.getUrls = function() { + return this.urls; +}; + + +/** + * Handle tile change events. + * @param {ol.events.Event} event Event. + * @protected + */ +ol.source.UrlTile.prototype.handleTileChange = function(event) { + var tile = /** @type {ol.Tile} */ (event.target); + switch (tile.getState()) { + case ol.Tile.State.LOADING: + this.dispatchEvent( + new ol.source.Tile.Event(ol.source.Tile.EventType.TILELOADSTART, tile)); + break; + case ol.Tile.State.LOADED: + this.dispatchEvent( + new ol.source.Tile.Event(ol.source.Tile.EventType.TILELOADEND, tile)); + break; + case ol.Tile.State.ERROR: + this.dispatchEvent( + new ol.source.Tile.Event(ol.source.Tile.EventType.TILELOADERROR, tile)); + break; + default: + // pass + } +}; + + +/** + * Set the tile load function of the source. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. + * @api + */ +ol.source.UrlTile.prototype.setTileLoadFunction = function(tileLoadFunction) { + this.tileCache.clear(); + this.tileLoadFunction = tileLoadFunction; + this.changed(); +}; + + +/** + * Set the tile URL function of the source. + * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function. + * @param {string=} opt_key Optional new tile key for the source. + * @api + */ +ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction, opt_key) { + this.tileUrlFunction = tileUrlFunction; + if (typeof opt_key !== 'undefined') { + this.setKey(opt_key); + } else { + this.changed(); + } +}; + + +/** + * Set the URL to use for requests. + * @param {string} url URL. + * @api stable + */ +ol.source.UrlTile.prototype.setUrl = function(url) { + var urls = this.urls = ol.TileUrlFunction.expandUrl(url); + this.setTileUrlFunction(this.fixedTileUrlFunction ? + this.fixedTileUrlFunction.bind(this) : + ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), url); +}; + + +/** + * Set the URLs to use for requests. + * @param {Array.<string>} urls URLs. + * @api stable + */ +ol.source.UrlTile.prototype.setUrls = function(urls) { + this.urls = urls; + var key = urls.join('\n'); + this.setTileUrlFunction(this.fixedTileUrlFunction ? + this.fixedTileUrlFunction.bind(this) : + ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), key); +}; + + +/** + * @inheritDoc + */ +ol.source.UrlTile.prototype.useTile = function(z, x, y) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + this.tileCache.get(tileCoordKey); + } +}; + +goog.provide('ol.source.TileImage'); + +goog.require('ol'); +goog.require('ol.ImageTile'); +goog.require('ol.Tile'); +goog.require('ol.TileCache'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.proj'); +goog.require('ol.reproj.Tile'); +goog.require('ol.source.UrlTile'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * Base class for sources providing images divided into a tile grid. + * + * @constructor + * @fires ol.source.Tile.Event + * @extends {ol.source.UrlTile} + * @param {olx.source.TileImageOptions} options Image tile options. + * @api + */ +ol.source.TileImage = function(options) { + + ol.source.UrlTile.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + extent: options.extent, + logo: options.logo, + opaque: options.opaque, + projection: options.projection, + state: options.state, + tileGrid: options.tileGrid, + tileLoadFunction: options.tileLoadFunction ? + options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction, + tilePixelRatio: options.tilePixelRatio, + tileUrlFunction: options.tileUrlFunction, + url: options.url, + urls: options.urls, + wrapX: options.wrapX + }); + + /** + * @protected + * @type {?string} + */ + this.crossOrigin = + options.crossOrigin !== undefined ? options.crossOrigin : null; + + /** + * @protected + * @type {function(new: ol.ImageTile, ol.TileCoord, ol.Tile.State, string, + * ?string, ol.TileLoadFunctionType)} + */ + this.tileClass = options.tileClass !== undefined ? + options.tileClass : ol.ImageTile; + + /** + * @protected + * @type {Object.<string, ol.TileCache>} + */ + this.tileCacheForProjection = {}; + + /** + * @protected + * @type {Object.<string, ol.tilegrid.TileGrid>} + */ + this.tileGridForProjection = {}; + + /** + * @private + * @type {number|undefined} + */ + this.reprojectionErrorThreshold_ = options.reprojectionErrorThreshold; + + /** + * @private + * @type {boolean} + */ + this.renderReprojectionEdges_ = false; +}; +ol.inherits(ol.source.TileImage, ol.source.UrlTile); + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.canExpireCache = function() { + if (!ol.ENABLE_RASTER_REPROJECTION) { + return ol.source.UrlTile.prototype.canExpireCache.call(this); + } + if (this.tileCache.canExpireCache()) { + return true; + } else { + for (var key in this.tileCacheForProjection) { + if (this.tileCacheForProjection[key].canExpireCache()) { + return true; + } + } + } + return false; +}; + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.expireCache = function(projection, usedTiles) { + if (!ol.ENABLE_RASTER_REPROJECTION) { + ol.source.UrlTile.prototype.expireCache.call(this, projection, usedTiles); + return; + } + var usedTileCache = this.getTileCacheForProjection(projection); + + this.tileCache.expireCache(this.tileCache == usedTileCache ? usedTiles : {}); + for (var id in this.tileCacheForProjection) { + var tileCache = this.tileCacheForProjection[id]; + tileCache.expireCache(tileCache == usedTileCache ? usedTiles : {}); + } +}; + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.getGutter = function(projection) { + if (ol.ENABLE_RASTER_REPROJECTION && + this.getProjection() && projection && + !ol.proj.equivalent(this.getProjection(), projection)) { + return 0; + } else { + return this.getGutterInternal(); + } +}; + + +/** + * @protected + * @return {number} Gutter. + */ +ol.source.TileImage.prototype.getGutterInternal = function() { + return 0; +}; + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.getOpaque = function(projection) { + if (ol.ENABLE_RASTER_REPROJECTION && + this.getProjection() && projection && + !ol.proj.equivalent(this.getProjection(), projection)) { + return false; + } else { + return ol.source.UrlTile.prototype.getOpaque.call(this, projection); + } +}; + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.getTileGridForProjection = function(projection) { + if (!ol.ENABLE_RASTER_REPROJECTION) { + return ol.source.UrlTile.prototype.getTileGridForProjection.call(this, projection); + } + var thisProj = this.getProjection(); + if (this.tileGrid && + (!thisProj || ol.proj.equivalent(thisProj, projection))) { + return this.tileGrid; + } else { + var projKey = ol.getUid(projection).toString(); + if (!(projKey in this.tileGridForProjection)) { + this.tileGridForProjection[projKey] = + ol.tilegrid.getForProjection(projection); + } + return /** @type {!ol.tilegrid.TileGrid} */ (this.tileGridForProjection[projKey]); + } +}; + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.getTileCacheForProjection = function(projection) { + if (!ol.ENABLE_RASTER_REPROJECTION) { + return ol.source.UrlTile.prototype.getTileCacheForProjection.call(this, projection); + } + var thisProj = this.getProjection(); + if (!thisProj || ol.proj.equivalent(thisProj, projection)) { + return this.tileCache; + } else { + var projKey = ol.getUid(projection).toString(); + if (!(projKey in this.tileCacheForProjection)) { + this.tileCacheForProjection[projKey] = new ol.TileCache(); + } + return this.tileCacheForProjection[projKey]; + } +}; + + +/** + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {string} key The key set on the tile. + * @return {!ol.Tile} Tile. + * @private + */ +ol.source.TileImage.prototype.createTile_ = function(z, x, y, pixelRatio, projection, key) { + var tileCoord = [z, x, y]; + var urlTileCoord = this.getTileCoordForTileUrlFunction( + tileCoord, projection); + var tileUrl = urlTileCoord ? + this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined; + var tile = new this.tileClass( + tileCoord, + tileUrl !== undefined ? ol.Tile.State.IDLE : ol.Tile.State.EMPTY, + tileUrl !== undefined ? tileUrl : '', + this.crossOrigin, + this.tileLoadFunction); + tile.key = key; + ol.events.listen(tile, ol.events.EventType.CHANGE, + this.handleTileChange, this); + return tile; +}; + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.getTile = function(z, x, y, pixelRatio, projection) { + if (!ol.ENABLE_RASTER_REPROJECTION || + !this.getProjection() || + !projection || + ol.proj.equivalent(this.getProjection(), projection)) { + return this.getTileInternal(z, x, y, pixelRatio, /** @type {!ol.proj.Projection} */ (projection)); + } else { + var cache = this.getTileCacheForProjection(projection); + var tileCoord = [z, x, y]; + var tile; + var tileCoordKey = this.getKeyZXY.apply(this, tileCoord); + if (cache.containsKey(tileCoordKey)) { + tile = /** @type {!ol.Tile} */ (cache.get(tileCoordKey)); + } + var key = this.getKey(); + if (tile && tile.key == key) { + return tile; + } else { + var sourceProjection = /** @type {!ol.proj.Projection} */ (this.getProjection()); + var sourceTileGrid = this.getTileGridForProjection(sourceProjection); + var targetTileGrid = this.getTileGridForProjection(projection); + var wrappedTileCoord = + this.getTileCoordForTileUrlFunction(tileCoord, projection); + var newTile = new ol.reproj.Tile( + sourceProjection, sourceTileGrid, + projection, targetTileGrid, + tileCoord, wrappedTileCoord, this.getTilePixelRatio(pixelRatio), + this.getGutterInternal(), + function(z, x, y, pixelRatio) { + return this.getTileInternal(z, x, y, pixelRatio, sourceProjection); + }.bind(this), this.reprojectionErrorThreshold_, + this.renderReprojectionEdges_); + newTile.key = key; + + if (tile) { + newTile.interimTile = tile; + cache.replace(tileCoordKey, newTile); + } else { + cache.set(tileCoordKey, newTile); + } + return newTile; + } + } +}; + + +/** + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @param {number} pixelRatio Pixel ratio. + * @param {!ol.proj.Projection} projection Projection. + * @return {!ol.Tile} Tile. + * @protected + */ +ol.source.TileImage.prototype.getTileInternal = function(z, x, y, pixelRatio, projection) { + var tile = null; + var tileCoordKey = this.getKeyZXY(z, x, y); + var key = this.getKey(); + if (!this.tileCache.containsKey(tileCoordKey)) { + tile = this.createTile_(z, x, y, pixelRatio, projection, key); + this.tileCache.set(tileCoordKey, tile); + } else { + tile = this.tileCache.get(tileCoordKey); + if (tile.key != key) { + // The source's params changed. If the tile has an interim tile and if we + // can use it then we use it. Otherwise we create a new tile. In both + // cases we attempt to assign an interim tile to the new tile. + var interimTile = tile; + tile = this.createTile_(z, x, y, pixelRatio, projection, key); + + //make the new tile the head of the list, + if (interimTile.getState() == ol.Tile.State.IDLE) { + //the old tile hasn't begun loading yet, and is now outdated, so we can simply discard it + tile.interimTile = interimTile.interimTile; + } else { + tile.interimTile = interimTile; + } + tile.refreshInterimChain(); + this.tileCache.replace(tileCoordKey, tile); + } + } + return tile; +}; + + +/** + * Sets whether to render reprojection edges or not (usually for debugging). + * @param {boolean} render Render the edges. + * @api + */ +ol.source.TileImage.prototype.setRenderReprojectionEdges = function(render) { + if (!ol.ENABLE_RASTER_REPROJECTION || + this.renderReprojectionEdges_ == render) { + return; + } + this.renderReprojectionEdges_ = render; + for (var id in this.tileCacheForProjection) { + this.tileCacheForProjection[id].clear(); + } + this.changed(); +}; + + +/** + * Sets the tile grid to use when reprojecting the tiles to the given + * projection instead of the default tile grid for the projection. + * + * This can be useful when the default tile grid cannot be created + * (e.g. projection has no extent defined) or + * for optimization reasons (custom tile size, resolutions, ...). + * + * @param {ol.ProjectionLike} projection Projection. + * @param {ol.tilegrid.TileGrid} tilegrid Tile grid to use for the projection. + * @api + */ +ol.source.TileImage.prototype.setTileGridForProjection = function(projection, tilegrid) { + if (ol.ENABLE_RASTER_REPROJECTION) { + var proj = ol.proj.get(projection); + if (proj) { + var projKey = ol.getUid(proj).toString(); + if (!(projKey in this.tileGridForProjection)) { + this.tileGridForProjection[projKey] = tilegrid; + } + } + } +}; + + +/** + * @param {ol.ImageTile} imageTile Image tile. + * @param {string} src Source. + */ +ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) { + imageTile.getImage().src = src; +}; + +goog.provide('ol.source.BingMaps'); + +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.extent'); +goog.require('ol.net'); +goog.require('ol.proj'); +goog.require('ol.source.State'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilecoord'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * Layer source for Bing Maps tile data. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.BingMapsOptions} options Bing Maps options. + * @api stable + */ +ol.source.BingMaps = function(options) { + + /** + * @private + * @type {boolean} + */ + this.hidpi_ = options.hidpi !== undefined ? options.hidpi : false; + + ol.source.TileImage.call(this, { + cacheSize: options.cacheSize, + crossOrigin: 'anonymous', + opaque: true, + projection: ol.proj.get('EPSG:3857'), + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + state: ol.source.State.LOADING, + tileLoadFunction: options.tileLoadFunction, + tilePixelRatio: this.hidpi_ ? 2 : 1, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + + /** + * @private + * @type {string} + */ + this.culture_ = options.culture !== undefined ? options.culture : 'en-us'; + + /** + * @private + * @type {number} + */ + this.maxZoom_ = options.maxZoom !== undefined ? options.maxZoom : -1; + + /** + * @private + * @type {string} + */ + this.apiKey_ = options.key; + + /** + * @private + * @type {string} + */ + this.imagerySet_ = options.imagerySet; + + var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/' + + this.imagerySet_ + + '?uriScheme=https&include=ImageryProviders&key=' + this.apiKey_; + + ol.net.jsonp(url, this.handleImageryMetadataResponse.bind(this), undefined, + 'jsonp'); + +}; +ol.inherits(ol.source.BingMaps, ol.source.TileImage); + + +/** + * The attribution containing a link to the Microsoft® Bingâ„¢ Maps Platform APIs’ + * Terms Of Use. + * @const + * @type {ol.Attribution} + * @api + */ +ol.source.BingMaps.TOS_ATTRIBUTION = new ol.Attribution({ + html: '<a class="ol-attribution-bing-tos" ' + + 'href="http://www.microsoft.com/maps/product/terms.html">' + + 'Terms of Use</a>' +}); + + +/** + * Get the api key used for this source. + * + * @return {string} The api key. + * @api + */ +ol.source.BingMaps.prototype.getApiKey = function() { + return this.apiKey_; +}; + + +/** + * Get the imagery set associated with this source. + * + * @return {string} The imagery set. + * @api + */ +ol.source.BingMaps.prototype.getImagerySet = function() { + return this.imagerySet_; +}; + + +/** + * @param {BingMapsImageryMetadataResponse} response Response. + */ +ol.source.BingMaps.prototype.handleImageryMetadataResponse = function(response) { + + if (response.statusCode != 200 || + response.statusDescription != 'OK' || + response.authenticationResultCode != 'ValidCredentials' || + response.resourceSets.length != 1 || + response.resourceSets[0].resources.length != 1) { + this.setState(ol.source.State.ERROR); + return; + } + + var brandLogoUri = response.brandLogoUri; + if (brandLogoUri.indexOf('https') == -1) { + brandLogoUri = brandLogoUri.replace('http', 'https'); + } + //var copyright = response.copyright; // FIXME do we need to display this? + var resource = response.resourceSets[0].resources[0]; + ol.DEBUG && console.assert(resource.imageWidth == resource.imageHeight, + 'resource has imageWidth equal to imageHeight, i.e. is square'); + var maxZoom = this.maxZoom_ == -1 ? resource.zoomMax : this.maxZoom_; + + var sourceProjection = this.getProjection(); + var extent = ol.tilegrid.extentFromProjection(sourceProjection); + var tileSize = resource.imageWidth == resource.imageHeight ? + resource.imageWidth : [resource.imageWidth, resource.imageHeight]; + var tileGrid = ol.tilegrid.createXYZ({ + extent: extent, + minZoom: resource.zoomMin, + maxZoom: maxZoom, + tileSize: tileSize / this.getTilePixelRatio() + }); + this.tileGrid = tileGrid; + + var culture = this.culture_; + var hidpi = this.hidpi_; + this.tileUrlFunction = ol.TileUrlFunction.createFromTileUrlFunctions( + resource.imageUrlSubdomains.map(function(subdomain) { + var quadKeyTileCoord = [0, 0, 0]; + var imageUrl = resource.imageUrl + .replace('{subdomain}', subdomain) + .replace('{culture}', culture); + return ( + /** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function(tileCoord, pixelRatio, projection) { + ol.DEBUG && console.assert(ol.proj.equivalent( + projection, sourceProjection), + 'projections are equivalent'); + if (!tileCoord) { + return undefined; + } else { + ol.tilecoord.createOrUpdate(tileCoord[0], tileCoord[1], + -tileCoord[2] - 1, quadKeyTileCoord); + var url = imageUrl; + if (hidpi) { + url += '&dpi=d1&device=mobile'; + } + return url.replace('{quadkey}', ol.tilecoord.quadKey( + quadKeyTileCoord)); + } + }); + })); + + if (resource.imageryProviders) { + var transform = ol.proj.getTransformFromProjections( + ol.proj.get('EPSG:4326'), this.getProjection()); + + var attributions = resource.imageryProviders.map(function(imageryProvider) { + var html = imageryProvider.attribution; + /** @type {Object.<string, Array.<ol.TileRange>>} */ + var tileRanges = {}; + imageryProvider.coverageAreas.forEach(function(coverageArea) { + var minZ = coverageArea.zoomMin; + var maxZ = Math.min(coverageArea.zoomMax, maxZoom); + var bbox = coverageArea.bbox; + var epsg4326Extent = [bbox[1], bbox[0], bbox[3], bbox[2]]; + var extent = ol.extent.applyTransform(epsg4326Extent, transform); + var tileRange, z, zKey; + for (z = minZ; z <= maxZ; ++z) { + zKey = z.toString(); + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + if (zKey in tileRanges) { + tileRanges[zKey].push(tileRange); + } else { + tileRanges[zKey] = [tileRange]; + } + } + }); + return new ol.Attribution({html: html, tileRanges: tileRanges}); + }); + attributions.push(ol.source.BingMaps.TOS_ATTRIBUTION); + this.setAttributions(attributions); + } + + this.setLogo(brandLogoUri); + + this.setState(ol.source.State.READY); + +}; + +goog.provide('ol.source.XYZ'); + +goog.require('ol'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * Layer source for tile data with URLs in a set XYZ format that are + * defined in a URL template. By default, this follows the widely-used + * Google grid where `x` 0 and `y` 0 are in the top left. Grids like + * TMS where `x` 0 and `y` 0 are in the bottom left can be used by + * using the `{-y}` placeholder in the URL template, so long as the + * source does not have a custom tile grid. In this case, + * {@link ol.source.TileImage} can be used with a `tileUrlFunction` + * such as: + * + * tileUrlFunction: function(coordinate) { + * return 'http://mapserver.com/' + coordinate[0] + '/' + + * coordinate[1] + '/' + coordinate[2] + '.png'; + * } + * + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.XYZOptions=} opt_options XYZ options. + * @api stable + */ +ol.source.XYZ = function(opt_options) { + var options = opt_options || {}; + var projection = options.projection !== undefined ? + options.projection : 'EPSG:3857'; + + var tileGrid = options.tileGrid !== undefined ? options.tileGrid : + ol.tilegrid.createXYZ({ + extent: ol.tilegrid.extentFromProjection(projection), + maxZoom: options.maxZoom, + minZoom: options.minZoom, + tileSize: options.tileSize + }); + + ol.source.TileImage.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + logo: options.logo, + opaque: options.opaque, + projection: projection, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileGrid: tileGrid, + tileLoadFunction: options.tileLoadFunction, + tilePixelRatio: options.tilePixelRatio, + tileUrlFunction: options.tileUrlFunction, + url: options.url, + urls: options.urls, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + +}; +ol.inherits(ol.source.XYZ, ol.source.TileImage); + +goog.provide('ol.source.CartoDB'); + +goog.require('ol'); +goog.require('ol.obj'); +goog.require('ol.source.State'); +goog.require('ol.source.XYZ'); + + +/** + * @classdesc + * Layer source for the CartoDB tiles. + * + * @constructor + * @extends {ol.source.XYZ} + * @param {olx.source.CartoDBOptions} options CartoDB options. + * @api + */ +ol.source.CartoDB = function(options) { + + /** + * @type {string} + * @private + */ + this.account_ = options.account; + + /** + * @type {string} + * @private + */ + this.mapId_ = options.map || ''; + + /** + * @type {!Object} + * @private + */ + this.config_ = options.config || {}; + + /** + * @type {!Object.<string, CartoDBLayerInfo>} + * @private + */ + this.templateCache_ = {}; + + ol.source.XYZ.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + logo: options.logo, + maxZoom: options.maxZoom !== undefined ? options.maxZoom : 18, + minZoom: options.minZoom, + projection: options.projection, + state: ol.source.State.LOADING, + wrapX: options.wrapX + }); + this.initializeMap_(); +}; +ol.inherits(ol.source.CartoDB, ol.source.XYZ); + + +/** + * Returns the current config. + * @return {!Object} The current configuration. + * @api + */ +ol.source.CartoDB.prototype.getConfig = function() { + return this.config_; +}; + + +/** + * Updates the carto db config. + * @param {Object} config a key-value lookup. Values will replace current values + * in the config. + * @api + */ +ol.source.CartoDB.prototype.updateConfig = function(config) { + ol.obj.assign(this.config_, config); + this.initializeMap_(); +}; + + +/** + * Sets the CartoDB config + * @param {Object} config In the case of anonymous maps, a CartoDB configuration + * object. + * If using named maps, a key-value lookup with the template parameters. + * @api + */ +ol.source.CartoDB.prototype.setConfig = function(config) { + this.config_ = config || {}; + this.initializeMap_(); +}; + + +/** + * Issue a request to initialize the CartoDB map. + * @private + */ +ol.source.CartoDB.prototype.initializeMap_ = function() { + var paramHash = JSON.stringify(this.config_); + if (this.templateCache_[paramHash]) { + this.applyTemplate_(this.templateCache_[paramHash]); + return; + } + var mapUrl = 'https://' + this.account_ + '.cartodb.com/api/v1/map'; + + if (this.mapId_) { + mapUrl += '/named/' + this.mapId_; + } + + var client = new XMLHttpRequest(); + client.addEventListener('load', this.handleInitResponse_.bind(this, paramHash)); + client.addEventListener('error', this.handleInitError_.bind(this)); + client.open('POST', mapUrl); + client.setRequestHeader('Content-type', 'application/json'); + client.send(JSON.stringify(this.config_)); +}; + + +/** + * Handle map initialization response. + * @param {string} paramHash a hash representing the parameter set that was used + * for the request + * @param {Event} event Event. + * @private + */ +ol.source.CartoDB.prototype.handleInitResponse_ = function(paramHash, event) { + var client = /** @type {XMLHttpRequest} */ (event.target); + // status will be 0 for file:// urls + if (!client.status || client.status >= 200 && client.status < 300) { + var response; + try { + response = /** @type {CartoDBLayerInfo} */(JSON.parse(client.responseText)); + } catch (err) { + this.setState(ol.source.State.ERROR); + return; + } + this.applyTemplate_(response); + this.templateCache_[paramHash] = response; + this.setState(ol.source.State.READY); + } else { + this.setState(ol.source.State.ERROR); + } +}; + + +/** + * @private + * @param {Event} event Event. + */ +ol.source.CartoDB.prototype.handleInitError_ = function(event) { + this.setState(ol.source.State.ERROR); +}; + + +/** + * Apply the new tile urls returned by carto db + * @param {CartoDBLayerInfo} data Result of carto db call. + * @private + */ +ol.source.CartoDB.prototype.applyTemplate_ = function(data) { + var tilesUrl = 'https://' + data.cdn_url.https + '/' + this.account_ + + '/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png'; + this.setUrl(tilesUrl); +}; + +// FIXME keep cluster cache by resolution ? +// FIXME distance not respected because of the centroid + +goog.provide('ol.source.Cluster'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.Feature'); +goog.require('ol.coordinate'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.geom.Point'); +goog.require('ol.source.Vector'); + + +/** + * @classdesc + * Layer source to cluster vector data. Works out of the box with point + * geometries. For other geometry types, or if not all geometries should be + * considered for clustering, a custom `geometryFunction` can be defined. + * + * @constructor + * @param {olx.source.ClusterOptions} options Constructor options. + * @extends {ol.source.Vector} + * @api + */ +ol.source.Cluster = function(options) { + ol.source.Vector.call(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + projection: options.projection, + wrapX: options.wrapX + }); + + /** + * @type {number|undefined} + * @private + */ + this.resolution_ = undefined; + + /** + * @type {number} + * @private + */ + this.distance_ = options.distance !== undefined ? options.distance : 20; + + /** + * @type {Array.<ol.Feature>} + * @private + */ + this.features_ = []; + + /** + * @param {ol.Feature} feature Feature. + * @return {ol.geom.Point} Cluster calculation point. + */ + this.geometryFunction_ = options.geometryFunction || function(feature) { + var geometry = /** @type {ol.geom.Point} */ (feature.getGeometry()); + ol.asserts.assert(geometry instanceof ol.geom.Point, + 10); // The default `geometryFunction` can only handle `ol.geom.Point` geometries + return geometry; + }; + + /** + * @type {ol.source.Vector} + * @private + */ + this.source_ = options.source; + + this.source_.on(ol.events.EventType.CHANGE, + ol.source.Cluster.prototype.refresh_, this); +}; +ol.inherits(ol.source.Cluster, ol.source.Vector); + + +/** + * Get a reference to the wrapped source. + * @return {ol.source.Vector} Source. + * @api + */ +ol.source.Cluster.prototype.getSource = function() { + return this.source_; +}; + + +/** + * @inheritDoc + */ +ol.source.Cluster.prototype.loadFeatures = function(extent, resolution, + projection) { + this.source_.loadFeatures(extent, resolution, projection); + if (resolution !== this.resolution_) { + this.clear(); + this.resolution_ = resolution; + this.cluster_(); + this.addFeatures(this.features_); + } +}; + + +/** + * Set the distance in pixels between clusters. + * @param {number} distance The distance in pixels. + * @api + */ +ol.source.Cluster.prototype.setDistance = function(distance) { + this.distance_ = distance; + this.refresh_(); +}; + + +/** + * handle the source changing + * @private + */ +ol.source.Cluster.prototype.refresh_ = function() { + this.clear(); + this.cluster_(); + this.addFeatures(this.features_); + this.changed(); +}; + + +/** + * @private + */ +ol.source.Cluster.prototype.cluster_ = function() { + if (this.resolution_ === undefined) { + return; + } + this.features_.length = 0; + var extent = ol.extent.createEmpty(); + var mapDistance = this.distance_ * this.resolution_; + var features = this.source_.getFeatures(); + + /** + * @type {!Object.<string, boolean>} + */ + var clustered = {}; + + for (var i = 0, ii = features.length; i < ii; i++) { + var feature = features[i]; + if (!(ol.getUid(feature).toString() in clustered)) { + var geometry = this.geometryFunction_(feature); + if (geometry) { + var coordinates = geometry.getCoordinates(); + ol.extent.createOrUpdateFromCoordinate(coordinates, extent); + ol.extent.buffer(extent, mapDistance, extent); + + var neighbors = this.source_.getFeaturesInExtent(extent); + ol.DEBUG && console.assert(neighbors.length >= 1, 'at least one neighbor found'); + neighbors = neighbors.filter(function(neighbor) { + var uid = ol.getUid(neighbor).toString(); + if (!(uid in clustered)) { + clustered[uid] = true; + return true; + } else { + return false; + } + }); + this.features_.push(this.createCluster_(neighbors)); + } + } + } + ol.DEBUG && console.assert( + Object.keys(clustered).length == this.source_.getFeatures().length, + 'number of clustered equals number of features in the source'); +}; + + +/** + * @param {Array.<ol.Feature>} features Features + * @return {ol.Feature} The cluster feature. + * @private + */ +ol.source.Cluster.prototype.createCluster_ = function(features) { + var centroid = [0, 0]; + for (var i = features.length - 1; i >= 0; --i) { + var geometry = this.geometryFunction_(features[i]); + if (geometry) { + ol.coordinate.add(centroid, geometry.getCoordinates()); + } else { + features.splice(i, 1); + } + } + ol.coordinate.scale(centroid, 1 / features.length); + + var cluster = new ol.Feature(new ol.geom.Point(centroid)); + cluster.set('features', features); + return cluster; +}; + +goog.provide('ol.uri'); + + +/** + * Appends query parameters to a URI. + * + * @param {string} uri The original URI, which may already have query data. + * @param {!Object} params An object where keys are URI-encoded parameter keys, + * and the values are arbitrary types or arrays. + * @return {string} The new URI. + */ +ol.uri.appendParams = function(uri, params) { + var keyParams = []; + // Skip any null or undefined parameter values + Object.keys(params).forEach(function(k) { + if (params[k] !== null && params[k] !== undefined) { + keyParams.push(k + '=' + encodeURIComponent(params[k])); + } + }); + var qs = keyParams.join('&'); + // remove any trailing ? or & + uri = uri.replace(/[?&]$/, ''); + // append ? or & depending on whether uri has existing parameters + uri = uri.indexOf('?') === -1 ? uri + '?' : uri + '&'; + return uri + qs; +}; + +goog.provide('ol.source.ImageArcGISRest'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.asserts'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.source.Image'); +goog.require('ol.uri'); + + +/** + * @classdesc + * Source for data from ArcGIS Rest services providing single, untiled images. + * Useful when underlying map service has labels. + * + * If underlying map service is not using labels, + * take advantage of ol image caching and use + * {@link ol.source.TileArcGISRest} data source. + * + * @constructor + * @fires ol.source.Image.Event + * @extends {ol.source.Image} + * @param {olx.source.ImageArcGISRestOptions=} opt_options Image ArcGIS Rest Options. + * @api + */ +ol.source.ImageArcGISRest = function(opt_options) { + + var options = opt_options || {}; + + ol.source.Image.call(this, { + attributions: options.attributions, + logo: options.logo, + projection: options.projection, + resolutions: options.resolutions + }); + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + options.crossOrigin !== undefined ? options.crossOrigin : null; + + /** + * @private + * @type {string|undefined} + */ + this.url_ = options.url; + + /** + * @private + * @type {ol.ImageLoadFunctionType} + */ + this.imageLoadFunction_ = options.imageLoadFunction !== undefined ? + options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction; + + + /** + * @private + * @type {!Object} + */ + this.params_ = options.params || {}; + + /** + * @private + * @type {ol.Image} + */ + this.image_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.imageSize_ = [0, 0]; + + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = 0; + + /** + * @private + * @type {number} + */ + this.ratio_ = options.ratio !== undefined ? options.ratio : 1.5; + +}; +ol.inherits(ol.source.ImageArcGISRest, ol.source.Image); + + +/** + * Get the user-provided params, i.e. those passed to the constructor through + * the "params" option, and possibly updated using the updateParams method. + * @return {Object} Params. + * @api stable + */ +ol.source.ImageArcGISRest.prototype.getParams = function() { + return this.params_; +}; + + +/** + * @inheritDoc + */ +ol.source.ImageArcGISRest.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) { + + if (this.url_ === undefined) { + return null; + } + + resolution = this.findNearestResolution(resolution); + + var image = this.image_; + if (image && + this.renderedRevision_ == this.getRevision() && + image.getResolution() == resolution && + image.getPixelRatio() == pixelRatio && + ol.extent.containsExtent(image.getExtent(), extent)) { + return image; + } + + var params = { + 'F': 'image', + 'FORMAT': 'PNG32', + 'TRANSPARENT': true + }; + ol.obj.assign(params, this.params_); + + extent = extent.slice(); + var centerX = (extent[0] + extent[2]) / 2; + var centerY = (extent[1] + extent[3]) / 2; + if (this.ratio_ != 1) { + var halfWidth = this.ratio_ * ol.extent.getWidth(extent) / 2; + var halfHeight = this.ratio_ * ol.extent.getHeight(extent) / 2; + extent[0] = centerX - halfWidth; + extent[1] = centerY - halfHeight; + extent[2] = centerX + halfWidth; + extent[3] = centerY + halfHeight; + } + + var imageResolution = resolution / pixelRatio; + + // Compute an integer width and height. + var width = Math.ceil(ol.extent.getWidth(extent) / imageResolution); + var height = Math.ceil(ol.extent.getHeight(extent) / imageResolution); + + // Modify the extent to match the integer width and height. + extent[0] = centerX - imageResolution * width / 2; + extent[2] = centerX + imageResolution * width / 2; + extent[1] = centerY - imageResolution * height / 2; + extent[3] = centerY + imageResolution * height / 2; + + this.imageSize_[0] = width; + this.imageSize_[1] = height; + + var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio, + projection, params); + + this.image_ = new ol.Image(extent, resolution, pixelRatio, + this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_); + + this.renderedRevision_ = this.getRevision(); + + ol.events.listen(this.image_, ol.events.EventType.CHANGE, + this.handleImageChange, this); + + return this.image_; + +}; + + +/** + * Return the image load function of the source. + * @return {ol.ImageLoadFunctionType} The image load function. + * @api + */ +ol.source.ImageArcGISRest.prototype.getImageLoadFunction = function() { + return this.imageLoadFunction_; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {Object} params Params. + * @return {string} Request URL. + * @private + */ +ol.source.ImageArcGISRest.prototype.getRequestUrl_ = function(extent, size, pixelRatio, projection, params) { + + ol.DEBUG && console.assert(this.url_ !== undefined, 'url is defined'); + + // ArcGIS Server only wants the numeric portion of the projection ID. + var srid = projection.getCode().split(':').pop(); + + params['SIZE'] = size[0] + ',' + size[1]; + params['BBOX'] = extent.join(','); + params['BBOXSR'] = srid; + params['IMAGESR'] = srid; + params['DPI'] = 90 * pixelRatio; + + var url = this.url_; + + var modifiedUrl = url + .replace(/MapServer\/?$/, 'MapServer/export') + .replace(/ImageServer\/?$/, 'ImageServer/exportImage'); + if (modifiedUrl == url) { + ol.asserts.assert(false, 50); // `options.featureTypes` should be an Array + } + return ol.uri.appendParams(modifiedUrl, params); +}; + + +/** + * Return the URL used for this ArcGIS source. + * @return {string|undefined} URL. + * @api stable + */ +ol.source.ImageArcGISRest.prototype.getUrl = function() { + return this.url_; +}; + + +/** + * Set the image load function of the source. + * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function. + * @api + */ +ol.source.ImageArcGISRest.prototype.setImageLoadFunction = function(imageLoadFunction) { + this.image_ = null; + this.imageLoadFunction_ = imageLoadFunction; + this.changed(); +}; + + +/** + * Set the URL to use for requests. + * @param {string|undefined} url URL. + * @api stable + */ +ol.source.ImageArcGISRest.prototype.setUrl = function(url) { + if (url != this.url_) { + this.url_ = url; + this.image_ = null; + this.changed(); + } +}; + + +/** + * Update the user-provided params. + * @param {Object} params Params. + * @api stable + */ +ol.source.ImageArcGISRest.prototype.updateParams = function(params) { + ol.obj.assign(this.params_, params); + this.image_ = null; + this.changed(); +}; + +goog.provide('ol.source.ImageMapGuide'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.source.Image'); +goog.require('ol.uri'); + + +/** + * @classdesc + * Source for images from Mapguide servers + * + * @constructor + * @fires ol.source.Image.Event + * @extends {ol.source.Image} + * @param {olx.source.ImageMapGuideOptions} options Options. + * @api stable + */ +ol.source.ImageMapGuide = function(options) { + + ol.source.Image.call(this, { + projection: options.projection, + resolutions: options.resolutions + }); + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + options.crossOrigin !== undefined ? options.crossOrigin : null; + + /** + * @private + * @type {number} + */ + this.displayDpi_ = options.displayDpi !== undefined ? + options.displayDpi : 96; + + /** + * @private + * @type {!Object} + */ + this.params_ = options.params || {}; + + /** + * @private + * @type {string|undefined} + */ + this.url_ = options.url; + + /** + * @private + * @type {ol.ImageLoadFunctionType} + */ + this.imageLoadFunction_ = options.imageLoadFunction !== undefined ? + options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction; + + /** + * @private + * @type {boolean} + */ + this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true; + + /** + * @private + * @type {number} + */ + this.metersPerUnit_ = options.metersPerUnit !== undefined ? + options.metersPerUnit : 1; + + /** + * @private + * @type {number} + */ + this.ratio_ = options.ratio !== undefined ? options.ratio : 1; + + /** + * @private + * @type {boolean} + */ + this.useOverlay_ = options.useOverlay !== undefined ? + options.useOverlay : false; + + /** + * @private + * @type {ol.Image} + */ + this.image_ = null; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = 0; + +}; +ol.inherits(ol.source.ImageMapGuide, ol.source.Image); + + +/** + * Get the user-provided params, i.e. those passed to the constructor through + * the "params" option, and possibly updated using the updateParams method. + * @return {Object} Params. + * @api stable + */ +ol.source.ImageMapGuide.prototype.getParams = function() { + return this.params_; +}; + + +/** + * @inheritDoc + */ +ol.source.ImageMapGuide.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) { + resolution = this.findNearestResolution(resolution); + pixelRatio = this.hidpi_ ? pixelRatio : 1; + + var image = this.image_; + if (image && + this.renderedRevision_ == this.getRevision() && + image.getResolution() == resolution && + image.getPixelRatio() == pixelRatio && + ol.extent.containsExtent(image.getExtent(), extent)) { + return image; + } + + if (this.ratio_ != 1) { + extent = extent.slice(); + ol.extent.scaleFromCenter(extent, this.ratio_); + } + var width = ol.extent.getWidth(extent) / resolution; + var height = ol.extent.getHeight(extent) / resolution; + var size = [width * pixelRatio, height * pixelRatio]; + + if (this.url_ !== undefined) { + var imageUrl = this.getUrl(this.url_, this.params_, extent, size, + projection); + image = new ol.Image(extent, resolution, pixelRatio, + this.getAttributions(), imageUrl, this.crossOrigin_, + this.imageLoadFunction_); + ol.events.listen(image, ol.events.EventType.CHANGE, + this.handleImageChange, this); + } else { + image = null; + } + this.image_ = image; + this.renderedRevision_ = this.getRevision(); + + return image; +}; + + +/** + * Return the image load function of the source. + * @return {ol.ImageLoadFunctionType} The image load function. + * @api + */ +ol.source.ImageMapGuide.prototype.getImageLoadFunction = function() { + return this.imageLoadFunction_; +}; + + +/** + * @param {ol.Extent} extent The map extents. + * @param {ol.Size} size The viewport size. + * @param {number} metersPerUnit The meters-per-unit value. + * @param {number} dpi The display resolution. + * @return {number} The computed map scale. + */ +ol.source.ImageMapGuide.getScale = function(extent, size, metersPerUnit, dpi) { + var mcsW = ol.extent.getWidth(extent); + var mcsH = ol.extent.getHeight(extent); + var devW = size[0]; + var devH = size[1]; + var mpp = 0.0254 / dpi; + if (devH * mcsW > devW * mcsH) { + return mcsW * metersPerUnit / (devW * mpp); // width limited + } else { + return mcsH * metersPerUnit / (devH * mpp); // height limited + } +}; + + +/** + * Update the user-provided params. + * @param {Object} params Params. + * @api stable + */ +ol.source.ImageMapGuide.prototype.updateParams = function(params) { + ol.obj.assign(this.params_, params); + this.changed(); +}; + + +/** + * @param {string} baseUrl The mapagent url. + * @param {Object.<string, string|number>} params Request parameters. + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Size. + * @param {ol.proj.Projection} projection Projection. + * @return {string} The mapagent map image request URL. + */ +ol.source.ImageMapGuide.prototype.getUrl = function(baseUrl, params, extent, size, projection) { + var scale = ol.source.ImageMapGuide.getScale(extent, size, + this.metersPerUnit_, this.displayDpi_); + var center = ol.extent.getCenter(extent); + var baseParams = { + 'OPERATION': this.useOverlay_ ? 'GETDYNAMICMAPOVERLAYIMAGE' : 'GETMAPIMAGE', + 'VERSION': '2.0.0', + 'LOCALE': 'en', + 'CLIENTAGENT': 'ol.source.ImageMapGuide source', + 'CLIP': '1', + 'SETDISPLAYDPI': this.displayDpi_, + 'SETDISPLAYWIDTH': Math.round(size[0]), + 'SETDISPLAYHEIGHT': Math.round(size[1]), + 'SETVIEWSCALE': scale, + 'SETVIEWCENTERX': center[0], + 'SETVIEWCENTERY': center[1] + }; + ol.obj.assign(baseParams, params); + return ol.uri.appendParams(baseUrl, baseParams); +}; + + +/** + * Set the image load function of the MapGuide source. + * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function. + * @api + */ +ol.source.ImageMapGuide.prototype.setImageLoadFunction = function( + imageLoadFunction) { + this.image_ = null; + this.imageLoadFunction_ = imageLoadFunction; + this.changed(); +}; + +goog.provide('ol.source.ImageStatic'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.proj'); +goog.require('ol.source.Image'); + + +/** + * @classdesc + * A layer source for displaying a single, static image. + * + * @constructor + * @extends {ol.source.Image} + * @param {olx.source.ImageStaticOptions} options Options. + * @api stable + */ +ol.source.ImageStatic = function(options) { + var imageExtent = options.imageExtent; + + var crossOrigin = options.crossOrigin !== undefined ? + options.crossOrigin : null; + + var /** @type {ol.ImageLoadFunctionType} */ imageLoadFunction = + options.imageLoadFunction !== undefined ? + options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction; + + ol.source.Image.call(this, { + attributions: options.attributions, + logo: options.logo, + projection: ol.proj.get(options.projection) + }); + + /** + * @private + * @type {ol.Image} + */ + this.image_ = new ol.Image(imageExtent, undefined, 1, this.getAttributions(), + options.url, crossOrigin, imageLoadFunction); + + /** + * @private + * @type {ol.Size} + */ + this.imageSize_ = options.imageSize ? options.imageSize : null; + + ol.events.listen(this.image_, ol.events.EventType.CHANGE, + this.handleImageChange, this); + +}; +ol.inherits(ol.source.ImageStatic, ol.source.Image); + + +/** + * @inheritDoc + */ +ol.source.ImageStatic.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) { + if (ol.extent.intersects(extent, this.image_.getExtent())) { + return this.image_; + } + return null; +}; + + +/** + * @inheritDoc + */ +ol.source.ImageStatic.prototype.handleImageChange = function(evt) { + if (this.image_.getState() == ol.Image.State.LOADED) { + var imageExtent = this.image_.getExtent(); + var image = this.image_.getImage(); + var imageWidth, imageHeight; + if (this.imageSize_) { + imageWidth = this.imageSize_[0]; + imageHeight = this.imageSize_[1]; + } else { + // TODO: remove the type cast when a closure-compiler > 20160315 is used. + // see: https://github.com/google/closure-compiler/pull/1664 + imageWidth = /** @type {number} */ (image.width); + imageHeight = /** @type {number} */ (image.height); + } + var resolution = ol.extent.getHeight(imageExtent) / imageHeight; + var targetWidth = Math.ceil(ol.extent.getWidth(imageExtent) / resolution); + if (targetWidth != imageWidth) { + var context = ol.dom.createCanvasContext2D(targetWidth, imageHeight); + var canvas = context.canvas; + context.drawImage(image, 0, 0, imageWidth, imageHeight, + 0, 0, canvas.width, canvas.height); + this.image_.setImage(canvas); + } + } + ol.source.Image.prototype.handleImageChange.call(this, evt); +}; + +goog.provide('ol.source.WMSServerType'); + + +/** + * Available server types: `'carmentaserver'`, `'geoserver'`, `'mapserver'`, + * `'qgis'`. These are servers that have vendor parameters beyond the WMS + * specification that OpenLayers can make use of. + * @enum {string} + */ +ol.source.WMSServerType = { + CARMENTA_SERVER: 'carmentaserver', + GEOSERVER: 'geoserver', + MAPSERVER: 'mapserver', + QGIS: 'qgis' +}; + +// FIXME cannot be shared between maps with different projections + +goog.provide('ol.source.ImageWMS'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.asserts'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.source.Image'); +goog.require('ol.source.WMSServerType'); +goog.require('ol.string'); +goog.require('ol.uri'); + + +/** + * @classdesc + * Source for WMS servers providing single, untiled images. + * + * @constructor + * @fires ol.source.Image.Event + * @extends {ol.source.Image} + * @param {olx.source.ImageWMSOptions=} opt_options Options. + * @api stable + */ +ol.source.ImageWMS = function(opt_options) { + + var options = opt_options || {}; + + ol.source.Image.call(this, { + attributions: options.attributions, + logo: options.logo, + projection: options.projection, + resolutions: options.resolutions + }); + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + options.crossOrigin !== undefined ? options.crossOrigin : null; + + /** + * @private + * @type {string|undefined} + */ + this.url_ = options.url; + + /** + * @private + * @type {ol.ImageLoadFunctionType} + */ + this.imageLoadFunction_ = options.imageLoadFunction !== undefined ? + options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction; + + /** + * @private + * @type {!Object} + */ + this.params_ = options.params || {}; + + /** + * @private + * @type {boolean} + */ + this.v13_ = true; + this.updateV13_(); + + /** + * @private + * @type {ol.source.WMSServerType|undefined} + */ + this.serverType_ = + /** @type {ol.source.WMSServerType|undefined} */ (options.serverType); + + /** + * @private + * @type {boolean} + */ + this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true; + + /** + * @private + * @type {ol.Image} + */ + this.image_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.imageSize_ = [0, 0]; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = 0; + + /** + * @private + * @type {number} + */ + this.ratio_ = options.ratio !== undefined ? options.ratio : 1.5; + +}; +ol.inherits(ol.source.ImageWMS, ol.source.Image); + + +/** + * @const + * @type {ol.Size} + * @private + */ +ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_ = [101, 101]; + + +/** + * Return the GetFeatureInfo URL for the passed coordinate, resolution, and + * projection. Return `undefined` if the GetFeatureInfo URL cannot be + * constructed. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {ol.ProjectionLike} projection Projection. + * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should + * be provided. If `QUERY_LAYERS` is not provided then the layers specified + * in the `LAYERS` parameter will be used. `VERSION` should not be + * specified here. + * @return {string|undefined} GetFeatureInfo URL. + * @api stable + */ +ol.source.ImageWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, params) { + + ol.DEBUG && console.assert(!('VERSION' in params), + 'key VERSION is not allowed in params'); + + if (this.url_ === undefined) { + return undefined; + } + + var extent = ol.extent.getForViewAndSize( + coordinate, resolution, 0, + ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_); + + var baseParams = { + 'SERVICE': 'WMS', + 'VERSION': ol.DEFAULT_WMS_VERSION, + 'REQUEST': 'GetFeatureInfo', + 'FORMAT': 'image/png', + 'TRANSPARENT': true, + 'QUERY_LAYERS': this.params_['LAYERS'] + }; + ol.obj.assign(baseParams, this.params_, params); + + var x = Math.floor((coordinate[0] - extent[0]) / resolution); + var y = Math.floor((extent[3] - coordinate[1]) / resolution); + baseParams[this.v13_ ? 'I' : 'X'] = x; + baseParams[this.v13_ ? 'J' : 'Y'] = y; + + return this.getRequestUrl_( + extent, ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_, + 1, ol.proj.get(projection), baseParams); +}; + + +/** + * Get the user-provided params, i.e. those passed to the constructor through + * the "params" option, and possibly updated using the updateParams method. + * @return {Object} Params. + * @api stable + */ +ol.source.ImageWMS.prototype.getParams = function() { + return this.params_; +}; + + +/** + * @inheritDoc + */ +ol.source.ImageWMS.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) { + + if (this.url_ === undefined) { + return null; + } + + resolution = this.findNearestResolution(resolution); + + if (pixelRatio != 1 && (!this.hidpi_ || this.serverType_ === undefined)) { + pixelRatio = 1; + } + + extent = extent.slice(); + var centerX = (extent[0] + extent[2]) / 2; + var centerY = (extent[1] + extent[3]) / 2; + + var imageResolution = resolution / pixelRatio; + var imageWidth = ol.extent.getWidth(extent) / imageResolution; + var imageHeight = ol.extent.getHeight(extent) / imageResolution; + + var image = this.image_; + if (image && + this.renderedRevision_ == this.getRevision() && + image.getResolution() == resolution && + image.getPixelRatio() == pixelRatio && + ol.extent.containsExtent(image.getExtent(), extent)) { + return image; + } + + if (this.ratio_ != 1) { + var halfWidth = this.ratio_ * ol.extent.getWidth(extent) / 2; + var halfHeight = this.ratio_ * ol.extent.getHeight(extent) / 2; + extent[0] = centerX - halfWidth; + extent[1] = centerY - halfHeight; + extent[2] = centerX + halfWidth; + extent[3] = centerY + halfHeight; + } + + var params = { + 'SERVICE': 'WMS', + 'VERSION': ol.DEFAULT_WMS_VERSION, + 'REQUEST': 'GetMap', + 'FORMAT': 'image/png', + 'TRANSPARENT': true + }; + ol.obj.assign(params, this.params_); + + this.imageSize_[0] = Math.ceil(imageWidth * this.ratio_); + this.imageSize_[1] = Math.ceil(imageHeight * this.ratio_); + + var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio, + projection, params); + + this.image_ = new ol.Image(extent, resolution, pixelRatio, + this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_); + + this.renderedRevision_ = this.getRevision(); + + ol.events.listen(this.image_, ol.events.EventType.CHANGE, + this.handleImageChange, this); + + return this.image_; + +}; + + +/** + * Return the image load function of the source. + * @return {ol.ImageLoadFunctionType} The image load function. + * @api + */ +ol.source.ImageWMS.prototype.getImageLoadFunction = function() { + return this.imageLoadFunction_; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {Object} params Params. + * @return {string} Request URL. + * @private + */ +ol.source.ImageWMS.prototype.getRequestUrl_ = function(extent, size, pixelRatio, projection, params) { + + ol.asserts.assert(this.url_ !== undefined, 9); // `url` must be configured or set using `#setUrl()` + + params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode(); + + if (!('STYLES' in this.params_)) { + params['STYLES'] = ''; + } + + if (pixelRatio != 1) { + switch (this.serverType_) { + case ol.source.WMSServerType.GEOSERVER: + var dpi = (90 * pixelRatio + 0.5) | 0; + if ('FORMAT_OPTIONS' in params) { + params['FORMAT_OPTIONS'] += ';dpi:' + dpi; + } else { + params['FORMAT_OPTIONS'] = 'dpi:' + dpi; + } + break; + case ol.source.WMSServerType.MAPSERVER: + params['MAP_RESOLUTION'] = 90 * pixelRatio; + break; + case ol.source.WMSServerType.CARMENTA_SERVER: + case ol.source.WMSServerType.QGIS: + params['DPI'] = 90 * pixelRatio; + break; + default: + ol.asserts.assert(false, 8); // Unknown `serverType` configured + break; + } + } + + params['WIDTH'] = size[0]; + params['HEIGHT'] = size[1]; + + var axisOrientation = projection.getAxisOrientation(); + var bbox; + if (this.v13_ && axisOrientation.substr(0, 2) == 'ne') { + bbox = [extent[1], extent[0], extent[3], extent[2]]; + } else { + bbox = extent; + } + params['BBOX'] = bbox.join(','); + + return ol.uri.appendParams(/** @type {string} */ (this.url_), params); +}; + + +/** + * Return the URL used for this WMS source. + * @return {string|undefined} URL. + * @api stable + */ +ol.source.ImageWMS.prototype.getUrl = function() { + return this.url_; +}; + + +/** + * Set the image load function of the source. + * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function. + * @api + */ +ol.source.ImageWMS.prototype.setImageLoadFunction = function( + imageLoadFunction) { + this.image_ = null; + this.imageLoadFunction_ = imageLoadFunction; + this.changed(); +}; + + +/** + * Set the URL to use for requests. + * @param {string|undefined} url URL. + * @api stable + */ +ol.source.ImageWMS.prototype.setUrl = function(url) { + if (url != this.url_) { + this.url_ = url; + this.image_ = null; + this.changed(); + } +}; + + +/** + * Update the user-provided params. + * @param {Object} params Params. + * @api stable + */ +ol.source.ImageWMS.prototype.updateParams = function(params) { + ol.obj.assign(this.params_, params); + this.updateV13_(); + this.image_ = null; + this.changed(); +}; + + +/** + * @private + */ +ol.source.ImageWMS.prototype.updateV13_ = function() { + var version = this.params_['VERSION'] || ol.DEFAULT_WMS_VERSION; + this.v13_ = ol.string.compareVersions(version, '1.3') >= 0; +}; + +goog.provide('ol.source.OSM'); + +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.source.XYZ'); + + +/** + * @classdesc + * Layer source for the OpenStreetMap tile server. + * + * @constructor + * @extends {ol.source.XYZ} + * @param {olx.source.OSMOptions=} opt_options Open Street Map options. + * @api stable + */ +ol.source.OSM = function(opt_options) { + + var options = opt_options || {}; + + var attributions; + if (options.attributions !== undefined) { + attributions = options.attributions; + } else { + attributions = [ol.source.OSM.ATTRIBUTION]; + } + + var crossOrigin = options.crossOrigin !== undefined ? + options.crossOrigin : 'anonymous'; + + var url = options.url !== undefined ? + options.url : 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + + ol.source.XYZ.call(this, { + attributions: attributions, + cacheSize: options.cacheSize, + crossOrigin: crossOrigin, + opaque: options.opaque !== undefined ? options.opaque : true, + maxZoom: options.maxZoom !== undefined ? options.maxZoom : 19, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileLoadFunction: options.tileLoadFunction, + url: url, + wrapX: options.wrapX + }); + +}; +ol.inherits(ol.source.OSM, ol.source.XYZ); + + +/** + * The attribution containing a link to the OpenStreetMap Copyright and License + * page. + * @const + * @type {ol.Attribution} + * @api + */ +ol.source.OSM.ATTRIBUTION = new ol.Attribution({ + html: '© ' + + '<a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> ' + + 'contributors.' +}); + +goog.provide('ol.ext.pixelworks'); +/** @typedef {function(*)} */ +ol.ext.pixelworks; +(function() { +var exports = {}; +var module = {exports: exports}; +var define; +/** + * @fileoverview + * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility} + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pixelworks = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ +var Processor = _dereq_('./processor'); + +exports.Processor = Processor; + +},{"./processor":2}],2:[function(_dereq_,module,exports){ +var newImageData = _dereq_('./util').newImageData; + +/** + * Create a function for running operations. This function is serialized for + * use in a worker. + * @param {function(Array, Object):*} operation The operation. + * @return {function(Object):ArrayBuffer} A function that takes an object with + * buffers, meta, imageOps, width, and height properties and returns an array + * buffer. + */ +function createMinion(operation) { + var workerHasImageData = true; + try { + new ImageData(10, 10); + } catch (_) { + workerHasImageData = false; + } + + function newWorkerImageData(data, width, height) { + if (workerHasImageData) { + return new ImageData(data, width, height); + } else { + return {data: data, width: width, height: height}; + } + } + + return function(data) { + // bracket notation for minification support + var buffers = data['buffers']; + var meta = data['meta']; + var imageOps = data['imageOps']; + var width = data['width']; + var height = data['height']; + + var numBuffers = buffers.length; + var numBytes = buffers[0].byteLength; + var output, b; + + if (imageOps) { + var images = new Array(numBuffers); + for (b = 0; b < numBuffers; ++b) { + images[b] = newWorkerImageData( + new Uint8ClampedArray(buffers[b]), width, height); + } + output = operation(images, meta).data; + } else { + output = new Uint8ClampedArray(numBytes); + var arrays = new Array(numBuffers); + var pixels = new Array(numBuffers); + for (b = 0; b < numBuffers; ++b) { + arrays[b] = new Uint8ClampedArray(buffers[b]); + pixels[b] = [0, 0, 0, 0]; + } + for (var i = 0; i < numBytes; i += 4) { + for (var j = 0; j < numBuffers; ++j) { + var array = arrays[j]; + pixels[j][0] = array[i]; + pixels[j][1] = array[i + 1]; + pixels[j][2] = array[i + 2]; + pixels[j][3] = array[i + 3]; + } + var pixel = operation(pixels, meta); + output[i] = pixel[0]; + output[i + 1] = pixel[1]; + output[i + 2] = pixel[2]; + output[i + 3] = pixel[3]; + } + } + return output.buffer; + }; +} + +/** + * Create a worker for running operations. + * @param {Object} config Configuration. + * @param {function(MessageEvent)} onMessage Called with a message event. + * @return {Worker} The worker. + */ +function createWorker(config, onMessage) { + var lib = Object.keys(config.lib || {}).map(function(name) { + return 'var ' + name + ' = ' + config.lib[name].toString() + ';'; + }); + + var lines = lib.concat([ + 'var __minion__ = (' + createMinion.toString() + ')(', config.operation.toString(), ');', + 'self.addEventListener("message", function(event) {', + ' var buffer = __minion__(event.data);', + ' self.postMessage({buffer: buffer, meta: event.data.meta}, [buffer]);', + '});' + ]); + + var blob = new Blob(lines, {type: 'text/javascript'}); + var source = URL.createObjectURL(blob); + var worker = new Worker(source); + worker.addEventListener('message', onMessage); + return worker; +} + +/** + * Create a faux worker for running operations. + * @param {Object} config Configuration. + * @param {function(MessageEvent)} onMessage Called with a message event. + * @return {Object} The faux worker. + */ +function createFauxWorker(config, onMessage) { + var minion = createMinion(config.operation); + return { + postMessage: function(data) { + setTimeout(function() { + onMessage({'data': {'buffer': minion(data), 'meta': data['meta']}}); + }, 0); + } + }; +} + +/** + * A processor runs pixel or image operations in workers. + * @param {Object} config Configuration. + */ +function Processor(config) { + this._imageOps = !!config.imageOps; + var threads; + if (config.threads === 0) { + threads = 0; + } else if (this._imageOps) { + threads = 1; + } else { + threads = config.threads || 1; + } + var workers = []; + if (threads) { + for (var i = 0; i < threads; ++i) { + workers[i] = createWorker(config, this._onWorkerMessage.bind(this, i)); + } + } else { + workers[0] = createFauxWorker(config, this._onWorkerMessage.bind(this, 0)); + } + this._workers = workers; + this._queue = []; + this._maxQueueLength = config.queue || Infinity; + this._running = 0; + this._dataLookup = {}; + this._job = null; +} + +/** + * Run operation on input data. + * @param {Array.<Array|ImageData>} inputs Array of pixels or image data + * (depending on the operation type). + * @param {Object} meta A user data object. This is passed to all operations + * and must be serializable. + * @param {function(Error, ImageData, Object)} callback Called when work + * completes. The first argument is any error. The second is the ImageData + * generated by operations. The third is the user data object. + */ +Processor.prototype.process = function(inputs, meta, callback) { + this._enqueue({ + inputs: inputs, + meta: meta, + callback: callback + }); + this._dispatch(); +}; + +/** + * Stop responding to any completed work and destroy the processor. + */ +Processor.prototype.destroy = function() { + for (var key in this) { + this[key] = null; + } + this._destroyed = true; +}; + +/** + * Add a job to the queue. + * @param {Object} job The job. + */ +Processor.prototype._enqueue = function(job) { + this._queue.push(job); + while (this._queue.length > this._maxQueueLength) { + this._queue.shift().callback(null, null); + } +}; + +/** + * Dispatch a job. + */ +Processor.prototype._dispatch = function() { + if (this._running === 0 && this._queue.length > 0) { + var job = this._job = this._queue.shift(); + var width = job.inputs[0].width; + var height = job.inputs[0].height; + var buffers = job.inputs.map(function(input) { + return input.data.buffer; + }); + var threads = this._workers.length; + this._running = threads; + if (threads === 1) { + this._workers[0].postMessage({ + 'buffers': buffers, + 'meta': job.meta, + 'imageOps': this._imageOps, + 'width': width, + 'height': height + }, buffers); + } else { + var length = job.inputs[0].data.length; + var segmentLength = 4 * Math.ceil(length / 4 / threads); + for (var i = 0; i < threads; ++i) { + var offset = i * segmentLength; + var slices = []; + for (var j = 0, jj = buffers.length; j < jj; ++j) { + slices.push(buffers[i].slice(offset, offset + segmentLength)); + } + this._workers[i].postMessage({ + 'buffers': slices, + 'meta': job.meta, + 'imageOps': this._imageOps, + 'width': width, + 'height': height + }, slices); + } + } + } +}; + +/** + * Handle messages from the worker. + * @param {number} index The worker index. + * @param {MessageEvent} event The message event. + */ +Processor.prototype._onWorkerMessage = function(index, event) { + if (this._destroyed) { + return; + } + this._dataLookup[index] = event.data; + --this._running; + if (this._running === 0) { + this._resolveJob(); + } +}; + +/** + * Resolve a job. If there are no more worker threads, the processor callback + * will be called. + */ +Processor.prototype._resolveJob = function() { + var job = this._job; + var threads = this._workers.length; + var data, meta; + if (threads === 1) { + data = new Uint8ClampedArray(this._dataLookup[0]['buffer']); + meta = this._dataLookup[0]['meta']; + } else { + var length = job.inputs[0].data.length; + data = new Uint8ClampedArray(length); + meta = new Array(length); + var segmentLength = 4 * Math.ceil(length / 4 / threads); + for (var i = 0; i < threads; ++i) { + var buffer = this._dataLookup[i]['buffer']; + var offset = i * segmentLength; + data.set(new Uint8ClampedArray(buffer), offset); + meta[i] = this._dataLookup[i]['meta']; + } + } + this._job = null; + this._dataLookup = {}; + job.callback(null, + newImageData(data, job.inputs[0].width, job.inputs[0].height), meta); + this._dispatch(); +}; + +module.exports = Processor; + +},{"./util":3}],3:[function(_dereq_,module,exports){ +var hasImageData = true; +try { + new ImageData(10, 10); +} catch (_) { + hasImageData = false; +} + +var context = document.createElement('canvas').getContext('2d'); + +function newImageData(data, width, height) { + if (hasImageData) { + return new ImageData(data, width, height); + } else { + var imageData = context.createImageData(width, height); + imageData.data.set(data); + return imageData; + } +} + +exports.newImageData = newImageData; + +},{}]},{},[1])(1) +}); +ol.ext.pixelworks = module.exports; +})(); + +goog.provide('ol.source.Raster'); + +goog.require('ol'); +goog.require('ol.transform'); +goog.require('ol.ImageCanvas'); +goog.require('ol.TileQueue'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.EventType'); +goog.require('ol.ext.pixelworks'); +goog.require('ol.extent'); +goog.require('ol.layer.Image'); +goog.require('ol.layer.Tile'); +goog.require('ol.obj'); +goog.require('ol.renderer.canvas.ImageLayer'); +goog.require('ol.renderer.canvas.TileLayer'); +goog.require('ol.source.Image'); +goog.require('ol.source.State'); +goog.require('ol.source.Tile'); + + +/** + * @classdesc + * A source that transforms data from any number of input sources using an array + * of {@link ol.RasterOperation} functions to transform input pixel values into + * output pixel values. + * + * @constructor + * @extends {ol.source.Image} + * @fires ol.source.Raster.Event + * @param {olx.source.RasterOptions} options Options. + * @api + */ +ol.source.Raster = function(options) { + + /** + * @private + * @type {*} + */ + this.worker_ = null; + + /** + * @private + * @type {ol.source.Raster.OperationType} + */ + this.operationType_ = options.operationType !== undefined ? + options.operationType : ol.source.Raster.OperationType.PIXEL; + + /** + * @private + * @type {number} + */ + this.threads_ = options.threads !== undefined ? options.threads : 1; + + /** + * @private + * @type {Array.<ol.renderer.canvas.Layer>} + */ + this.renderers_ = ol.source.Raster.createRenderers_(options.sources); + + for (var r = 0, rr = this.renderers_.length; r < rr; ++r) { + ol.events.listen(this.renderers_[r], ol.events.EventType.CHANGE, + this.changed, this); + } + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.canvasContext_ = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {ol.TileQueue} + */ + this.tileQueue_ = new ol.TileQueue( + function() { + return 1; + }, + this.changed.bind(this)); + + var layerStatesArray = ol.source.Raster.getLayerStatesArray_(this.renderers_); + var layerStates = {}; + for (var i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerStates[ol.getUid(layerStatesArray[i].layer)] = layerStatesArray[i]; + } + + /** + * The most recently rendered state. + * @type {?ol.SourceRasterRenderedState} + * @private + */ + this.renderedState_ = null; + + /** + * The most recently rendered image canvas. + * @type {ol.ImageCanvas} + * @private + */ + this.renderedImageCanvas_ = null; + + /** + * @private + * @type {olx.FrameState} + */ + this.frameState_ = { + animate: false, + attributions: {}, + coordinateToPixelTransform: ol.transform.create(), + extent: null, + focus: null, + index: 0, + layerStates: layerStates, + layerStatesArray: layerStatesArray, + logos: {}, + pixelRatio: 1, + pixelToCoordinateTransform: ol.transform.create(), + postRenderFunctions: [], + size: [0, 0], + skippedFeatureUids: {}, + tileQueue: this.tileQueue_, + time: Date.now(), + usedTiles: {}, + viewState: /** @type {olx.ViewState} */ ({ + rotation: 0 + }), + viewHints: [], + wantedTiles: {} + }; + + ol.source.Image.call(this, {}); + + if (options.operation !== undefined) { + this.setOperation(options.operation, options.lib); + } + +}; +ol.inherits(ol.source.Raster, ol.source.Image); + + +/** + * Set the operation. + * @param {ol.RasterOperation} operation New operation. + * @param {Object=} opt_lib Functions that will be available to operations run + * in a worker. + * @api + */ +ol.source.Raster.prototype.setOperation = function(operation, opt_lib) { + this.worker_ = new ol.ext.pixelworks.Processor({ + operation: operation, + imageOps: this.operationType_ === ol.source.Raster.OperationType.IMAGE, + queue: 1, + lib: opt_lib, + threads: this.threads_ + }); + this.changed(); +}; + + +/** + * Update the stored frame state. + * @param {ol.Extent} extent The view extent (in map units). + * @param {number} resolution The view resolution. + * @param {ol.proj.Projection} projection The view projection. + * @return {olx.FrameState} The updated frame state. + * @private + */ +ol.source.Raster.prototype.updateFrameState_ = function(extent, resolution, projection) { + + var frameState = /** @type {olx.FrameState} */ ( + ol.obj.assign({}, this.frameState_)); + + frameState.viewState = /** @type {olx.ViewState} */ ( + ol.obj.assign({}, frameState.viewState)); + + var center = ol.extent.getCenter(extent); + var width = Math.round(ol.extent.getWidth(extent) / resolution); + var height = Math.round(ol.extent.getHeight(extent) / resolution); + + frameState.extent = extent; + frameState.focus = ol.extent.getCenter(extent); + frameState.size[0] = width; + frameState.size[1] = height; + + var viewState = frameState.viewState; + viewState.center = center; + viewState.projection = projection; + viewState.resolution = resolution; + return frameState; +}; + + +/** + * Determine if the most recently rendered image canvas is dirty. + * @param {ol.Extent} extent The requested extent. + * @param {number} resolution The requested resolution. + * @return {boolean} The image is dirty. + * @private + */ +ol.source.Raster.prototype.isDirty_ = function(extent, resolution) { + var state = this.renderedState_; + return !state || + this.getRevision() !== state.revision || + resolution !== state.resolution || + !ol.extent.equals(extent, state.extent); +}; + + +/** + * @inheritDoc + */ +ol.source.Raster.prototype.getImage = function(extent, resolution, pixelRatio, projection) { + + if (!this.allSourcesReady_()) { + return null; + } + + var currentExtent = extent.slice(); + if (!this.isDirty_(currentExtent, resolution)) { + return this.renderedImageCanvas_; + } + + var context = this.canvasContext_; + var canvas = context.canvas; + + var width = Math.round(ol.extent.getWidth(currentExtent) / resolution); + var height = Math.round(ol.extent.getHeight(currentExtent) / resolution); + + if (width !== canvas.width || + height !== canvas.height) { + canvas.width = width; + canvas.height = height; + } + + var frameState = this.updateFrameState_(currentExtent, resolution, projection); + + var imageCanvas = new ol.ImageCanvas( + currentExtent, resolution, 1, this.getAttributions(), canvas, + this.composeFrame_.bind(this, frameState)); + + this.renderedImageCanvas_ = imageCanvas; + + this.renderedState_ = { + extent: currentExtent, + resolution: resolution, + revision: this.getRevision() + }; + + return imageCanvas; +}; + + +/** + * Determine if all sources are ready. + * @return {boolean} All sources are ready. + * @private + */ +ol.source.Raster.prototype.allSourcesReady_ = function() { + var ready = true; + var source; + for (var i = 0, ii = this.renderers_.length; i < ii; ++i) { + source = this.renderers_[i].getLayer().getSource(); + if (source.getState() !== ol.source.State.READY) { + ready = false; + break; + } + } + return ready; +}; + + +/** + * Compose the frame. This renders data from all sources, runs pixel-wise + * operations, and renders the result to the stored canvas context. + * @param {olx.FrameState} frameState The frame state. + * @param {function(Error)} callback Called when composition is complete. + * @private + */ +ol.source.Raster.prototype.composeFrame_ = function(frameState, callback) { + var len = this.renderers_.length; + var imageDatas = new Array(len); + for (var i = 0; i < len; ++i) { + var imageData = ol.source.Raster.getImageData_( + this.renderers_[i], frameState, frameState.layerStatesArray[i]); + if (imageData) { + imageDatas[i] = imageData; + } else { + // image not yet ready + imageDatas = null; + break; + } + } + + if (imageDatas) { + var data = {}; + this.dispatchEvent(new ol.source.Raster.Event( + ol.source.Raster.EventType.BEFOREOPERATIONS, frameState, data)); + + this.worker_.process(imageDatas, data, + this.onWorkerComplete_.bind(this, frameState, callback)); + } + + frameState.tileQueue.loadMoreTiles(16, 16); +}; + + +/** + * Called when pixel processing is complete. + * @param {olx.FrameState} frameState The frame state. + * @param {function(Error)} callback Called when rendering is complete. + * @param {Error} err Any error during processing. + * @param {ImageData} output The output image data. + * @param {Object} data The user data. + * @private + */ +ol.source.Raster.prototype.onWorkerComplete_ = function(frameState, callback, err, output, data) { + if (err) { + callback(err); + return; + } + if (!output) { + // job aborted + return; + } + + this.dispatchEvent(new ol.source.Raster.Event( + ol.source.Raster.EventType.AFTEROPERATIONS, frameState, data)); + + var resolution = frameState.viewState.resolution / frameState.pixelRatio; + if (!this.isDirty_(frameState.extent, resolution)) { + this.canvasContext_.putImageData(output, 0, 0); + } + + callback(null); +}; + + +/** + * Get image data from a renderer. + * @param {ol.renderer.canvas.Layer} renderer Layer renderer. + * @param {olx.FrameState} frameState The frame state. + * @param {ol.LayerState} layerState The layer state. + * @return {ImageData} The image data. + * @private + */ +ol.source.Raster.getImageData_ = function(renderer, frameState, layerState) { + if (!renderer.prepareFrame(frameState, layerState)) { + return null; + } + var width = frameState.size[0]; + var height = frameState.size[1]; + if (!ol.source.Raster.context_) { + ol.source.Raster.context_ = ol.dom.createCanvasContext2D(width, height); + } else { + var canvas = ol.source.Raster.context_.canvas; + if (canvas.width !== width || canvas.height !== height) { + ol.source.Raster.context_ = ol.dom.createCanvasContext2D(width, height); + } else { + ol.source.Raster.context_.clearRect(0, 0, width, height); + } + } + renderer.composeFrame(frameState, layerState, ol.source.Raster.context_); + return ol.source.Raster.context_.getImageData(0, 0, width, height); +}; + + +/** + * A reusable canvas context. + * @type {CanvasRenderingContext2D} + * @private + */ +ol.source.Raster.context_ = null; + + +/** + * Get a list of layer states from a list of renderers. + * @param {Array.<ol.renderer.canvas.Layer>} renderers Layer renderers. + * @return {Array.<ol.LayerState>} The layer states. + * @private + */ +ol.source.Raster.getLayerStatesArray_ = function(renderers) { + return renderers.map(function(renderer) { + return renderer.getLayer().getLayerState(); + }); +}; + + +/** + * Create renderers for all sources. + * @param {Array.<ol.source.Source>} sources The sources. + * @return {Array.<ol.renderer.canvas.Layer>} Array of layer renderers. + * @private + */ +ol.source.Raster.createRenderers_ = function(sources) { + var len = sources.length; + var renderers = new Array(len); + for (var i = 0; i < len; ++i) { + renderers[i] = ol.source.Raster.createRenderer_(sources[i]); + } + return renderers; +}; + + +/** + * Create a renderer for the provided source. + * @param {ol.source.Source} source The source. + * @return {ol.renderer.canvas.Layer} The renderer. + * @private + */ +ol.source.Raster.createRenderer_ = function(source) { + var renderer = null; + if (source instanceof ol.source.Tile) { + renderer = ol.source.Raster.createTileRenderer_(source); + } else if (source instanceof ol.source.Image) { + renderer = ol.source.Raster.createImageRenderer_(source); + } else { + ol.DEBUG && console.assert(false, 'Unsupported source type: ' + source); + } + return renderer; +}; + + +/** + * Create an image renderer for the provided source. + * @param {ol.source.Image} source The source. + * @return {ol.renderer.canvas.Layer} The renderer. + * @private + */ +ol.source.Raster.createImageRenderer_ = function(source) { + var layer = new ol.layer.Image({source: source}); + return new ol.renderer.canvas.ImageLayer(layer); +}; + + +/** + * Create a tile renderer for the provided source. + * @param {ol.source.Tile} source The source. + * @return {ol.renderer.canvas.Layer} The renderer. + * @private + */ +ol.source.Raster.createTileRenderer_ = function(source) { + var layer = new ol.layer.Tile({source: source}); + return new ol.renderer.canvas.TileLayer(layer); +}; + + +/** + * @classdesc + * Events emitted by {@link ol.source.Raster} instances are instances of this + * type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.source.RasterEvent} + * @param {string} type Type. + * @param {olx.FrameState} frameState The frame state. + * @param {Object} data An object made available to operations. + */ +ol.source.Raster.Event = function(type, frameState, data) { + ol.events.Event.call(this, type); + + /** + * The raster extent. + * @type {ol.Extent} + * @api + */ + this.extent = frameState.extent; + + /** + * The pixel resolution (map units per pixel). + * @type {number} + * @api + */ + this.resolution = frameState.viewState.resolution / frameState.pixelRatio; + + /** + * An object made available to all operations. This can be used by operations + * as a storage object (e.g. for calculating statistics). + * @type {Object} + * @api + */ + this.data = data; + +}; +ol.inherits(ol.source.Raster.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.source.Raster.EventType = { + /** + * Triggered before operations are run. + * @event ol.source.Raster.Event#beforeoperations + * @api + */ + BEFOREOPERATIONS: 'beforeoperations', + + /** + * Triggered after operations are run. + * @event ol.source.Raster.Event#afteroperations + * @api + */ + AFTEROPERATIONS: 'afteroperations' +}; + + +/** + * Raster operation type. Supported values are `'pixel'` and `'image'`. + * @enum {string} + */ +ol.source.Raster.OperationType = { + PIXEL: 'pixel', + IMAGE: 'image' +}; + +goog.provide('ol.source.Stamen'); + +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.source.OSM'); +goog.require('ol.source.XYZ'); + + +/** + * @classdesc + * Layer source for the Stamen tile server. + * + * @constructor + * @extends {ol.source.XYZ} + * @param {olx.source.StamenOptions} options Stamen options. + * @api stable + */ +ol.source.Stamen = function(options) { + + var i = options.layer.indexOf('-'); + var provider = i == -1 ? options.layer : options.layer.slice(0, i); + ol.DEBUG && console.assert(provider in ol.source.Stamen.ProviderConfig, + 'known provider configured'); + var providerConfig = ol.source.Stamen.ProviderConfig[provider]; + + ol.DEBUG && console.assert(options.layer in ol.source.Stamen.LayerConfig, + 'known layer configured'); + var layerConfig = ol.source.Stamen.LayerConfig[options.layer]; + + var url = options.url !== undefined ? options.url : + 'https://stamen-tiles-{a-d}.a.ssl.fastly.net/' + options.layer + + '/{z}/{x}/{y}.' + layerConfig.extension; + + ol.source.XYZ.call(this, { + attributions: ol.source.Stamen.ATTRIBUTIONS, + cacheSize: options.cacheSize, + crossOrigin: 'anonymous', + maxZoom: options.maxZoom != undefined ? options.maxZoom : providerConfig.maxZoom, + minZoom: options.minZoom != undefined ? options.minZoom : providerConfig.minZoom, + opaque: layerConfig.opaque, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileLoadFunction: options.tileLoadFunction, + url: url + }); + +}; +ol.inherits(ol.source.Stamen, ol.source.XYZ); + + +/** + * @const + * @type {Array.<ol.Attribution>} + */ +ol.source.Stamen.ATTRIBUTIONS = [ + new ol.Attribution({ + html: 'Map tiles by <a href="http://stamen.com/">Stamen Design</a>, ' + + 'under <a href="http://creativecommons.org/licenses/by/3.0/">CC BY' + + ' 3.0</a>.' + }), + ol.source.OSM.ATTRIBUTION +]; + +/** + * @type {Object.<string, {extension: string, opaque: boolean}>} + */ +ol.source.Stamen.LayerConfig = { + 'terrain': { + extension: 'jpg', + opaque: true + }, + 'terrain-background': { + extension: 'jpg', + opaque: true + }, + 'terrain-labels': { + extension: 'png', + opaque: false + }, + 'terrain-lines': { + extension: 'png', + opaque: false + }, + 'toner-background': { + extension: 'png', + opaque: true + }, + 'toner': { + extension: 'png', + opaque: true + }, + 'toner-hybrid': { + extension: 'png', + opaque: false + }, + 'toner-labels': { + extension: 'png', + opaque: false + }, + 'toner-lines': { + extension: 'png', + opaque: false + }, + 'toner-lite': { + extension: 'png', + opaque: true + }, + 'watercolor': { + extension: 'jpg', + opaque: true + } +}; + +/** + * @type {Object.<string, {minZoom: number, maxZoom: number}>} + */ +ol.source.Stamen.ProviderConfig = { + 'terrain': { + minZoom: 4, + maxZoom: 18 + }, + 'toner': { + minZoom: 0, + maxZoom: 20 + }, + 'watercolor': { + minZoom: 1, + maxZoom: 16 + } +}; + +goog.provide('ol.source.TileArcGISRest'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.math'); +goog.require('ol.obj'); +goog.require('ol.size'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilecoord'); +goog.require('ol.uri'); + + +/** + * @classdesc + * Layer source for tile data from ArcGIS Rest services. Map and Image + * Services are supported. + * + * For cached ArcGIS services, better performance is available using the + * {@link ol.source.XYZ} data source. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.TileArcGISRestOptions=} opt_options Tile ArcGIS Rest + * options. + * @api + */ +ol.source.TileArcGISRest = function(opt_options) { + + var options = opt_options || {}; + + ol.source.TileImage.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + logo: options.logo, + projection: options.projection, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileGrid: options.tileGrid, + tileLoadFunction: options.tileLoadFunction, + url: options.url, + urls: options.urls, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + + /** + * @private + * @type {!Object} + */ + this.params_ = options.params || {}; + + /** + * @private + * @type {ol.Extent} + */ + this.tmpExtent_ = ol.extent.createEmpty(); + + this.setKey(this.getKeyForParams_()); +}; +ol.inherits(ol.source.TileArcGISRest, ol.source.TileImage); + + +/** + * @private + * @return {string} The key for the current params. + */ +ol.source.TileArcGISRest.prototype.getKeyForParams_ = function() { + var i = 0; + var res = []; + for (var key in this.params_) { + res[i++] = key + '-' + this.params_[key]; + } + return res.join('/'); +}; + + +/** + * Get the user-provided params, i.e. those passed to the constructor through + * the "params" option, and possibly updated using the updateParams method. + * @return {Object} Params. + * @api + */ +ol.source.TileArcGISRest.prototype.getParams = function() { + return this.params_; +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Size} tileSize Tile size. + * @param {ol.Extent} tileExtent Tile extent. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {Object} params Params. + * @return {string|undefined} Request URL. + * @private + */ +ol.source.TileArcGISRest.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent, + pixelRatio, projection, params) { + + var urls = this.urls; + if (!urls) { + return undefined; + } + + // ArcGIS Server only wants the numeric portion of the projection ID. + var srid = projection.getCode().split(':').pop(); + + params['SIZE'] = tileSize[0] + ',' + tileSize[1]; + params['BBOX'] = tileExtent.join(','); + params['BBOXSR'] = srid; + params['IMAGESR'] = srid; + params['DPI'] = Math.round( + params['DPI'] ? params['DPI'] * pixelRatio : 90 * pixelRatio + ); + + var url; + if (urls.length == 1) { + url = urls[0]; + } else { + var index = ol.math.modulo(ol.tilecoord.hash(tileCoord), urls.length); + url = urls[index]; + } + + var modifiedUrl = url + .replace(/MapServer\/?$/, 'MapServer/export') + .replace(/ImageServer\/?$/, 'ImageServer/exportImage'); + return ol.uri.appendParams(modifiedUrl, params); +}; + + +/** + * @inheritDoc + */ +ol.source.TileArcGISRest.prototype.getTilePixelRatio = function(pixelRatio) { + return /** @type {number} */ (pixelRatio); +}; + + +/** + * @inheritDoc + */ +ol.source.TileArcGISRest.prototype.fixedTileUrlFunction = function(tileCoord, pixelRatio, projection) { + + var tileGrid = this.getTileGrid(); + if (!tileGrid) { + tileGrid = this.getTileGridForProjection(projection); + } + + if (tileGrid.getResolutions().length <= tileCoord[0]) { + return undefined; + } + + var tileExtent = tileGrid.getTileCoordExtent( + tileCoord, this.tmpExtent_); + var tileSize = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0]), this.tmpSize); + + if (pixelRatio != 1) { + tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize); + } + + // Apply default params and override with user specified values. + var baseParams = { + 'F': 'image', + 'FORMAT': 'PNG32', + 'TRANSPARENT': true + }; + ol.obj.assign(baseParams, this.params_); + + return this.getRequestUrl_(tileCoord, tileSize, tileExtent, + pixelRatio, projection, baseParams); +}; + + +/** + * Update the user-provided params. + * @param {Object} params Params. + * @api stable + */ +ol.source.TileArcGISRest.prototype.updateParams = function(params) { + ol.obj.assign(this.params_, params); + this.setKey(this.getKeyForParams_()); +}; + +goog.provide('ol.source.TileDebug'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.dom'); +goog.require('ol.size'); +goog.require('ol.source.Tile'); + + +/** + * @classdesc + * A pseudo tile source, which does not fetch tiles from a server, but renders + * a grid outline for the tile grid/projection along with the coordinates for + * each tile. See examples/canvas-tiles for an example. + * + * Uses Canvas context2d, so requires Canvas support. + * + * @constructor + * @extends {ol.source.Tile} + * @param {olx.source.TileDebugOptions} options Debug tile options. + * @api + */ +ol.source.TileDebug = function(options) { + + ol.source.Tile.call(this, { + opaque: false, + projection: options.projection, + tileGrid: options.tileGrid, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + +}; +ol.inherits(ol.source.TileDebug, ol.source.Tile); + + +/** + * @inheritDoc + */ +ol.source.TileDebug.prototype.getTile = function(z, x, y) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + return /** @type {!ol.source.TileDebug.Tile_} */ (this.tileCache.get(tileCoordKey)); + } else { + var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z)); + var tileCoord = [z, x, y]; + var textTileCoord = this.getTileCoordForTileUrlFunction(tileCoord); + var text = !textTileCoord ? '' : + this.getTileCoordForTileUrlFunction(textTileCoord).toString(); + var tile = new ol.source.TileDebug.Tile_(tileCoord, tileSize, text); + this.tileCache.set(tileCoordKey, tile); + return tile; + } +}; + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Size} tileSize Tile size. + * @param {string} text Text. + * @private + */ +ol.source.TileDebug.Tile_ = function(tileCoord, tileSize, text) { + + ol.Tile.call(this, tileCoord, ol.Tile.State.LOADED); + + /** + * @private + * @type {ol.Size} + */ + this.tileSize_ = tileSize; + + /** + * @private + * @type {string} + */ + this.text_ = text; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = null; + +}; +ol.inherits(ol.source.TileDebug.Tile_, ol.Tile); + + +/** + * Get the image element for this tile. + * @return {HTMLCanvasElement} Image. + */ +ol.source.TileDebug.Tile_.prototype.getImage = function() { + if (this.canvas_) { + return this.canvas_; + } else { + var tileSize = this.tileSize_; + var context = ol.dom.createCanvasContext2D(tileSize[0], tileSize[1]); + + context.strokeStyle = 'black'; + context.strokeRect(0.5, 0.5, tileSize[0] + 0.5, tileSize[1] + 0.5); + + context.fillStyle = 'black'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.font = '24px sans-serif'; + context.fillText(this.text_, tileSize[0] / 2, tileSize[1] / 2); + + this.canvas_ = context.canvas; + return context.canvas; + } +}; + +// FIXME check order of async callbacks + +/** + * @see http://mapbox.com/developers/api/ + */ + +goog.provide('ol.source.TileJSON'); + +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.extent'); +goog.require('ol.net'); +goog.require('ol.proj'); +goog.require('ol.source.State'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * Layer source for tile data in TileJSON format. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.TileJSONOptions} options TileJSON options. + * @api stable + */ +ol.source.TileJSON = function(options) { + + /** + * @type {TileJSON} + * @private + */ + this.tileJSON_ = null; + + ol.source.TileImage.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + projection: ol.proj.get('EPSG:3857'), + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + state: ol.source.State.LOADING, + tileLoadFunction: options.tileLoadFunction, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + + if (options.jsonp) { + ol.net.jsonp(options.url, this.handleTileJSONResponse.bind(this), + this.handleTileJSONError.bind(this)); + } else { + var client = new XMLHttpRequest(); + client.addEventListener('load', this.onXHRLoad_.bind(this)); + client.addEventListener('error', this.onXHRError_.bind(this)); + client.open('GET', options.url); + client.send(); + } + +}; +ol.inherits(ol.source.TileJSON, ol.source.TileImage); + + +/** + * @private + * @param {Event} event The load event. + */ +ol.source.TileJSON.prototype.onXHRLoad_ = function(event) { + var client = /** @type {XMLHttpRequest} */ (event.target); + // status will be 0 for file:// urls + if (!client.status || client.status >= 200 && client.status < 300) { + var response; + try { + response = /** @type {TileJSON} */(JSON.parse(client.responseText)); + } catch (err) { + this.handleTileJSONError(); + return; + } + this.handleTileJSONResponse(response); + } else { + this.handleTileJSONError(); + } +}; + + +/** + * @private + * @param {Event} event The error event. + */ +ol.source.TileJSON.prototype.onXHRError_ = function(event) { + this.handleTileJSONError(); +}; + + +/** + * @return {TileJSON} The tilejson object. + * @api + */ +ol.source.TileJSON.prototype.getTileJSON = function() { + return this.tileJSON_; +}; + + +/** + * @protected + * @param {TileJSON} tileJSON Tile JSON. + */ +ol.source.TileJSON.prototype.handleTileJSONResponse = function(tileJSON) { + + var epsg4326Projection = ol.proj.get('EPSG:4326'); + + var sourceProjection = this.getProjection(); + var extent; + if (tileJSON.bounds !== undefined) { + var transform = ol.proj.getTransformFromProjections( + epsg4326Projection, sourceProjection); + extent = ol.extent.applyTransform(tileJSON.bounds, transform); + } + + if (tileJSON.scheme !== undefined) { + ol.DEBUG && console.assert(tileJSON.scheme == 'xyz', 'tileJSON-scheme is "xyz"'); + } + var minZoom = tileJSON.minzoom || 0; + var maxZoom = tileJSON.maxzoom || 22; + var tileGrid = ol.tilegrid.createXYZ({ + extent: ol.tilegrid.extentFromProjection(sourceProjection), + maxZoom: maxZoom, + minZoom: minZoom + }); + this.tileGrid = tileGrid; + + this.tileUrlFunction = + ol.TileUrlFunction.createFromTemplates(tileJSON.tiles, tileGrid); + + if (tileJSON.attribution !== undefined && !this.getAttributions()) { + var attributionExtent = extent !== undefined ? + extent : epsg4326Projection.getExtent(); + /** @type {Object.<string, Array.<ol.TileRange>>} */ + var tileRanges = {}; + var z, zKey; + for (z = minZoom; z <= maxZoom; ++z) { + zKey = z.toString(); + tileRanges[zKey] = + [tileGrid.getTileRangeForExtentAndZ(attributionExtent, z)]; + } + this.setAttributions([ + new ol.Attribution({ + html: tileJSON.attribution, + tileRanges: tileRanges + }) + ]); + } + this.tileJSON_ = tileJSON; + this.setState(ol.source.State.READY); + +}; + + +/** + * @protected + */ +ol.source.TileJSON.prototype.handleTileJSONError = function() { + this.setState(ol.source.State.ERROR); +}; + +goog.provide('ol.source.TileUTFGrid'); + +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.Tile'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.asserts'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.net'); +goog.require('ol.proj'); +goog.require('ol.source.State'); +goog.require('ol.source.Tile'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * Layer source for UTFGrid interaction data loaded from TileJSON format. + * + * @constructor + * @extends {ol.source.Tile} + * @param {olx.source.TileUTFGridOptions} options Source options. + * @api + */ +ol.source.TileUTFGrid = function(options) { + ol.source.Tile.call(this, { + projection: ol.proj.get('EPSG:3857'), + state: ol.source.State.LOADING + }); + + /** + * @private + * @type {boolean} + */ + this.preemptive_ = options.preemptive !== undefined ? + options.preemptive : true; + + /** + * @private + * @type {!ol.TileUrlFunctionType} + */ + this.tileUrlFunction_ = ol.TileUrlFunction.nullTileUrlFunction; + + /** + * @private + * @type {string|undefined} + */ + this.template_ = undefined; + + /** + * @private + * @type {boolean} + */ + this.jsonp_ = options.jsonp || false; + + if (options.url) { + if (this.jsonp_) { + ol.net.jsonp(options.url, this.handleTileJSONResponse.bind(this), + this.handleTileJSONError.bind(this)); + } else { + var client = new XMLHttpRequest(); + client.addEventListener('load', this.onXHRLoad_.bind(this)); + client.addEventListener('error', this.onXHRError_.bind(this)); + client.open('GET', options.url); + client.send(); + } + } else if (options.tileJSON) { + this.handleTileJSONResponse(options.tileJSON); + } else { + ol.asserts.assert(false, 51); // Either `url` or `tileJSON` options must be provided + } +}; +ol.inherits(ol.source.TileUTFGrid, ol.source.Tile); + + +/** + * @private + * @param {Event} event The load event. + */ +ol.source.TileUTFGrid.prototype.onXHRLoad_ = function(event) { + var client = /** @type {XMLHttpRequest} */ (event.target); + // status will be 0 for file:// urls + if (!client.status || client.status >= 200 && client.status < 300) { + var response; + try { + response = /** @type {TileJSON} */(JSON.parse(client.responseText)); + } catch (err) { + this.handleTileJSONError(); + return; + } + this.handleTileJSONResponse(response); + } else { + this.handleTileJSONError(); + } +}; + + +/** + * @private + * @param {Event} event The error event. + */ +ol.source.TileUTFGrid.prototype.onXHRError_ = function(event) { + this.handleTileJSONError(); +}; + + +/** + * Return the template from TileJSON. + * @return {string|undefined} The template from TileJSON. + * @api + */ +ol.source.TileUTFGrid.prototype.getTemplate = function() { + return this.template_; +}; + + +/** + * Calls the callback (synchronously by default) with the available data + * for given coordinate and resolution (or `null` if not yet loaded or + * in case of an error). + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {function(this: T, *)} callback Callback. + * @param {T=} opt_this The object to use as `this` in the callback. + * @param {boolean=} opt_request If `true` the callback is always async. + * The tile data is requested if not yet loaded. + * @template T + * @api + */ +ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution = function( + coordinate, resolution, callback, opt_this, opt_request) { + if (this.tileGrid) { + var tileCoord = this.tileGrid.getTileCoordForCoordAndResolution( + coordinate, resolution); + var tile = /** @type {!ol.source.TileUTFGrid.Tile_} */(this.getTile( + tileCoord[0], tileCoord[1], tileCoord[2], 1, this.getProjection())); + tile.forDataAtCoordinate(coordinate, callback, opt_this, opt_request); + } else { + if (opt_request === true) { + setTimeout(function() { + callback.call(opt_this, null); + }, 0); + } else { + callback.call(opt_this, null); + } + } +}; + + +/** + * @protected + */ +ol.source.TileUTFGrid.prototype.handleTileJSONError = function() { + this.setState(ol.source.State.ERROR); +}; + + +/** + * TODO: very similar to ol.source.TileJSON#handleTileJSONResponse + * @protected + * @param {TileJSON} tileJSON Tile JSON. + */ +ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) { + + var epsg4326Projection = ol.proj.get('EPSG:4326'); + + var sourceProjection = this.getProjection(); + var extent; + if (tileJSON.bounds !== undefined) { + var transform = ol.proj.getTransformFromProjections( + epsg4326Projection, sourceProjection); + extent = ol.extent.applyTransform(tileJSON.bounds, transform); + } + + if (tileJSON.scheme !== undefined) { + ol.DEBUG && console.assert(tileJSON.scheme == 'xyz', 'tileJSON-scheme is "xyz"'); + } + var minZoom = tileJSON.minzoom || 0; + var maxZoom = tileJSON.maxzoom || 22; + var tileGrid = ol.tilegrid.createXYZ({ + extent: ol.tilegrid.extentFromProjection(sourceProjection), + maxZoom: maxZoom, + minZoom: minZoom + }); + this.tileGrid = tileGrid; + + this.template_ = tileJSON.template; + + var grids = tileJSON.grids; + if (!grids) { + this.setState(ol.source.State.ERROR); + return; + } + + this.tileUrlFunction_ = + ol.TileUrlFunction.createFromTemplates(grids, tileGrid); + + if (tileJSON.attribution !== undefined) { + var attributionExtent = extent !== undefined ? + extent : epsg4326Projection.getExtent(); + /** @type {Object.<string, Array.<ol.TileRange>>} */ + var tileRanges = {}; + var z, zKey; + for (z = minZoom; z <= maxZoom; ++z) { + zKey = z.toString(); + tileRanges[zKey] = + [tileGrid.getTileRangeForExtentAndZ(attributionExtent, z)]; + } + this.setAttributions([ + new ol.Attribution({ + html: tileJSON.attribution, + tileRanges: tileRanges + }) + ]); + } + + this.setState(ol.source.State.READY); + +}; + + +/** + * @inheritDoc + */ +ol.source.TileUTFGrid.prototype.getTile = function(z, x, y, pixelRatio, projection) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey)); + } else { + ol.DEBUG && console.assert(projection, 'argument projection is truthy'); + var tileCoord = [z, x, y]; + var urlTileCoord = + this.getTileCoordForTileUrlFunction(tileCoord, projection); + var tileUrl = this.tileUrlFunction_(urlTileCoord, pixelRatio, projection); + var tile = new ol.source.TileUTFGrid.Tile_( + tileCoord, + tileUrl !== undefined ? ol.Tile.State.IDLE : ol.Tile.State.EMPTY, + tileUrl !== undefined ? tileUrl : '', + this.tileGrid.getTileCoordExtent(tileCoord), + this.preemptive_, + this.jsonp_); + this.tileCache.set(tileCoordKey, tile); + return tile; + } +}; + + +/** + * @inheritDoc + */ +ol.source.TileUTFGrid.prototype.useTile = function(z, x, y) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + this.tileCache.get(tileCoordKey); + } +}; + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Tile.State} state State. + * @param {string} src Image source URI. + * @param {ol.Extent} extent Extent of the tile. + * @param {boolean} preemptive Load the tile when visible (before it's needed). + * @param {boolean} jsonp Load the tile as a script. + * @private + */ +ol.source.TileUTFGrid.Tile_ = function(tileCoord, state, src, extent, preemptive, jsonp) { + + ol.Tile.call(this, tileCoord, state); + + /** + * @private + * @type {string} + */ + this.src_ = src; + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = extent; + + /** + * @private + * @type {boolean} + */ + this.preemptive_ = preemptive; + + /** + * @private + * @type {Array.<string>} + */ + this.grid_ = null; + + /** + * @private + * @type {Array.<string>} + */ + this.keys_ = null; + + /** + * @private + * @type {Object.<string, Object>|undefined} + */ + this.data_ = null; + + + /** + * @private + * @type {boolean} + */ + this.jsonp_ = jsonp; + +}; +ol.inherits(ol.source.TileUTFGrid.Tile_, ol.Tile); + + +/** + * Get the image element for this tile. + * @return {Image} Image. + */ +ol.source.TileUTFGrid.Tile_.prototype.getImage = function() { + return null; +}; + + +/** + * Synchronously returns data at given coordinate (if available). + * @param {ol.Coordinate} coordinate Coordinate. + * @return {*} The data. + */ +ol.source.TileUTFGrid.Tile_.prototype.getData = function(coordinate) { + if (!this.grid_ || !this.keys_) { + return null; + } + var xRelative = (coordinate[0] - this.extent_[0]) / + (this.extent_[2] - this.extent_[0]); + var yRelative = (coordinate[1] - this.extent_[1]) / + (this.extent_[3] - this.extent_[1]); + + var row = this.grid_[Math.floor((1 - yRelative) * this.grid_.length)]; + + if (typeof row !== 'string') { + return null; + } + + var code = row.charCodeAt(Math.floor(xRelative * row.length)); + if (code >= 93) { + code--; + } + if (code >= 35) { + code--; + } + code -= 32; + + var data = null; + if (code in this.keys_) { + var id = this.keys_[code]; + if (this.data_ && id in this.data_) { + data = this.data_[id]; + } else { + data = id; + } + } + return data; +}; + + +/** + * Calls the callback (synchronously by default) with the available data + * for given coordinate (or `null` if not yet loaded). + * @param {ol.Coordinate} coordinate Coordinate. + * @param {function(this: T, *)} callback Callback. + * @param {T=} opt_this The object to use as `this` in the callback. + * @param {boolean=} opt_request If `true` the callback is always async. + * The tile data is requested if not yet loaded. + * @template T + */ +ol.source.TileUTFGrid.Tile_.prototype.forDataAtCoordinate = function(coordinate, callback, opt_this, opt_request) { + if (this.state == ol.Tile.State.IDLE && opt_request === true) { + ol.events.listenOnce(this, ol.events.EventType.CHANGE, function(e) { + callback.call(opt_this, this.getData(coordinate)); + }, this); + this.loadInternal_(); + } else { + if (opt_request === true) { + setTimeout(function() { + callback.call(opt_this, this.getData(coordinate)); + }.bind(this), 0); + } else { + callback.call(opt_this, this.getData(coordinate)); + } + } +}; + + +/** + * @inheritDoc + */ +ol.source.TileUTFGrid.Tile_.prototype.getKey = function() { + return this.src_; +}; + + +/** + * @private + */ +ol.source.TileUTFGrid.Tile_.prototype.handleError_ = function() { + this.state = ol.Tile.State.ERROR; + this.changed(); +}; + + +/** + * @param {!UTFGridJSON} json UTFGrid data. + * @private + */ +ol.source.TileUTFGrid.Tile_.prototype.handleLoad_ = function(json) { + this.grid_ = json.grid; + this.keys_ = json.keys; + this.data_ = json.data; + + this.state = ol.Tile.State.EMPTY; + this.changed(); +}; + + +/** + * @private + */ +ol.source.TileUTFGrid.Tile_.prototype.loadInternal_ = function() { + if (this.state == ol.Tile.State.IDLE) { + this.state = ol.Tile.State.LOADING; + if (this.jsonp_) { + ol.net.jsonp(this.src_, this.handleLoad_.bind(this), + this.handleError_.bind(this)); + } else { + var client = new XMLHttpRequest(); + client.addEventListener('load', this.onXHRLoad_.bind(this)); + client.addEventListener('error', this.onXHRError_.bind(this)); + client.open('GET', this.src_); + client.send(); + } + } +}; + + +/** + * @private + * @param {Event} event The load event. + */ +ol.source.TileUTFGrid.Tile_.prototype.onXHRLoad_ = function(event) { + var client = /** @type {XMLHttpRequest} */ (event.target); + // status will be 0 for file:// urls + if (!client.status || client.status >= 200 && client.status < 300) { + var response; + try { + response = /** @type {!UTFGridJSON} */(JSON.parse(client.responseText)); + } catch (err) { + this.handleError_(); + return; + } + this.handleLoad_(response); + } else { + this.handleError_(); + } +}; + + +/** + * @private + * @param {Event} event The error event. + */ +ol.source.TileUTFGrid.Tile_.prototype.onXHRError_ = function(event) { + this.handleError_(); +}; + + +/** + * Load not yet loaded URI. + */ +ol.source.TileUTFGrid.Tile_.prototype.load = function() { + if (this.preemptive_) { + this.loadInternal_(); + } +}; + +// FIXME add minZoom support +// FIXME add date line wrap (tile coord transform) +// FIXME cannot be shared between maps with different projections + +goog.provide('ol.source.TileWMS'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.math'); +goog.require('ol.proj'); +goog.require('ol.size'); +goog.require('ol.source.TileImage'); +goog.require('ol.source.WMSServerType'); +goog.require('ol.tilecoord'); +goog.require('ol.string'); +goog.require('ol.uri'); + +/** + * @classdesc + * Layer source for tile data from WMS servers. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.TileWMSOptions=} opt_options Tile WMS options. + * @api stable + */ +ol.source.TileWMS = function(opt_options) { + + var options = opt_options || {}; + + var params = options.params || {}; + + var transparent = 'TRANSPARENT' in params ? params['TRANSPARENT'] : true; + + ol.source.TileImage.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + logo: options.logo, + opaque: !transparent, + projection: options.projection, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileGrid: options.tileGrid, + tileLoadFunction: options.tileLoadFunction, + url: options.url, + urls: options.urls, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + + /** + * @private + * @type {number} + */ + this.gutter_ = options.gutter !== undefined ? options.gutter : 0; + + /** + * @private + * @type {!Object} + */ + this.params_ = params; + + /** + * @private + * @type {boolean} + */ + this.v13_ = true; + + /** + * @private + * @type {ol.source.WMSServerType|undefined} + */ + this.serverType_ = + /** @type {ol.source.WMSServerType|undefined} */ (options.serverType); + + /** + * @private + * @type {boolean} + */ + this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true; + + /** + * @private + * @type {string} + */ + this.coordKeyPrefix_ = ''; + this.resetCoordKeyPrefix_(); + + /** + * @private + * @type {ol.Extent} + */ + this.tmpExtent_ = ol.extent.createEmpty(); + + this.updateV13_(); + this.setKey(this.getKeyForParams_()); + +}; +ol.inherits(ol.source.TileWMS, ol.source.TileImage); + + +/** + * Return the GetFeatureInfo URL for the passed coordinate, resolution, and + * projection. Return `undefined` if the GetFeatureInfo URL cannot be + * constructed. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {ol.ProjectionLike} projection Projection. + * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should + * be provided. If `QUERY_LAYERS` is not provided then the layers specified + * in the `LAYERS` parameter will be used. `VERSION` should not be + * specified here. + * @return {string|undefined} GetFeatureInfo URL. + * @api stable + */ +ol.source.TileWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, params) { + + ol.DEBUG && console.assert(!('VERSION' in params), + 'key VERSION is not allowed in params'); + + var projectionObj = ol.proj.get(projection); + + var tileGrid = this.getTileGrid(); + if (!tileGrid) { + tileGrid = this.getTileGridForProjection(projectionObj); + } + + var tileCoord = tileGrid.getTileCoordForCoordAndResolution( + coordinate, resolution); + + if (tileGrid.getResolutions().length <= tileCoord[0]) { + return undefined; + } + + var tileResolution = tileGrid.getResolution(tileCoord[0]); + var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_); + var tileSize = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0]), this.tmpSize); + + var gutter = this.gutter_; + if (gutter !== 0) { + tileSize = ol.size.buffer(tileSize, gutter, this.tmpSize); + tileExtent = ol.extent.buffer(tileExtent, + tileResolution * gutter, tileExtent); + } + + var baseParams = { + 'SERVICE': 'WMS', + 'VERSION': ol.DEFAULT_WMS_VERSION, + 'REQUEST': 'GetFeatureInfo', + 'FORMAT': 'image/png', + 'TRANSPARENT': true, + 'QUERY_LAYERS': this.params_['LAYERS'] + }; + ol.obj.assign(baseParams, this.params_, params); + + var x = Math.floor((coordinate[0] - tileExtent[0]) / tileResolution); + var y = Math.floor((tileExtent[3] - coordinate[1]) / tileResolution); + + baseParams[this.v13_ ? 'I' : 'X'] = x; + baseParams[this.v13_ ? 'J' : 'Y'] = y; + + return this.getRequestUrl_(tileCoord, tileSize, tileExtent, + 1, projectionObj, baseParams); +}; + + +/** + * @inheritDoc + */ +ol.source.TileWMS.prototype.getGutterInternal = function() { + return this.gutter_; +}; + + +/** + * @inheritDoc + */ +ol.source.TileWMS.prototype.getKeyZXY = function(z, x, y) { + return this.coordKeyPrefix_ + ol.source.TileImage.prototype.getKeyZXY.call(this, z, x, y); +}; + + +/** + * Get the user-provided params, i.e. those passed to the constructor through + * the "params" option, and possibly updated using the updateParams method. + * @return {Object} Params. + * @api stable + */ +ol.source.TileWMS.prototype.getParams = function() { + return this.params_; +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Size} tileSize Tile size. + * @param {ol.Extent} tileExtent Tile extent. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {Object} params Params. + * @return {string|undefined} Request URL. + * @private + */ +ol.source.TileWMS.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent, + pixelRatio, projection, params) { + + var urls = this.urls; + if (!urls) { + return undefined; + } + + params['WIDTH'] = tileSize[0]; + params['HEIGHT'] = tileSize[1]; + + params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode(); + + if (!('STYLES' in this.params_)) { + params['STYLES'] = ''; + } + + if (pixelRatio != 1) { + switch (this.serverType_) { + case ol.source.WMSServerType.GEOSERVER: + var dpi = (90 * pixelRatio + 0.5) | 0; + if ('FORMAT_OPTIONS' in params) { + params['FORMAT_OPTIONS'] += ';dpi:' + dpi; + } else { + params['FORMAT_OPTIONS'] = 'dpi:' + dpi; + } + break; + case ol.source.WMSServerType.MAPSERVER: + params['MAP_RESOLUTION'] = 90 * pixelRatio; + break; + case ol.source.WMSServerType.CARMENTA_SERVER: + case ol.source.WMSServerType.QGIS: + params['DPI'] = 90 * pixelRatio; + break; + default: + ol.asserts.assert(false, 52); // Unknown `serverType` configured + break; + } + } + + var axisOrientation = projection.getAxisOrientation(); + var bbox = tileExtent; + if (this.v13_ && axisOrientation.substr(0, 2) == 'ne') { + var tmp; + tmp = tileExtent[0]; + bbox[0] = tileExtent[1]; + bbox[1] = tmp; + tmp = tileExtent[2]; + bbox[2] = tileExtent[3]; + bbox[3] = tmp; + } + params['BBOX'] = bbox.join(','); + + var url; + if (urls.length == 1) { + url = urls[0]; + } else { + var index = ol.math.modulo(ol.tilecoord.hash(tileCoord), urls.length); + url = urls[index]; + } + return ol.uri.appendParams(url, params); +}; + + +/** + * @inheritDoc + */ +ol.source.TileWMS.prototype.getTilePixelRatio = function(pixelRatio) { + return (!this.hidpi_ || this.serverType_ === undefined) ? 1 : + /** @type {number} */ (pixelRatio); +}; + + +/** + * @private + */ +ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() { + var i = 0; + var res = []; + + if (this.urls) { + var j, jj; + for (j = 0, jj = this.urls.length; j < jj; ++j) { + res[i++] = this.urls[j]; + } + } + + this.coordKeyPrefix_ = res.join('#'); +}; + + +/** + * @private + * @return {string} The key for the current params. + */ +ol.source.TileWMS.prototype.getKeyForParams_ = function() { + var i = 0; + var res = []; + for (var key in this.params_) { + res[i++] = key + '-' + this.params_[key]; + } + return res.join('/'); +}; + + +/** + * @inheritDoc + */ +ol.source.TileWMS.prototype.fixedTileUrlFunction = function(tileCoord, pixelRatio, projection) { + + var tileGrid = this.getTileGrid(); + if (!tileGrid) { + tileGrid = this.getTileGridForProjection(projection); + } + + if (tileGrid.getResolutions().length <= tileCoord[0]) { + return undefined; + } + + if (pixelRatio != 1 && (!this.hidpi_ || this.serverType_ === undefined)) { + pixelRatio = 1; + } + + var tileResolution = tileGrid.getResolution(tileCoord[0]); + var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_); + var tileSize = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0]), this.tmpSize); + + var gutter = this.gutter_; + if (gutter !== 0) { + tileSize = ol.size.buffer(tileSize, gutter, this.tmpSize); + tileExtent = ol.extent.buffer(tileExtent, + tileResolution * gutter, tileExtent); + } + + if (pixelRatio != 1) { + tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize); + } + + var baseParams = { + 'SERVICE': 'WMS', + 'VERSION': ol.DEFAULT_WMS_VERSION, + 'REQUEST': 'GetMap', + 'FORMAT': 'image/png', + 'TRANSPARENT': true + }; + ol.obj.assign(baseParams, this.params_); + + return this.getRequestUrl_(tileCoord, tileSize, tileExtent, + pixelRatio, projection, baseParams); +}; + +/** + * @inheritDoc + */ +ol.source.TileWMS.prototype.setUrls = function(urls) { + ol.source.TileImage.prototype.setUrls.call(this, urls); + this.resetCoordKeyPrefix_(); +}; + + +/** + * Update the user-provided params. + * @param {Object} params Params. + * @api stable + */ +ol.source.TileWMS.prototype.updateParams = function(params) { + ol.obj.assign(this.params_, params); + this.resetCoordKeyPrefix_(); + this.updateV13_(); + this.setKey(this.getKeyForParams_()); +}; + + +/** + * @private + */ +ol.source.TileWMS.prototype.updateV13_ = function() { + var version = this.params_['VERSION'] || ol.DEFAULT_WMS_VERSION; + this.v13_ = ol.string.compareVersions(version, '1.3') >= 0; +}; + +goog.provide('ol.VectorTile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.dom'); +goog.require('ol.featureloader'); + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Tile.State} state State. + * @param {string} src Data source url. + * @param {ol.format.Feature} format Feature format. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. + */ +ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) { + + ol.Tile.call(this, tileCoord, state); + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {ol.format.Feature} + */ + this.format_ = format; + + /** + * @private + * @type {Array.<ol.Feature>} + */ + this.features_ = null; + + /** + * @private + * @type {ol.FeatureLoader} + */ + this.loader_; + + /** + * Data projection + * @private + * @type {ol.proj.Projection} + */ + this.projection_; + + /** + * @private + * @type {ol.TileReplayState} + */ + this.replayState_ = { + dirty: false, + renderedRenderOrder: null, + renderedRevision: -1, + renderedTileRevision: -1, + replayGroup: null + }; + + /** + * @private + * @type {ol.TileLoadFunctionType} + */ + this.tileLoadFunction_ = tileLoadFunction; + + /** + * @private + * @type {string} + */ + this.url_ = src; + +}; +ol.inherits(ol.VectorTile, ol.Tile); + + +/** + * @return {CanvasRenderingContext2D} The rendering context. + */ +ol.VectorTile.prototype.getContext = function() { + return this.context_; +}; + + +/** + * @inheritDoc + */ +ol.VectorTile.prototype.getImage = function() { + return this.replayState_.renderedTileRevision == -1 ? + null : this.context_.canvas; +}; + + +/** + * Get the feature format assigned for reading this tile's features. + * @return {ol.format.Feature} Feature format. + * @api + */ +ol.VectorTile.prototype.getFormat = function() { + return this.format_; +}; + + +/** + * @return {Array.<ol.Feature>} Features. + */ +ol.VectorTile.prototype.getFeatures = function() { + return this.features_; +}; + + +/** + * @return {ol.TileReplayState} The replay state. + */ +ol.VectorTile.prototype.getReplayState = function() { + return this.replayState_; +}; + + +/** + * @inheritDoc + */ +ol.VectorTile.prototype.getKey = function() { + return this.url_; +}; + + +/** + * @return {ol.proj.Projection} Feature projection. + */ +ol.VectorTile.prototype.getProjection = function() { + return this.projection_; +}; + + +/** + * Load the tile. + */ +ol.VectorTile.prototype.load = function() { + if (this.state == ol.Tile.State.IDLE) { + this.setState(ol.Tile.State.LOADING); + this.tileLoadFunction_(this, this.url_); + this.loader_(null, NaN, null); + } +}; + + +/** + * Handler for successful tile load. + * @param {Array.<ol.Feature>} features The loaded features. + * @param {ol.proj.Projection} dataProjection Data projection. + */ +ol.VectorTile.prototype.onLoad_ = function(features, dataProjection) { + this.setProjection(dataProjection); + this.setFeatures(features); +}; + + +/** + * Handler for tile load errors. + */ +ol.VectorTile.prototype.onError_ = function() { + this.setState(ol.Tile.State.ERROR); +}; + + +/** + * @param {Array.<ol.Feature>} features Features. + * @api + */ +ol.VectorTile.prototype.setFeatures = function(features) { + this.features_ = features; + this.setState(ol.Tile.State.LOADED); +}; + + +/** + * Set the projection of the features that were added with {@link #setFeatures}. + * @param {ol.proj.Projection} projection Feature projection. + * @api + */ +ol.VectorTile.prototype.setProjection = function(projection) { + this.projection_ = projection; +}; + + +/** + * @param {ol.Tile.State} tileState Tile state. + */ +ol.VectorTile.prototype.setState = function(tileState) { + this.state = tileState; + this.changed(); +}; + + +/** + * Set the feature loader for reading this tile's features. + * @param {ol.FeatureLoader} loader Feature loader. + * @api + */ +ol.VectorTile.prototype.setLoader = function(loader) { + this.loader_ = loader; +}; + + +/** + * Sets the loader for a tile. + * @param {ol.VectorTile} tile Vector tile. + * @param {string} url URL. + */ +ol.VectorTile.defaultLoadFunction = function(tile, url) { + var loader = ol.featureloader.loadFeaturesXhr( + url, tile.getFormat(), tile.onLoad_.bind(tile), tile.onError_.bind(tile)); + + tile.setLoader(loader); +}; + +goog.provide('ol.source.VectorTile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.VectorTile'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.size'); +goog.require('ol.source.UrlTile'); + + +/** + * @classdesc + * Class for layer sources providing vector data divided into a tile grid, to be + * used with {@link ol.layer.VectorTile}. Although this source receives tiles + * with vector features from the server, it is not meant for feature editing. + * Features are optimized for rendering, their geometries are clipped at or near + * tile boundaries and simplified for a view resolution. See + * {@link ol.source.Vector} for vector sources that are suitable for feature + * editing. + * + * @constructor + * @fires ol.source.Tile.Event + * @extends {ol.source.UrlTile} + * @param {olx.source.VectorTileOptions} options Vector tile options. + * @api + */ +ol.source.VectorTile = function(options) { + + ol.source.UrlTile.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize !== undefined ? options.cacheSize : 128, + extent: options.extent, + logo: options.logo, + opaque: false, + projection: options.projection, + state: options.state, + tileGrid: options.tileGrid, + tileLoadFunction: options.tileLoadFunction ? + options.tileLoadFunction : ol.VectorTile.defaultLoadFunction, + tileUrlFunction: options.tileUrlFunction, + tilePixelRatio: options.tilePixelRatio, + url: options.url, + urls: options.urls, + wrapX: options.wrapX === undefined ? true : options.wrapX + }); + + /** + * @private + * @type {ol.format.Feature} + */ + this.format_ = options.format ? options.format : null; + + /** + * @private + * @type {boolean} + */ + this.overlaps_ = options.overlaps == undefined ? true : options.overlaps; + + /** + * @protected + * @type {function(new: ol.VectorTile, ol.TileCoord, ol.Tile.State, string, + * ol.format.Feature, ol.TileLoadFunctionType)} + */ + this.tileClass = options.tileClass ? options.tileClass : ol.VectorTile; + +}; +ol.inherits(ol.source.VectorTile, ol.source.UrlTile); + + +/** + * @return {boolean} The source can have overlapping geometries. + */ +ol.source.VectorTile.prototype.getOverlaps = function() { + return this.overlaps_; +}; + + +/** + * @inheritDoc + */ +ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projection) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey)); + } else { + var tileCoord = [z, x, y]; + var urlTileCoord = this.getTileCoordForTileUrlFunction( + tileCoord, projection); + var tileUrl = urlTileCoord ? + this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined; + var tile = new this.tileClass( + tileCoord, + tileUrl !== undefined ? ol.Tile.State.IDLE : ol.Tile.State.EMPTY, + tileUrl !== undefined ? tileUrl : '', + this.format_, this.tileLoadFunction); + ol.events.listen(tile, ol.events.EventType.CHANGE, + this.handleTileChange, this); + + this.tileCache.set(tileCoordKey, tile); + return tile; + } +}; + + +/** + * @inheritDoc + */ +ol.source.VectorTile.prototype.getTilePixelRatio = function(opt_pixelRatio) { + return opt_pixelRatio == undefined ? + ol.source.UrlTile.prototype.getTilePixelRatio.call(this, opt_pixelRatio) : + opt_pixelRatio; +}; + + +/** + * @inheritDoc + */ +ol.source.VectorTile.prototype.getTilePixelSize = function(z, pixelRatio, projection) { + var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z)); + return [Math.round(tileSize[0] * pixelRatio), Math.round(tileSize[1] * pixelRatio)]; +}; + +goog.provide('ol.tilegrid.WMTS'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.proj'); +goog.require('ol.tilegrid.TileGrid'); + + +/** + * @classdesc + * Set the grid pattern for sources accessing WMTS tiled-image servers. + * + * @constructor + * @extends {ol.tilegrid.TileGrid} + * @param {olx.tilegrid.WMTSOptions} options WMTS options. + * @struct + * @api + */ +ol.tilegrid.WMTS = function(options) { + + ol.DEBUG && console.assert( + options.resolutions.length == options.matrixIds.length, + 'options resolutions and matrixIds must have equal length (%s == %s)', + options.resolutions.length, options.matrixIds.length); + + /** + * @private + * @type {!Array.<string>} + */ + this.matrixIds_ = options.matrixIds; + // FIXME: should the matrixIds become optionnal? + + ol.tilegrid.TileGrid.call(this, { + extent: options.extent, + origin: options.origin, + origins: options.origins, + resolutions: options.resolutions, + tileSize: options.tileSize, + tileSizes: options.tileSizes, + sizes: options.sizes + }); + +}; +ol.inherits(ol.tilegrid.WMTS, ol.tilegrid.TileGrid); + + +/** + * @param {number} z Z. + * @return {string} MatrixId.. + */ +ol.tilegrid.WMTS.prototype.getMatrixId = function(z) { + ol.DEBUG && console.assert(0 <= z && z < this.matrixIds_.length, + 'attempted to retrieve matrixId for illegal z (%s)', z); + return this.matrixIds_[z]; +}; + + +/** + * Get the list of matrix identifiers. + * @return {Array.<string>} MatrixIds. + * @api + */ +ol.tilegrid.WMTS.prototype.getMatrixIds = function() { + return this.matrixIds_; +}; + + +/** + * Create a tile grid from a WMTS capabilities matrix set and an + * optional TileMatrixSetLimits. + * @param {Object} matrixSet An object representing a matrixSet in the + * capabilities document. + * @param {ol.Extent=} opt_extent An optional extent to restrict the tile + * ranges the server provides. + * @param {Array.<Object>=} opt_matrixLimits An optional object representing + * the available matrices for tileGrid. + * @return {ol.tilegrid.WMTS} WMTS tileGrid instance. + * @api + */ +ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = function(matrixSet, opt_extent, + opt_matrixLimits) { + + /** @type {!Array.<number>} */ + var resolutions = []; + /** @type {!Array.<string>} */ + var matrixIds = []; + /** @type {!Array.<ol.Coordinate>} */ + var origins = []; + /** @type {!Array.<ol.Size>} */ + var tileSizes = []; + /** @type {!Array.<ol.Size>} */ + var sizes = []; + + var matrixLimits = opt_matrixLimits !== undefined ? opt_matrixLimits : []; + + var supportedCRSPropName = 'SupportedCRS'; + var matrixIdsPropName = 'TileMatrix'; + var identifierPropName = 'Identifier'; + var scaleDenominatorPropName = 'ScaleDenominator'; + var topLeftCornerPropName = 'TopLeftCorner'; + var tileWidthPropName = 'TileWidth'; + var tileHeightPropName = 'TileHeight'; + + var projection; + projection = ol.proj.get(matrixSet[supportedCRSPropName].replace( + /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3')); + var metersPerUnit = projection.getMetersPerUnit(); + // swap origin x and y coordinates if axis orientation is lat/long + var switchOriginXY = projection.getAxisOrientation().substr(0, 2) == 'ne'; + + matrixSet[matrixIdsPropName].sort(function(a, b) { + return b[scaleDenominatorPropName] - a[scaleDenominatorPropName]; + }); + + matrixSet[matrixIdsPropName].forEach(function(elt, index, array) { + + var matrixAvailable; + // use of matrixLimits to filter TileMatrices from GetCapabilities + // TileMatrixSet from unavailable matrix levels. + if (matrixLimits.length > 0) { + matrixAvailable = ol.array.find(matrixLimits, + function(elt_ml, index_ml, array_ml) { + return elt[identifierPropName] == elt_ml[matrixIdsPropName]; + }); + } else { + matrixAvailable = true; + } + + if (matrixAvailable) { + matrixIds.push(elt[identifierPropName]); + var resolution = elt[scaleDenominatorPropName] * 0.28E-3 / metersPerUnit; + var tileWidth = elt[tileWidthPropName]; + var tileHeight = elt[tileHeightPropName]; + if (switchOriginXY) { + origins.push([elt[topLeftCornerPropName][1], + elt[topLeftCornerPropName][0]]); + } else { + origins.push(elt[topLeftCornerPropName]); + } + resolutions.push(resolution); + tileSizes.push(tileWidth == tileHeight ? + tileWidth : [tileWidth, tileHeight]); + // top-left origin, so height is negative + sizes.push([elt['MatrixWidth'], -elt['MatrixHeight']]); + } + }); + + return new ol.tilegrid.WMTS({ + extent: opt_extent, + origins: origins, + resolutions: resolutions, + matrixIds: matrixIds, + tileSizes: tileSizes, + sizes: sizes + }); +}; + +goog.provide('ol.source.WMTS'); + +goog.require('ol'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilegrid.WMTS'); +goog.require('ol.uri'); + + +/** + * @classdesc + * Layer source for tile data from WMTS servers. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.WMTSOptions} options WMTS options. + * @api stable + */ +ol.source.WMTS = function(options) { + + // TODO: add support for TileMatrixLimits + + /** + * @private + * @type {string} + */ + this.version_ = options.version !== undefined ? options.version : '1.0.0'; + + /** + * @private + * @type {string} + */ + this.format_ = options.format !== undefined ? options.format : 'image/jpeg'; + + /** + * @private + * @type {!Object} + */ + this.dimensions_ = options.dimensions !== undefined ? options.dimensions : {}; + + /** + * @private + * @type {string} + */ + this.layer_ = options.layer; + + /** + * @private + * @type {string} + */ + this.matrixSet_ = options.matrixSet; + + /** + * @private + * @type {string} + */ + this.style_ = options.style; + + var urls = options.urls; + if (urls === undefined && options.url !== undefined) { + urls = ol.TileUrlFunction.expandUrl(options.url); + } + + // FIXME: should we guess this requestEncoding from options.url(s) + // structure? that would mean KVP only if a template is not provided. + + /** + * @private + * @type {ol.source.WMTS.RequestEncoding} + */ + this.requestEncoding_ = options.requestEncoding !== undefined ? + /** @type {ol.source.WMTS.RequestEncoding} */ (options.requestEncoding) : + ol.source.WMTS.RequestEncoding.KVP; + + var requestEncoding = this.requestEncoding_; + + // FIXME: should we create a default tileGrid? + // we could issue a getCapabilities xhr to retrieve missing configuration + var tileGrid = options.tileGrid; + + // context property names are lower case to allow for a case insensitive + // replacement as some services use different naming conventions + var context = { + 'layer': this.layer_, + 'style': this.style_, + 'tilematrixset': this.matrixSet_ + }; + + if (requestEncoding == ol.source.WMTS.RequestEncoding.KVP) { + ol.obj.assign(context, { + 'Service': 'WMTS', + 'Request': 'GetTile', + 'Version': this.version_, + 'Format': this.format_ + }); + } + + var dimensions = this.dimensions_; + + /** + * @param {string} template Template. + * @return {ol.TileUrlFunctionType} Tile URL function. + */ + function createFromWMTSTemplate(template) { + + // TODO: we may want to create our own appendParams function so that params + // order conforms to wmts spec guidance, and so that we can avoid to escape + // special template params + + template = (requestEncoding == ol.source.WMTS.RequestEncoding.KVP) ? + ol.uri.appendParams(template, context) : + template.replace(/\{(\w+?)\}/g, function(m, p) { + return (p.toLowerCase() in context) ? context[p.toLowerCase()] : m; + }); + + return ( + /** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function(tileCoord, pixelRatio, projection) { + if (!tileCoord) { + return undefined; + } else { + var localContext = { + 'TileMatrix': tileGrid.getMatrixId(tileCoord[0]), + 'TileCol': tileCoord[1], + 'TileRow': -tileCoord[2] - 1 + }; + ol.obj.assign(localContext, dimensions); + var url = template; + if (requestEncoding == ol.source.WMTS.RequestEncoding.KVP) { + url = ol.uri.appendParams(url, localContext); + } else { + url = url.replace(/\{(\w+?)\}/g, function(m, p) { + return localContext[p]; + }); + } + return url; + } + }); + } + + var tileUrlFunction = (urls && urls.length > 0) ? + ol.TileUrlFunction.createFromTileUrlFunctions( + urls.map(createFromWMTSTemplate)) : + ol.TileUrlFunction.nullTileUrlFunction; + + ol.source.TileImage.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + logo: options.logo, + projection: options.projection, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileClass: options.tileClass, + tileGrid: tileGrid, + tileLoadFunction: options.tileLoadFunction, + tilePixelRatio: options.tilePixelRatio, + tileUrlFunction: tileUrlFunction, + urls: urls, + wrapX: options.wrapX !== undefined ? options.wrapX : false + }); + + this.setKey(this.getKeyForDimensions_()); + +}; +ol.inherits(ol.source.WMTS, ol.source.TileImage); + + +/** + * Get the dimensions, i.e. those passed to the constructor through the + * "dimensions" option, and possibly updated using the updateDimensions + * method. + * @return {!Object} Dimensions. + * @api + */ +ol.source.WMTS.prototype.getDimensions = function() { + return this.dimensions_; +}; + + +/** + * Return the image format of the WMTS source. + * @return {string} Format. + * @api + */ +ol.source.WMTS.prototype.getFormat = function() { + return this.format_; +}; + + +/** + * Return the layer of the WMTS source. + * @return {string} Layer. + * @api + */ +ol.source.WMTS.prototype.getLayer = function() { + return this.layer_; +}; + + +/** + * Return the matrix set of the WMTS source. + * @return {string} MatrixSet. + * @api + */ +ol.source.WMTS.prototype.getMatrixSet = function() { + return this.matrixSet_; +}; + + +/** + * Return the request encoding, either "KVP" or "REST". + * @return {ol.source.WMTS.RequestEncoding} Request encoding. + * @api + */ +ol.source.WMTS.prototype.getRequestEncoding = function() { + return this.requestEncoding_; +}; + + +/** + * Return the style of the WMTS source. + * @return {string} Style. + * @api + */ +ol.source.WMTS.prototype.getStyle = function() { + return this.style_; +}; + + +/** + * Return the version of the WMTS source. + * @return {string} Version. + * @api + */ +ol.source.WMTS.prototype.getVersion = function() { + return this.version_; +}; + + +/** + * @private + * @return {string} The key for the current dimensions. + */ +ol.source.WMTS.prototype.getKeyForDimensions_ = function() { + var i = 0; + var res = []; + for (var key in this.dimensions_) { + res[i++] = key + '-' + this.dimensions_[key]; + } + return res.join('/'); +}; + + +/** + * Update the dimensions. + * @param {Object} dimensions Dimensions. + * @api + */ +ol.source.WMTS.prototype.updateDimensions = function(dimensions) { + ol.obj.assign(this.dimensions_, dimensions); + this.setKey(this.getKeyForDimensions_()); +}; + + +/** + * Generate source options from a capabilities object. + * @param {Object} wmtsCap An object representing the capabilities document. + * @param {Object} config Configuration properties for the layer. Defaults for + * the layer will apply if not provided. + * + * Required config properties: + * - layer - {string} The layer identifier. + * + * Optional config properties: + * - matrixSet - {string} The matrix set identifier, required if there is + * more than one matrix set in the layer capabilities. + * - projection - {string} The desired CRS when no matrixSet is specified. + * eg: "EPSG:3857". If the desired projection is not available, + * an error is thrown. + * - requestEncoding - {string} url encoding format for the layer. Default is + * the first tile url format found in the GetCapabilities response. + * - style - {string} The name of the style + * - format - {string} Image format for the layer. Default is the first + * format returned in the GetCapabilities response. + * @return {olx.source.WMTSOptions} WMTS source options object. + * @api + */ +ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) { + + // TODO: add support for TileMatrixLimits + ol.DEBUG && console.assert(config['layer'], + 'config "layer" must not be null'); + + var layers = wmtsCap['Contents']['Layer']; + var l = ol.array.find(layers, function(elt, index, array) { + return elt['Identifier'] == config['layer']; + }); + ol.DEBUG && console.assert(l, 'found a matching layer in Contents/Layer'); + + ol.DEBUG && console.assert(l['TileMatrixSetLink'].length > 0, + 'layer has TileMatrixSetLink'); + var tileMatrixSets = wmtsCap['Contents']['TileMatrixSet']; + var idx, matrixSet, matrixLimits; + if (l['TileMatrixSetLink'].length > 1) { + if ('projection' in config) { + idx = ol.array.findIndex(l['TileMatrixSetLink'], + function(elt, index, array) { + var tileMatrixSet = ol.array.find(tileMatrixSets, function(el) { + return el['Identifier'] == elt['TileMatrixSet']; + }); + var supportedCRS = tileMatrixSet['SupportedCRS'].replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3'); + var proj1 = ol.proj.get(supportedCRS); + var proj2 = ol.proj.get(config['projection']); + if (proj1 && proj2) { + return ol.proj.equivalent(proj1, proj2); + } else { + return supportedCRS == config['projection']; + } + }); + } else { + idx = ol.array.findIndex(l['TileMatrixSetLink'], + function(elt, index, array) { + return elt['TileMatrixSet'] == config['matrixSet']; + }); + } + } else { + idx = 0; + } + if (idx < 0) { + idx = 0; + } + matrixSet = /** @type {string} */ + (l['TileMatrixSetLink'][idx]['TileMatrixSet']); + matrixLimits = /** @type {Array.<Object>} */ + (l['TileMatrixSetLink'][idx]['TileMatrixSetLimits']); + + ol.DEBUG && console.assert(matrixSet, 'TileMatrixSet must not be null'); + + var format = /** @type {string} */ (l['Format'][0]); + if ('format' in config) { + format = config['format']; + } + idx = ol.array.findIndex(l['Style'], function(elt, index, array) { + if ('style' in config) { + return elt['Title'] == config['style']; + } else { + return elt['isDefault']; + } + }); + if (idx < 0) { + idx = 0; + } + var style = /** @type {string} */ (l['Style'][idx]['Identifier']); + + var dimensions = {}; + if ('Dimension' in l) { + l['Dimension'].forEach(function(elt, index, array) { + var key = elt['Identifier']; + var value = elt['Default']; + if (value !== undefined) { + ol.DEBUG && console.assert(ol.array.includes(elt['Value'], value), + 'default value contained in values'); + } else { + value = elt['Value'][0]; + } + ol.DEBUG && console.assert(value !== undefined, 'value could be found'); + dimensions[key] = value; + }); + } + + var matrixSets = wmtsCap['Contents']['TileMatrixSet']; + var matrixSetObj = ol.array.find(matrixSets, function(elt, index, array) { + return elt['Identifier'] == matrixSet; + }); + ol.DEBUG && console.assert(matrixSetObj, + 'found matrixSet in Contents/TileMatrixSet'); + + var projection; + if ('projection' in config) { + projection = ol.proj.get(config['projection']); + } else { + projection = ol.proj.get(matrixSetObj['SupportedCRS'].replace( + /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3')); + } + + var wgs84BoundingBox = l['WGS84BoundingBox']; + var extent, wrapX; + if (wgs84BoundingBox !== undefined) { + var wgs84ProjectionExtent = ol.proj.get('EPSG:4326').getExtent(); + wrapX = (wgs84BoundingBox[0] == wgs84ProjectionExtent[0] && + wgs84BoundingBox[2] == wgs84ProjectionExtent[2]); + extent = ol.proj.transformExtent( + wgs84BoundingBox, 'EPSG:4326', projection); + var projectionExtent = projection.getExtent(); + if (projectionExtent) { + // If possible, do a sanity check on the extent - it should never be + // bigger than the validity extent of the projection of a matrix set. + if (!ol.extent.containsExtent(projectionExtent, extent)) { + extent = undefined; + } + } + } + + var tileGrid = ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet( + matrixSetObj, extent, matrixLimits); + + /** @type {!Array.<string>} */ + var urls = []; + var requestEncoding = config['requestEncoding']; + requestEncoding = requestEncoding !== undefined ? requestEncoding : ''; + + ol.DEBUG && console.assert( + ol.array.includes(['REST', 'RESTful', 'KVP', ''], requestEncoding), + 'requestEncoding (%s) is one of "REST", "RESTful", "KVP" or ""', + requestEncoding); + + if ('OperationsMetadata' in wmtsCap && 'GetTile' in wmtsCap['OperationsMetadata']) { + var gets = wmtsCap['OperationsMetadata']['GetTile']['DCP']['HTTP']['Get']; + ol.DEBUG && console.assert(gets.length >= 1); + + for (var i = 0, ii = gets.length; i < ii; ++i) { + var constraint = ol.array.find(gets[i]['Constraint'], function(element) { + return element['name'] == 'GetEncoding'; + }); + var encodings = constraint['AllowedValues']['Value']; + ol.DEBUG && console.assert(encodings.length >= 1); + + if (requestEncoding === '') { + // requestEncoding not provided, use the first encoding from the list + requestEncoding = encodings[0]; + } + if (requestEncoding === ol.source.WMTS.RequestEncoding.KVP) { + if (ol.array.includes(encodings, ol.source.WMTS.RequestEncoding.KVP)) { + urls.push(/** @type {string} */ (gets[i]['href'])); + } + } else { + break; + } + } + } + if (urls.length === 0) { + requestEncoding = ol.source.WMTS.RequestEncoding.REST; + l['ResourceURL'].forEach(function(element) { + if (element['resourceType'] === 'tile') { + format = element['format']; + urls.push(/** @type {string} */ (element['template'])); + } + }); + } + ol.DEBUG && console.assert(urls.length > 0, 'At least one URL found'); + + return { + urls: urls, + layer: config['layer'], + matrixSet: matrixSet, + format: format, + projection: projection, + requestEncoding: requestEncoding, + tileGrid: tileGrid, + style: style, + dimensions: dimensions, + wrapX: wrapX + }; + +}; + + +/** + * Request encoding. One of 'KVP', 'REST'. + * @enum {string} + */ +ol.source.WMTS.RequestEncoding = { + KVP: 'KVP', // see spec §8 + REST: 'REST' // see spec §10 +}; + +goog.provide('ol.source.Zoomify'); + +goog.require('ol'); +goog.require('ol.ImageTile'); +goog.require('ol.Tile'); +goog.require('ol.asserts'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilegrid.TileGrid'); + + +/** + * @classdesc + * Layer source for tile data in Zoomify format. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.ZoomifyOptions=} opt_options Options. + * @api stable + */ +ol.source.Zoomify = function(opt_options) { + + var options = opt_options || {}; + + var size = options.size; + var tierSizeCalculation = options.tierSizeCalculation !== undefined ? + options.tierSizeCalculation : + ol.source.Zoomify.TierSizeCalculation.DEFAULT; + + var imageWidth = size[0]; + var imageHeight = size[1]; + var tierSizeInTiles = []; + var tileSize = ol.DEFAULT_TILE_SIZE; + + switch (tierSizeCalculation) { + case ol.source.Zoomify.TierSizeCalculation.DEFAULT: + while (imageWidth > tileSize || imageHeight > tileSize) { + tierSizeInTiles.push([ + Math.ceil(imageWidth / tileSize), + Math.ceil(imageHeight / tileSize) + ]); + tileSize += tileSize; + } + break; + case ol.source.Zoomify.TierSizeCalculation.TRUNCATED: + var width = imageWidth; + var height = imageHeight; + while (width > tileSize || height > tileSize) { + tierSizeInTiles.push([ + Math.ceil(width / tileSize), + Math.ceil(height / tileSize) + ]); + width >>= 1; + height >>= 1; + } + break; + default: + ol.asserts.assert(false, 53); // Unknown `tierSizeCalculation` configured + break; + } + + tierSizeInTiles.push([1, 1]); + tierSizeInTiles.reverse(); + + var resolutions = [1]; + var tileCountUpToTier = [0]; + var i, ii; + for (i = 1, ii = tierSizeInTiles.length; i < ii; i++) { + resolutions.push(1 << i); + tileCountUpToTier.push( + tierSizeInTiles[i - 1][0] * tierSizeInTiles[i - 1][1] + + tileCountUpToTier[i - 1] + ); + } + resolutions.reverse(); + + var extent = [0, -size[1], size[0], 0]; + var tileGrid = new ol.tilegrid.TileGrid({ + extent: extent, + origin: ol.extent.getTopLeft(extent), + resolutions: resolutions + }); + + var url = options.url; + + /** + * @this {ol.source.TileImage} + * @param {ol.TileCoord} tileCoord Tile Coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function tileUrlFunction(tileCoord, pixelRatio, projection) { + if (!tileCoord) { + return undefined; + } else { + var tileCoordZ = tileCoord[0]; + var tileCoordX = tileCoord[1]; + var tileCoordY = -tileCoord[2] - 1; + var tileIndex = + tileCoordX + + tileCoordY * tierSizeInTiles[tileCoordZ][0] + + tileCountUpToTier[tileCoordZ]; + var tileGroup = (tileIndex / ol.DEFAULT_TILE_SIZE) | 0; + return url + 'TileGroup' + tileGroup + '/' + + tileCoordZ + '-' + tileCoordX + '-' + tileCoordY + '.jpg'; + } + } + + ol.source.TileImage.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + logo: options.logo, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileClass: ol.source.Zoomify.Tile_, + tileGrid: tileGrid, + tileUrlFunction: tileUrlFunction + }); + +}; +ol.inherits(ol.source.Zoomify, ol.source.TileImage); + + +/** + * @constructor + * @extends {ol.ImageTile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Tile.State} state State. + * @param {string} src Image source URI. + * @param {?string} crossOrigin Cross origin. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. + * @private + */ +ol.source.Zoomify.Tile_ = function( + tileCoord, state, src, crossOrigin, tileLoadFunction) { + + ol.ImageTile.call(this, tileCoord, state, src, crossOrigin, tileLoadFunction); + + /** + * @private + * @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} + */ + this.zoomifyImage_ = null; + +}; +ol.inherits(ol.source.Zoomify.Tile_, ol.ImageTile); + + +/** + * @inheritDoc + */ +ol.source.Zoomify.Tile_.prototype.getImage = function() { + if (this.zoomifyImage_) { + return this.zoomifyImage_; + } + var tileSize = ol.DEFAULT_TILE_SIZE; + var image = ol.ImageTile.prototype.getImage.call(this); + if (this.state == ol.Tile.State.LOADED) { + if (image.width == tileSize && image.height == tileSize) { + this.zoomifyImage_ = image; + return image; + } else { + var context = ol.dom.createCanvasContext2D(tileSize, tileSize); + context.drawImage(image, 0, 0); + this.zoomifyImage_ = context.canvas; + return context.canvas; + } + } else { + return image; + } +}; + + +/** + * @enum {string} + */ +ol.source.Zoomify.TierSizeCalculation = { + DEFAULT: 'default', + TRUNCATED: 'truncated' +}; + +goog.provide('ol.style.Atlas'); + +goog.require('ol'); +goog.require('ol.dom'); + + +/** + * This class facilitates the creation of image atlases. + * + * Images added to an atlas will be rendered onto a single + * atlas canvas. The distribution of images on the canvas is + * managed with the bin packing algorithm described in: + * http://www.blackpawn.com/texts/lightmaps/ + * + * @constructor + * @struct + * @param {number} size The size in pixels of the sprite image. + * @param {number} space The space in pixels between images. + * Because texture coordinates are float values, the edges of + * images might not be completely correct (in a way that the + * edges overlap when being rendered). To avoid this we add a + * padding around each image. + */ +ol.style.Atlas = function(size, space) { + + /** + * @private + * @type {number} + */ + this.space_ = space; + + /** + * @private + * @type {Array.<ol.AtlasBlock>} + */ + this.emptyBlocks_ = [{x: 0, y: 0, width: size, height: size}]; + + /** + * @private + * @type {Object.<string, ol.AtlasInfo>} + */ + this.entries_ = {}; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = ol.dom.createCanvasContext2D(size, size); + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = this.context_.canvas; +}; + + +/** + * @param {string} id The identifier of the entry to check. + * @return {?ol.AtlasInfo} The atlas info. + */ +ol.style.Atlas.prototype.get = function(id) { + return this.entries_[id] || null; +}; + + +/** + * @param {string} id The identifier of the entry to add. + * @param {number} width The width. + * @param {number} height The height. + * @param {function(CanvasRenderingContext2D, number, number)} renderCallback + * Called to render the new image onto an atlas image. + * @param {Object=} opt_this Value to use as `this` when executing + * `renderCallback`. + * @return {?ol.AtlasInfo} The position and atlas image for the entry. + */ +ol.style.Atlas.prototype.add = function(id, width, height, renderCallback, opt_this) { + var block, i, ii; + for (i = 0, ii = this.emptyBlocks_.length; i < ii; ++i) { + block = this.emptyBlocks_[i]; + if (block.width >= width + this.space_ && + block.height >= height + this.space_) { + // we found a block that is big enough for our entry + var entry = { + offsetX: block.x + this.space_, + offsetY: block.y + this.space_, + image: this.canvas_ + }; + this.entries_[id] = entry; + + // render the image on the atlas image + renderCallback.call(opt_this, this.context_, + block.x + this.space_, block.y + this.space_); + + // split the block after the insertion, either horizontally or vertically + this.split_(i, block, width + this.space_, height + this.space_); + + return entry; + } + } + + // there is no space for the new entry in this atlas + return null; +}; + + +/** + * @private + * @param {number} index The index of the block. + * @param {ol.AtlasBlock} block The block to split. + * @param {number} width The width of the entry to insert. + * @param {number} height The height of the entry to insert. + */ +ol.style.Atlas.prototype.split_ = function(index, block, width, height) { + var deltaWidth = block.width - width; + var deltaHeight = block.height - height; + + /** @type {ol.AtlasBlock} */ + var newBlock1; + /** @type {ol.AtlasBlock} */ + var newBlock2; + + if (deltaWidth > deltaHeight) { + // split vertically + // block right of the inserted entry + newBlock1 = { + x: block.x + width, + y: block.y, + width: block.width - width, + height: block.height + }; + + // block below the inserted entry + newBlock2 = { + x: block.x, + y: block.y + height, + width: width, + height: block.height - height + }; + this.updateBlocks_(index, newBlock1, newBlock2); + } else { + // split horizontally + // block right of the inserted entry + newBlock1 = { + x: block.x + width, + y: block.y, + width: block.width - width, + height: height + }; + + // block below the inserted entry + newBlock2 = { + x: block.x, + y: block.y + height, + width: block.width, + height: block.height - height + }; + this.updateBlocks_(index, newBlock1, newBlock2); + } +}; + + +/** + * Remove the old block and insert new blocks at the same array position. + * The new blocks are inserted at the same position, so that splitted + * blocks (that are potentially smaller) are filled first. + * @private + * @param {number} index The index of the block to remove. + * @param {ol.AtlasBlock} newBlock1 The 1st block to add. + * @param {ol.AtlasBlock} newBlock2 The 2nd block to add. + */ +ol.style.Atlas.prototype.updateBlocks_ = function(index, newBlock1, newBlock2) { + var args = [index, 1]; + if (newBlock1.width > 0 && newBlock1.height > 0) { + args.push(newBlock1); + } + if (newBlock2.width > 0 && newBlock2.height > 0) { + args.push(newBlock2); + } + this.emptyBlocks_.splice.apply(this.emptyBlocks_, args); +}; + +goog.provide('ol.style.AtlasManager'); + +goog.require('ol'); +goog.require('ol.style.Atlas'); + + +/** + * Manages the creation of image atlases. + * + * Images added to this manager will be inserted into an atlas, which + * will be used for rendering. + * The `size` given in the constructor is the size for the first + * atlas. After that, when new atlases are created, they will have + * twice the size as the latest atlas (until `maxSize` is reached). + * + * If an application uses many images or very large images, it is recommended + * to set a higher `size` value to avoid the creation of too many atlases. + * + * @constructor + * @struct + * @api + * @param {olx.style.AtlasManagerOptions=} opt_options Options. + */ +ol.style.AtlasManager = function(opt_options) { + + var options = opt_options || {}; + + /** + * The size in pixels of the latest atlas image. + * @private + * @type {number} + */ + this.currentSize_ = options.initialSize !== undefined ? + options.initialSize : ol.INITIAL_ATLAS_SIZE; + + /** + * The maximum size in pixels of atlas images. + * @private + * @type {number} + */ + this.maxSize_ = options.maxSize !== undefined ? + options.maxSize : ol.MAX_ATLAS_SIZE != -1 ? + ol.MAX_ATLAS_SIZE : ol.WEBGL_MAX_TEXTURE_SIZE !== undefined ? + ol.WEBGL_MAX_TEXTURE_SIZE : 2048; + + /** + * The size in pixels between images. + * @private + * @type {number} + */ + this.space_ = options.space !== undefined ? options.space : 1; + + /** + * @private + * @type {Array.<ol.style.Atlas>} + */ + this.atlases_ = [new ol.style.Atlas(this.currentSize_, this.space_)]; + + /** + * The size in pixels of the latest atlas image for hit-detection images. + * @private + * @type {number} + */ + this.currentHitSize_ = this.currentSize_; + + /** + * @private + * @type {Array.<ol.style.Atlas>} + */ + this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)]; +}; + + +/** + * @param {string} id The identifier of the entry to check. + * @return {?ol.AtlasManagerInfo} The position and atlas image for the + * entry, or `null` if the entry is not part of the atlas manager. + */ +ol.style.AtlasManager.prototype.getInfo = function(id) { + /** @type {?ol.AtlasInfo} */ + var info = this.getInfo_(this.atlases_, id); + + if (!info) { + return null; + } + var hitInfo = /** @type {ol.AtlasInfo} */ (this.getInfo_(this.hitAtlases_, id)); + + return this.mergeInfos_(info, hitInfo); +}; + + +/** + * @private + * @param {Array.<ol.style.Atlas>} atlases The atlases to search. + * @param {string} id The identifier of the entry to check. + * @return {?ol.AtlasInfo} The position and atlas image for the entry, + * or `null` if the entry is not part of the atlases. + */ +ol.style.AtlasManager.prototype.getInfo_ = function(atlases, id) { + var atlas, info, i, ii; + for (i = 0, ii = atlases.length; i < ii; ++i) { + atlas = atlases[i]; + info = atlas.get(id); + if (info) { + return info; + } + } + return null; +}; + + +/** + * @private + * @param {ol.AtlasInfo} info The info for the real image. + * @param {ol.AtlasInfo} hitInfo The info for the hit-detection + * image. + * @return {?ol.AtlasManagerInfo} The position and atlas image for the + * entry, or `null` if the entry is not part of the atlases. + */ +ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) { + ol.DEBUG && console.assert(info.offsetX === hitInfo.offsetX, + 'in order to merge, offsetX of info and hitInfo must be equal'); + ol.DEBUG && console.assert(info.offsetY === hitInfo.offsetY, + 'in order to merge, offsetY of info and hitInfo must be equal'); + return /** @type {ol.AtlasManagerInfo} */ ({ + offsetX: info.offsetX, + offsetY: info.offsetY, + image: info.image, + hitImage: hitInfo.image + }); +}; + + +/** + * Add an image to the atlas manager. + * + * If an entry for the given id already exists, the entry will + * be overridden (but the space on the atlas graphic will not be freed). + * + * If `renderHitCallback` is provided, the image (or the hit-detection version + * of the image) will be rendered into a separate hit-detection atlas image. + * + * @param {string} id The identifier of the entry to add. + * @param {number} width The width. + * @param {number} height The height. + * @param {function(CanvasRenderingContext2D, number, number)} renderCallback + * Called to render the new image onto an atlas image. + * @param {function(CanvasRenderingContext2D, number, number)=} + * opt_renderHitCallback Called to render a hit-detection image onto a hit + * detection atlas image. + * @param {Object=} opt_this Value to use as `this` when executing + * `renderCallback` and `renderHitCallback`. + * @return {?ol.AtlasManagerInfo} The position and atlas image for the + * entry, or `null` if the image is too big. + */ +ol.style.AtlasManager.prototype.add = function(id, width, height, + renderCallback, opt_renderHitCallback, opt_this) { + if (width + this.space_ > this.maxSize_ || + height + this.space_ > this.maxSize_) { + return null; + } + + /** @type {?ol.AtlasInfo} */ + var info = this.add_(false, + id, width, height, renderCallback, opt_this); + if (!info) { + return null; + } + + // even if no hit-detection entry is requested, we insert a fake entry into + // the hit-detection atlas, to make sure that the offset is the same for + // the original image and the hit-detection image. + var renderHitCallback = opt_renderHitCallback !== undefined ? + opt_renderHitCallback : ol.nullFunction; + + var hitInfo = /** @type {ol.AtlasInfo} */ (this.add_(true, + id, width, height, renderHitCallback, opt_this)); + + return this.mergeInfos_(info, hitInfo); +}; + + +/** + * @private + * @param {boolean} isHitAtlas If the hit-detection atlases are used. + * @param {string} id The identifier of the entry to add. + * @param {number} width The width. + * @param {number} height The height. + * @param {function(CanvasRenderingContext2D, number, number)} renderCallback + * Called to render the new image onto an atlas image. + * @param {Object=} opt_this Value to use as `this` when executing + * `renderCallback` and `renderHitCallback`. + * @return {?ol.AtlasInfo} The position and atlas image for the entry, + * or `null` if the image is too big. + */ +ol.style.AtlasManager.prototype.add_ = function(isHitAtlas, id, width, height, + renderCallback, opt_this) { + var atlases = (isHitAtlas) ? this.hitAtlases_ : this.atlases_; + var atlas, info, i, ii; + for (i = 0, ii = atlases.length; i < ii; ++i) { + atlas = atlases[i]; + info = atlas.add(id, width, height, renderCallback, opt_this); + if (info) { + return info; + } else if (!info && i === ii - 1) { + // the entry could not be added to one of the existing atlases, + // create a new atlas that is twice as big and try to add to this one. + var size; + if (isHitAtlas) { + size = Math.min(this.currentHitSize_ * 2, this.maxSize_); + this.currentHitSize_ = size; + } else { + size = Math.min(this.currentSize_ * 2, this.maxSize_); + this.currentSize_ = size; + } + atlas = new ol.style.Atlas(size, this.space_); + atlases.push(atlas); + // run the loop another time + ++ii; + } + } + ol.DEBUG && console.assert(false, 'Failed to add to atlasmanager'); + return null; +}; + +// Copyright 2009 The Closure Library Authors. +// All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// This file has been auto-generated by GenJsDeps, please do not edit. + +goog.addDependency( + 'demos/editor/equationeditor.js', ['goog.demos.editor.EquationEditor'], + ['goog.ui.equation.EquationEditorDialog']); +goog.addDependency( + 'demos/editor/helloworld.js', ['goog.demos.editor.HelloWorld'], + ['goog.dom', 'goog.dom.TagName', 'goog.editor.Plugin']); +goog.addDependency( + 'demos/editor/helloworlddialog.js', + [ + 'goog.demos.editor.HelloWorldDialog', + 'goog.demos.editor.HelloWorldDialog.OkEvent' + ], + [ + 'goog.dom.TagName', 'goog.events.Event', 'goog.string', + 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.AbstractDialog.Builder', + 'goog.ui.editor.AbstractDialog.EventType' + ]); +goog.addDependency( + 'demos/editor/helloworlddialogplugin.js', + [ + 'goog.demos.editor.HelloWorldDialogPlugin', + 'goog.demos.editor.HelloWorldDialogPlugin.Command' + ], + [ + 'goog.demos.editor.HelloWorldDialog', 'goog.dom.TagName', + 'goog.editor.plugins.AbstractDialogPlugin', 'goog.editor.range', + 'goog.functions', 'goog.ui.editor.AbstractDialog.EventType' + ]); + +/** + * @fileoverview Custom exports file. + * @suppress {checkVars,extraRequire} + */ + +goog.require('ol'); +goog.require('ol.AssertionError'); +goog.require('ol.Attribution'); +goog.require('ol.Collection'); +goog.require('ol.DeviceOrientation'); +goog.require('ol.Feature'); +goog.require('ol.Geolocation'); +goog.require('ol.Graticule'); +goog.require('ol.Image'); +goog.require('ol.ImageTile'); +goog.require('ol.Kinetic'); +goog.require('ol.Map'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapEvent'); +goog.require('ol.Object'); +goog.require('ol.Observable'); +goog.require('ol.Overlay'); +goog.require('ol.Sphere'); +goog.require('ol.Tile'); +goog.require('ol.VectorTile'); +goog.require('ol.View'); +goog.require('ol.animation'); +goog.require('ol.color'); +goog.require('ol.colorlike'); +goog.require('ol.control'); +goog.require('ol.control.Attribution'); +goog.require('ol.control.Control'); +goog.require('ol.control.FullScreen'); +goog.require('ol.control.MousePosition'); +goog.require('ol.control.OverviewMap'); +goog.require('ol.control.Rotate'); +goog.require('ol.control.ScaleLine'); +goog.require('ol.control.Zoom'); +goog.require('ol.control.ZoomSlider'); +goog.require('ol.control.ZoomToExtent'); +goog.require('ol.coordinate'); +goog.require('ol.easing'); +goog.require('ol.events.Event'); +goog.require('ol.events.condition'); +goog.require('ol.extent'); +goog.require('ol.featureloader'); +goog.require('ol.format.EsriJSON'); +goog.require('ol.format.Feature'); +goog.require('ol.format.GML'); +goog.require('ol.format.GML2'); +goog.require('ol.format.GML3'); +goog.require('ol.format.GMLBase'); +goog.require('ol.format.GPX'); +goog.require('ol.format.GeoJSON'); +goog.require('ol.format.IGC'); +goog.require('ol.format.KML'); +goog.require('ol.format.MVT'); +goog.require('ol.format.OSMXML'); +goog.require('ol.format.Polyline'); +goog.require('ol.format.TopoJSON'); +goog.require('ol.format.WFS'); +goog.require('ol.format.WKT'); +goog.require('ol.format.WMSCapabilities'); +goog.require('ol.format.WMSGetFeatureInfo'); +goog.require('ol.format.WMTSCapabilities'); +goog.require('ol.format.filter'); +goog.require('ol.format.filter.And'); +goog.require('ol.format.filter.Bbox'); +goog.require('ol.format.filter.Comparison'); +goog.require('ol.format.filter.ComparisonBinary'); +goog.require('ol.format.filter.EqualTo'); +goog.require('ol.format.filter.Filter'); +goog.require('ol.format.filter.GreaterThan'); +goog.require('ol.format.filter.GreaterThanOrEqualTo'); +goog.require('ol.format.filter.Intersects'); +goog.require('ol.format.filter.IsBetween'); +goog.require('ol.format.filter.IsLike'); +goog.require('ol.format.filter.IsNull'); +goog.require('ol.format.filter.LessThan'); +goog.require('ol.format.filter.LessThanOrEqualTo'); +goog.require('ol.format.filter.Not'); +goog.require('ol.format.filter.NotEqualTo'); +goog.require('ol.format.filter.Or'); +goog.require('ol.format.filter.Spatial'); +goog.require('ol.format.filter.Within'); +goog.require('ol.geom.Circle'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.has'); +goog.require('ol.interaction'); +goog.require('ol.interaction.DoubleClickZoom'); +goog.require('ol.interaction.DragAndDrop'); +goog.require('ol.interaction.DragBox'); +goog.require('ol.interaction.DragPan'); +goog.require('ol.interaction.DragRotate'); +goog.require('ol.interaction.DragRotateAndZoom'); +goog.require('ol.interaction.DragZoom'); +goog.require('ol.interaction.Draw'); +goog.require('ol.interaction.Extent'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.KeyboardPan'); +goog.require('ol.interaction.KeyboardZoom'); +goog.require('ol.interaction.Modify'); +goog.require('ol.interaction.MouseWheelZoom'); +goog.require('ol.interaction.PinchRotate'); +goog.require('ol.interaction.PinchZoom'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.interaction.Select'); +goog.require('ol.interaction.Snap'); +goog.require('ol.interaction.Translate'); +goog.require('ol.layer.Base'); +goog.require('ol.layer.Group'); +goog.require('ol.layer.Heatmap'); +goog.require('ol.layer.Image'); +goog.require('ol.layer.Layer'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); +goog.require('ol.layer.VectorTile'); +goog.require('ol.loadingstrategy'); +goog.require('ol.proj'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); +goog.require('ol.proj.common'); +goog.require('ol.render'); +goog.require('ol.render.Event'); +goog.require('ol.render.Feature'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.canvas.Immediate'); +goog.require('ol.render.webgl.Immediate'); +goog.require('ol.size'); +goog.require('ol.source.BingMaps'); +goog.require('ol.source.CartoDB'); +goog.require('ol.source.Cluster'); +goog.require('ol.source.Image'); +goog.require('ol.source.ImageArcGISRest'); +goog.require('ol.source.ImageCanvas'); +goog.require('ol.source.ImageMapGuide'); +goog.require('ol.source.ImageStatic'); +goog.require('ol.source.ImageVector'); +goog.require('ol.source.ImageWMS'); +goog.require('ol.source.OSM'); +goog.require('ol.source.Raster'); +goog.require('ol.source.Source'); +goog.require('ol.source.Stamen'); +goog.require('ol.source.Tile'); +goog.require('ol.source.TileArcGISRest'); +goog.require('ol.source.TileDebug'); +goog.require('ol.source.TileImage'); +goog.require('ol.source.TileJSON'); +goog.require('ol.source.TileUTFGrid'); +goog.require('ol.source.TileWMS'); +goog.require('ol.source.UrlTile'); +goog.require('ol.source.Vector'); +goog.require('ol.source.VectorTile'); +goog.require('ol.source.WMTS'); +goog.require('ol.source.XYZ'); +goog.require('ol.source.Zoomify'); +goog.require('ol.style.AtlasManager'); +goog.require('ol.style.Circle'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Icon'); +goog.require('ol.style.Image'); +goog.require('ol.style.RegularShape'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Style'); +goog.require('ol.style.Text'); +goog.require('ol.tilegrid'); +goog.require('ol.tilegrid.TileGrid'); +goog.require('ol.tilegrid.WMTS'); +goog.require('ol.webgl.Context'); +goog.require('ol.xml'); + + + +goog.exportSymbol( + 'ol.animation.bounce', + ol.animation.bounce, + OPENLAYERS); + +goog.exportSymbol( + 'ol.animation.pan', + ol.animation.pan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.animation.rotate', + ol.animation.rotate, + OPENLAYERS); + +goog.exportSymbol( + 'ol.animation.zoom', + ol.animation.zoom, + OPENLAYERS); + +goog.exportProperty( + ol.AssertionError.prototype, + 'code', + ol.AssertionError.prototype.code); + +goog.exportSymbol( + 'ol.Attribution', + ol.Attribution, + OPENLAYERS); + +goog.exportProperty( + ol.Attribution.prototype, + 'getHTML', + ol.Attribution.prototype.getHTML); + +goog.exportSymbol( + 'ol.Collection', + ol.Collection, + OPENLAYERS); + +goog.exportProperty( + ol.Collection.prototype, + 'clear', + ol.Collection.prototype.clear); + +goog.exportProperty( + ol.Collection.prototype, + 'extend', + ol.Collection.prototype.extend); + +goog.exportProperty( + ol.Collection.prototype, + 'forEach', + ol.Collection.prototype.forEach); + +goog.exportProperty( + ol.Collection.prototype, + 'getArray', + ol.Collection.prototype.getArray); + +goog.exportProperty( + ol.Collection.prototype, + 'item', + ol.Collection.prototype.item); + +goog.exportProperty( + ol.Collection.prototype, + 'getLength', + ol.Collection.prototype.getLength); + +goog.exportProperty( + ol.Collection.prototype, + 'insertAt', + ol.Collection.prototype.insertAt); + +goog.exportProperty( + ol.Collection.prototype, + 'pop', + ol.Collection.prototype.pop); + +goog.exportProperty( + ol.Collection.prototype, + 'push', + ol.Collection.prototype.push); + +goog.exportProperty( + ol.Collection.prototype, + 'remove', + ol.Collection.prototype.remove); + +goog.exportProperty( + ol.Collection.prototype, + 'removeAt', + ol.Collection.prototype.removeAt); + +goog.exportProperty( + ol.Collection.prototype, + 'setAt', + ol.Collection.prototype.setAt); + +goog.exportProperty( + ol.Collection.Event.prototype, + 'element', + ol.Collection.Event.prototype.element); + +goog.exportSymbol( + 'ol.color.asArray', + ol.color.asArray, + OPENLAYERS); + +goog.exportSymbol( + 'ol.color.asString', + ol.color.asString, + OPENLAYERS); + +goog.exportSymbol( + 'ol.colorlike.asColorLike', + ol.colorlike.asColorLike, + OPENLAYERS); + +goog.exportSymbol( + 'ol.coordinate.add', + ol.coordinate.add, + OPENLAYERS); + +goog.exportSymbol( + 'ol.coordinate.createStringXY', + ol.coordinate.createStringXY, + OPENLAYERS); + +goog.exportSymbol( + 'ol.coordinate.format', + ol.coordinate.format, + OPENLAYERS); + +goog.exportSymbol( + 'ol.coordinate.rotate', + ol.coordinate.rotate, + OPENLAYERS); + +goog.exportSymbol( + 'ol.coordinate.toStringHDMS', + ol.coordinate.toStringHDMS, + OPENLAYERS); + +goog.exportSymbol( + 'ol.coordinate.toStringXY', + ol.coordinate.toStringXY, + OPENLAYERS); + +goog.exportSymbol( + 'ol.DeviceOrientation', + ol.DeviceOrientation, + OPENLAYERS); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getAlpha', + ol.DeviceOrientation.prototype.getAlpha); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getBeta', + ol.DeviceOrientation.prototype.getBeta); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getGamma', + ol.DeviceOrientation.prototype.getGamma); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getHeading', + ol.DeviceOrientation.prototype.getHeading); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getTracking', + ol.DeviceOrientation.prototype.getTracking); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'setTracking', + ol.DeviceOrientation.prototype.setTracking); + +goog.exportSymbol( + 'ol.easing.easeIn', + ol.easing.easeIn, + OPENLAYERS); + +goog.exportSymbol( + 'ol.easing.easeOut', + ol.easing.easeOut, + OPENLAYERS); + +goog.exportSymbol( + 'ol.easing.inAndOut', + ol.easing.inAndOut, + OPENLAYERS); + +goog.exportSymbol( + 'ol.easing.linear', + ol.easing.linear, + OPENLAYERS); + +goog.exportSymbol( + 'ol.easing.upAndDown', + ol.easing.upAndDown, + OPENLAYERS); + +goog.exportSymbol( + 'ol.Feature', + ol.Feature, + OPENLAYERS); + +goog.exportProperty( + ol.Feature.prototype, + 'clone', + ol.Feature.prototype.clone); + +goog.exportProperty( + ol.Feature.prototype, + 'getGeometry', + ol.Feature.prototype.getGeometry); + +goog.exportProperty( + ol.Feature.prototype, + 'getId', + ol.Feature.prototype.getId); + +goog.exportProperty( + ol.Feature.prototype, + 'getGeometryName', + ol.Feature.prototype.getGeometryName); + +goog.exportProperty( + ol.Feature.prototype, + 'getStyle', + ol.Feature.prototype.getStyle); + +goog.exportProperty( + ol.Feature.prototype, + 'getStyleFunction', + ol.Feature.prototype.getStyleFunction); + +goog.exportProperty( + ol.Feature.prototype, + 'setGeometry', + ol.Feature.prototype.setGeometry); + +goog.exportProperty( + ol.Feature.prototype, + 'setStyle', + ol.Feature.prototype.setStyle); + +goog.exportProperty( + ol.Feature.prototype, + 'setId', + ol.Feature.prototype.setId); + +goog.exportProperty( + ol.Feature.prototype, + 'setGeometryName', + ol.Feature.prototype.setGeometryName); + +goog.exportSymbol( + 'ol.featureloader.xhr', + ol.featureloader.xhr, + OPENLAYERS); + +goog.exportSymbol( + 'ol.Geolocation', + ol.Geolocation, + OPENLAYERS); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getAccuracy', + ol.Geolocation.prototype.getAccuracy); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getAccuracyGeometry', + ol.Geolocation.prototype.getAccuracyGeometry); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getAltitude', + ol.Geolocation.prototype.getAltitude); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getAltitudeAccuracy', + ol.Geolocation.prototype.getAltitudeAccuracy); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getHeading', + ol.Geolocation.prototype.getHeading); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getPosition', + ol.Geolocation.prototype.getPosition); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getProjection', + ol.Geolocation.prototype.getProjection); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getSpeed', + ol.Geolocation.prototype.getSpeed); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getTracking', + ol.Geolocation.prototype.getTracking); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getTrackingOptions', + ol.Geolocation.prototype.getTrackingOptions); + +goog.exportProperty( + ol.Geolocation.prototype, + 'setProjection', + ol.Geolocation.prototype.setProjection); + +goog.exportProperty( + ol.Geolocation.prototype, + 'setTracking', + ol.Geolocation.prototype.setTracking); + +goog.exportProperty( + ol.Geolocation.prototype, + 'setTrackingOptions', + ol.Geolocation.prototype.setTrackingOptions); + +goog.exportSymbol( + 'ol.Graticule', + ol.Graticule, + OPENLAYERS); + +goog.exportProperty( + ol.Graticule.prototype, + 'getMap', + ol.Graticule.prototype.getMap); + +goog.exportProperty( + ol.Graticule.prototype, + 'getMeridians', + ol.Graticule.prototype.getMeridians); + +goog.exportProperty( + ol.Graticule.prototype, + 'getParallels', + ol.Graticule.prototype.getParallels); + +goog.exportProperty( + ol.Graticule.prototype, + 'setMap', + ol.Graticule.prototype.setMap); + +goog.exportSymbol( + 'ol.has.DEVICE_PIXEL_RATIO', + ol.has.DEVICE_PIXEL_RATIO, + OPENLAYERS); + +goog.exportSymbol( + 'ol.has.CANVAS', + ol.has.CANVAS, + OPENLAYERS); + +goog.exportSymbol( + 'ol.has.DEVICE_ORIENTATION', + ol.has.DEVICE_ORIENTATION, + OPENLAYERS); + +goog.exportSymbol( + 'ol.has.GEOLOCATION', + ol.has.GEOLOCATION, + OPENLAYERS); + +goog.exportSymbol( + 'ol.has.TOUCH', + ol.has.TOUCH, + OPENLAYERS); + +goog.exportSymbol( + 'ol.has.WEBGL', + ol.has.WEBGL, + OPENLAYERS); + +goog.exportProperty( + ol.Image.prototype, + 'getImage', + ol.Image.prototype.getImage); + +goog.exportProperty( + ol.Image.prototype, + 'load', + ol.Image.prototype.load); + +goog.exportProperty( + ol.ImageTile.prototype, + 'getImage', + ol.ImageTile.prototype.getImage); + +goog.exportProperty( + ol.ImageTile.prototype, + 'load', + ol.ImageTile.prototype.load); + +goog.exportSymbol( + 'ol.inherits', + ol.inherits, + OPENLAYERS); + +goog.exportSymbol( + 'ol.Kinetic', + ol.Kinetic, + OPENLAYERS); + +goog.exportSymbol( + 'ol.loadingstrategy.all', + ol.loadingstrategy.all, + OPENLAYERS); + +goog.exportSymbol( + 'ol.loadingstrategy.bbox', + ol.loadingstrategy.bbox, + OPENLAYERS); + +goog.exportSymbol( + 'ol.loadingstrategy.tile', + ol.loadingstrategy.tile, + OPENLAYERS); + +goog.exportSymbol( + 'ol.Map', + ol.Map, + OPENLAYERS); + +goog.exportProperty( + ol.Map.prototype, + 'addControl', + ol.Map.prototype.addControl); + +goog.exportProperty( + ol.Map.prototype, + 'addInteraction', + ol.Map.prototype.addInteraction); + +goog.exportProperty( + ol.Map.prototype, + 'addLayer', + ol.Map.prototype.addLayer); + +goog.exportProperty( + ol.Map.prototype, + 'addOverlay', + ol.Map.prototype.addOverlay); + +goog.exportProperty( + ol.Map.prototype, + 'beforeRender', + ol.Map.prototype.beforeRender); + +goog.exportProperty( + ol.Map.prototype, + 'forEachFeatureAtPixel', + ol.Map.prototype.forEachFeatureAtPixel); + +goog.exportProperty( + ol.Map.prototype, + 'forEachLayerAtPixel', + ol.Map.prototype.forEachLayerAtPixel); + +goog.exportProperty( + ol.Map.prototype, + 'hasFeatureAtPixel', + ol.Map.prototype.hasFeatureAtPixel); + +goog.exportProperty( + ol.Map.prototype, + 'getEventCoordinate', + ol.Map.prototype.getEventCoordinate); + +goog.exportProperty( + ol.Map.prototype, + 'getEventPixel', + ol.Map.prototype.getEventPixel); + +goog.exportProperty( + ol.Map.prototype, + 'getTarget', + ol.Map.prototype.getTarget); + +goog.exportProperty( + ol.Map.prototype, + 'getTargetElement', + ol.Map.prototype.getTargetElement); + +goog.exportProperty( + ol.Map.prototype, + 'getCoordinateFromPixel', + ol.Map.prototype.getCoordinateFromPixel); + +goog.exportProperty( + ol.Map.prototype, + 'getControls', + ol.Map.prototype.getControls); + +goog.exportProperty( + ol.Map.prototype, + 'getOverlays', + ol.Map.prototype.getOverlays); + +goog.exportProperty( + ol.Map.prototype, + 'getOverlayById', + ol.Map.prototype.getOverlayById); + +goog.exportProperty( + ol.Map.prototype, + 'getInteractions', + ol.Map.prototype.getInteractions); + +goog.exportProperty( + ol.Map.prototype, + 'getLayerGroup', + ol.Map.prototype.getLayerGroup); + +goog.exportProperty( + ol.Map.prototype, + 'getLayers', + ol.Map.prototype.getLayers); + +goog.exportProperty( + ol.Map.prototype, + 'getPixelFromCoordinate', + ol.Map.prototype.getPixelFromCoordinate); + +goog.exportProperty( + ol.Map.prototype, + 'getSize', + ol.Map.prototype.getSize); + +goog.exportProperty( + ol.Map.prototype, + 'getView', + ol.Map.prototype.getView); + +goog.exportProperty( + ol.Map.prototype, + 'getViewport', + ol.Map.prototype.getViewport); + +goog.exportProperty( + ol.Map.prototype, + 'renderSync', + ol.Map.prototype.renderSync); + +goog.exportProperty( + ol.Map.prototype, + 'render', + ol.Map.prototype.render); + +goog.exportProperty( + ol.Map.prototype, + 'removeControl', + ol.Map.prototype.removeControl); + +goog.exportProperty( + ol.Map.prototype, + 'removeInteraction', + ol.Map.prototype.removeInteraction); + +goog.exportProperty( + ol.Map.prototype, + 'removeLayer', + ol.Map.prototype.removeLayer); + +goog.exportProperty( + ol.Map.prototype, + 'removeOverlay', + ol.Map.prototype.removeOverlay); + +goog.exportProperty( + ol.Map.prototype, + 'setLayerGroup', + ol.Map.prototype.setLayerGroup); + +goog.exportProperty( + ol.Map.prototype, + 'setSize', + ol.Map.prototype.setSize); + +goog.exportProperty( + ol.Map.prototype, + 'setTarget', + ol.Map.prototype.setTarget); + +goog.exportProperty( + ol.Map.prototype, + 'setView', + ol.Map.prototype.setView); + +goog.exportProperty( + ol.Map.prototype, + 'updateSize', + ol.Map.prototype.updateSize); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'originalEvent', + ol.MapBrowserEvent.prototype.originalEvent); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'pixel', + ol.MapBrowserEvent.prototype.pixel); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'coordinate', + ol.MapBrowserEvent.prototype.coordinate); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'dragging', + ol.MapBrowserEvent.prototype.dragging); + +goog.exportProperty( + ol.MapEvent.prototype, + 'map', + ol.MapEvent.prototype.map); + +goog.exportProperty( + ol.MapEvent.prototype, + 'frameState', + ol.MapEvent.prototype.frameState); + +goog.exportSymbol( + 'ol.Object', + ol.Object, + OPENLAYERS); + +goog.exportProperty( + ol.Object.prototype, + 'get', + ol.Object.prototype.get); + +goog.exportProperty( + ol.Object.prototype, + 'getKeys', + ol.Object.prototype.getKeys); + +goog.exportProperty( + ol.Object.prototype, + 'getProperties', + ol.Object.prototype.getProperties); + +goog.exportProperty( + ol.Object.prototype, + 'set', + ol.Object.prototype.set); + +goog.exportProperty( + ol.Object.prototype, + 'setProperties', + ol.Object.prototype.setProperties); + +goog.exportProperty( + ol.Object.prototype, + 'unset', + ol.Object.prototype.unset); + +goog.exportProperty( + ol.Object.Event.prototype, + 'key', + ol.Object.Event.prototype.key); + +goog.exportProperty( + ol.Object.Event.prototype, + 'oldValue', + ol.Object.Event.prototype.oldValue); + +goog.exportSymbol( + 'ol.Observable', + ol.Observable, + OPENLAYERS); + +goog.exportSymbol( + 'ol.Observable.unByKey', + ol.Observable.unByKey, + OPENLAYERS); + +goog.exportProperty( + ol.Observable.prototype, + 'changed', + ol.Observable.prototype.changed); + +goog.exportProperty( + ol.Observable.prototype, + 'dispatchEvent', + ol.Observable.prototype.dispatchEvent); + +goog.exportProperty( + ol.Observable.prototype, + 'getRevision', + ol.Observable.prototype.getRevision); + +goog.exportProperty( + ol.Observable.prototype, + 'on', + ol.Observable.prototype.on); + +goog.exportProperty( + ol.Observable.prototype, + 'once', + ol.Observable.prototype.once); + +goog.exportProperty( + ol.Observable.prototype, + 'un', + ol.Observable.prototype.un); + +goog.exportProperty( + ol.Observable.prototype, + 'unByKey', + ol.Observable.prototype.unByKey); + +goog.exportSymbol( + 'ol.Overlay', + ol.Overlay, + OPENLAYERS); + +goog.exportProperty( + ol.Overlay.prototype, + 'getElement', + ol.Overlay.prototype.getElement); + +goog.exportProperty( + ol.Overlay.prototype, + 'getId', + ol.Overlay.prototype.getId); + +goog.exportProperty( + ol.Overlay.prototype, + 'getMap', + ol.Overlay.prototype.getMap); + +goog.exportProperty( + ol.Overlay.prototype, + 'getOffset', + ol.Overlay.prototype.getOffset); + +goog.exportProperty( + ol.Overlay.prototype, + 'getPosition', + ol.Overlay.prototype.getPosition); + +goog.exportProperty( + ol.Overlay.prototype, + 'getPositioning', + ol.Overlay.prototype.getPositioning); + +goog.exportProperty( + ol.Overlay.prototype, + 'setElement', + ol.Overlay.prototype.setElement); + +goog.exportProperty( + ol.Overlay.prototype, + 'setMap', + ol.Overlay.prototype.setMap); + +goog.exportProperty( + ol.Overlay.prototype, + 'setOffset', + ol.Overlay.prototype.setOffset); + +goog.exportProperty( + ol.Overlay.prototype, + 'setPosition', + ol.Overlay.prototype.setPosition); + +goog.exportProperty( + ol.Overlay.prototype, + 'setPositioning', + ol.Overlay.prototype.setPositioning); + +goog.exportSymbol( + 'ol.render.toContext', + ol.render.toContext, + OPENLAYERS); + +goog.exportSymbol( + 'ol.size.toSize', + ol.size.toSize, + OPENLAYERS); + +goog.exportProperty( + ol.Tile.prototype, + 'getTileCoord', + ol.Tile.prototype.getTileCoord); + +goog.exportProperty( + ol.Tile.prototype, + 'load', + ol.Tile.prototype.load); + +goog.exportProperty( + ol.VectorTile.prototype, + 'getFormat', + ol.VectorTile.prototype.getFormat); + +goog.exportProperty( + ol.VectorTile.prototype, + 'setFeatures', + ol.VectorTile.prototype.setFeatures); + +goog.exportProperty( + ol.VectorTile.prototype, + 'setProjection', + ol.VectorTile.prototype.setProjection); + +goog.exportProperty( + ol.VectorTile.prototype, + 'setLoader', + ol.VectorTile.prototype.setLoader); + +goog.exportSymbol( + 'ol.View', + ol.View, + OPENLAYERS); + +goog.exportProperty( + ol.View.prototype, + 'animate', + ol.View.prototype.animate); + +goog.exportProperty( + ol.View.prototype, + 'constrainCenter', + ol.View.prototype.constrainCenter); + +goog.exportProperty( + ol.View.prototype, + 'constrainResolution', + ol.View.prototype.constrainResolution); + +goog.exportProperty( + ol.View.prototype, + 'constrainRotation', + ol.View.prototype.constrainRotation); + +goog.exportProperty( + ol.View.prototype, + 'getCenter', + ol.View.prototype.getCenter); + +goog.exportProperty( + ol.View.prototype, + 'calculateExtent', + ol.View.prototype.calculateExtent); + +goog.exportProperty( + ol.View.prototype, + 'getMaxResolution', + ol.View.prototype.getMaxResolution); + +goog.exportProperty( + ol.View.prototype, + 'getMinResolution', + ol.View.prototype.getMinResolution); + +goog.exportProperty( + ol.View.prototype, + 'getProjection', + ol.View.prototype.getProjection); + +goog.exportProperty( + ol.View.prototype, + 'getResolution', + ol.View.prototype.getResolution); + +goog.exportProperty( + ol.View.prototype, + 'getResolutions', + ol.View.prototype.getResolutions); + +goog.exportProperty( + ol.View.prototype, + 'getRotation', + ol.View.prototype.getRotation); + +goog.exportProperty( + ol.View.prototype, + 'getZoom', + ol.View.prototype.getZoom); + +goog.exportProperty( + ol.View.prototype, + 'fit', + ol.View.prototype.fit); + +goog.exportProperty( + ol.View.prototype, + 'centerOn', + ol.View.prototype.centerOn); + +goog.exportProperty( + ol.View.prototype, + 'rotate', + ol.View.prototype.rotate); + +goog.exportProperty( + ol.View.prototype, + 'setCenter', + ol.View.prototype.setCenter); + +goog.exportProperty( + ol.View.prototype, + 'setResolution', + ol.View.prototype.setResolution); + +goog.exportProperty( + ol.View.prototype, + 'setRotation', + ol.View.prototype.setRotation); + +goog.exportProperty( + ol.View.prototype, + 'setZoom', + ol.View.prototype.setZoom); + +goog.exportSymbol( + 'ol.xml.getAllTextContent', + ol.xml.getAllTextContent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.xml.parse', + ol.xml.parse, + OPENLAYERS); + +goog.exportProperty( + ol.webgl.Context.prototype, + 'getGL', + ol.webgl.Context.prototype.getGL); + +goog.exportProperty( + ol.webgl.Context.prototype, + 'useProgram', + ol.webgl.Context.prototype.useProgram); + +goog.exportSymbol( + 'ol.tilegrid.createXYZ', + ol.tilegrid.createXYZ, + OPENLAYERS); + +goog.exportSymbol( + 'ol.tilegrid.TileGrid', + ol.tilegrid.TileGrid, + OPENLAYERS); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'forEachTileCoord', + ol.tilegrid.TileGrid.prototype.forEachTileCoord); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getMaxZoom', + ol.tilegrid.TileGrid.prototype.getMaxZoom); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getMinZoom', + ol.tilegrid.TileGrid.prototype.getMinZoom); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getOrigin', + ol.tilegrid.TileGrid.prototype.getOrigin); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getResolution', + ol.tilegrid.TileGrid.prototype.getResolution); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getResolutions', + ol.tilegrid.TileGrid.prototype.getResolutions); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getTileCoordExtent', + ol.tilegrid.TileGrid.prototype.getTileCoordExtent); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getTileCoordForCoordAndResolution', + ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getTileCoordForCoordAndZ', + ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getTileSize', + ol.tilegrid.TileGrid.prototype.getTileSize); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getZForResolution', + ol.tilegrid.TileGrid.prototype.getZForResolution); + +goog.exportSymbol( + 'ol.tilegrid.WMTS', + ol.tilegrid.WMTS, + OPENLAYERS); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getMatrixIds', + ol.tilegrid.WMTS.prototype.getMatrixIds); + +goog.exportSymbol( + 'ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet', + ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet, + OPENLAYERS); + +goog.exportSymbol( + 'ol.style.AtlasManager', + ol.style.AtlasManager, + OPENLAYERS); + +goog.exportSymbol( + 'ol.style.Circle', + ol.style.Circle, + OPENLAYERS); + +goog.exportProperty( + ol.style.Circle.prototype, + 'clone', + ol.style.Circle.prototype.clone); + +goog.exportProperty( + ol.style.Circle.prototype, + 'setRadius', + ol.style.Circle.prototype.setRadius); + +goog.exportSymbol( + 'ol.style.Fill', + ol.style.Fill, + OPENLAYERS); + +goog.exportProperty( + ol.style.Fill.prototype, + 'clone', + ol.style.Fill.prototype.clone); + +goog.exportProperty( + ol.style.Fill.prototype, + 'getColor', + ol.style.Fill.prototype.getColor); + +goog.exportProperty( + ol.style.Fill.prototype, + 'setColor', + ol.style.Fill.prototype.setColor); + +goog.exportSymbol( + 'ol.style.Icon', + ol.style.Icon, + OPENLAYERS); + +goog.exportProperty( + ol.style.Icon.prototype, + 'clone', + ol.style.Icon.prototype.clone); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getAnchor', + ol.style.Icon.prototype.getAnchor); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getColor', + ol.style.Icon.prototype.getColor); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getImage', + ol.style.Icon.prototype.getImage); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getOrigin', + ol.style.Icon.prototype.getOrigin); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getSrc', + ol.style.Icon.prototype.getSrc); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getSize', + ol.style.Icon.prototype.getSize); + +goog.exportProperty( + ol.style.Icon.prototype, + 'load', + ol.style.Icon.prototype.load); + +goog.exportSymbol( + 'ol.style.Image', + ol.style.Image, + OPENLAYERS); + +goog.exportProperty( + ol.style.Image.prototype, + 'getOpacity', + ol.style.Image.prototype.getOpacity); + +goog.exportProperty( + ol.style.Image.prototype, + 'getRotateWithView', + ol.style.Image.prototype.getRotateWithView); + +goog.exportProperty( + ol.style.Image.prototype, + 'getRotation', + ol.style.Image.prototype.getRotation); + +goog.exportProperty( + ol.style.Image.prototype, + 'getScale', + ol.style.Image.prototype.getScale); + +goog.exportProperty( + ol.style.Image.prototype, + 'getSnapToPixel', + ol.style.Image.prototype.getSnapToPixel); + +goog.exportProperty( + ol.style.Image.prototype, + 'setOpacity', + ol.style.Image.prototype.setOpacity); + +goog.exportProperty( + ol.style.Image.prototype, + 'setRotation', + ol.style.Image.prototype.setRotation); + +goog.exportProperty( + ol.style.Image.prototype, + 'setScale', + ol.style.Image.prototype.setScale); + +goog.exportSymbol( + 'ol.style.RegularShape', + ol.style.RegularShape, + OPENLAYERS); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'clone', + ol.style.RegularShape.prototype.clone); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getAnchor', + ol.style.RegularShape.prototype.getAnchor); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getAngle', + ol.style.RegularShape.prototype.getAngle); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getFill', + ol.style.RegularShape.prototype.getFill); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getImage', + ol.style.RegularShape.prototype.getImage); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getOrigin', + ol.style.RegularShape.prototype.getOrigin); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getPoints', + ol.style.RegularShape.prototype.getPoints); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getRadius', + ol.style.RegularShape.prototype.getRadius); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getRadius2', + ol.style.RegularShape.prototype.getRadius2); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getSize', + ol.style.RegularShape.prototype.getSize); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getStroke', + ol.style.RegularShape.prototype.getStroke); + +goog.exportSymbol( + 'ol.style.Stroke', + ol.style.Stroke, + OPENLAYERS); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'clone', + ol.style.Stroke.prototype.clone); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'getColor', + ol.style.Stroke.prototype.getColor); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'getLineCap', + ol.style.Stroke.prototype.getLineCap); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'getLineDash', + ol.style.Stroke.prototype.getLineDash); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'getLineJoin', + ol.style.Stroke.prototype.getLineJoin); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'getMiterLimit', + ol.style.Stroke.prototype.getMiterLimit); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'getWidth', + ol.style.Stroke.prototype.getWidth); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'setColor', + ol.style.Stroke.prototype.setColor); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'setLineCap', + ol.style.Stroke.prototype.setLineCap); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'setLineDash', + ol.style.Stroke.prototype.setLineDash); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'setLineJoin', + ol.style.Stroke.prototype.setLineJoin); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'setMiterLimit', + ol.style.Stroke.prototype.setMiterLimit); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'setWidth', + ol.style.Stroke.prototype.setWidth); + +goog.exportSymbol( + 'ol.style.Style', + ol.style.Style, + OPENLAYERS); + +goog.exportProperty( + ol.style.Style.prototype, + 'clone', + ol.style.Style.prototype.clone); + +goog.exportProperty( + ol.style.Style.prototype, + 'getGeometry', + ol.style.Style.prototype.getGeometry); + +goog.exportProperty( + ol.style.Style.prototype, + 'getGeometryFunction', + ol.style.Style.prototype.getGeometryFunction); + +goog.exportProperty( + ol.style.Style.prototype, + 'getFill', + ol.style.Style.prototype.getFill); + +goog.exportProperty( + ol.style.Style.prototype, + 'setFill', + ol.style.Style.prototype.setFill); + +goog.exportProperty( + ol.style.Style.prototype, + 'getImage', + ol.style.Style.prototype.getImage); + +goog.exportProperty( + ol.style.Style.prototype, + 'setImage', + ol.style.Style.prototype.setImage); + +goog.exportProperty( + ol.style.Style.prototype, + 'getStroke', + ol.style.Style.prototype.getStroke); + +goog.exportProperty( + ol.style.Style.prototype, + 'setStroke', + ol.style.Style.prototype.setStroke); + +goog.exportProperty( + ol.style.Style.prototype, + 'getText', + ol.style.Style.prototype.getText); + +goog.exportProperty( + ol.style.Style.prototype, + 'setText', + ol.style.Style.prototype.setText); + +goog.exportProperty( + ol.style.Style.prototype, + 'getZIndex', + ol.style.Style.prototype.getZIndex); + +goog.exportProperty( + ol.style.Style.prototype, + 'setGeometry', + ol.style.Style.prototype.setGeometry); + +goog.exportProperty( + ol.style.Style.prototype, + 'setZIndex', + ol.style.Style.prototype.setZIndex); + +goog.exportSymbol( + 'ol.style.Text', + ol.style.Text, + OPENLAYERS); + +goog.exportProperty( + ol.style.Text.prototype, + 'clone', + ol.style.Text.prototype.clone); + +goog.exportProperty( + ol.style.Text.prototype, + 'getFont', + ol.style.Text.prototype.getFont); + +goog.exportProperty( + ol.style.Text.prototype, + 'getOffsetX', + ol.style.Text.prototype.getOffsetX); + +goog.exportProperty( + ol.style.Text.prototype, + 'getOffsetY', + ol.style.Text.prototype.getOffsetY); + +goog.exportProperty( + ol.style.Text.prototype, + 'getFill', + ol.style.Text.prototype.getFill); + +goog.exportProperty( + ol.style.Text.prototype, + 'getRotateWithView', + ol.style.Text.prototype.getRotateWithView); + +goog.exportProperty( + ol.style.Text.prototype, + 'getRotation', + ol.style.Text.prototype.getRotation); + +goog.exportProperty( + ol.style.Text.prototype, + 'getScale', + ol.style.Text.prototype.getScale); + +goog.exportProperty( + ol.style.Text.prototype, + 'getStroke', + ol.style.Text.prototype.getStroke); + +goog.exportProperty( + ol.style.Text.prototype, + 'getText', + ol.style.Text.prototype.getText); + +goog.exportProperty( + ol.style.Text.prototype, + 'getTextAlign', + ol.style.Text.prototype.getTextAlign); + +goog.exportProperty( + ol.style.Text.prototype, + 'getTextBaseline', + ol.style.Text.prototype.getTextBaseline); + +goog.exportProperty( + ol.style.Text.prototype, + 'setFont', + ol.style.Text.prototype.setFont); + +goog.exportProperty( + ol.style.Text.prototype, + 'setOffsetX', + ol.style.Text.prototype.setOffsetX); + +goog.exportProperty( + ol.style.Text.prototype, + 'setOffsetY', + ol.style.Text.prototype.setOffsetY); + +goog.exportProperty( + ol.style.Text.prototype, + 'setFill', + ol.style.Text.prototype.setFill); + +goog.exportProperty( + ol.style.Text.prototype, + 'setRotation', + ol.style.Text.prototype.setRotation); + +goog.exportProperty( + ol.style.Text.prototype, + 'setScale', + ol.style.Text.prototype.setScale); + +goog.exportProperty( + ol.style.Text.prototype, + 'setStroke', + ol.style.Text.prototype.setStroke); + +goog.exportProperty( + ol.style.Text.prototype, + 'setText', + ol.style.Text.prototype.setText); + +goog.exportProperty( + ol.style.Text.prototype, + 'setTextAlign', + ol.style.Text.prototype.setTextAlign); + +goog.exportProperty( + ol.style.Text.prototype, + 'setTextBaseline', + ol.style.Text.prototype.setTextBaseline); + +goog.exportSymbol( + 'ol.Sphere', + ol.Sphere, + OPENLAYERS); + +goog.exportProperty( + ol.Sphere.prototype, + 'geodesicArea', + ol.Sphere.prototype.geodesicArea); + +goog.exportProperty( + ol.Sphere.prototype, + 'haversineDistance', + ol.Sphere.prototype.haversineDistance); + +goog.exportSymbol( + 'ol.source.BingMaps', + ol.source.BingMaps, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.BingMaps.TOS_ATTRIBUTION', + ol.source.BingMaps.TOS_ATTRIBUTION, + OPENLAYERS); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getApiKey', + ol.source.BingMaps.prototype.getApiKey); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getImagerySet', + ol.source.BingMaps.prototype.getImagerySet); + +goog.exportSymbol( + 'ol.source.CartoDB', + ol.source.CartoDB, + OPENLAYERS); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getConfig', + ol.source.CartoDB.prototype.getConfig); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'updateConfig', + ol.source.CartoDB.prototype.updateConfig); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setConfig', + ol.source.CartoDB.prototype.setConfig); + +goog.exportSymbol( + 'ol.source.Cluster', + ol.source.Cluster, + OPENLAYERS); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getSource', + ol.source.Cluster.prototype.getSource); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'setDistance', + ol.source.Cluster.prototype.setDistance); + +goog.exportSymbol( + 'ol.source.Image', + ol.source.Image, + OPENLAYERS); + +goog.exportProperty( + ol.source.Image.Event.prototype, + 'image', + ol.source.Image.Event.prototype.image); + +goog.exportSymbol( + 'ol.source.ImageArcGISRest', + ol.source.ImageArcGISRest, + OPENLAYERS); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getParams', + ol.source.ImageArcGISRest.prototype.getParams); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getImageLoadFunction', + ol.source.ImageArcGISRest.prototype.getImageLoadFunction); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getUrl', + ol.source.ImageArcGISRest.prototype.getUrl); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'setImageLoadFunction', + ol.source.ImageArcGISRest.prototype.setImageLoadFunction); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'setUrl', + ol.source.ImageArcGISRest.prototype.setUrl); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'updateParams', + ol.source.ImageArcGISRest.prototype.updateParams); + +goog.exportSymbol( + 'ol.source.ImageCanvas', + ol.source.ImageCanvas, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.ImageMapGuide', + ol.source.ImageMapGuide, + OPENLAYERS); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getParams', + ol.source.ImageMapGuide.prototype.getParams); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getImageLoadFunction', + ol.source.ImageMapGuide.prototype.getImageLoadFunction); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'updateParams', + ol.source.ImageMapGuide.prototype.updateParams); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'setImageLoadFunction', + ol.source.ImageMapGuide.prototype.setImageLoadFunction); + +goog.exportSymbol( + 'ol.source.ImageStatic', + ol.source.ImageStatic, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.ImageVector', + ol.source.ImageVector, + OPENLAYERS); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getSource', + ol.source.ImageVector.prototype.getSource); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getStyle', + ol.source.ImageVector.prototype.getStyle); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getStyleFunction', + ol.source.ImageVector.prototype.getStyleFunction); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'setStyle', + ol.source.ImageVector.prototype.setStyle); + +goog.exportSymbol( + 'ol.source.ImageWMS', + ol.source.ImageWMS, + OPENLAYERS); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getGetFeatureInfoUrl', + ol.source.ImageWMS.prototype.getGetFeatureInfoUrl); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getParams', + ol.source.ImageWMS.prototype.getParams); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getImageLoadFunction', + ol.source.ImageWMS.prototype.getImageLoadFunction); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getUrl', + ol.source.ImageWMS.prototype.getUrl); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'setImageLoadFunction', + ol.source.ImageWMS.prototype.setImageLoadFunction); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'setUrl', + ol.source.ImageWMS.prototype.setUrl); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'updateParams', + ol.source.ImageWMS.prototype.updateParams); + +goog.exportSymbol( + 'ol.source.OSM', + ol.source.OSM, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.OSM.ATTRIBUTION', + ol.source.OSM.ATTRIBUTION, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.Raster', + ol.source.Raster, + OPENLAYERS); + +goog.exportProperty( + ol.source.Raster.prototype, + 'setOperation', + ol.source.Raster.prototype.setOperation); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'extent', + ol.source.Raster.Event.prototype.extent); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'resolution', + ol.source.Raster.Event.prototype.resolution); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'data', + ol.source.Raster.Event.prototype.data); + +goog.exportSymbol( + 'ol.source.Source', + ol.source.Source, + OPENLAYERS); + +goog.exportProperty( + ol.source.Source.prototype, + 'getAttributions', + ol.source.Source.prototype.getAttributions); + +goog.exportProperty( + ol.source.Source.prototype, + 'getLogo', + ol.source.Source.prototype.getLogo); + +goog.exportProperty( + ol.source.Source.prototype, + 'getProjection', + ol.source.Source.prototype.getProjection); + +goog.exportProperty( + ol.source.Source.prototype, + 'getState', + ol.source.Source.prototype.getState); + +goog.exportProperty( + ol.source.Source.prototype, + 'refresh', + ol.source.Source.prototype.refresh); + +goog.exportProperty( + ol.source.Source.prototype, + 'setAttributions', + ol.source.Source.prototype.setAttributions); + +goog.exportSymbol( + 'ol.source.Stamen', + ol.source.Stamen, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.Tile', + ol.source.Tile, + OPENLAYERS); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getTileGrid', + ol.source.Tile.prototype.getTileGrid); + +goog.exportProperty( + ol.source.Tile.Event.prototype, + 'tile', + ol.source.Tile.Event.prototype.tile); + +goog.exportSymbol( + 'ol.source.TileArcGISRest', + ol.source.TileArcGISRest, + OPENLAYERS); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getParams', + ol.source.TileArcGISRest.prototype.getParams); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'updateParams', + ol.source.TileArcGISRest.prototype.updateParams); + +goog.exportSymbol( + 'ol.source.TileDebug', + ol.source.TileDebug, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.TileImage', + ol.source.TileImage, + OPENLAYERS); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setRenderReprojectionEdges', + ol.source.TileImage.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setTileGridForProjection', + ol.source.TileImage.prototype.setTileGridForProjection); + +goog.exportSymbol( + 'ol.source.TileJSON', + ol.source.TileJSON, + OPENLAYERS); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getTileJSON', + ol.source.TileJSON.prototype.getTileJSON); + +goog.exportSymbol( + 'ol.source.TileUTFGrid', + ol.source.TileUTFGrid, + OPENLAYERS); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getTemplate', + ol.source.TileUTFGrid.prototype.getTemplate); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'forDataAtCoordinateAndResolution', + ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution); + +goog.exportSymbol( + 'ol.source.TileWMS', + ol.source.TileWMS, + OPENLAYERS); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getGetFeatureInfoUrl', + ol.source.TileWMS.prototype.getGetFeatureInfoUrl); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getParams', + ol.source.TileWMS.prototype.getParams); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'updateParams', + ol.source.TileWMS.prototype.updateParams); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getTileLoadFunction', + ol.source.UrlTile.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getTileUrlFunction', + ol.source.UrlTile.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getUrls', + ol.source.UrlTile.prototype.getUrls); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'setTileLoadFunction', + ol.source.UrlTile.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'setTileUrlFunction', + ol.source.UrlTile.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'setUrl', + ol.source.UrlTile.prototype.setUrl); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'setUrls', + ol.source.UrlTile.prototype.setUrls); + +goog.exportSymbol( + 'ol.source.Vector', + ol.source.Vector, + OPENLAYERS); + +goog.exportProperty( + ol.source.Vector.prototype, + 'addFeature', + ol.source.Vector.prototype.addFeature); + +goog.exportProperty( + ol.source.Vector.prototype, + 'addFeatures', + ol.source.Vector.prototype.addFeatures); + +goog.exportProperty( + ol.source.Vector.prototype, + 'clear', + ol.source.Vector.prototype.clear); + +goog.exportProperty( + ol.source.Vector.prototype, + 'forEachFeature', + ol.source.Vector.prototype.forEachFeature); + +goog.exportProperty( + ol.source.Vector.prototype, + 'forEachFeatureInExtent', + ol.source.Vector.prototype.forEachFeatureInExtent); + +goog.exportProperty( + ol.source.Vector.prototype, + 'forEachFeatureIntersectingExtent', + ol.source.Vector.prototype.forEachFeatureIntersectingExtent); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getFeaturesCollection', + ol.source.Vector.prototype.getFeaturesCollection); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getFeatures', + ol.source.Vector.prototype.getFeatures); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getFeaturesAtCoordinate', + ol.source.Vector.prototype.getFeaturesAtCoordinate); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getFeaturesInExtent', + ol.source.Vector.prototype.getFeaturesInExtent); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getClosestFeatureToCoordinate', + ol.source.Vector.prototype.getClosestFeatureToCoordinate); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getExtent', + ol.source.Vector.prototype.getExtent); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getFeatureById', + ol.source.Vector.prototype.getFeatureById); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getFormat', + ol.source.Vector.prototype.getFormat); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getUrl', + ol.source.Vector.prototype.getUrl); + +goog.exportProperty( + ol.source.Vector.prototype, + 'removeFeature', + ol.source.Vector.prototype.removeFeature); + +goog.exportProperty( + ol.source.Vector.Event.prototype, + 'feature', + ol.source.Vector.Event.prototype.feature); + +goog.exportSymbol( + 'ol.source.VectorTile', + ol.source.VectorTile, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.WMTS', + ol.source.WMTS, + OPENLAYERS); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getDimensions', + ol.source.WMTS.prototype.getDimensions); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getFormat', + ol.source.WMTS.prototype.getFormat); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getLayer', + ol.source.WMTS.prototype.getLayer); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getMatrixSet', + ol.source.WMTS.prototype.getMatrixSet); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getRequestEncoding', + ol.source.WMTS.prototype.getRequestEncoding); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getStyle', + ol.source.WMTS.prototype.getStyle); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getVersion', + ol.source.WMTS.prototype.getVersion); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'updateDimensions', + ol.source.WMTS.prototype.updateDimensions); + +goog.exportSymbol( + 'ol.source.WMTS.optionsFromCapabilities', + ol.source.WMTS.optionsFromCapabilities, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.XYZ', + ol.source.XYZ, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.Zoomify', + ol.source.Zoomify, + OPENLAYERS); + +goog.exportProperty( + ol.render.Event.prototype, + 'vectorContext', + ol.render.Event.prototype.vectorContext); + +goog.exportProperty( + ol.render.Event.prototype, + 'frameState', + ol.render.Event.prototype.frameState); + +goog.exportProperty( + ol.render.Event.prototype, + 'context', + ol.render.Event.prototype.context); + +goog.exportProperty( + ol.render.Event.prototype, + 'glContext', + ol.render.Event.prototype.glContext); + +goog.exportProperty( + ol.render.Feature.prototype, + 'get', + ol.render.Feature.prototype.get); + +goog.exportProperty( + ol.render.Feature.prototype, + 'getExtent', + ol.render.Feature.prototype.getExtent); + +goog.exportProperty( + ol.render.Feature.prototype, + 'getGeometry', + ol.render.Feature.prototype.getGeometry); + +goog.exportProperty( + ol.render.Feature.prototype, + 'getProperties', + ol.render.Feature.prototype.getProperties); + +goog.exportProperty( + ol.render.Feature.prototype, + 'getType', + ol.render.Feature.prototype.getType); + +goog.exportSymbol( + 'ol.render.VectorContext', + ol.render.VectorContext, + OPENLAYERS); + +goog.exportProperty( + ol.render.webgl.Immediate.prototype, + 'setStyle', + ol.render.webgl.Immediate.prototype.setStyle); + +goog.exportProperty( + ol.render.webgl.Immediate.prototype, + 'drawGeometry', + ol.render.webgl.Immediate.prototype.drawGeometry); + +goog.exportProperty( + ol.render.webgl.Immediate.prototype, + 'drawFeature', + ol.render.webgl.Immediate.prototype.drawFeature); + +goog.exportProperty( + ol.render.canvas.Immediate.prototype, + 'drawCircle', + ol.render.canvas.Immediate.prototype.drawCircle); + +goog.exportProperty( + ol.render.canvas.Immediate.prototype, + 'setStyle', + ol.render.canvas.Immediate.prototype.setStyle); + +goog.exportProperty( + ol.render.canvas.Immediate.prototype, + 'drawGeometry', + ol.render.canvas.Immediate.prototype.drawGeometry); + +goog.exportProperty( + ol.render.canvas.Immediate.prototype, + 'drawFeature', + ol.render.canvas.Immediate.prototype.drawFeature); + +goog.exportSymbol( + 'ol.proj.common.add', + ol.proj.common.add, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.METERS_PER_UNIT', + ol.proj.METERS_PER_UNIT, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.setProj4', + ol.proj.setProj4, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.getPointResolution', + ol.proj.getPointResolution, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.addEquivalentProjections', + ol.proj.addEquivalentProjections, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.addProjection', + ol.proj.addProjection, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.addCoordinateTransforms', + ol.proj.addCoordinateTransforms, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.fromLonLat', + ol.proj.fromLonLat, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.toLonLat', + ol.proj.toLonLat, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.get', + ol.proj.get, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.equivalent', + ol.proj.equivalent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.getTransform', + ol.proj.getTransform, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.transform', + ol.proj.transform, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.transformExtent', + ol.proj.transformExtent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.Projection', + ol.proj.Projection, + OPENLAYERS); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'getCode', + ol.proj.Projection.prototype.getCode); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'getExtent', + ol.proj.Projection.prototype.getExtent); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'getUnits', + ol.proj.Projection.prototype.getUnits); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'getMetersPerUnit', + ol.proj.Projection.prototype.getMetersPerUnit); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'getWorldExtent', + ol.proj.Projection.prototype.getWorldExtent); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'isGlobal', + ol.proj.Projection.prototype.isGlobal); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'setGlobal', + ol.proj.Projection.prototype.setGlobal); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'setExtent', + ol.proj.Projection.prototype.setExtent); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'setWorldExtent', + ol.proj.Projection.prototype.setWorldExtent); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'setGetPointResolution', + ol.proj.Projection.prototype.setGetPointResolution); + +goog.exportSymbol( + 'ol.proj.Units.METERS_PER_UNIT', + ol.proj.Units.METERS_PER_UNIT, + OPENLAYERS); + +goog.exportSymbol( + 'ol.layer.Base', + ol.layer.Base, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getExtent', + ol.layer.Base.prototype.getExtent); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getMaxResolution', + ol.layer.Base.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getMinResolution', + ol.layer.Base.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getOpacity', + ol.layer.Base.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getVisible', + ol.layer.Base.prototype.getVisible); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getZIndex', + ol.layer.Base.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setExtent', + ol.layer.Base.prototype.setExtent); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setMaxResolution', + ol.layer.Base.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setMinResolution', + ol.layer.Base.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setOpacity', + ol.layer.Base.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setVisible', + ol.layer.Base.prototype.setVisible); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setZIndex', + ol.layer.Base.prototype.setZIndex); + +goog.exportSymbol( + 'ol.layer.Group', + ol.layer.Group, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getLayers', + ol.layer.Group.prototype.getLayers); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setLayers', + ol.layer.Group.prototype.setLayers); + +goog.exportSymbol( + 'ol.layer.Heatmap', + ol.layer.Heatmap, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getBlur', + ol.layer.Heatmap.prototype.getBlur); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getGradient', + ol.layer.Heatmap.prototype.getGradient); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getRadius', + ol.layer.Heatmap.prototype.getRadius); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setBlur', + ol.layer.Heatmap.prototype.setBlur); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setGradient', + ol.layer.Heatmap.prototype.setGradient); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setRadius', + ol.layer.Heatmap.prototype.setRadius); + +goog.exportSymbol( + 'ol.layer.Image', + ol.layer.Image, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getSource', + ol.layer.Image.prototype.getSource); + +goog.exportSymbol( + 'ol.layer.Layer', + ol.layer.Layer, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getSource', + ol.layer.Layer.prototype.getSource); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setMap', + ol.layer.Layer.prototype.setMap); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setSource', + ol.layer.Layer.prototype.setSource); + +goog.exportSymbol( + 'ol.layer.Tile', + ol.layer.Tile, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getPreload', + ol.layer.Tile.prototype.getPreload); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getSource', + ol.layer.Tile.prototype.getSource); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setPreload', + ol.layer.Tile.prototype.setPreload); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getUseInterimTilesOnError', + ol.layer.Tile.prototype.getUseInterimTilesOnError); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setUseInterimTilesOnError', + ol.layer.Tile.prototype.setUseInterimTilesOnError); + +goog.exportSymbol( + 'ol.layer.Vector', + ol.layer.Vector, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getSource', + ol.layer.Vector.prototype.getSource); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getStyle', + ol.layer.Vector.prototype.getStyle); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getStyleFunction', + ol.layer.Vector.prototype.getStyleFunction); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setStyle', + ol.layer.Vector.prototype.setStyle); + +goog.exportSymbol( + 'ol.layer.VectorTile', + ol.layer.VectorTile, + OPENLAYERS); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getPreload', + ol.layer.VectorTile.prototype.getPreload); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getUseInterimTilesOnError', + ol.layer.VectorTile.prototype.getUseInterimTilesOnError); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setPreload', + ol.layer.VectorTile.prototype.setPreload); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setUseInterimTilesOnError', + ol.layer.VectorTile.prototype.setUseInterimTilesOnError); + +goog.exportSymbol( + 'ol.interaction.DoubleClickZoom', + ol.interaction.DoubleClickZoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.DoubleClickZoom.handleEvent', + ol.interaction.DoubleClickZoom.handleEvent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.DragAndDrop', + ol.interaction.DragAndDrop, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.DragAndDrop.handleEvent', + ol.interaction.DragAndDrop.handleEvent, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'features', + ol.interaction.DragAndDrop.Event.prototype.features); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'file', + ol.interaction.DragAndDrop.Event.prototype.file); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'projection', + ol.interaction.DragAndDrop.Event.prototype.projection); + +goog.exportSymbol( + 'ol.interaction.DragBox', + ol.interaction.DragBox, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'getGeometry', + ol.interaction.DragBox.prototype.getGeometry); + +goog.exportProperty( + ol.interaction.DragBox.Event.prototype, + 'coordinate', + ol.interaction.DragBox.Event.prototype.coordinate); + +goog.exportProperty( + ol.interaction.DragBox.Event.prototype, + 'mapBrowserEvent', + ol.interaction.DragBox.Event.prototype.mapBrowserEvent); + +goog.exportSymbol( + 'ol.interaction.DragPan', + ol.interaction.DragPan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.DragRotate', + ol.interaction.DragRotate, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.DragRotateAndZoom', + ol.interaction.DragRotateAndZoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.DragZoom', + ol.interaction.DragZoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Draw', + ol.interaction.Draw, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Draw.handleEvent', + ol.interaction.Draw.handleEvent, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'removeLastPoint', + ol.interaction.Draw.prototype.removeLastPoint); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'finishDrawing', + ol.interaction.Draw.prototype.finishDrawing); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'extend', + ol.interaction.Draw.prototype.extend); + +goog.exportSymbol( + 'ol.interaction.Draw.createRegularPolygon', + ol.interaction.Draw.createRegularPolygon, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Draw.createBox', + ol.interaction.Draw.createBox, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Draw.Event.prototype, + 'feature', + ol.interaction.Draw.Event.prototype.feature); + +goog.exportSymbol( + 'ol.interaction.Extent', + ol.interaction.Extent, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'getExtent', + ol.interaction.Extent.prototype.getExtent); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'setExtent', + ol.interaction.Extent.prototype.setExtent); + +goog.exportProperty( + ol.interaction.Extent.Event.prototype, + 'extent_', + ol.interaction.Extent.Event.prototype.extent_); + +goog.exportSymbol( + 'ol.interaction.defaults', + ol.interaction.defaults, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Interaction', + ol.interaction.Interaction, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'getActive', + ol.interaction.Interaction.prototype.getActive); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'getMap', + ol.interaction.Interaction.prototype.getMap); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'setActive', + ol.interaction.Interaction.prototype.setActive); + +goog.exportSymbol( + 'ol.interaction.KeyboardPan', + ol.interaction.KeyboardPan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.KeyboardPan.handleEvent', + ol.interaction.KeyboardPan.handleEvent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.KeyboardZoom', + ol.interaction.KeyboardZoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.KeyboardZoom.handleEvent', + ol.interaction.KeyboardZoom.handleEvent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Modify', + ol.interaction.Modify, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Modify.handleEvent', + ol.interaction.Modify.handleEvent, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'removePoint', + ol.interaction.Modify.prototype.removePoint); + +goog.exportProperty( + ol.interaction.Modify.Event.prototype, + 'features', + ol.interaction.Modify.Event.prototype.features); + +goog.exportProperty( + ol.interaction.Modify.Event.prototype, + 'mapBrowserEvent', + ol.interaction.Modify.Event.prototype.mapBrowserEvent); + +goog.exportSymbol( + 'ol.interaction.MouseWheelZoom', + ol.interaction.MouseWheelZoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.MouseWheelZoom.handleEvent', + ol.interaction.MouseWheelZoom.handleEvent, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'setMouseAnchor', + ol.interaction.MouseWheelZoom.prototype.setMouseAnchor); + +goog.exportSymbol( + 'ol.interaction.PinchRotate', + ol.interaction.PinchRotate, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.PinchZoom', + ol.interaction.PinchZoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Pointer', + ol.interaction.Pointer, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Pointer.handleEvent', + ol.interaction.Pointer.handleEvent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Select', + ol.interaction.Select, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getFeatures', + ol.interaction.Select.prototype.getFeatures); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getHitTolerance', + ol.interaction.Select.prototype.getHitTolerance); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getLayer', + ol.interaction.Select.prototype.getLayer); + +goog.exportSymbol( + 'ol.interaction.Select.handleEvent', + ol.interaction.Select.handleEvent, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'setHitTolerance', + ol.interaction.Select.prototype.setHitTolerance); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'setMap', + ol.interaction.Select.prototype.setMap); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'selected', + ol.interaction.Select.Event.prototype.selected); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'deselected', + ol.interaction.Select.Event.prototype.deselected); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'mapBrowserEvent', + ol.interaction.Select.Event.prototype.mapBrowserEvent); + +goog.exportSymbol( + 'ol.interaction.Snap', + ol.interaction.Snap, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'addFeature', + ol.interaction.Snap.prototype.addFeature); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'removeFeature', + ol.interaction.Snap.prototype.removeFeature); + +goog.exportSymbol( + 'ol.interaction.Translate', + ol.interaction.Translate, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'getHitTolerance', + ol.interaction.Translate.prototype.getHitTolerance); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'setHitTolerance', + ol.interaction.Translate.prototype.setHitTolerance); + +goog.exportProperty( + ol.interaction.Translate.Event.prototype, + 'features', + ol.interaction.Translate.Event.prototype.features); + +goog.exportProperty( + ol.interaction.Translate.Event.prototype, + 'coordinate', + ol.interaction.Translate.Event.prototype.coordinate); + +goog.exportSymbol( + 'ol.geom.Circle', + ol.geom.Circle, + OPENLAYERS); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'clone', + ol.geom.Circle.prototype.clone); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getCenter', + ol.geom.Circle.prototype.getCenter); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getRadius', + ol.geom.Circle.prototype.getRadius); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getType', + ol.geom.Circle.prototype.getType); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'intersectsExtent', + ol.geom.Circle.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'setCenter', + ol.geom.Circle.prototype.setCenter); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'setCenterAndRadius', + ol.geom.Circle.prototype.setCenterAndRadius); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'setRadius', + ol.geom.Circle.prototype.setRadius); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'transform', + ol.geom.Circle.prototype.transform); + +goog.exportSymbol( + 'ol.geom.Geometry', + ol.geom.Geometry, + OPENLAYERS); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'getClosestPoint', + ol.geom.Geometry.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'intersectsCoordinate', + ol.geom.Geometry.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'getExtent', + ol.geom.Geometry.prototype.getExtent); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'rotate', + ol.geom.Geometry.prototype.rotate); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'scale', + ol.geom.Geometry.prototype.scale); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'simplify', + ol.geom.Geometry.prototype.simplify); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'transform', + ol.geom.Geometry.prototype.transform); + +goog.exportSymbol( + 'ol.geom.GeometryCollection', + ol.geom.GeometryCollection, + OPENLAYERS); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'clone', + ol.geom.GeometryCollection.prototype.clone); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getGeometries', + ol.geom.GeometryCollection.prototype.getGeometries); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getType', + ol.geom.GeometryCollection.prototype.getType); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'intersectsExtent', + ol.geom.GeometryCollection.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'setGeometries', + ol.geom.GeometryCollection.prototype.setGeometries); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'applyTransform', + ol.geom.GeometryCollection.prototype.applyTransform); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'translate', + ol.geom.GeometryCollection.prototype.translate); + +goog.exportSymbol( + 'ol.geom.LinearRing', + ol.geom.LinearRing, + OPENLAYERS); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'clone', + ol.geom.LinearRing.prototype.clone); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getArea', + ol.geom.LinearRing.prototype.getArea); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getCoordinates', + ol.geom.LinearRing.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getType', + ol.geom.LinearRing.prototype.getType); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'setCoordinates', + ol.geom.LinearRing.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.LineString', + ol.geom.LineString, + OPENLAYERS); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'appendCoordinate', + ol.geom.LineString.prototype.appendCoordinate); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'clone', + ol.geom.LineString.prototype.clone); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'forEachSegment', + ol.geom.LineString.prototype.forEachSegment); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getCoordinateAtM', + ol.geom.LineString.prototype.getCoordinateAtM); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getCoordinates', + ol.geom.LineString.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getCoordinateAt', + ol.geom.LineString.prototype.getCoordinateAt); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getLength', + ol.geom.LineString.prototype.getLength); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getType', + ol.geom.LineString.prototype.getType); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'intersectsExtent', + ol.geom.LineString.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'setCoordinates', + ol.geom.LineString.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.MultiLineString', + ol.geom.MultiLineString, + OPENLAYERS); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'appendLineString', + ol.geom.MultiLineString.prototype.appendLineString); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'clone', + ol.geom.MultiLineString.prototype.clone); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getCoordinateAtM', + ol.geom.MultiLineString.prototype.getCoordinateAtM); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getCoordinates', + ol.geom.MultiLineString.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getLineString', + ol.geom.MultiLineString.prototype.getLineString); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getLineStrings', + ol.geom.MultiLineString.prototype.getLineStrings); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getType', + ol.geom.MultiLineString.prototype.getType); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'intersectsExtent', + ol.geom.MultiLineString.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'setCoordinates', + ol.geom.MultiLineString.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.MultiPoint', + ol.geom.MultiPoint, + OPENLAYERS); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'appendPoint', + ol.geom.MultiPoint.prototype.appendPoint); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'clone', + ol.geom.MultiPoint.prototype.clone); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getCoordinates', + ol.geom.MultiPoint.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getPoint', + ol.geom.MultiPoint.prototype.getPoint); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getPoints', + ol.geom.MultiPoint.prototype.getPoints); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getType', + ol.geom.MultiPoint.prototype.getType); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'intersectsExtent', + ol.geom.MultiPoint.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'setCoordinates', + ol.geom.MultiPoint.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.MultiPolygon', + ol.geom.MultiPolygon, + OPENLAYERS); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'appendPolygon', + ol.geom.MultiPolygon.prototype.appendPolygon); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'clone', + ol.geom.MultiPolygon.prototype.clone); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getArea', + ol.geom.MultiPolygon.prototype.getArea); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getCoordinates', + ol.geom.MultiPolygon.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getInteriorPoints', + ol.geom.MultiPolygon.prototype.getInteriorPoints); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getPolygon', + ol.geom.MultiPolygon.prototype.getPolygon); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getPolygons', + ol.geom.MultiPolygon.prototype.getPolygons); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getType', + ol.geom.MultiPolygon.prototype.getType); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'intersectsExtent', + ol.geom.MultiPolygon.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'setCoordinates', + ol.geom.MultiPolygon.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.Point', + ol.geom.Point, + OPENLAYERS); + +goog.exportProperty( + ol.geom.Point.prototype, + 'clone', + ol.geom.Point.prototype.clone); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getCoordinates', + ol.geom.Point.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getType', + ol.geom.Point.prototype.getType); + +goog.exportProperty( + ol.geom.Point.prototype, + 'intersectsExtent', + ol.geom.Point.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.Point.prototype, + 'setCoordinates', + ol.geom.Point.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.Polygon', + ol.geom.Polygon, + OPENLAYERS); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'appendLinearRing', + ol.geom.Polygon.prototype.appendLinearRing); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'clone', + ol.geom.Polygon.prototype.clone); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getArea', + ol.geom.Polygon.prototype.getArea); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getCoordinates', + ol.geom.Polygon.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getInteriorPoint', + ol.geom.Polygon.prototype.getInteriorPoint); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getLinearRingCount', + ol.geom.Polygon.prototype.getLinearRingCount); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getLinearRing', + ol.geom.Polygon.prototype.getLinearRing); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getLinearRings', + ol.geom.Polygon.prototype.getLinearRings); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getType', + ol.geom.Polygon.prototype.getType); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'intersectsExtent', + ol.geom.Polygon.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'setCoordinates', + ol.geom.Polygon.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.Polygon.circular', + ol.geom.Polygon.circular, + OPENLAYERS); + +goog.exportSymbol( + 'ol.geom.Polygon.fromExtent', + ol.geom.Polygon.fromExtent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.geom.Polygon.fromCircle', + ol.geom.Polygon.fromCircle, + OPENLAYERS); + +goog.exportSymbol( + 'ol.geom.SimpleGeometry', + ol.geom.SimpleGeometry, + OPENLAYERS); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getFirstCoordinate', + ol.geom.SimpleGeometry.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getLastCoordinate', + ol.geom.SimpleGeometry.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getLayout', + ol.geom.SimpleGeometry.prototype.getLayout); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'applyTransform', + ol.geom.SimpleGeometry.prototype.applyTransform); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'translate', + ol.geom.SimpleGeometry.prototype.translate); + +goog.exportSymbol( + 'ol.format.EsriJSON', + ol.format.EsriJSON, + OPENLAYERS); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'readFeature', + ol.format.EsriJSON.prototype.readFeature); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'readFeatures', + ol.format.EsriJSON.prototype.readFeatures); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'readGeometry', + ol.format.EsriJSON.prototype.readGeometry); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'readProjection', + ol.format.EsriJSON.prototype.readProjection); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeGeometry', + ol.format.EsriJSON.prototype.writeGeometry); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeGeometryObject', + ol.format.EsriJSON.prototype.writeGeometryObject); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeFeature', + ol.format.EsriJSON.prototype.writeFeature); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeFeatureObject', + ol.format.EsriJSON.prototype.writeFeatureObject); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeFeatures', + ol.format.EsriJSON.prototype.writeFeatures); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeFeaturesObject', + ol.format.EsriJSON.prototype.writeFeaturesObject); + +goog.exportSymbol( + 'ol.format.Feature', + ol.format.Feature, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.GeoJSON', + ol.format.GeoJSON, + OPENLAYERS); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'readFeature', + ol.format.GeoJSON.prototype.readFeature); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'readFeatures', + ol.format.GeoJSON.prototype.readFeatures); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'readGeometry', + ol.format.GeoJSON.prototype.readGeometry); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'readProjection', + ol.format.GeoJSON.prototype.readProjection); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'writeFeature', + ol.format.GeoJSON.prototype.writeFeature); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'writeFeatureObject', + ol.format.GeoJSON.prototype.writeFeatureObject); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'writeFeatures', + ol.format.GeoJSON.prototype.writeFeatures); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'writeFeaturesObject', + ol.format.GeoJSON.prototype.writeFeaturesObject); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'writeGeometry', + ol.format.GeoJSON.prototype.writeGeometry); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'writeGeometryObject', + ol.format.GeoJSON.prototype.writeGeometryObject); + +goog.exportSymbol( + 'ol.format.GML', + ol.format.GML, + OPENLAYERS); + +goog.exportProperty( + ol.format.GML.prototype, + 'writeFeatures', + ol.format.GML.prototype.writeFeatures); + +goog.exportProperty( + ol.format.GML.prototype, + 'writeFeaturesNode', + ol.format.GML.prototype.writeFeaturesNode); + +goog.exportSymbol( + 'ol.format.GML2', + ol.format.GML2, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.GML3', + ol.format.GML3, + OPENLAYERS); + +goog.exportProperty( + ol.format.GML3.prototype, + 'writeGeometryNode', + ol.format.GML3.prototype.writeGeometryNode); + +goog.exportProperty( + ol.format.GML3.prototype, + 'writeFeatures', + ol.format.GML3.prototype.writeFeatures); + +goog.exportProperty( + ol.format.GML3.prototype, + 'writeFeaturesNode', + ol.format.GML3.prototype.writeFeaturesNode); + +goog.exportProperty( + ol.format.GMLBase.prototype, + 'readFeatures', + ol.format.GMLBase.prototype.readFeatures); + +goog.exportSymbol( + 'ol.format.GPX', + ol.format.GPX, + OPENLAYERS); + +goog.exportProperty( + ol.format.GPX.prototype, + 'readFeature', + ol.format.GPX.prototype.readFeature); + +goog.exportProperty( + ol.format.GPX.prototype, + 'readFeatures', + ol.format.GPX.prototype.readFeatures); + +goog.exportProperty( + ol.format.GPX.prototype, + 'readProjection', + ol.format.GPX.prototype.readProjection); + +goog.exportProperty( + ol.format.GPX.prototype, + 'writeFeatures', + ol.format.GPX.prototype.writeFeatures); + +goog.exportProperty( + ol.format.GPX.prototype, + 'writeFeaturesNode', + ol.format.GPX.prototype.writeFeaturesNode); + +goog.exportSymbol( + 'ol.format.IGC', + ol.format.IGC, + OPENLAYERS); + +goog.exportProperty( + ol.format.IGC.prototype, + 'readFeature', + ol.format.IGC.prototype.readFeature); + +goog.exportProperty( + ol.format.IGC.prototype, + 'readFeatures', + ol.format.IGC.prototype.readFeatures); + +goog.exportProperty( + ol.format.IGC.prototype, + 'readProjection', + ol.format.IGC.prototype.readProjection); + +goog.exportSymbol( + 'ol.format.KML', + ol.format.KML, + OPENLAYERS); + +goog.exportProperty( + ol.format.KML.prototype, + 'readFeature', + ol.format.KML.prototype.readFeature); + +goog.exportProperty( + ol.format.KML.prototype, + 'readFeatures', + ol.format.KML.prototype.readFeatures); + +goog.exportProperty( + ol.format.KML.prototype, + 'readName', + ol.format.KML.prototype.readName); + +goog.exportProperty( + ol.format.KML.prototype, + 'readNetworkLinks', + ol.format.KML.prototype.readNetworkLinks); + +goog.exportProperty( + ol.format.KML.prototype, + 'readRegion', + ol.format.KML.prototype.readRegion); + +goog.exportProperty( + ol.format.KML.prototype, + 'readRegionFromNode', + ol.format.KML.prototype.readRegionFromNode); + +goog.exportProperty( + ol.format.KML.prototype, + 'readProjection', + ol.format.KML.prototype.readProjection); + +goog.exportProperty( + ol.format.KML.prototype, + 'writeFeatures', + ol.format.KML.prototype.writeFeatures); + +goog.exportProperty( + ol.format.KML.prototype, + 'writeFeaturesNode', + ol.format.KML.prototype.writeFeaturesNode); + +goog.exportSymbol( + 'ol.format.MVT', + ol.format.MVT, + OPENLAYERS); + +goog.exportProperty( + ol.format.MVT.prototype, + 'readFeatures', + ol.format.MVT.prototype.readFeatures); + +goog.exportProperty( + ol.format.MVT.prototype, + 'readProjection', + ol.format.MVT.prototype.readProjection); + +goog.exportProperty( + ol.format.MVT.prototype, + 'setLayers', + ol.format.MVT.prototype.setLayers); + +goog.exportSymbol( + 'ol.format.OSMXML', + ol.format.OSMXML, + OPENLAYERS); + +goog.exportProperty( + ol.format.OSMXML.prototype, + 'readFeatures', + ol.format.OSMXML.prototype.readFeatures); + +goog.exportProperty( + ol.format.OSMXML.prototype, + 'readProjection', + ol.format.OSMXML.prototype.readProjection); + +goog.exportSymbol( + 'ol.format.Polyline', + ol.format.Polyline, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.Polyline.encodeDeltas', + ol.format.Polyline.encodeDeltas, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.Polyline.decodeDeltas', + ol.format.Polyline.decodeDeltas, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.Polyline.encodeFloats', + ol.format.Polyline.encodeFloats, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.Polyline.decodeFloats', + ol.format.Polyline.decodeFloats, + OPENLAYERS); + +goog.exportProperty( + ol.format.Polyline.prototype, + 'readFeature', + ol.format.Polyline.prototype.readFeature); + +goog.exportProperty( + ol.format.Polyline.prototype, + 'readFeatures', + ol.format.Polyline.prototype.readFeatures); + +goog.exportProperty( + ol.format.Polyline.prototype, + 'readGeometry', + ol.format.Polyline.prototype.readGeometry); + +goog.exportProperty( + ol.format.Polyline.prototype, + 'readProjection', + ol.format.Polyline.prototype.readProjection); + +goog.exportProperty( + ol.format.Polyline.prototype, + 'writeGeometry', + ol.format.Polyline.prototype.writeGeometry); + +goog.exportSymbol( + 'ol.format.TopoJSON', + ol.format.TopoJSON, + OPENLAYERS); + +goog.exportProperty( + ol.format.TopoJSON.prototype, + 'readFeatures', + ol.format.TopoJSON.prototype.readFeatures); + +goog.exportProperty( + ol.format.TopoJSON.prototype, + 'readProjection', + ol.format.TopoJSON.prototype.readProjection); + +goog.exportSymbol( + 'ol.format.WFS', + ol.format.WFS, + OPENLAYERS); + +goog.exportProperty( + ol.format.WFS.prototype, + 'readFeatures', + ol.format.WFS.prototype.readFeatures); + +goog.exportProperty( + ol.format.WFS.prototype, + 'readTransactionResponse', + ol.format.WFS.prototype.readTransactionResponse); + +goog.exportProperty( + ol.format.WFS.prototype, + 'readFeatureCollectionMetadata', + ol.format.WFS.prototype.readFeatureCollectionMetadata); + +goog.exportProperty( + ol.format.WFS.prototype, + 'writeGetFeature', + ol.format.WFS.prototype.writeGetFeature); + +goog.exportProperty( + ol.format.WFS.prototype, + 'writeTransaction', + ol.format.WFS.prototype.writeTransaction); + +goog.exportProperty( + ol.format.WFS.prototype, + 'readProjection', + ol.format.WFS.prototype.readProjection); + +goog.exportSymbol( + 'ol.format.WKT', + ol.format.WKT, + OPENLAYERS); + +goog.exportProperty( + ol.format.WKT.prototype, + 'readFeature', + ol.format.WKT.prototype.readFeature); + +goog.exportProperty( + ol.format.WKT.prototype, + 'readFeatures', + ol.format.WKT.prototype.readFeatures); + +goog.exportProperty( + ol.format.WKT.prototype, + 'readGeometry', + ol.format.WKT.prototype.readGeometry); + +goog.exportProperty( + ol.format.WKT.prototype, + 'writeFeature', + ol.format.WKT.prototype.writeFeature); + +goog.exportProperty( + ol.format.WKT.prototype, + 'writeFeatures', + ol.format.WKT.prototype.writeFeatures); + +goog.exportProperty( + ol.format.WKT.prototype, + 'writeGeometry', + ol.format.WKT.prototype.writeGeometry); + +goog.exportSymbol( + 'ol.format.WMSCapabilities', + ol.format.WMSCapabilities, + OPENLAYERS); + +goog.exportProperty( + ol.format.WMSCapabilities.prototype, + 'read', + ol.format.WMSCapabilities.prototype.read); + +goog.exportSymbol( + 'ol.format.WMSGetFeatureInfo', + ol.format.WMSGetFeatureInfo, + OPENLAYERS); + +goog.exportProperty( + ol.format.WMSGetFeatureInfo.prototype, + 'readFeatures', + ol.format.WMSGetFeatureInfo.prototype.readFeatures); + +goog.exportSymbol( + 'ol.format.WMTSCapabilities', + ol.format.WMTSCapabilities, + OPENLAYERS); + +goog.exportProperty( + ol.format.WMTSCapabilities.prototype, + 'read', + ol.format.WMTSCapabilities.prototype.read); + +goog.exportSymbol( + 'ol.format.filter.And', + ol.format.filter.And, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Bbox', + ol.format.filter.Bbox, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Comparison', + ol.format.filter.Comparison, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.ComparisonBinary', + ol.format.filter.ComparisonBinary, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.EqualTo', + ol.format.filter.EqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Filter', + ol.format.filter.Filter, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.GreaterThan', + ol.format.filter.GreaterThan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.GreaterThanOrEqualTo', + ol.format.filter.GreaterThanOrEqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.and', + ol.format.filter.and, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.or', + ol.format.filter.or, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.not', + ol.format.filter.not, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.bbox', + ol.format.filter.bbox, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.intersects', + ol.format.filter.intersects, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.within', + ol.format.filter.within, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.equalTo', + ol.format.filter.equalTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.notEqualTo', + ol.format.filter.notEqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.lessThan', + ol.format.filter.lessThan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.lessThanOrEqualTo', + ol.format.filter.lessThanOrEqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.greaterThan', + ol.format.filter.greaterThan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.greaterThanOrEqualTo', + ol.format.filter.greaterThanOrEqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.isNull', + ol.format.filter.isNull, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.between', + ol.format.filter.between, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.like', + ol.format.filter.like, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Intersects', + ol.format.filter.Intersects, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.IsBetween', + ol.format.filter.IsBetween, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.IsLike', + ol.format.filter.IsLike, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.IsNull', + ol.format.filter.IsNull, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.LessThan', + ol.format.filter.LessThan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.LessThanOrEqualTo', + ol.format.filter.LessThanOrEqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Not', + ol.format.filter.Not, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.NotEqualTo', + ol.format.filter.NotEqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Or', + ol.format.filter.Or, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Spatial', + ol.format.filter.Spatial, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Within', + ol.format.filter.Within, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.boundingExtent', + ol.extent.boundingExtent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.buffer', + ol.extent.buffer, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.containsCoordinate', + ol.extent.containsCoordinate, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.containsExtent', + ol.extent.containsExtent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.containsXY', + ol.extent.containsXY, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.createEmpty', + ol.extent.createEmpty, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.equals', + ol.extent.equals, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.extend', + ol.extent.extend, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getBottomLeft', + ol.extent.getBottomLeft, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getBottomRight', + ol.extent.getBottomRight, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getCenter', + ol.extent.getCenter, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getHeight', + ol.extent.getHeight, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getIntersection', + ol.extent.getIntersection, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getSize', + ol.extent.getSize, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getTopLeft', + ol.extent.getTopLeft, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getTopRight', + ol.extent.getTopRight, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getWidth', + ol.extent.getWidth, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.intersects', + ol.extent.intersects, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.isEmpty', + ol.extent.isEmpty, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.applyTransform', + ol.extent.applyTransform, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.altKeyOnly', + ol.events.condition.altKeyOnly, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.altShiftKeysOnly', + ol.events.condition.altShiftKeysOnly, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.always', + ol.events.condition.always, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.click', + ol.events.condition.click, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.never', + ol.events.condition.never, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.pointerMove', + ol.events.condition.pointerMove, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.singleClick', + ol.events.condition.singleClick, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.doubleClick', + ol.events.condition.doubleClick, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.noModifierKeys', + ol.events.condition.noModifierKeys, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.platformModifierKeyOnly', + ol.events.condition.platformModifierKeyOnly, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.shiftKeyOnly', + ol.events.condition.shiftKeyOnly, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.targetNotEditable', + ol.events.condition.targetNotEditable, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.mouseOnly', + ol.events.condition.mouseOnly, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.primaryAction', + ol.events.condition.primaryAction, + OPENLAYERS); + +goog.exportProperty( + ol.events.Event.prototype, + 'type', + ol.events.Event.prototype.type); + +goog.exportProperty( + ol.events.Event.prototype, + 'target', + ol.events.Event.prototype.target); + +goog.exportProperty( + ol.events.Event.prototype, + 'preventDefault', + ol.events.Event.prototype.preventDefault); + +goog.exportProperty( + ol.events.Event.prototype, + 'stopPropagation', + ol.events.Event.prototype.stopPropagation); + +goog.exportSymbol( + 'ol.control.Attribution', + ol.control.Attribution, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.Attribution.render', + ol.control.Attribution.render, + OPENLAYERS); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'getCollapsible', + ol.control.Attribution.prototype.getCollapsible); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'setCollapsible', + ol.control.Attribution.prototype.setCollapsible); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'setCollapsed', + ol.control.Attribution.prototype.setCollapsed); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'getCollapsed', + ol.control.Attribution.prototype.getCollapsed); + +goog.exportSymbol( + 'ol.control.Control', + ol.control.Control, + OPENLAYERS); + +goog.exportProperty( + ol.control.Control.prototype, + 'getMap', + ol.control.Control.prototype.getMap); + +goog.exportProperty( + ol.control.Control.prototype, + 'setMap', + ol.control.Control.prototype.setMap); + +goog.exportProperty( + ol.control.Control.prototype, + 'setTarget', + ol.control.Control.prototype.setTarget); + +goog.exportSymbol( + 'ol.control.FullScreen', + ol.control.FullScreen, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.defaults', + ol.control.defaults, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.MousePosition', + ol.control.MousePosition, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.MousePosition.render', + ol.control.MousePosition.render, + OPENLAYERS); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'getCoordinateFormat', + ol.control.MousePosition.prototype.getCoordinateFormat); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'getProjection', + ol.control.MousePosition.prototype.getProjection); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'setCoordinateFormat', + ol.control.MousePosition.prototype.setCoordinateFormat); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'setProjection', + ol.control.MousePosition.prototype.setProjection); + +goog.exportSymbol( + 'ol.control.OverviewMap', + ol.control.OverviewMap, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.OverviewMap.render', + ol.control.OverviewMap.render, + OPENLAYERS); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getCollapsible', + ol.control.OverviewMap.prototype.getCollapsible); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'setCollapsible', + ol.control.OverviewMap.prototype.setCollapsible); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'setCollapsed', + ol.control.OverviewMap.prototype.setCollapsed); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getCollapsed', + ol.control.OverviewMap.prototype.getCollapsed); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getOverviewMap', + ol.control.OverviewMap.prototype.getOverviewMap); + +goog.exportSymbol( + 'ol.control.Rotate', + ol.control.Rotate, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.Rotate.render', + ol.control.Rotate.render, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.ScaleLine', + ol.control.ScaleLine, + OPENLAYERS); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'getUnits', + ol.control.ScaleLine.prototype.getUnits); + +goog.exportSymbol( + 'ol.control.ScaleLine.render', + ol.control.ScaleLine.render, + OPENLAYERS); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'setUnits', + ol.control.ScaleLine.prototype.setUnits); + +goog.exportSymbol( + 'ol.control.Zoom', + ol.control.Zoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.ZoomSlider', + ol.control.ZoomSlider, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.ZoomSlider.render', + ol.control.ZoomSlider.render, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.ZoomToExtent', + ol.control.ZoomToExtent, + OPENLAYERS); + +goog.exportProperty( + ol.Object.prototype, + 'changed', + ol.Object.prototype.changed); + +goog.exportProperty( + ol.Object.prototype, + 'dispatchEvent', + ol.Object.prototype.dispatchEvent); + +goog.exportProperty( + ol.Object.prototype, + 'getRevision', + ol.Object.prototype.getRevision); + +goog.exportProperty( + ol.Object.prototype, + 'on', + ol.Object.prototype.on); + +goog.exportProperty( + ol.Object.prototype, + 'once', + ol.Object.prototype.once); + +goog.exportProperty( + ol.Object.prototype, + 'un', + ol.Object.prototype.un); + +goog.exportProperty( + ol.Object.prototype, + 'unByKey', + ol.Object.prototype.unByKey); + +goog.exportProperty( + ol.Collection.prototype, + 'get', + ol.Collection.prototype.get); + +goog.exportProperty( + ol.Collection.prototype, + 'getKeys', + ol.Collection.prototype.getKeys); + +goog.exportProperty( + ol.Collection.prototype, + 'getProperties', + ol.Collection.prototype.getProperties); + +goog.exportProperty( + ol.Collection.prototype, + 'set', + ol.Collection.prototype.set); + +goog.exportProperty( + ol.Collection.prototype, + 'setProperties', + ol.Collection.prototype.setProperties); + +goog.exportProperty( + ol.Collection.prototype, + 'unset', + ol.Collection.prototype.unset); + +goog.exportProperty( + ol.Collection.prototype, + 'changed', + ol.Collection.prototype.changed); + +goog.exportProperty( + ol.Collection.prototype, + 'dispatchEvent', + ol.Collection.prototype.dispatchEvent); + +goog.exportProperty( + ol.Collection.prototype, + 'getRevision', + ol.Collection.prototype.getRevision); + +goog.exportProperty( + ol.Collection.prototype, + 'on', + ol.Collection.prototype.on); + +goog.exportProperty( + ol.Collection.prototype, + 'once', + ol.Collection.prototype.once); + +goog.exportProperty( + ol.Collection.prototype, + 'un', + ol.Collection.prototype.un); + +goog.exportProperty( + ol.Collection.prototype, + 'unByKey', + ol.Collection.prototype.unByKey); + +goog.exportProperty( + ol.Collection.Event.prototype, + 'type', + ol.Collection.Event.prototype.type); + +goog.exportProperty( + ol.Collection.Event.prototype, + 'target', + ol.Collection.Event.prototype.target); + +goog.exportProperty( + ol.Collection.Event.prototype, + 'preventDefault', + ol.Collection.Event.prototype.preventDefault); + +goog.exportProperty( + ol.Collection.Event.prototype, + 'stopPropagation', + ol.Collection.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'get', + ol.DeviceOrientation.prototype.get); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getKeys', + ol.DeviceOrientation.prototype.getKeys); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getProperties', + ol.DeviceOrientation.prototype.getProperties); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'set', + ol.DeviceOrientation.prototype.set); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'setProperties', + ol.DeviceOrientation.prototype.setProperties); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'unset', + ol.DeviceOrientation.prototype.unset); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'changed', + ol.DeviceOrientation.prototype.changed); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'dispatchEvent', + ol.DeviceOrientation.prototype.dispatchEvent); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getRevision', + ol.DeviceOrientation.prototype.getRevision); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'on', + ol.DeviceOrientation.prototype.on); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'once', + ol.DeviceOrientation.prototype.once); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'un', + ol.DeviceOrientation.prototype.un); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'unByKey', + ol.DeviceOrientation.prototype.unByKey); + +goog.exportProperty( + ol.Feature.prototype, + 'get', + ol.Feature.prototype.get); + +goog.exportProperty( + ol.Feature.prototype, + 'getKeys', + ol.Feature.prototype.getKeys); + +goog.exportProperty( + ol.Feature.prototype, + 'getProperties', + ol.Feature.prototype.getProperties); + +goog.exportProperty( + ol.Feature.prototype, + 'set', + ol.Feature.prototype.set); + +goog.exportProperty( + ol.Feature.prototype, + 'setProperties', + ol.Feature.prototype.setProperties); + +goog.exportProperty( + ol.Feature.prototype, + 'unset', + ol.Feature.prototype.unset); + +goog.exportProperty( + ol.Feature.prototype, + 'changed', + ol.Feature.prototype.changed); + +goog.exportProperty( + ol.Feature.prototype, + 'dispatchEvent', + ol.Feature.prototype.dispatchEvent); + +goog.exportProperty( + ol.Feature.prototype, + 'getRevision', + ol.Feature.prototype.getRevision); + +goog.exportProperty( + ol.Feature.prototype, + 'on', + ol.Feature.prototype.on); + +goog.exportProperty( + ol.Feature.prototype, + 'once', + ol.Feature.prototype.once); + +goog.exportProperty( + ol.Feature.prototype, + 'un', + ol.Feature.prototype.un); + +goog.exportProperty( + ol.Feature.prototype, + 'unByKey', + ol.Feature.prototype.unByKey); + +goog.exportProperty( + ol.Geolocation.prototype, + 'get', + ol.Geolocation.prototype.get); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getKeys', + ol.Geolocation.prototype.getKeys); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getProperties', + ol.Geolocation.prototype.getProperties); + +goog.exportProperty( + ol.Geolocation.prototype, + 'set', + ol.Geolocation.prototype.set); + +goog.exportProperty( + ol.Geolocation.prototype, + 'setProperties', + ol.Geolocation.prototype.setProperties); + +goog.exportProperty( + ol.Geolocation.prototype, + 'unset', + ol.Geolocation.prototype.unset); + +goog.exportProperty( + ol.Geolocation.prototype, + 'changed', + ol.Geolocation.prototype.changed); + +goog.exportProperty( + ol.Geolocation.prototype, + 'dispatchEvent', + ol.Geolocation.prototype.dispatchEvent); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getRevision', + ol.Geolocation.prototype.getRevision); + +goog.exportProperty( + ol.Geolocation.prototype, + 'on', + ol.Geolocation.prototype.on); + +goog.exportProperty( + ol.Geolocation.prototype, + 'once', + ol.Geolocation.prototype.once); + +goog.exportProperty( + ol.Geolocation.prototype, + 'un', + ol.Geolocation.prototype.un); + +goog.exportProperty( + ol.Geolocation.prototype, + 'unByKey', + ol.Geolocation.prototype.unByKey); + +goog.exportProperty( + ol.ImageTile.prototype, + 'getTileCoord', + ol.ImageTile.prototype.getTileCoord); + +goog.exportProperty( + ol.Map.prototype, + 'get', + ol.Map.prototype.get); + +goog.exportProperty( + ol.Map.prototype, + 'getKeys', + ol.Map.prototype.getKeys); + +goog.exportProperty( + ol.Map.prototype, + 'getProperties', + ol.Map.prototype.getProperties); + +goog.exportProperty( + ol.Map.prototype, + 'set', + ol.Map.prototype.set); + +goog.exportProperty( + ol.Map.prototype, + 'setProperties', + ol.Map.prototype.setProperties); + +goog.exportProperty( + ol.Map.prototype, + 'unset', + ol.Map.prototype.unset); + +goog.exportProperty( + ol.Map.prototype, + 'changed', + ol.Map.prototype.changed); + +goog.exportProperty( + ol.Map.prototype, + 'dispatchEvent', + ol.Map.prototype.dispatchEvent); + +goog.exportProperty( + ol.Map.prototype, + 'getRevision', + ol.Map.prototype.getRevision); + +goog.exportProperty( + ol.Map.prototype, + 'on', + ol.Map.prototype.on); + +goog.exportProperty( + ol.Map.prototype, + 'once', + ol.Map.prototype.once); + +goog.exportProperty( + ol.Map.prototype, + 'un', + ol.Map.prototype.un); + +goog.exportProperty( + ol.Map.prototype, + 'unByKey', + ol.Map.prototype.unByKey); + +goog.exportProperty( + ol.MapEvent.prototype, + 'type', + ol.MapEvent.prototype.type); + +goog.exportProperty( + ol.MapEvent.prototype, + 'target', + ol.MapEvent.prototype.target); + +goog.exportProperty( + ol.MapEvent.prototype, + 'preventDefault', + ol.MapEvent.prototype.preventDefault); + +goog.exportProperty( + ol.MapEvent.prototype, + 'stopPropagation', + ol.MapEvent.prototype.stopPropagation); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'map', + ol.MapBrowserEvent.prototype.map); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'frameState', + ol.MapBrowserEvent.prototype.frameState); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'type', + ol.MapBrowserEvent.prototype.type); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'target', + ol.MapBrowserEvent.prototype.target); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'preventDefault', + ol.MapBrowserEvent.prototype.preventDefault); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'stopPropagation', + ol.MapBrowserEvent.prototype.stopPropagation); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'originalEvent', + ol.MapBrowserPointerEvent.prototype.originalEvent); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'pixel', + ol.MapBrowserPointerEvent.prototype.pixel); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'coordinate', + ol.MapBrowserPointerEvent.prototype.coordinate); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'dragging', + ol.MapBrowserPointerEvent.prototype.dragging); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'preventDefault', + ol.MapBrowserPointerEvent.prototype.preventDefault); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'stopPropagation', + ol.MapBrowserPointerEvent.prototype.stopPropagation); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'map', + ol.MapBrowserPointerEvent.prototype.map); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'frameState', + ol.MapBrowserPointerEvent.prototype.frameState); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'type', + ol.MapBrowserPointerEvent.prototype.type); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'target', + ol.MapBrowserPointerEvent.prototype.target); + +goog.exportProperty( + ol.Object.Event.prototype, + 'type', + ol.Object.Event.prototype.type); + +goog.exportProperty( + ol.Object.Event.prototype, + 'target', + ol.Object.Event.prototype.target); + +goog.exportProperty( + ol.Object.Event.prototype, + 'preventDefault', + ol.Object.Event.prototype.preventDefault); + +goog.exportProperty( + ol.Object.Event.prototype, + 'stopPropagation', + ol.Object.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.Overlay.prototype, + 'get', + ol.Overlay.prototype.get); + +goog.exportProperty( + ol.Overlay.prototype, + 'getKeys', + ol.Overlay.prototype.getKeys); + +goog.exportProperty( + ol.Overlay.prototype, + 'getProperties', + ol.Overlay.prototype.getProperties); + +goog.exportProperty( + ol.Overlay.prototype, + 'set', + ol.Overlay.prototype.set); + +goog.exportProperty( + ol.Overlay.prototype, + 'setProperties', + ol.Overlay.prototype.setProperties); + +goog.exportProperty( + ol.Overlay.prototype, + 'unset', + ol.Overlay.prototype.unset); + +goog.exportProperty( + ol.Overlay.prototype, + 'changed', + ol.Overlay.prototype.changed); + +goog.exportProperty( + ol.Overlay.prototype, + 'dispatchEvent', + ol.Overlay.prototype.dispatchEvent); + +goog.exportProperty( + ol.Overlay.prototype, + 'getRevision', + ol.Overlay.prototype.getRevision); + +goog.exportProperty( + ol.Overlay.prototype, + 'on', + ol.Overlay.prototype.on); + +goog.exportProperty( + ol.Overlay.prototype, + 'once', + ol.Overlay.prototype.once); + +goog.exportProperty( + ol.Overlay.prototype, + 'un', + ol.Overlay.prototype.un); + +goog.exportProperty( + ol.Overlay.prototype, + 'unByKey', + ol.Overlay.prototype.unByKey); + +goog.exportProperty( + ol.VectorTile.prototype, + 'getTileCoord', + ol.VectorTile.prototype.getTileCoord); + +goog.exportProperty( + ol.View.prototype, + 'get', + ol.View.prototype.get); + +goog.exportProperty( + ol.View.prototype, + 'getKeys', + ol.View.prototype.getKeys); + +goog.exportProperty( + ol.View.prototype, + 'getProperties', + ol.View.prototype.getProperties); + +goog.exportProperty( + ol.View.prototype, + 'set', + ol.View.prototype.set); + +goog.exportProperty( + ol.View.prototype, + 'setProperties', + ol.View.prototype.setProperties); + +goog.exportProperty( + ol.View.prototype, + 'unset', + ol.View.prototype.unset); + +goog.exportProperty( + ol.View.prototype, + 'changed', + ol.View.prototype.changed); + +goog.exportProperty( + ol.View.prototype, + 'dispatchEvent', + ol.View.prototype.dispatchEvent); + +goog.exportProperty( + ol.View.prototype, + 'getRevision', + ol.View.prototype.getRevision); + +goog.exportProperty( + ol.View.prototype, + 'on', + ol.View.prototype.on); + +goog.exportProperty( + ol.View.prototype, + 'once', + ol.View.prototype.once); + +goog.exportProperty( + ol.View.prototype, + 'un', + ol.View.prototype.un); + +goog.exportProperty( + ol.View.prototype, + 'unByKey', + ol.View.prototype.unByKey); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'forEachTileCoord', + ol.tilegrid.WMTS.prototype.forEachTileCoord); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getMaxZoom', + ol.tilegrid.WMTS.prototype.getMaxZoom); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getMinZoom', + ol.tilegrid.WMTS.prototype.getMinZoom); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getOrigin', + ol.tilegrid.WMTS.prototype.getOrigin); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getResolution', + ol.tilegrid.WMTS.prototype.getResolution); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getResolutions', + ol.tilegrid.WMTS.prototype.getResolutions); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getTileCoordExtent', + ol.tilegrid.WMTS.prototype.getTileCoordExtent); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getTileCoordForCoordAndResolution', + ol.tilegrid.WMTS.prototype.getTileCoordForCoordAndResolution); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getTileCoordForCoordAndZ', + ol.tilegrid.WMTS.prototype.getTileCoordForCoordAndZ); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getTileSize', + ol.tilegrid.WMTS.prototype.getTileSize); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getZForResolution', + ol.tilegrid.WMTS.prototype.getZForResolution); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getOpacity', + ol.style.RegularShape.prototype.getOpacity); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getRotateWithView', + ol.style.RegularShape.prototype.getRotateWithView); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getRotation', + ol.style.RegularShape.prototype.getRotation); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getScale', + ol.style.RegularShape.prototype.getScale); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getSnapToPixel', + ol.style.RegularShape.prototype.getSnapToPixel); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'setOpacity', + ol.style.RegularShape.prototype.setOpacity); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'setRotation', + ol.style.RegularShape.prototype.setRotation); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'setScale', + ol.style.RegularShape.prototype.setScale); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getAngle', + ol.style.Circle.prototype.getAngle); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getFill', + ol.style.Circle.prototype.getFill); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getPoints', + ol.style.Circle.prototype.getPoints); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getRadius', + ol.style.Circle.prototype.getRadius); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getRadius2', + ol.style.Circle.prototype.getRadius2); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getStroke', + ol.style.Circle.prototype.getStroke); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getOpacity', + ol.style.Circle.prototype.getOpacity); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getRotateWithView', + ol.style.Circle.prototype.getRotateWithView); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getRotation', + ol.style.Circle.prototype.getRotation); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getScale', + ol.style.Circle.prototype.getScale); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getSnapToPixel', + ol.style.Circle.prototype.getSnapToPixel); + +goog.exportProperty( + ol.style.Circle.prototype, + 'setOpacity', + ol.style.Circle.prototype.setOpacity); + +goog.exportProperty( + ol.style.Circle.prototype, + 'setRotation', + ol.style.Circle.prototype.setRotation); + +goog.exportProperty( + ol.style.Circle.prototype, + 'setScale', + ol.style.Circle.prototype.setScale); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getOpacity', + ol.style.Icon.prototype.getOpacity); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getRotateWithView', + ol.style.Icon.prototype.getRotateWithView); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getRotation', + ol.style.Icon.prototype.getRotation); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getScale', + ol.style.Icon.prototype.getScale); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getSnapToPixel', + ol.style.Icon.prototype.getSnapToPixel); + +goog.exportProperty( + ol.style.Icon.prototype, + 'setOpacity', + ol.style.Icon.prototype.setOpacity); + +goog.exportProperty( + ol.style.Icon.prototype, + 'setRotation', + ol.style.Icon.prototype.setRotation); + +goog.exportProperty( + ol.style.Icon.prototype, + 'setScale', + ol.style.Icon.prototype.setScale); + +goog.exportProperty( + ol.source.Source.prototype, + 'get', + ol.source.Source.prototype.get); + +goog.exportProperty( + ol.source.Source.prototype, + 'getKeys', + ol.source.Source.prototype.getKeys); + +goog.exportProperty( + ol.source.Source.prototype, + 'getProperties', + ol.source.Source.prototype.getProperties); + +goog.exportProperty( + ol.source.Source.prototype, + 'set', + ol.source.Source.prototype.set); + +goog.exportProperty( + ol.source.Source.prototype, + 'setProperties', + ol.source.Source.prototype.setProperties); + +goog.exportProperty( + ol.source.Source.prototype, + 'unset', + ol.source.Source.prototype.unset); + +goog.exportProperty( + ol.source.Source.prototype, + 'changed', + ol.source.Source.prototype.changed); + +goog.exportProperty( + ol.source.Source.prototype, + 'dispatchEvent', + ol.source.Source.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Source.prototype, + 'getRevision', + ol.source.Source.prototype.getRevision); + +goog.exportProperty( + ol.source.Source.prototype, + 'on', + ol.source.Source.prototype.on); + +goog.exportProperty( + ol.source.Source.prototype, + 'once', + ol.source.Source.prototype.once); + +goog.exportProperty( + ol.source.Source.prototype, + 'un', + ol.source.Source.prototype.un); + +goog.exportProperty( + ol.source.Source.prototype, + 'unByKey', + ol.source.Source.prototype.unByKey); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getAttributions', + ol.source.Tile.prototype.getAttributions); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getLogo', + ol.source.Tile.prototype.getLogo); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getProjection', + ol.source.Tile.prototype.getProjection); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getState', + ol.source.Tile.prototype.getState); + +goog.exportProperty( + ol.source.Tile.prototype, + 'refresh', + ol.source.Tile.prototype.refresh); + +goog.exportProperty( + ol.source.Tile.prototype, + 'setAttributions', + ol.source.Tile.prototype.setAttributions); + +goog.exportProperty( + ol.source.Tile.prototype, + 'get', + ol.source.Tile.prototype.get); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getKeys', + ol.source.Tile.prototype.getKeys); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getProperties', + ol.source.Tile.prototype.getProperties); + +goog.exportProperty( + ol.source.Tile.prototype, + 'set', + ol.source.Tile.prototype.set); + +goog.exportProperty( + ol.source.Tile.prototype, + 'setProperties', + ol.source.Tile.prototype.setProperties); + +goog.exportProperty( + ol.source.Tile.prototype, + 'unset', + ol.source.Tile.prototype.unset); + +goog.exportProperty( + ol.source.Tile.prototype, + 'changed', + ol.source.Tile.prototype.changed); + +goog.exportProperty( + ol.source.Tile.prototype, + 'dispatchEvent', + ol.source.Tile.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getRevision', + ol.source.Tile.prototype.getRevision); + +goog.exportProperty( + ol.source.Tile.prototype, + 'on', + ol.source.Tile.prototype.on); + +goog.exportProperty( + ol.source.Tile.prototype, + 'once', + ol.source.Tile.prototype.once); + +goog.exportProperty( + ol.source.Tile.prototype, + 'un', + ol.source.Tile.prototype.un); + +goog.exportProperty( + ol.source.Tile.prototype, + 'unByKey', + ol.source.Tile.prototype.unByKey); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getTileGrid', + ol.source.UrlTile.prototype.getTileGrid); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'refresh', + ol.source.UrlTile.prototype.refresh); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getAttributions', + ol.source.UrlTile.prototype.getAttributions); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getLogo', + ol.source.UrlTile.prototype.getLogo); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getProjection', + ol.source.UrlTile.prototype.getProjection); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getState', + ol.source.UrlTile.prototype.getState); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'setAttributions', + ol.source.UrlTile.prototype.setAttributions); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'get', + ol.source.UrlTile.prototype.get); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getKeys', + ol.source.UrlTile.prototype.getKeys); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getProperties', + ol.source.UrlTile.prototype.getProperties); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'set', + ol.source.UrlTile.prototype.set); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'setProperties', + ol.source.UrlTile.prototype.setProperties); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'unset', + ol.source.UrlTile.prototype.unset); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'changed', + ol.source.UrlTile.prototype.changed); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'dispatchEvent', + ol.source.UrlTile.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getRevision', + ol.source.UrlTile.prototype.getRevision); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'on', + ol.source.UrlTile.prototype.on); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'once', + ol.source.UrlTile.prototype.once); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'un', + ol.source.UrlTile.prototype.un); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'unByKey', + ol.source.UrlTile.prototype.unByKey); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getTileLoadFunction', + ol.source.TileImage.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getTileUrlFunction', + ol.source.TileImage.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getUrls', + ol.source.TileImage.prototype.getUrls); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setTileLoadFunction', + ol.source.TileImage.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setTileUrlFunction', + ol.source.TileImage.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setUrl', + ol.source.TileImage.prototype.setUrl); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setUrls', + ol.source.TileImage.prototype.setUrls); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getTileGrid', + ol.source.TileImage.prototype.getTileGrid); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'refresh', + ol.source.TileImage.prototype.refresh); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getAttributions', + ol.source.TileImage.prototype.getAttributions); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getLogo', + ol.source.TileImage.prototype.getLogo); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getProjection', + ol.source.TileImage.prototype.getProjection); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getState', + ol.source.TileImage.prototype.getState); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setAttributions', + ol.source.TileImage.prototype.setAttributions); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'get', + ol.source.TileImage.prototype.get); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getKeys', + ol.source.TileImage.prototype.getKeys); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getProperties', + ol.source.TileImage.prototype.getProperties); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'set', + ol.source.TileImage.prototype.set); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setProperties', + ol.source.TileImage.prototype.setProperties); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'unset', + ol.source.TileImage.prototype.unset); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'changed', + ol.source.TileImage.prototype.changed); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'dispatchEvent', + ol.source.TileImage.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getRevision', + ol.source.TileImage.prototype.getRevision); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'on', + ol.source.TileImage.prototype.on); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'once', + ol.source.TileImage.prototype.once); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'un', + ol.source.TileImage.prototype.un); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'unByKey', + ol.source.TileImage.prototype.unByKey); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setRenderReprojectionEdges', + ol.source.BingMaps.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setTileGridForProjection', + ol.source.BingMaps.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getTileLoadFunction', + ol.source.BingMaps.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getTileUrlFunction', + ol.source.BingMaps.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getUrls', + ol.source.BingMaps.prototype.getUrls); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setTileLoadFunction', + ol.source.BingMaps.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setTileUrlFunction', + ol.source.BingMaps.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setUrl', + ol.source.BingMaps.prototype.setUrl); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setUrls', + ol.source.BingMaps.prototype.setUrls); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getTileGrid', + ol.source.BingMaps.prototype.getTileGrid); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'refresh', + ol.source.BingMaps.prototype.refresh); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getAttributions', + ol.source.BingMaps.prototype.getAttributions); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getLogo', + ol.source.BingMaps.prototype.getLogo); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getProjection', + ol.source.BingMaps.prototype.getProjection); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getState', + ol.source.BingMaps.prototype.getState); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setAttributions', + ol.source.BingMaps.prototype.setAttributions); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'get', + ol.source.BingMaps.prototype.get); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getKeys', + ol.source.BingMaps.prototype.getKeys); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getProperties', + ol.source.BingMaps.prototype.getProperties); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'set', + ol.source.BingMaps.prototype.set); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setProperties', + ol.source.BingMaps.prototype.setProperties); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'unset', + ol.source.BingMaps.prototype.unset); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'changed', + ol.source.BingMaps.prototype.changed); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'dispatchEvent', + ol.source.BingMaps.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getRevision', + ol.source.BingMaps.prototype.getRevision); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'on', + ol.source.BingMaps.prototype.on); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'once', + ol.source.BingMaps.prototype.once); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'un', + ol.source.BingMaps.prototype.un); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'unByKey', + ol.source.BingMaps.prototype.unByKey); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setRenderReprojectionEdges', + ol.source.XYZ.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setTileGridForProjection', + ol.source.XYZ.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getTileLoadFunction', + ol.source.XYZ.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getTileUrlFunction', + ol.source.XYZ.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getUrls', + ol.source.XYZ.prototype.getUrls); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setTileLoadFunction', + ol.source.XYZ.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setTileUrlFunction', + ol.source.XYZ.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setUrl', + ol.source.XYZ.prototype.setUrl); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setUrls', + ol.source.XYZ.prototype.setUrls); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getTileGrid', + ol.source.XYZ.prototype.getTileGrid); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'refresh', + ol.source.XYZ.prototype.refresh); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getAttributions', + ol.source.XYZ.prototype.getAttributions); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getLogo', + ol.source.XYZ.prototype.getLogo); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getProjection', + ol.source.XYZ.prototype.getProjection); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getState', + ol.source.XYZ.prototype.getState); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setAttributions', + ol.source.XYZ.prototype.setAttributions); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'get', + ol.source.XYZ.prototype.get); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getKeys', + ol.source.XYZ.prototype.getKeys); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getProperties', + ol.source.XYZ.prototype.getProperties); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'set', + ol.source.XYZ.prototype.set); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setProperties', + ol.source.XYZ.prototype.setProperties); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'unset', + ol.source.XYZ.prototype.unset); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'changed', + ol.source.XYZ.prototype.changed); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'dispatchEvent', + ol.source.XYZ.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getRevision', + ol.source.XYZ.prototype.getRevision); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'on', + ol.source.XYZ.prototype.on); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'once', + ol.source.XYZ.prototype.once); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'un', + ol.source.XYZ.prototype.un); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'unByKey', + ol.source.XYZ.prototype.unByKey); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setRenderReprojectionEdges', + ol.source.CartoDB.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setTileGridForProjection', + ol.source.CartoDB.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getTileLoadFunction', + ol.source.CartoDB.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getTileUrlFunction', + ol.source.CartoDB.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getUrls', + ol.source.CartoDB.prototype.getUrls); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setTileLoadFunction', + ol.source.CartoDB.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setTileUrlFunction', + ol.source.CartoDB.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setUrl', + ol.source.CartoDB.prototype.setUrl); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setUrls', + ol.source.CartoDB.prototype.setUrls); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getTileGrid', + ol.source.CartoDB.prototype.getTileGrid); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'refresh', + ol.source.CartoDB.prototype.refresh); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getAttributions', + ol.source.CartoDB.prototype.getAttributions); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getLogo', + ol.source.CartoDB.prototype.getLogo); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getProjection', + ol.source.CartoDB.prototype.getProjection); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getState', + ol.source.CartoDB.prototype.getState); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setAttributions', + ol.source.CartoDB.prototype.setAttributions); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'get', + ol.source.CartoDB.prototype.get); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getKeys', + ol.source.CartoDB.prototype.getKeys); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getProperties', + ol.source.CartoDB.prototype.getProperties); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'set', + ol.source.CartoDB.prototype.set); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setProperties', + ol.source.CartoDB.prototype.setProperties); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'unset', + ol.source.CartoDB.prototype.unset); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'changed', + ol.source.CartoDB.prototype.changed); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'dispatchEvent', + ol.source.CartoDB.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getRevision', + ol.source.CartoDB.prototype.getRevision); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'on', + ol.source.CartoDB.prototype.on); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'once', + ol.source.CartoDB.prototype.once); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'un', + ol.source.CartoDB.prototype.un); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'unByKey', + ol.source.CartoDB.prototype.unByKey); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getAttributions', + ol.source.Vector.prototype.getAttributions); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getLogo', + ol.source.Vector.prototype.getLogo); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getProjection', + ol.source.Vector.prototype.getProjection); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getState', + ol.source.Vector.prototype.getState); + +goog.exportProperty( + ol.source.Vector.prototype, + 'refresh', + ol.source.Vector.prototype.refresh); + +goog.exportProperty( + ol.source.Vector.prototype, + 'setAttributions', + ol.source.Vector.prototype.setAttributions); + +goog.exportProperty( + ol.source.Vector.prototype, + 'get', + ol.source.Vector.prototype.get); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getKeys', + ol.source.Vector.prototype.getKeys); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getProperties', + ol.source.Vector.prototype.getProperties); + +goog.exportProperty( + ol.source.Vector.prototype, + 'set', + ol.source.Vector.prototype.set); + +goog.exportProperty( + ol.source.Vector.prototype, + 'setProperties', + ol.source.Vector.prototype.setProperties); + +goog.exportProperty( + ol.source.Vector.prototype, + 'unset', + ol.source.Vector.prototype.unset); + +goog.exportProperty( + ol.source.Vector.prototype, + 'changed', + ol.source.Vector.prototype.changed); + +goog.exportProperty( + ol.source.Vector.prototype, + 'dispatchEvent', + ol.source.Vector.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getRevision', + ol.source.Vector.prototype.getRevision); + +goog.exportProperty( + ol.source.Vector.prototype, + 'on', + ol.source.Vector.prototype.on); + +goog.exportProperty( + ol.source.Vector.prototype, + 'once', + ol.source.Vector.prototype.once); + +goog.exportProperty( + ol.source.Vector.prototype, + 'un', + ol.source.Vector.prototype.un); + +goog.exportProperty( + ol.source.Vector.prototype, + 'unByKey', + ol.source.Vector.prototype.unByKey); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'addFeature', + ol.source.Cluster.prototype.addFeature); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'addFeatures', + ol.source.Cluster.prototype.addFeatures); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'clear', + ol.source.Cluster.prototype.clear); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'forEachFeature', + ol.source.Cluster.prototype.forEachFeature); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'forEachFeatureInExtent', + ol.source.Cluster.prototype.forEachFeatureInExtent); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'forEachFeatureIntersectingExtent', + ol.source.Cluster.prototype.forEachFeatureIntersectingExtent); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFeaturesCollection', + ol.source.Cluster.prototype.getFeaturesCollection); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFeatures', + ol.source.Cluster.prototype.getFeatures); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFeaturesAtCoordinate', + ol.source.Cluster.prototype.getFeaturesAtCoordinate); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFeaturesInExtent', + ol.source.Cluster.prototype.getFeaturesInExtent); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getClosestFeatureToCoordinate', + ol.source.Cluster.prototype.getClosestFeatureToCoordinate); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getExtent', + ol.source.Cluster.prototype.getExtent); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFeatureById', + ol.source.Cluster.prototype.getFeatureById); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFormat', + ol.source.Cluster.prototype.getFormat); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getUrl', + ol.source.Cluster.prototype.getUrl); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'removeFeature', + ol.source.Cluster.prototype.removeFeature); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getAttributions', + ol.source.Cluster.prototype.getAttributions); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getLogo', + ol.source.Cluster.prototype.getLogo); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getProjection', + ol.source.Cluster.prototype.getProjection); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getState', + ol.source.Cluster.prototype.getState); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'refresh', + ol.source.Cluster.prototype.refresh); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'setAttributions', + ol.source.Cluster.prototype.setAttributions); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'get', + ol.source.Cluster.prototype.get); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getKeys', + ol.source.Cluster.prototype.getKeys); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getProperties', + ol.source.Cluster.prototype.getProperties); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'set', + ol.source.Cluster.prototype.set); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'setProperties', + ol.source.Cluster.prototype.setProperties); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'unset', + ol.source.Cluster.prototype.unset); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'changed', + ol.source.Cluster.prototype.changed); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'dispatchEvent', + ol.source.Cluster.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getRevision', + ol.source.Cluster.prototype.getRevision); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'on', + ol.source.Cluster.prototype.on); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'once', + ol.source.Cluster.prototype.once); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'un', + ol.source.Cluster.prototype.un); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'unByKey', + ol.source.Cluster.prototype.unByKey); + +goog.exportProperty( + ol.source.Image.prototype, + 'getAttributions', + ol.source.Image.prototype.getAttributions); + +goog.exportProperty( + ol.source.Image.prototype, + 'getLogo', + ol.source.Image.prototype.getLogo); + +goog.exportProperty( + ol.source.Image.prototype, + 'getProjection', + ol.source.Image.prototype.getProjection); + +goog.exportProperty( + ol.source.Image.prototype, + 'getState', + ol.source.Image.prototype.getState); + +goog.exportProperty( + ol.source.Image.prototype, + 'refresh', + ol.source.Image.prototype.refresh); + +goog.exportProperty( + ol.source.Image.prototype, + 'setAttributions', + ol.source.Image.prototype.setAttributions); + +goog.exportProperty( + ol.source.Image.prototype, + 'get', + ol.source.Image.prototype.get); + +goog.exportProperty( + ol.source.Image.prototype, + 'getKeys', + ol.source.Image.prototype.getKeys); + +goog.exportProperty( + ol.source.Image.prototype, + 'getProperties', + ol.source.Image.prototype.getProperties); + +goog.exportProperty( + ol.source.Image.prototype, + 'set', + ol.source.Image.prototype.set); + +goog.exportProperty( + ol.source.Image.prototype, + 'setProperties', + ol.source.Image.prototype.setProperties); + +goog.exportProperty( + ol.source.Image.prototype, + 'unset', + ol.source.Image.prototype.unset); + +goog.exportProperty( + ol.source.Image.prototype, + 'changed', + ol.source.Image.prototype.changed); + +goog.exportProperty( + ol.source.Image.prototype, + 'dispatchEvent', + ol.source.Image.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Image.prototype, + 'getRevision', + ol.source.Image.prototype.getRevision); + +goog.exportProperty( + ol.source.Image.prototype, + 'on', + ol.source.Image.prototype.on); + +goog.exportProperty( + ol.source.Image.prototype, + 'once', + ol.source.Image.prototype.once); + +goog.exportProperty( + ol.source.Image.prototype, + 'un', + ol.source.Image.prototype.un); + +goog.exportProperty( + ol.source.Image.prototype, + 'unByKey', + ol.source.Image.prototype.unByKey); + +goog.exportProperty( + ol.source.Image.Event.prototype, + 'type', + ol.source.Image.Event.prototype.type); + +goog.exportProperty( + ol.source.Image.Event.prototype, + 'target', + ol.source.Image.Event.prototype.target); + +goog.exportProperty( + ol.source.Image.Event.prototype, + 'preventDefault', + ol.source.Image.Event.prototype.preventDefault); + +goog.exportProperty( + ol.source.Image.Event.prototype, + 'stopPropagation', + ol.source.Image.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getAttributions', + ol.source.ImageArcGISRest.prototype.getAttributions); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getLogo', + ol.source.ImageArcGISRest.prototype.getLogo); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getProjection', + ol.source.ImageArcGISRest.prototype.getProjection); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getState', + ol.source.ImageArcGISRest.prototype.getState); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'refresh', + ol.source.ImageArcGISRest.prototype.refresh); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'setAttributions', + ol.source.ImageArcGISRest.prototype.setAttributions); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'get', + ol.source.ImageArcGISRest.prototype.get); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getKeys', + ol.source.ImageArcGISRest.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getProperties', + ol.source.ImageArcGISRest.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'set', + ol.source.ImageArcGISRest.prototype.set); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'setProperties', + ol.source.ImageArcGISRest.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'unset', + ol.source.ImageArcGISRest.prototype.unset); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'changed', + ol.source.ImageArcGISRest.prototype.changed); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'dispatchEvent', + ol.source.ImageArcGISRest.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getRevision', + ol.source.ImageArcGISRest.prototype.getRevision); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'on', + ol.source.ImageArcGISRest.prototype.on); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'once', + ol.source.ImageArcGISRest.prototype.once); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'un', + ol.source.ImageArcGISRest.prototype.un); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'unByKey', + ol.source.ImageArcGISRest.prototype.unByKey); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getAttributions', + ol.source.ImageCanvas.prototype.getAttributions); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getLogo', + ol.source.ImageCanvas.prototype.getLogo); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getProjection', + ol.source.ImageCanvas.prototype.getProjection); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getState', + ol.source.ImageCanvas.prototype.getState); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'refresh', + ol.source.ImageCanvas.prototype.refresh); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'setAttributions', + ol.source.ImageCanvas.prototype.setAttributions); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'get', + ol.source.ImageCanvas.prototype.get); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getKeys', + ol.source.ImageCanvas.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getProperties', + ol.source.ImageCanvas.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'set', + ol.source.ImageCanvas.prototype.set); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'setProperties', + ol.source.ImageCanvas.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'unset', + ol.source.ImageCanvas.prototype.unset); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'changed', + ol.source.ImageCanvas.prototype.changed); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'dispatchEvent', + ol.source.ImageCanvas.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getRevision', + ol.source.ImageCanvas.prototype.getRevision); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'on', + ol.source.ImageCanvas.prototype.on); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'once', + ol.source.ImageCanvas.prototype.once); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'un', + ol.source.ImageCanvas.prototype.un); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'unByKey', + ol.source.ImageCanvas.prototype.unByKey); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getAttributions', + ol.source.ImageMapGuide.prototype.getAttributions); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getLogo', + ol.source.ImageMapGuide.prototype.getLogo); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getProjection', + ol.source.ImageMapGuide.prototype.getProjection); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getState', + ol.source.ImageMapGuide.prototype.getState); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'refresh', + ol.source.ImageMapGuide.prototype.refresh); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'setAttributions', + ol.source.ImageMapGuide.prototype.setAttributions); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'get', + ol.source.ImageMapGuide.prototype.get); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getKeys', + ol.source.ImageMapGuide.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getProperties', + ol.source.ImageMapGuide.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'set', + ol.source.ImageMapGuide.prototype.set); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'setProperties', + ol.source.ImageMapGuide.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'unset', + ol.source.ImageMapGuide.prototype.unset); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'changed', + ol.source.ImageMapGuide.prototype.changed); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'dispatchEvent', + ol.source.ImageMapGuide.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getRevision', + ol.source.ImageMapGuide.prototype.getRevision); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'on', + ol.source.ImageMapGuide.prototype.on); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'once', + ol.source.ImageMapGuide.prototype.once); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'un', + ol.source.ImageMapGuide.prototype.un); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'unByKey', + ol.source.ImageMapGuide.prototype.unByKey); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getAttributions', + ol.source.ImageStatic.prototype.getAttributions); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getLogo', + ol.source.ImageStatic.prototype.getLogo); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getProjection', + ol.source.ImageStatic.prototype.getProjection); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getState', + ol.source.ImageStatic.prototype.getState); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'refresh', + ol.source.ImageStatic.prototype.refresh); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'setAttributions', + ol.source.ImageStatic.prototype.setAttributions); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'get', + ol.source.ImageStatic.prototype.get); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getKeys', + ol.source.ImageStatic.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getProperties', + ol.source.ImageStatic.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'set', + ol.source.ImageStatic.prototype.set); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'setProperties', + ol.source.ImageStatic.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'unset', + ol.source.ImageStatic.prototype.unset); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'changed', + ol.source.ImageStatic.prototype.changed); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'dispatchEvent', + ol.source.ImageStatic.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getRevision', + ol.source.ImageStatic.prototype.getRevision); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'on', + ol.source.ImageStatic.prototype.on); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'once', + ol.source.ImageStatic.prototype.once); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'un', + ol.source.ImageStatic.prototype.un); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'unByKey', + ol.source.ImageStatic.prototype.unByKey); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getAttributions', + ol.source.ImageVector.prototype.getAttributions); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getLogo', + ol.source.ImageVector.prototype.getLogo); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getProjection', + ol.source.ImageVector.prototype.getProjection); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getState', + ol.source.ImageVector.prototype.getState); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'refresh', + ol.source.ImageVector.prototype.refresh); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'setAttributions', + ol.source.ImageVector.prototype.setAttributions); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'get', + ol.source.ImageVector.prototype.get); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getKeys', + ol.source.ImageVector.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getProperties', + ol.source.ImageVector.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'set', + ol.source.ImageVector.prototype.set); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'setProperties', + ol.source.ImageVector.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'unset', + ol.source.ImageVector.prototype.unset); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'changed', + ol.source.ImageVector.prototype.changed); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'dispatchEvent', + ol.source.ImageVector.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getRevision', + ol.source.ImageVector.prototype.getRevision); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'on', + ol.source.ImageVector.prototype.on); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'once', + ol.source.ImageVector.prototype.once); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'un', + ol.source.ImageVector.prototype.un); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'unByKey', + ol.source.ImageVector.prototype.unByKey); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getAttributions', + ol.source.ImageWMS.prototype.getAttributions); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getLogo', + ol.source.ImageWMS.prototype.getLogo); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getProjection', + ol.source.ImageWMS.prototype.getProjection); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getState', + ol.source.ImageWMS.prototype.getState); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'refresh', + ol.source.ImageWMS.prototype.refresh); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'setAttributions', + ol.source.ImageWMS.prototype.setAttributions); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'get', + ol.source.ImageWMS.prototype.get); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getKeys', + ol.source.ImageWMS.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getProperties', + ol.source.ImageWMS.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'set', + ol.source.ImageWMS.prototype.set); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'setProperties', + ol.source.ImageWMS.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'unset', + ol.source.ImageWMS.prototype.unset); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'changed', + ol.source.ImageWMS.prototype.changed); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'dispatchEvent', + ol.source.ImageWMS.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getRevision', + ol.source.ImageWMS.prototype.getRevision); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'on', + ol.source.ImageWMS.prototype.on); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'once', + ol.source.ImageWMS.prototype.once); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'un', + ol.source.ImageWMS.prototype.un); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'unByKey', + ol.source.ImageWMS.prototype.unByKey); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setRenderReprojectionEdges', + ol.source.OSM.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setTileGridForProjection', + ol.source.OSM.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getTileLoadFunction', + ol.source.OSM.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getTileUrlFunction', + ol.source.OSM.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getUrls', + ol.source.OSM.prototype.getUrls); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setTileLoadFunction', + ol.source.OSM.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setTileUrlFunction', + ol.source.OSM.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setUrl', + ol.source.OSM.prototype.setUrl); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setUrls', + ol.source.OSM.prototype.setUrls); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getTileGrid', + ol.source.OSM.prototype.getTileGrid); + +goog.exportProperty( + ol.source.OSM.prototype, + 'refresh', + ol.source.OSM.prototype.refresh); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getAttributions', + ol.source.OSM.prototype.getAttributions); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getLogo', + ol.source.OSM.prototype.getLogo); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getProjection', + ol.source.OSM.prototype.getProjection); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getState', + ol.source.OSM.prototype.getState); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setAttributions', + ol.source.OSM.prototype.setAttributions); + +goog.exportProperty( + ol.source.OSM.prototype, + 'get', + ol.source.OSM.prototype.get); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getKeys', + ol.source.OSM.prototype.getKeys); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getProperties', + ol.source.OSM.prototype.getProperties); + +goog.exportProperty( + ol.source.OSM.prototype, + 'set', + ol.source.OSM.prototype.set); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setProperties', + ol.source.OSM.prototype.setProperties); + +goog.exportProperty( + ol.source.OSM.prototype, + 'unset', + ol.source.OSM.prototype.unset); + +goog.exportProperty( + ol.source.OSM.prototype, + 'changed', + ol.source.OSM.prototype.changed); + +goog.exportProperty( + ol.source.OSM.prototype, + 'dispatchEvent', + ol.source.OSM.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getRevision', + ol.source.OSM.prototype.getRevision); + +goog.exportProperty( + ol.source.OSM.prototype, + 'on', + ol.source.OSM.prototype.on); + +goog.exportProperty( + ol.source.OSM.prototype, + 'once', + ol.source.OSM.prototype.once); + +goog.exportProperty( + ol.source.OSM.prototype, + 'un', + ol.source.OSM.prototype.un); + +goog.exportProperty( + ol.source.OSM.prototype, + 'unByKey', + ol.source.OSM.prototype.unByKey); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getAttributions', + ol.source.Raster.prototype.getAttributions); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getLogo', + ol.source.Raster.prototype.getLogo); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getProjection', + ol.source.Raster.prototype.getProjection); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getState', + ol.source.Raster.prototype.getState); + +goog.exportProperty( + ol.source.Raster.prototype, + 'refresh', + ol.source.Raster.prototype.refresh); + +goog.exportProperty( + ol.source.Raster.prototype, + 'setAttributions', + ol.source.Raster.prototype.setAttributions); + +goog.exportProperty( + ol.source.Raster.prototype, + 'get', + ol.source.Raster.prototype.get); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getKeys', + ol.source.Raster.prototype.getKeys); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getProperties', + ol.source.Raster.prototype.getProperties); + +goog.exportProperty( + ol.source.Raster.prototype, + 'set', + ol.source.Raster.prototype.set); + +goog.exportProperty( + ol.source.Raster.prototype, + 'setProperties', + ol.source.Raster.prototype.setProperties); + +goog.exportProperty( + ol.source.Raster.prototype, + 'unset', + ol.source.Raster.prototype.unset); + +goog.exportProperty( + ol.source.Raster.prototype, + 'changed', + ol.source.Raster.prototype.changed); + +goog.exportProperty( + ol.source.Raster.prototype, + 'dispatchEvent', + ol.source.Raster.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getRevision', + ol.source.Raster.prototype.getRevision); + +goog.exportProperty( + ol.source.Raster.prototype, + 'on', + ol.source.Raster.prototype.on); + +goog.exportProperty( + ol.source.Raster.prototype, + 'once', + ol.source.Raster.prototype.once); + +goog.exportProperty( + ol.source.Raster.prototype, + 'un', + ol.source.Raster.prototype.un); + +goog.exportProperty( + ol.source.Raster.prototype, + 'unByKey', + ol.source.Raster.prototype.unByKey); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'type', + ol.source.Raster.Event.prototype.type); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'target', + ol.source.Raster.Event.prototype.target); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'preventDefault', + ol.source.Raster.Event.prototype.preventDefault); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'stopPropagation', + ol.source.Raster.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setRenderReprojectionEdges', + ol.source.Stamen.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setTileGridForProjection', + ol.source.Stamen.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getTileLoadFunction', + ol.source.Stamen.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getTileUrlFunction', + ol.source.Stamen.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getUrls', + ol.source.Stamen.prototype.getUrls); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setTileLoadFunction', + ol.source.Stamen.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setTileUrlFunction', + ol.source.Stamen.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setUrl', + ol.source.Stamen.prototype.setUrl); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setUrls', + ol.source.Stamen.prototype.setUrls); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getTileGrid', + ol.source.Stamen.prototype.getTileGrid); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'refresh', + ol.source.Stamen.prototype.refresh); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getAttributions', + ol.source.Stamen.prototype.getAttributions); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getLogo', + ol.source.Stamen.prototype.getLogo); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getProjection', + ol.source.Stamen.prototype.getProjection); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getState', + ol.source.Stamen.prototype.getState); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setAttributions', + ol.source.Stamen.prototype.setAttributions); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'get', + ol.source.Stamen.prototype.get); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getKeys', + ol.source.Stamen.prototype.getKeys); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getProperties', + ol.source.Stamen.prototype.getProperties); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'set', + ol.source.Stamen.prototype.set); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setProperties', + ol.source.Stamen.prototype.setProperties); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'unset', + ol.source.Stamen.prototype.unset); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'changed', + ol.source.Stamen.prototype.changed); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'dispatchEvent', + ol.source.Stamen.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getRevision', + ol.source.Stamen.prototype.getRevision); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'on', + ol.source.Stamen.prototype.on); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'once', + ol.source.Stamen.prototype.once); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'un', + ol.source.Stamen.prototype.un); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'unByKey', + ol.source.Stamen.prototype.unByKey); + +goog.exportProperty( + ol.source.Tile.Event.prototype, + 'type', + ol.source.Tile.Event.prototype.type); + +goog.exportProperty( + ol.source.Tile.Event.prototype, + 'target', + ol.source.Tile.Event.prototype.target); + +goog.exportProperty( + ol.source.Tile.Event.prototype, + 'preventDefault', + ol.source.Tile.Event.prototype.preventDefault); + +goog.exportProperty( + ol.source.Tile.Event.prototype, + 'stopPropagation', + ol.source.Tile.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setRenderReprojectionEdges', + ol.source.TileArcGISRest.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setTileGridForProjection', + ol.source.TileArcGISRest.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getTileLoadFunction', + ol.source.TileArcGISRest.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getTileUrlFunction', + ol.source.TileArcGISRest.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getUrls', + ol.source.TileArcGISRest.prototype.getUrls); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setTileLoadFunction', + ol.source.TileArcGISRest.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setTileUrlFunction', + ol.source.TileArcGISRest.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setUrl', + ol.source.TileArcGISRest.prototype.setUrl); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setUrls', + ol.source.TileArcGISRest.prototype.setUrls); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getTileGrid', + ol.source.TileArcGISRest.prototype.getTileGrid); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'refresh', + ol.source.TileArcGISRest.prototype.refresh); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getAttributions', + ol.source.TileArcGISRest.prototype.getAttributions); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getLogo', + ol.source.TileArcGISRest.prototype.getLogo); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getProjection', + ol.source.TileArcGISRest.prototype.getProjection); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getState', + ol.source.TileArcGISRest.prototype.getState); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setAttributions', + ol.source.TileArcGISRest.prototype.setAttributions); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'get', + ol.source.TileArcGISRest.prototype.get); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getKeys', + ol.source.TileArcGISRest.prototype.getKeys); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getProperties', + ol.source.TileArcGISRest.prototype.getProperties); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'set', + ol.source.TileArcGISRest.prototype.set); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setProperties', + ol.source.TileArcGISRest.prototype.setProperties); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'unset', + ol.source.TileArcGISRest.prototype.unset); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'changed', + ol.source.TileArcGISRest.prototype.changed); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'dispatchEvent', + ol.source.TileArcGISRest.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getRevision', + ol.source.TileArcGISRest.prototype.getRevision); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'on', + ol.source.TileArcGISRest.prototype.on); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'once', + ol.source.TileArcGISRest.prototype.once); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'un', + ol.source.TileArcGISRest.prototype.un); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'unByKey', + ol.source.TileArcGISRest.prototype.unByKey); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getTileGrid', + ol.source.TileDebug.prototype.getTileGrid); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'refresh', + ol.source.TileDebug.prototype.refresh); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getAttributions', + ol.source.TileDebug.prototype.getAttributions); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getLogo', + ol.source.TileDebug.prototype.getLogo); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getProjection', + ol.source.TileDebug.prototype.getProjection); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getState', + ol.source.TileDebug.prototype.getState); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'setAttributions', + ol.source.TileDebug.prototype.setAttributions); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'get', + ol.source.TileDebug.prototype.get); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getKeys', + ol.source.TileDebug.prototype.getKeys); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getProperties', + ol.source.TileDebug.prototype.getProperties); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'set', + ol.source.TileDebug.prototype.set); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'setProperties', + ol.source.TileDebug.prototype.setProperties); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'unset', + ol.source.TileDebug.prototype.unset); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'changed', + ol.source.TileDebug.prototype.changed); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'dispatchEvent', + ol.source.TileDebug.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getRevision', + ol.source.TileDebug.prototype.getRevision); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'on', + ol.source.TileDebug.prototype.on); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'once', + ol.source.TileDebug.prototype.once); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'un', + ol.source.TileDebug.prototype.un); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'unByKey', + ol.source.TileDebug.prototype.unByKey); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setRenderReprojectionEdges', + ol.source.TileJSON.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setTileGridForProjection', + ol.source.TileJSON.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getTileLoadFunction', + ol.source.TileJSON.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getTileUrlFunction', + ol.source.TileJSON.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getUrls', + ol.source.TileJSON.prototype.getUrls); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setTileLoadFunction', + ol.source.TileJSON.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setTileUrlFunction', + ol.source.TileJSON.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setUrl', + ol.source.TileJSON.prototype.setUrl); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setUrls', + ol.source.TileJSON.prototype.setUrls); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getTileGrid', + ol.source.TileJSON.prototype.getTileGrid); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'refresh', + ol.source.TileJSON.prototype.refresh); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getAttributions', + ol.source.TileJSON.prototype.getAttributions); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getLogo', + ol.source.TileJSON.prototype.getLogo); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getProjection', + ol.source.TileJSON.prototype.getProjection); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getState', + ol.source.TileJSON.prototype.getState); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setAttributions', + ol.source.TileJSON.prototype.setAttributions); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'get', + ol.source.TileJSON.prototype.get); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getKeys', + ol.source.TileJSON.prototype.getKeys); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getProperties', + ol.source.TileJSON.prototype.getProperties); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'set', + ol.source.TileJSON.prototype.set); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setProperties', + ol.source.TileJSON.prototype.setProperties); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'unset', + ol.source.TileJSON.prototype.unset); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'changed', + ol.source.TileJSON.prototype.changed); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'dispatchEvent', + ol.source.TileJSON.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getRevision', + ol.source.TileJSON.prototype.getRevision); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'on', + ol.source.TileJSON.prototype.on); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'once', + ol.source.TileJSON.prototype.once); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'un', + ol.source.TileJSON.prototype.un); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'unByKey', + ol.source.TileJSON.prototype.unByKey); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getTileGrid', + ol.source.TileUTFGrid.prototype.getTileGrid); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'refresh', + ol.source.TileUTFGrid.prototype.refresh); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getAttributions', + ol.source.TileUTFGrid.prototype.getAttributions); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getLogo', + ol.source.TileUTFGrid.prototype.getLogo); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getProjection', + ol.source.TileUTFGrid.prototype.getProjection); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getState', + ol.source.TileUTFGrid.prototype.getState); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'setAttributions', + ol.source.TileUTFGrid.prototype.setAttributions); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'get', + ol.source.TileUTFGrid.prototype.get); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getKeys', + ol.source.TileUTFGrid.prototype.getKeys); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getProperties', + ol.source.TileUTFGrid.prototype.getProperties); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'set', + ol.source.TileUTFGrid.prototype.set); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'setProperties', + ol.source.TileUTFGrid.prototype.setProperties); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'unset', + ol.source.TileUTFGrid.prototype.unset); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'changed', + ol.source.TileUTFGrid.prototype.changed); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'dispatchEvent', + ol.source.TileUTFGrid.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getRevision', + ol.source.TileUTFGrid.prototype.getRevision); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'on', + ol.source.TileUTFGrid.prototype.on); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'once', + ol.source.TileUTFGrid.prototype.once); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'un', + ol.source.TileUTFGrid.prototype.un); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'unByKey', + ol.source.TileUTFGrid.prototype.unByKey); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setRenderReprojectionEdges', + ol.source.TileWMS.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setTileGridForProjection', + ol.source.TileWMS.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getTileLoadFunction', + ol.source.TileWMS.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getTileUrlFunction', + ol.source.TileWMS.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getUrls', + ol.source.TileWMS.prototype.getUrls); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setTileLoadFunction', + ol.source.TileWMS.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setTileUrlFunction', + ol.source.TileWMS.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setUrl', + ol.source.TileWMS.prototype.setUrl); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setUrls', + ol.source.TileWMS.prototype.setUrls); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getTileGrid', + ol.source.TileWMS.prototype.getTileGrid); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'refresh', + ol.source.TileWMS.prototype.refresh); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getAttributions', + ol.source.TileWMS.prototype.getAttributions); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getLogo', + ol.source.TileWMS.prototype.getLogo); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getProjection', + ol.source.TileWMS.prototype.getProjection); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getState', + ol.source.TileWMS.prototype.getState); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setAttributions', + ol.source.TileWMS.prototype.setAttributions); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'get', + ol.source.TileWMS.prototype.get); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getKeys', + ol.source.TileWMS.prototype.getKeys); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getProperties', + ol.source.TileWMS.prototype.getProperties); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'set', + ol.source.TileWMS.prototype.set); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setProperties', + ol.source.TileWMS.prototype.setProperties); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'unset', + ol.source.TileWMS.prototype.unset); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'changed', + ol.source.TileWMS.prototype.changed); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'dispatchEvent', + ol.source.TileWMS.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getRevision', + ol.source.TileWMS.prototype.getRevision); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'on', + ol.source.TileWMS.prototype.on); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'once', + ol.source.TileWMS.prototype.once); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'un', + ol.source.TileWMS.prototype.un); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'unByKey', + ol.source.TileWMS.prototype.unByKey); + +goog.exportProperty( + ol.source.Vector.Event.prototype, + 'type', + ol.source.Vector.Event.prototype.type); + +goog.exportProperty( + ol.source.Vector.Event.prototype, + 'target', + ol.source.Vector.Event.prototype.target); + +goog.exportProperty( + ol.source.Vector.Event.prototype, + 'preventDefault', + ol.source.Vector.Event.prototype.preventDefault); + +goog.exportProperty( + ol.source.Vector.Event.prototype, + 'stopPropagation', + ol.source.Vector.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getTileLoadFunction', + ol.source.VectorTile.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getTileUrlFunction', + ol.source.VectorTile.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getUrls', + ol.source.VectorTile.prototype.getUrls); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'setTileLoadFunction', + ol.source.VectorTile.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'setTileUrlFunction', + ol.source.VectorTile.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'setUrl', + ol.source.VectorTile.prototype.setUrl); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'setUrls', + ol.source.VectorTile.prototype.setUrls); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getTileGrid', + ol.source.VectorTile.prototype.getTileGrid); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'refresh', + ol.source.VectorTile.prototype.refresh); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getAttributions', + ol.source.VectorTile.prototype.getAttributions); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getLogo', + ol.source.VectorTile.prototype.getLogo); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getProjection', + ol.source.VectorTile.prototype.getProjection); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getState', + ol.source.VectorTile.prototype.getState); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'setAttributions', + ol.source.VectorTile.prototype.setAttributions); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'get', + ol.source.VectorTile.prototype.get); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getKeys', + ol.source.VectorTile.prototype.getKeys); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getProperties', + ol.source.VectorTile.prototype.getProperties); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'set', + ol.source.VectorTile.prototype.set); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'setProperties', + ol.source.VectorTile.prototype.setProperties); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'unset', + ol.source.VectorTile.prototype.unset); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'changed', + ol.source.VectorTile.prototype.changed); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'dispatchEvent', + ol.source.VectorTile.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getRevision', + ol.source.VectorTile.prototype.getRevision); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'on', + ol.source.VectorTile.prototype.on); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'once', + ol.source.VectorTile.prototype.once); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'un', + ol.source.VectorTile.prototype.un); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'unByKey', + ol.source.VectorTile.prototype.unByKey); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setRenderReprojectionEdges', + ol.source.WMTS.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setTileGridForProjection', + ol.source.WMTS.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getTileLoadFunction', + ol.source.WMTS.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getTileUrlFunction', + ol.source.WMTS.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getUrls', + ol.source.WMTS.prototype.getUrls); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setTileLoadFunction', + ol.source.WMTS.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setTileUrlFunction', + ol.source.WMTS.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setUrl', + ol.source.WMTS.prototype.setUrl); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setUrls', + ol.source.WMTS.prototype.setUrls); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getTileGrid', + ol.source.WMTS.prototype.getTileGrid); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'refresh', + ol.source.WMTS.prototype.refresh); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getAttributions', + ol.source.WMTS.prototype.getAttributions); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getLogo', + ol.source.WMTS.prototype.getLogo); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getProjection', + ol.source.WMTS.prototype.getProjection); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getState', + ol.source.WMTS.prototype.getState); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setAttributions', + ol.source.WMTS.prototype.setAttributions); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'get', + ol.source.WMTS.prototype.get); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getKeys', + ol.source.WMTS.prototype.getKeys); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getProperties', + ol.source.WMTS.prototype.getProperties); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'set', + ol.source.WMTS.prototype.set); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setProperties', + ol.source.WMTS.prototype.setProperties); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'unset', + ol.source.WMTS.prototype.unset); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'changed', + ol.source.WMTS.prototype.changed); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'dispatchEvent', + ol.source.WMTS.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getRevision', + ol.source.WMTS.prototype.getRevision); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'on', + ol.source.WMTS.prototype.on); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'once', + ol.source.WMTS.prototype.once); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'un', + ol.source.WMTS.prototype.un); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'unByKey', + ol.source.WMTS.prototype.unByKey); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setRenderReprojectionEdges', + ol.source.Zoomify.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setTileGridForProjection', + ol.source.Zoomify.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getTileLoadFunction', + ol.source.Zoomify.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getTileUrlFunction', + ol.source.Zoomify.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getUrls', + ol.source.Zoomify.prototype.getUrls); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setTileLoadFunction', + ol.source.Zoomify.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setTileUrlFunction', + ol.source.Zoomify.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setUrl', + ol.source.Zoomify.prototype.setUrl); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setUrls', + ol.source.Zoomify.prototype.setUrls); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getTileGrid', + ol.source.Zoomify.prototype.getTileGrid); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'refresh', + ol.source.Zoomify.prototype.refresh); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getAttributions', + ol.source.Zoomify.prototype.getAttributions); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getLogo', + ol.source.Zoomify.prototype.getLogo); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getProjection', + ol.source.Zoomify.prototype.getProjection); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getState', + ol.source.Zoomify.prototype.getState); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setAttributions', + ol.source.Zoomify.prototype.setAttributions); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'get', + ol.source.Zoomify.prototype.get); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getKeys', + ol.source.Zoomify.prototype.getKeys); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getProperties', + ol.source.Zoomify.prototype.getProperties); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'set', + ol.source.Zoomify.prototype.set); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setProperties', + ol.source.Zoomify.prototype.setProperties); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'unset', + ol.source.Zoomify.prototype.unset); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'changed', + ol.source.Zoomify.prototype.changed); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'dispatchEvent', + ol.source.Zoomify.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getRevision', + ol.source.Zoomify.prototype.getRevision); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'on', + ol.source.Zoomify.prototype.on); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'once', + ol.source.Zoomify.prototype.once); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'un', + ol.source.Zoomify.prototype.un); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'unByKey', + ol.source.Zoomify.prototype.unByKey); + +goog.exportProperty( + ol.reproj.Tile.prototype, + 'getTileCoord', + ol.reproj.Tile.prototype.getTileCoord); + +goog.exportProperty( + ol.reproj.Tile.prototype, + 'load', + ol.reproj.Tile.prototype.load); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'changed', + ol.renderer.Layer.prototype.changed); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'dispatchEvent', + ol.renderer.Layer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'getRevision', + ol.renderer.Layer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'on', + ol.renderer.Layer.prototype.on); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'once', + ol.renderer.Layer.prototype.once); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'un', + ol.renderer.Layer.prototype.un); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'unByKey', + ol.renderer.Layer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'changed', + ol.renderer.webgl.Layer.prototype.changed); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'dispatchEvent', + ol.renderer.webgl.Layer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'getRevision', + ol.renderer.webgl.Layer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'on', + ol.renderer.webgl.Layer.prototype.on); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'once', + ol.renderer.webgl.Layer.prototype.once); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'un', + ol.renderer.webgl.Layer.prototype.un); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'unByKey', + ol.renderer.webgl.Layer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'changed', + ol.renderer.webgl.ImageLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'dispatchEvent', + ol.renderer.webgl.ImageLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'getRevision', + ol.renderer.webgl.ImageLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'on', + ol.renderer.webgl.ImageLayer.prototype.on); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'once', + ol.renderer.webgl.ImageLayer.prototype.once); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'un', + ol.renderer.webgl.ImageLayer.prototype.un); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'unByKey', + ol.renderer.webgl.ImageLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'changed', + ol.renderer.webgl.TileLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'dispatchEvent', + ol.renderer.webgl.TileLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'getRevision', + ol.renderer.webgl.TileLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'on', + ol.renderer.webgl.TileLayer.prototype.on); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'once', + ol.renderer.webgl.TileLayer.prototype.once); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'un', + ol.renderer.webgl.TileLayer.prototype.un); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'unByKey', + ol.renderer.webgl.TileLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'changed', + ol.renderer.webgl.VectorLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'dispatchEvent', + ol.renderer.webgl.VectorLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'getRevision', + ol.renderer.webgl.VectorLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'on', + ol.renderer.webgl.VectorLayer.prototype.on); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'once', + ol.renderer.webgl.VectorLayer.prototype.once); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'un', + ol.renderer.webgl.VectorLayer.prototype.un); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'unByKey', + ol.renderer.webgl.VectorLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'changed', + ol.renderer.canvas.Layer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'dispatchEvent', + ol.renderer.canvas.Layer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'getRevision', + ol.renderer.canvas.Layer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'on', + ol.renderer.canvas.Layer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'once', + ol.renderer.canvas.Layer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'un', + ol.renderer.canvas.Layer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'unByKey', + ol.renderer.canvas.Layer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'changed', + ol.renderer.canvas.IntermediateCanvas.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'dispatchEvent', + ol.renderer.canvas.IntermediateCanvas.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'getRevision', + ol.renderer.canvas.IntermediateCanvas.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'on', + ol.renderer.canvas.IntermediateCanvas.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'once', + ol.renderer.canvas.IntermediateCanvas.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'un', + ol.renderer.canvas.IntermediateCanvas.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'unByKey', + ol.renderer.canvas.IntermediateCanvas.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'changed', + ol.renderer.canvas.ImageLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'dispatchEvent', + ol.renderer.canvas.ImageLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'getRevision', + ol.renderer.canvas.ImageLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'on', + ol.renderer.canvas.ImageLayer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'once', + ol.renderer.canvas.ImageLayer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'un', + ol.renderer.canvas.ImageLayer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'unByKey', + ol.renderer.canvas.ImageLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'changed', + ol.renderer.canvas.TileLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'dispatchEvent', + ol.renderer.canvas.TileLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'getRevision', + ol.renderer.canvas.TileLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'on', + ol.renderer.canvas.TileLayer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'once', + ol.renderer.canvas.TileLayer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'un', + ol.renderer.canvas.TileLayer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'unByKey', + ol.renderer.canvas.TileLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'changed', + ol.renderer.canvas.VectorLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'dispatchEvent', + ol.renderer.canvas.VectorLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'getRevision', + ol.renderer.canvas.VectorLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'on', + ol.renderer.canvas.VectorLayer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'once', + ol.renderer.canvas.VectorLayer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'un', + ol.renderer.canvas.VectorLayer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'unByKey', + ol.renderer.canvas.VectorLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'changed', + ol.renderer.canvas.VectorTileLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'dispatchEvent', + ol.renderer.canvas.VectorTileLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'getRevision', + ol.renderer.canvas.VectorTileLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'on', + ol.renderer.canvas.VectorTileLayer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'once', + ol.renderer.canvas.VectorTileLayer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'un', + ol.renderer.canvas.VectorTileLayer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'unByKey', + ol.renderer.canvas.VectorTileLayer.prototype.unByKey); + +goog.exportProperty( + ol.render.Event.prototype, + 'type', + ol.render.Event.prototype.type); + +goog.exportProperty( + ol.render.Event.prototype, + 'target', + ol.render.Event.prototype.target); + +goog.exportProperty( + ol.render.Event.prototype, + 'preventDefault', + ol.render.Event.prototype.preventDefault); + +goog.exportProperty( + ol.render.Event.prototype, + 'stopPropagation', + ol.render.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.pointer.PointerEvent.prototype, + 'type', + ol.pointer.PointerEvent.prototype.type); + +goog.exportProperty( + ol.pointer.PointerEvent.prototype, + 'target', + ol.pointer.PointerEvent.prototype.target); + +goog.exportProperty( + ol.pointer.PointerEvent.prototype, + 'preventDefault', + ol.pointer.PointerEvent.prototype.preventDefault); + +goog.exportProperty( + ol.pointer.PointerEvent.prototype, + 'stopPropagation', + ol.pointer.PointerEvent.prototype.stopPropagation); + +goog.exportProperty( + ol.layer.Base.prototype, + 'get', + ol.layer.Base.prototype.get); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getKeys', + ol.layer.Base.prototype.getKeys); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getProperties', + ol.layer.Base.prototype.getProperties); + +goog.exportProperty( + ol.layer.Base.prototype, + 'set', + ol.layer.Base.prototype.set); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setProperties', + ol.layer.Base.prototype.setProperties); + +goog.exportProperty( + ol.layer.Base.prototype, + 'unset', + ol.layer.Base.prototype.unset); + +goog.exportProperty( + ol.layer.Base.prototype, + 'changed', + ol.layer.Base.prototype.changed); + +goog.exportProperty( + ol.layer.Base.prototype, + 'dispatchEvent', + ol.layer.Base.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getRevision', + ol.layer.Base.prototype.getRevision); + +goog.exportProperty( + ol.layer.Base.prototype, + 'on', + ol.layer.Base.prototype.on); + +goog.exportProperty( + ol.layer.Base.prototype, + 'once', + ol.layer.Base.prototype.once); + +goog.exportProperty( + ol.layer.Base.prototype, + 'un', + ol.layer.Base.prototype.un); + +goog.exportProperty( + ol.layer.Base.prototype, + 'unByKey', + ol.layer.Base.prototype.unByKey); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getExtent', + ol.layer.Group.prototype.getExtent); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getMaxResolution', + ol.layer.Group.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getMinResolution', + ol.layer.Group.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getOpacity', + ol.layer.Group.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getVisible', + ol.layer.Group.prototype.getVisible); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getZIndex', + ol.layer.Group.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setExtent', + ol.layer.Group.prototype.setExtent); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setMaxResolution', + ol.layer.Group.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setMinResolution', + ol.layer.Group.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setOpacity', + ol.layer.Group.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setVisible', + ol.layer.Group.prototype.setVisible); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setZIndex', + ol.layer.Group.prototype.setZIndex); + +goog.exportProperty( + ol.layer.Group.prototype, + 'get', + ol.layer.Group.prototype.get); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getKeys', + ol.layer.Group.prototype.getKeys); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getProperties', + ol.layer.Group.prototype.getProperties); + +goog.exportProperty( + ol.layer.Group.prototype, + 'set', + ol.layer.Group.prototype.set); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setProperties', + ol.layer.Group.prototype.setProperties); + +goog.exportProperty( + ol.layer.Group.prototype, + 'unset', + ol.layer.Group.prototype.unset); + +goog.exportProperty( + ol.layer.Group.prototype, + 'changed', + ol.layer.Group.prototype.changed); + +goog.exportProperty( + ol.layer.Group.prototype, + 'dispatchEvent', + ol.layer.Group.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getRevision', + ol.layer.Group.prototype.getRevision); + +goog.exportProperty( + ol.layer.Group.prototype, + 'on', + ol.layer.Group.prototype.on); + +goog.exportProperty( + ol.layer.Group.prototype, + 'once', + ol.layer.Group.prototype.once); + +goog.exportProperty( + ol.layer.Group.prototype, + 'un', + ol.layer.Group.prototype.un); + +goog.exportProperty( + ol.layer.Group.prototype, + 'unByKey', + ol.layer.Group.prototype.unByKey); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getExtent', + ol.layer.Layer.prototype.getExtent); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getMaxResolution', + ol.layer.Layer.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getMinResolution', + ol.layer.Layer.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getOpacity', + ol.layer.Layer.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getVisible', + ol.layer.Layer.prototype.getVisible); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getZIndex', + ol.layer.Layer.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setExtent', + ol.layer.Layer.prototype.setExtent); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setMaxResolution', + ol.layer.Layer.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setMinResolution', + ol.layer.Layer.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setOpacity', + ol.layer.Layer.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setVisible', + ol.layer.Layer.prototype.setVisible); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setZIndex', + ol.layer.Layer.prototype.setZIndex); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'get', + ol.layer.Layer.prototype.get); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getKeys', + ol.layer.Layer.prototype.getKeys); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getProperties', + ol.layer.Layer.prototype.getProperties); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'set', + ol.layer.Layer.prototype.set); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setProperties', + ol.layer.Layer.prototype.setProperties); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'unset', + ol.layer.Layer.prototype.unset); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'changed', + ol.layer.Layer.prototype.changed); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'dispatchEvent', + ol.layer.Layer.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getRevision', + ol.layer.Layer.prototype.getRevision); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'on', + ol.layer.Layer.prototype.on); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'once', + ol.layer.Layer.prototype.once); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'un', + ol.layer.Layer.prototype.un); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'unByKey', + ol.layer.Layer.prototype.unByKey); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setMap', + ol.layer.Vector.prototype.setMap); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setSource', + ol.layer.Vector.prototype.setSource); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getExtent', + ol.layer.Vector.prototype.getExtent); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getMaxResolution', + ol.layer.Vector.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getMinResolution', + ol.layer.Vector.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getOpacity', + ol.layer.Vector.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getVisible', + ol.layer.Vector.prototype.getVisible); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getZIndex', + ol.layer.Vector.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setExtent', + ol.layer.Vector.prototype.setExtent); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setMaxResolution', + ol.layer.Vector.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setMinResolution', + ol.layer.Vector.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setOpacity', + ol.layer.Vector.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setVisible', + ol.layer.Vector.prototype.setVisible); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setZIndex', + ol.layer.Vector.prototype.setZIndex); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'get', + ol.layer.Vector.prototype.get); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getKeys', + ol.layer.Vector.prototype.getKeys); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getProperties', + ol.layer.Vector.prototype.getProperties); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'set', + ol.layer.Vector.prototype.set); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setProperties', + ol.layer.Vector.prototype.setProperties); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'unset', + ol.layer.Vector.prototype.unset); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'changed', + ol.layer.Vector.prototype.changed); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'dispatchEvent', + ol.layer.Vector.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getRevision', + ol.layer.Vector.prototype.getRevision); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'on', + ol.layer.Vector.prototype.on); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'once', + ol.layer.Vector.prototype.once); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'un', + ol.layer.Vector.prototype.un); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'unByKey', + ol.layer.Vector.prototype.unByKey); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getSource', + ol.layer.Heatmap.prototype.getSource); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getStyle', + ol.layer.Heatmap.prototype.getStyle); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getStyleFunction', + ol.layer.Heatmap.prototype.getStyleFunction); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setStyle', + ol.layer.Heatmap.prototype.setStyle); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setMap', + ol.layer.Heatmap.prototype.setMap); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setSource', + ol.layer.Heatmap.prototype.setSource); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getExtent', + ol.layer.Heatmap.prototype.getExtent); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getMaxResolution', + ol.layer.Heatmap.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getMinResolution', + ol.layer.Heatmap.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getOpacity', + ol.layer.Heatmap.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getVisible', + ol.layer.Heatmap.prototype.getVisible); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getZIndex', + ol.layer.Heatmap.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setExtent', + ol.layer.Heatmap.prototype.setExtent); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setMaxResolution', + ol.layer.Heatmap.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setMinResolution', + ol.layer.Heatmap.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setOpacity', + ol.layer.Heatmap.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setVisible', + ol.layer.Heatmap.prototype.setVisible); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setZIndex', + ol.layer.Heatmap.prototype.setZIndex); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'get', + ol.layer.Heatmap.prototype.get); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getKeys', + ol.layer.Heatmap.prototype.getKeys); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getProperties', + ol.layer.Heatmap.prototype.getProperties); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'set', + ol.layer.Heatmap.prototype.set); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setProperties', + ol.layer.Heatmap.prototype.setProperties); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'unset', + ol.layer.Heatmap.prototype.unset); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'changed', + ol.layer.Heatmap.prototype.changed); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'dispatchEvent', + ol.layer.Heatmap.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getRevision', + ol.layer.Heatmap.prototype.getRevision); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'on', + ol.layer.Heatmap.prototype.on); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'once', + ol.layer.Heatmap.prototype.once); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'un', + ol.layer.Heatmap.prototype.un); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'unByKey', + ol.layer.Heatmap.prototype.unByKey); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setMap', + ol.layer.Image.prototype.setMap); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setSource', + ol.layer.Image.prototype.setSource); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getExtent', + ol.layer.Image.prototype.getExtent); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getMaxResolution', + ol.layer.Image.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getMinResolution', + ol.layer.Image.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getOpacity', + ol.layer.Image.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getVisible', + ol.layer.Image.prototype.getVisible); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getZIndex', + ol.layer.Image.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setExtent', + ol.layer.Image.prototype.setExtent); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setMaxResolution', + ol.layer.Image.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setMinResolution', + ol.layer.Image.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setOpacity', + ol.layer.Image.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setVisible', + ol.layer.Image.prototype.setVisible); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setZIndex', + ol.layer.Image.prototype.setZIndex); + +goog.exportProperty( + ol.layer.Image.prototype, + 'get', + ol.layer.Image.prototype.get); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getKeys', + ol.layer.Image.prototype.getKeys); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getProperties', + ol.layer.Image.prototype.getProperties); + +goog.exportProperty( + ol.layer.Image.prototype, + 'set', + ol.layer.Image.prototype.set); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setProperties', + ol.layer.Image.prototype.setProperties); + +goog.exportProperty( + ol.layer.Image.prototype, + 'unset', + ol.layer.Image.prototype.unset); + +goog.exportProperty( + ol.layer.Image.prototype, + 'changed', + ol.layer.Image.prototype.changed); + +goog.exportProperty( + ol.layer.Image.prototype, + 'dispatchEvent', + ol.layer.Image.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getRevision', + ol.layer.Image.prototype.getRevision); + +goog.exportProperty( + ol.layer.Image.prototype, + 'on', + ol.layer.Image.prototype.on); + +goog.exportProperty( + ol.layer.Image.prototype, + 'once', + ol.layer.Image.prototype.once); + +goog.exportProperty( + ol.layer.Image.prototype, + 'un', + ol.layer.Image.prototype.un); + +goog.exportProperty( + ol.layer.Image.prototype, + 'unByKey', + ol.layer.Image.prototype.unByKey); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setMap', + ol.layer.Tile.prototype.setMap); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setSource', + ol.layer.Tile.prototype.setSource); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getExtent', + ol.layer.Tile.prototype.getExtent); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getMaxResolution', + ol.layer.Tile.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getMinResolution', + ol.layer.Tile.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getOpacity', + ol.layer.Tile.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getVisible', + ol.layer.Tile.prototype.getVisible); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getZIndex', + ol.layer.Tile.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setExtent', + ol.layer.Tile.prototype.setExtent); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setMaxResolution', + ol.layer.Tile.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setMinResolution', + ol.layer.Tile.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setOpacity', + ol.layer.Tile.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setVisible', + ol.layer.Tile.prototype.setVisible); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setZIndex', + ol.layer.Tile.prototype.setZIndex); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'get', + ol.layer.Tile.prototype.get); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getKeys', + ol.layer.Tile.prototype.getKeys); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getProperties', + ol.layer.Tile.prototype.getProperties); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'set', + ol.layer.Tile.prototype.set); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setProperties', + ol.layer.Tile.prototype.setProperties); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'unset', + ol.layer.Tile.prototype.unset); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'changed', + ol.layer.Tile.prototype.changed); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'dispatchEvent', + ol.layer.Tile.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getRevision', + ol.layer.Tile.prototype.getRevision); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'on', + ol.layer.Tile.prototype.on); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'once', + ol.layer.Tile.prototype.once); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'un', + ol.layer.Tile.prototype.un); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'unByKey', + ol.layer.Tile.prototype.unByKey); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getSource', + ol.layer.VectorTile.prototype.getSource); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getStyle', + ol.layer.VectorTile.prototype.getStyle); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getStyleFunction', + ol.layer.VectorTile.prototype.getStyleFunction); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setStyle', + ol.layer.VectorTile.prototype.setStyle); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setMap', + ol.layer.VectorTile.prototype.setMap); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setSource', + ol.layer.VectorTile.prototype.setSource); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getExtent', + ol.layer.VectorTile.prototype.getExtent); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getMaxResolution', + ol.layer.VectorTile.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getMinResolution', + ol.layer.VectorTile.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getOpacity', + ol.layer.VectorTile.prototype.getOpacity); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getVisible', + ol.layer.VectorTile.prototype.getVisible); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getZIndex', + ol.layer.VectorTile.prototype.getZIndex); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setExtent', + ol.layer.VectorTile.prototype.setExtent); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setMaxResolution', + ol.layer.VectorTile.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setMinResolution', + ol.layer.VectorTile.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setOpacity', + ol.layer.VectorTile.prototype.setOpacity); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setVisible', + ol.layer.VectorTile.prototype.setVisible); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setZIndex', + ol.layer.VectorTile.prototype.setZIndex); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'get', + ol.layer.VectorTile.prototype.get); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getKeys', + ol.layer.VectorTile.prototype.getKeys); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getProperties', + ol.layer.VectorTile.prototype.getProperties); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'set', + ol.layer.VectorTile.prototype.set); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setProperties', + ol.layer.VectorTile.prototype.setProperties); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'unset', + ol.layer.VectorTile.prototype.unset); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'changed', + ol.layer.VectorTile.prototype.changed); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'dispatchEvent', + ol.layer.VectorTile.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getRevision', + ol.layer.VectorTile.prototype.getRevision); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'on', + ol.layer.VectorTile.prototype.on); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'once', + ol.layer.VectorTile.prototype.once); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'un', + ol.layer.VectorTile.prototype.un); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'unByKey', + ol.layer.VectorTile.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'get', + ol.interaction.Interaction.prototype.get); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'getKeys', + ol.interaction.Interaction.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'getProperties', + ol.interaction.Interaction.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'set', + ol.interaction.Interaction.prototype.set); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'setProperties', + ol.interaction.Interaction.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'unset', + ol.interaction.Interaction.prototype.unset); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'changed', + ol.interaction.Interaction.prototype.changed); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'dispatchEvent', + ol.interaction.Interaction.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'getRevision', + ol.interaction.Interaction.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'on', + ol.interaction.Interaction.prototype.on); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'once', + ol.interaction.Interaction.prototype.once); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'un', + ol.interaction.Interaction.prototype.un); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'unByKey', + ol.interaction.Interaction.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'getActive', + ol.interaction.DoubleClickZoom.prototype.getActive); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'getMap', + ol.interaction.DoubleClickZoom.prototype.getMap); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'setActive', + ol.interaction.DoubleClickZoom.prototype.setActive); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'get', + ol.interaction.DoubleClickZoom.prototype.get); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'getKeys', + ol.interaction.DoubleClickZoom.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'getProperties', + ol.interaction.DoubleClickZoom.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'set', + ol.interaction.DoubleClickZoom.prototype.set); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'setProperties', + ol.interaction.DoubleClickZoom.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'unset', + ol.interaction.DoubleClickZoom.prototype.unset); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'changed', + ol.interaction.DoubleClickZoom.prototype.changed); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'dispatchEvent', + ol.interaction.DoubleClickZoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'getRevision', + ol.interaction.DoubleClickZoom.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'on', + ol.interaction.DoubleClickZoom.prototype.on); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'once', + ol.interaction.DoubleClickZoom.prototype.once); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'un', + ol.interaction.DoubleClickZoom.prototype.un); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'unByKey', + ol.interaction.DoubleClickZoom.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'getActive', + ol.interaction.DragAndDrop.prototype.getActive); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'getMap', + ol.interaction.DragAndDrop.prototype.getMap); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'setActive', + ol.interaction.DragAndDrop.prototype.setActive); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'get', + ol.interaction.DragAndDrop.prototype.get); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'getKeys', + ol.interaction.DragAndDrop.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'getProperties', + ol.interaction.DragAndDrop.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'set', + ol.interaction.DragAndDrop.prototype.set); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'setProperties', + ol.interaction.DragAndDrop.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'unset', + ol.interaction.DragAndDrop.prototype.unset); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'changed', + ol.interaction.DragAndDrop.prototype.changed); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'dispatchEvent', + ol.interaction.DragAndDrop.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'getRevision', + ol.interaction.DragAndDrop.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'on', + ol.interaction.DragAndDrop.prototype.on); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'once', + ol.interaction.DragAndDrop.prototype.once); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'un', + ol.interaction.DragAndDrop.prototype.un); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'unByKey', + ol.interaction.DragAndDrop.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'type', + ol.interaction.DragAndDrop.Event.prototype.type); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'target', + ol.interaction.DragAndDrop.Event.prototype.target); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'preventDefault', + ol.interaction.DragAndDrop.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'stopPropagation', + ol.interaction.DragAndDrop.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'getActive', + ol.interaction.Pointer.prototype.getActive); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'getMap', + ol.interaction.Pointer.prototype.getMap); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'setActive', + ol.interaction.Pointer.prototype.setActive); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'get', + ol.interaction.Pointer.prototype.get); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'getKeys', + ol.interaction.Pointer.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'getProperties', + ol.interaction.Pointer.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'set', + ol.interaction.Pointer.prototype.set); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'setProperties', + ol.interaction.Pointer.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'unset', + ol.interaction.Pointer.prototype.unset); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'changed', + ol.interaction.Pointer.prototype.changed); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'dispatchEvent', + ol.interaction.Pointer.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'getRevision', + ol.interaction.Pointer.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'on', + ol.interaction.Pointer.prototype.on); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'once', + ol.interaction.Pointer.prototype.once); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'un', + ol.interaction.Pointer.prototype.un); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'unByKey', + ol.interaction.Pointer.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'getActive', + ol.interaction.DragBox.prototype.getActive); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'getMap', + ol.interaction.DragBox.prototype.getMap); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'setActive', + ol.interaction.DragBox.prototype.setActive); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'get', + ol.interaction.DragBox.prototype.get); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'getKeys', + ol.interaction.DragBox.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'getProperties', + ol.interaction.DragBox.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'set', + ol.interaction.DragBox.prototype.set); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'setProperties', + ol.interaction.DragBox.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'unset', + ol.interaction.DragBox.prototype.unset); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'changed', + ol.interaction.DragBox.prototype.changed); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'dispatchEvent', + ol.interaction.DragBox.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'getRevision', + ol.interaction.DragBox.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'on', + ol.interaction.DragBox.prototype.on); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'once', + ol.interaction.DragBox.prototype.once); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'un', + ol.interaction.DragBox.prototype.un); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'unByKey', + ol.interaction.DragBox.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragBox.Event.prototype, + 'type', + ol.interaction.DragBox.Event.prototype.type); + +goog.exportProperty( + ol.interaction.DragBox.Event.prototype, + 'target', + ol.interaction.DragBox.Event.prototype.target); + +goog.exportProperty( + ol.interaction.DragBox.Event.prototype, + 'preventDefault', + ol.interaction.DragBox.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.DragBox.Event.prototype, + 'stopPropagation', + ol.interaction.DragBox.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'getActive', + ol.interaction.DragPan.prototype.getActive); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'getMap', + ol.interaction.DragPan.prototype.getMap); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'setActive', + ol.interaction.DragPan.prototype.setActive); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'get', + ol.interaction.DragPan.prototype.get); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'getKeys', + ol.interaction.DragPan.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'getProperties', + ol.interaction.DragPan.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'set', + ol.interaction.DragPan.prototype.set); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'setProperties', + ol.interaction.DragPan.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'unset', + ol.interaction.DragPan.prototype.unset); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'changed', + ol.interaction.DragPan.prototype.changed); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'dispatchEvent', + ol.interaction.DragPan.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'getRevision', + ol.interaction.DragPan.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'on', + ol.interaction.DragPan.prototype.on); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'once', + ol.interaction.DragPan.prototype.once); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'un', + ol.interaction.DragPan.prototype.un); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'unByKey', + ol.interaction.DragPan.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'getActive', + ol.interaction.DragRotate.prototype.getActive); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'getMap', + ol.interaction.DragRotate.prototype.getMap); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'setActive', + ol.interaction.DragRotate.prototype.setActive); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'get', + ol.interaction.DragRotate.prototype.get); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'getKeys', + ol.interaction.DragRotate.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'getProperties', + ol.interaction.DragRotate.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'set', + ol.interaction.DragRotate.prototype.set); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'setProperties', + ol.interaction.DragRotate.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'unset', + ol.interaction.DragRotate.prototype.unset); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'changed', + ol.interaction.DragRotate.prototype.changed); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'dispatchEvent', + ol.interaction.DragRotate.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'getRevision', + ol.interaction.DragRotate.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'on', + ol.interaction.DragRotate.prototype.on); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'once', + ol.interaction.DragRotate.prototype.once); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'un', + ol.interaction.DragRotate.prototype.un); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'unByKey', + ol.interaction.DragRotate.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'getActive', + ol.interaction.DragRotateAndZoom.prototype.getActive); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'getMap', + ol.interaction.DragRotateAndZoom.prototype.getMap); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'setActive', + ol.interaction.DragRotateAndZoom.prototype.setActive); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'get', + ol.interaction.DragRotateAndZoom.prototype.get); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'getKeys', + ol.interaction.DragRotateAndZoom.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'getProperties', + ol.interaction.DragRotateAndZoom.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'set', + ol.interaction.DragRotateAndZoom.prototype.set); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'setProperties', + ol.interaction.DragRotateAndZoom.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'unset', + ol.interaction.DragRotateAndZoom.prototype.unset); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'changed', + ol.interaction.DragRotateAndZoom.prototype.changed); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'dispatchEvent', + ol.interaction.DragRotateAndZoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'getRevision', + ol.interaction.DragRotateAndZoom.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'on', + ol.interaction.DragRotateAndZoom.prototype.on); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'once', + ol.interaction.DragRotateAndZoom.prototype.once); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'un', + ol.interaction.DragRotateAndZoom.prototype.un); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'unByKey', + ol.interaction.DragRotateAndZoom.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'getGeometry', + ol.interaction.DragZoom.prototype.getGeometry); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'getActive', + ol.interaction.DragZoom.prototype.getActive); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'getMap', + ol.interaction.DragZoom.prototype.getMap); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'setActive', + ol.interaction.DragZoom.prototype.setActive); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'get', + ol.interaction.DragZoom.prototype.get); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'getKeys', + ol.interaction.DragZoom.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'getProperties', + ol.interaction.DragZoom.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'set', + ol.interaction.DragZoom.prototype.set); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'setProperties', + ol.interaction.DragZoom.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'unset', + ol.interaction.DragZoom.prototype.unset); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'changed', + ol.interaction.DragZoom.prototype.changed); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'dispatchEvent', + ol.interaction.DragZoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'getRevision', + ol.interaction.DragZoom.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'on', + ol.interaction.DragZoom.prototype.on); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'once', + ol.interaction.DragZoom.prototype.once); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'un', + ol.interaction.DragZoom.prototype.un); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'unByKey', + ol.interaction.DragZoom.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'getActive', + ol.interaction.Draw.prototype.getActive); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'getMap', + ol.interaction.Draw.prototype.getMap); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'setActive', + ol.interaction.Draw.prototype.setActive); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'get', + ol.interaction.Draw.prototype.get); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'getKeys', + ol.interaction.Draw.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'getProperties', + ol.interaction.Draw.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'set', + ol.interaction.Draw.prototype.set); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'setProperties', + ol.interaction.Draw.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'unset', + ol.interaction.Draw.prototype.unset); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'changed', + ol.interaction.Draw.prototype.changed); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'dispatchEvent', + ol.interaction.Draw.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'getRevision', + ol.interaction.Draw.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'on', + ol.interaction.Draw.prototype.on); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'once', + ol.interaction.Draw.prototype.once); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'un', + ol.interaction.Draw.prototype.un); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'unByKey', + ol.interaction.Draw.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Draw.Event.prototype, + 'type', + ol.interaction.Draw.Event.prototype.type); + +goog.exportProperty( + ol.interaction.Draw.Event.prototype, + 'target', + ol.interaction.Draw.Event.prototype.target); + +goog.exportProperty( + ol.interaction.Draw.Event.prototype, + 'preventDefault', + ol.interaction.Draw.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.Draw.Event.prototype, + 'stopPropagation', + ol.interaction.Draw.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'getActive', + ol.interaction.Extent.prototype.getActive); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'getMap', + ol.interaction.Extent.prototype.getMap); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'setActive', + ol.interaction.Extent.prototype.setActive); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'get', + ol.interaction.Extent.prototype.get); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'getKeys', + ol.interaction.Extent.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'getProperties', + ol.interaction.Extent.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'set', + ol.interaction.Extent.prototype.set); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'setProperties', + ol.interaction.Extent.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'unset', + ol.interaction.Extent.prototype.unset); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'changed', + ol.interaction.Extent.prototype.changed); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'dispatchEvent', + ol.interaction.Extent.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'getRevision', + ol.interaction.Extent.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'on', + ol.interaction.Extent.prototype.on); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'once', + ol.interaction.Extent.prototype.once); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'un', + ol.interaction.Extent.prototype.un); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'unByKey', + ol.interaction.Extent.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Extent.Event.prototype, + 'type', + ol.interaction.Extent.Event.prototype.type); + +goog.exportProperty( + ol.interaction.Extent.Event.prototype, + 'target', + ol.interaction.Extent.Event.prototype.target); + +goog.exportProperty( + ol.interaction.Extent.Event.prototype, + 'preventDefault', + ol.interaction.Extent.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.Extent.Event.prototype, + 'stopPropagation', + ol.interaction.Extent.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'getActive', + ol.interaction.KeyboardPan.prototype.getActive); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'getMap', + ol.interaction.KeyboardPan.prototype.getMap); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'setActive', + ol.interaction.KeyboardPan.prototype.setActive); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'get', + ol.interaction.KeyboardPan.prototype.get); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'getKeys', + ol.interaction.KeyboardPan.prototype.getKeys); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'getProperties', + ol.interaction.KeyboardPan.prototype.getProperties); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'set', + ol.interaction.KeyboardPan.prototype.set); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'setProperties', + ol.interaction.KeyboardPan.prototype.setProperties); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'unset', + ol.interaction.KeyboardPan.prototype.unset); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'changed', + ol.interaction.KeyboardPan.prototype.changed); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'dispatchEvent', + ol.interaction.KeyboardPan.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'getRevision', + ol.interaction.KeyboardPan.prototype.getRevision); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'on', + ol.interaction.KeyboardPan.prototype.on); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'once', + ol.interaction.KeyboardPan.prototype.once); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'un', + ol.interaction.KeyboardPan.prototype.un); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'unByKey', + ol.interaction.KeyboardPan.prototype.unByKey); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'getActive', + ol.interaction.KeyboardZoom.prototype.getActive); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'getMap', + ol.interaction.KeyboardZoom.prototype.getMap); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'setActive', + ol.interaction.KeyboardZoom.prototype.setActive); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'get', + ol.interaction.KeyboardZoom.prototype.get); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'getKeys', + ol.interaction.KeyboardZoom.prototype.getKeys); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'getProperties', + ol.interaction.KeyboardZoom.prototype.getProperties); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'set', + ol.interaction.KeyboardZoom.prototype.set); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'setProperties', + ol.interaction.KeyboardZoom.prototype.setProperties); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'unset', + ol.interaction.KeyboardZoom.prototype.unset); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'changed', + ol.interaction.KeyboardZoom.prototype.changed); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'dispatchEvent', + ol.interaction.KeyboardZoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'getRevision', + ol.interaction.KeyboardZoom.prototype.getRevision); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'on', + ol.interaction.KeyboardZoom.prototype.on); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'once', + ol.interaction.KeyboardZoom.prototype.once); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'un', + ol.interaction.KeyboardZoom.prototype.un); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'unByKey', + ol.interaction.KeyboardZoom.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'getActive', + ol.interaction.Modify.prototype.getActive); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'getMap', + ol.interaction.Modify.prototype.getMap); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'setActive', + ol.interaction.Modify.prototype.setActive); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'get', + ol.interaction.Modify.prototype.get); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'getKeys', + ol.interaction.Modify.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'getProperties', + ol.interaction.Modify.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'set', + ol.interaction.Modify.prototype.set); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'setProperties', + ol.interaction.Modify.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'unset', + ol.interaction.Modify.prototype.unset); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'changed', + ol.interaction.Modify.prototype.changed); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'dispatchEvent', + ol.interaction.Modify.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'getRevision', + ol.interaction.Modify.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'on', + ol.interaction.Modify.prototype.on); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'once', + ol.interaction.Modify.prototype.once); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'un', + ol.interaction.Modify.prototype.un); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'unByKey', + ol.interaction.Modify.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Modify.Event.prototype, + 'type', + ol.interaction.Modify.Event.prototype.type); + +goog.exportProperty( + ol.interaction.Modify.Event.prototype, + 'target', + ol.interaction.Modify.Event.prototype.target); + +goog.exportProperty( + ol.interaction.Modify.Event.prototype, + 'preventDefault', + ol.interaction.Modify.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.Modify.Event.prototype, + 'stopPropagation', + ol.interaction.Modify.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'getActive', + ol.interaction.MouseWheelZoom.prototype.getActive); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'getMap', + ol.interaction.MouseWheelZoom.prototype.getMap); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'setActive', + ol.interaction.MouseWheelZoom.prototype.setActive); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'get', + ol.interaction.MouseWheelZoom.prototype.get); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'getKeys', + ol.interaction.MouseWheelZoom.prototype.getKeys); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'getProperties', + ol.interaction.MouseWheelZoom.prototype.getProperties); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'set', + ol.interaction.MouseWheelZoom.prototype.set); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'setProperties', + ol.interaction.MouseWheelZoom.prototype.setProperties); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'unset', + ol.interaction.MouseWheelZoom.prototype.unset); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'changed', + ol.interaction.MouseWheelZoom.prototype.changed); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'dispatchEvent', + ol.interaction.MouseWheelZoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'getRevision', + ol.interaction.MouseWheelZoom.prototype.getRevision); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'on', + ol.interaction.MouseWheelZoom.prototype.on); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'once', + ol.interaction.MouseWheelZoom.prototype.once); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'un', + ol.interaction.MouseWheelZoom.prototype.un); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'unByKey', + ol.interaction.MouseWheelZoom.prototype.unByKey); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'getActive', + ol.interaction.PinchRotate.prototype.getActive); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'getMap', + ol.interaction.PinchRotate.prototype.getMap); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'setActive', + ol.interaction.PinchRotate.prototype.setActive); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'get', + ol.interaction.PinchRotate.prototype.get); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'getKeys', + ol.interaction.PinchRotate.prototype.getKeys); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'getProperties', + ol.interaction.PinchRotate.prototype.getProperties); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'set', + ol.interaction.PinchRotate.prototype.set); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'setProperties', + ol.interaction.PinchRotate.prototype.setProperties); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'unset', + ol.interaction.PinchRotate.prototype.unset); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'changed', + ol.interaction.PinchRotate.prototype.changed); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'dispatchEvent', + ol.interaction.PinchRotate.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'getRevision', + ol.interaction.PinchRotate.prototype.getRevision); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'on', + ol.interaction.PinchRotate.prototype.on); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'once', + ol.interaction.PinchRotate.prototype.once); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'un', + ol.interaction.PinchRotate.prototype.un); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'unByKey', + ol.interaction.PinchRotate.prototype.unByKey); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'getActive', + ol.interaction.PinchZoom.prototype.getActive); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'getMap', + ol.interaction.PinchZoom.prototype.getMap); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'setActive', + ol.interaction.PinchZoom.prototype.setActive); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'get', + ol.interaction.PinchZoom.prototype.get); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'getKeys', + ol.interaction.PinchZoom.prototype.getKeys); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'getProperties', + ol.interaction.PinchZoom.prototype.getProperties); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'set', + ol.interaction.PinchZoom.prototype.set); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'setProperties', + ol.interaction.PinchZoom.prototype.setProperties); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'unset', + ol.interaction.PinchZoom.prototype.unset); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'changed', + ol.interaction.PinchZoom.prototype.changed); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'dispatchEvent', + ol.interaction.PinchZoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'getRevision', + ol.interaction.PinchZoom.prototype.getRevision); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'on', + ol.interaction.PinchZoom.prototype.on); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'once', + ol.interaction.PinchZoom.prototype.once); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'un', + ol.interaction.PinchZoom.prototype.un); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'unByKey', + ol.interaction.PinchZoom.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getActive', + ol.interaction.Select.prototype.getActive); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getMap', + ol.interaction.Select.prototype.getMap); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'setActive', + ol.interaction.Select.prototype.setActive); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'get', + ol.interaction.Select.prototype.get); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getKeys', + ol.interaction.Select.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getProperties', + ol.interaction.Select.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'set', + ol.interaction.Select.prototype.set); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'setProperties', + ol.interaction.Select.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'unset', + ol.interaction.Select.prototype.unset); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'changed', + ol.interaction.Select.prototype.changed); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'dispatchEvent', + ol.interaction.Select.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getRevision', + ol.interaction.Select.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'on', + ol.interaction.Select.prototype.on); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'once', + ol.interaction.Select.prototype.once); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'un', + ol.interaction.Select.prototype.un); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'unByKey', + ol.interaction.Select.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'type', + ol.interaction.Select.Event.prototype.type); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'target', + ol.interaction.Select.Event.prototype.target); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'preventDefault', + ol.interaction.Select.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'stopPropagation', + ol.interaction.Select.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getActive', + ol.interaction.Snap.prototype.getActive); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getMap', + ol.interaction.Snap.prototype.getMap); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'setActive', + ol.interaction.Snap.prototype.setActive); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'get', + ol.interaction.Snap.prototype.get); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getKeys', + ol.interaction.Snap.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getProperties', + ol.interaction.Snap.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'set', + ol.interaction.Snap.prototype.set); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'setProperties', + ol.interaction.Snap.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'unset', + ol.interaction.Snap.prototype.unset); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'changed', + ol.interaction.Snap.prototype.changed); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'dispatchEvent', + ol.interaction.Snap.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getRevision', + ol.interaction.Snap.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'on', + ol.interaction.Snap.prototype.on); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'once', + ol.interaction.Snap.prototype.once); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'un', + ol.interaction.Snap.prototype.un); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'unByKey', + ol.interaction.Snap.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'getActive', + ol.interaction.Translate.prototype.getActive); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'getMap', + ol.interaction.Translate.prototype.getMap); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'setActive', + ol.interaction.Translate.prototype.setActive); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'get', + ol.interaction.Translate.prototype.get); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'getKeys', + ol.interaction.Translate.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'getProperties', + ol.interaction.Translate.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'set', + ol.interaction.Translate.prototype.set); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'setProperties', + ol.interaction.Translate.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'unset', + ol.interaction.Translate.prototype.unset); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'changed', + ol.interaction.Translate.prototype.changed); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'dispatchEvent', + ol.interaction.Translate.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'getRevision', + ol.interaction.Translate.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'on', + ol.interaction.Translate.prototype.on); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'once', + ol.interaction.Translate.prototype.once); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'un', + ol.interaction.Translate.prototype.un); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'unByKey', + ol.interaction.Translate.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Translate.Event.prototype, + 'type', + ol.interaction.Translate.Event.prototype.type); + +goog.exportProperty( + ol.interaction.Translate.Event.prototype, + 'target', + ol.interaction.Translate.Event.prototype.target); + +goog.exportProperty( + ol.interaction.Translate.Event.prototype, + 'preventDefault', + ol.interaction.Translate.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.Translate.Event.prototype, + 'stopPropagation', + ol.interaction.Translate.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'get', + ol.geom.Geometry.prototype.get); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'getKeys', + ol.geom.Geometry.prototype.getKeys); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'getProperties', + ol.geom.Geometry.prototype.getProperties); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'set', + ol.geom.Geometry.prototype.set); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'setProperties', + ol.geom.Geometry.prototype.setProperties); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'unset', + ol.geom.Geometry.prototype.unset); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'changed', + ol.geom.Geometry.prototype.changed); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'dispatchEvent', + ol.geom.Geometry.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'getRevision', + ol.geom.Geometry.prototype.getRevision); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'on', + ol.geom.Geometry.prototype.on); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'once', + ol.geom.Geometry.prototype.once); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'un', + ol.geom.Geometry.prototype.un); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'unByKey', + ol.geom.Geometry.prototype.unByKey); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getClosestPoint', + ol.geom.SimpleGeometry.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'intersectsCoordinate', + ol.geom.SimpleGeometry.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getExtent', + ol.geom.SimpleGeometry.prototype.getExtent); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'rotate', + ol.geom.SimpleGeometry.prototype.rotate); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'scale', + ol.geom.SimpleGeometry.prototype.scale); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'simplify', + ol.geom.SimpleGeometry.prototype.simplify); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'transform', + ol.geom.SimpleGeometry.prototype.transform); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'get', + ol.geom.SimpleGeometry.prototype.get); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getKeys', + ol.geom.SimpleGeometry.prototype.getKeys); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getProperties', + ol.geom.SimpleGeometry.prototype.getProperties); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'set', + ol.geom.SimpleGeometry.prototype.set); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'setProperties', + ol.geom.SimpleGeometry.prototype.setProperties); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'unset', + ol.geom.SimpleGeometry.prototype.unset); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'changed', + ol.geom.SimpleGeometry.prototype.changed); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'dispatchEvent', + ol.geom.SimpleGeometry.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getRevision', + ol.geom.SimpleGeometry.prototype.getRevision); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'on', + ol.geom.SimpleGeometry.prototype.on); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'once', + ol.geom.SimpleGeometry.prototype.once); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'un', + ol.geom.SimpleGeometry.prototype.un); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'unByKey', + ol.geom.SimpleGeometry.prototype.unByKey); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getFirstCoordinate', + ol.geom.Circle.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getLastCoordinate', + ol.geom.Circle.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getLayout', + ol.geom.Circle.prototype.getLayout); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'rotate', + ol.geom.Circle.prototype.rotate); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'scale', + ol.geom.Circle.prototype.scale); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getClosestPoint', + ol.geom.Circle.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'intersectsCoordinate', + ol.geom.Circle.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getExtent', + ol.geom.Circle.prototype.getExtent); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'simplify', + ol.geom.Circle.prototype.simplify); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'get', + ol.geom.Circle.prototype.get); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getKeys', + ol.geom.Circle.prototype.getKeys); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getProperties', + ol.geom.Circle.prototype.getProperties); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'set', + ol.geom.Circle.prototype.set); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'setProperties', + ol.geom.Circle.prototype.setProperties); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'unset', + ol.geom.Circle.prototype.unset); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'changed', + ol.geom.Circle.prototype.changed); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'dispatchEvent', + ol.geom.Circle.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getRevision', + ol.geom.Circle.prototype.getRevision); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'on', + ol.geom.Circle.prototype.on); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'once', + ol.geom.Circle.prototype.once); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'un', + ol.geom.Circle.prototype.un); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'unByKey', + ol.geom.Circle.prototype.unByKey); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getClosestPoint', + ol.geom.GeometryCollection.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'intersectsCoordinate', + ol.geom.GeometryCollection.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getExtent', + ol.geom.GeometryCollection.prototype.getExtent); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'rotate', + ol.geom.GeometryCollection.prototype.rotate); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'scale', + ol.geom.GeometryCollection.prototype.scale); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'simplify', + ol.geom.GeometryCollection.prototype.simplify); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'transform', + ol.geom.GeometryCollection.prototype.transform); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'get', + ol.geom.GeometryCollection.prototype.get); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getKeys', + ol.geom.GeometryCollection.prototype.getKeys); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getProperties', + ol.geom.GeometryCollection.prototype.getProperties); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'set', + ol.geom.GeometryCollection.prototype.set); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'setProperties', + ol.geom.GeometryCollection.prototype.setProperties); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'unset', + ol.geom.GeometryCollection.prototype.unset); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'changed', + ol.geom.GeometryCollection.prototype.changed); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'dispatchEvent', + ol.geom.GeometryCollection.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getRevision', + ol.geom.GeometryCollection.prototype.getRevision); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'on', + ol.geom.GeometryCollection.prototype.on); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'once', + ol.geom.GeometryCollection.prototype.once); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'un', + ol.geom.GeometryCollection.prototype.un); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'unByKey', + ol.geom.GeometryCollection.prototype.unByKey); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getFirstCoordinate', + ol.geom.LinearRing.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getLastCoordinate', + ol.geom.LinearRing.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getLayout', + ol.geom.LinearRing.prototype.getLayout); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'rotate', + ol.geom.LinearRing.prototype.rotate); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'scale', + ol.geom.LinearRing.prototype.scale); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getClosestPoint', + ol.geom.LinearRing.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'intersectsCoordinate', + ol.geom.LinearRing.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getExtent', + ol.geom.LinearRing.prototype.getExtent); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'simplify', + ol.geom.LinearRing.prototype.simplify); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'transform', + ol.geom.LinearRing.prototype.transform); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'get', + ol.geom.LinearRing.prototype.get); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getKeys', + ol.geom.LinearRing.prototype.getKeys); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getProperties', + ol.geom.LinearRing.prototype.getProperties); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'set', + ol.geom.LinearRing.prototype.set); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'setProperties', + ol.geom.LinearRing.prototype.setProperties); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'unset', + ol.geom.LinearRing.prototype.unset); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'changed', + ol.geom.LinearRing.prototype.changed); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'dispatchEvent', + ol.geom.LinearRing.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getRevision', + ol.geom.LinearRing.prototype.getRevision); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'on', + ol.geom.LinearRing.prototype.on); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'once', + ol.geom.LinearRing.prototype.once); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'un', + ol.geom.LinearRing.prototype.un); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'unByKey', + ol.geom.LinearRing.prototype.unByKey); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getFirstCoordinate', + ol.geom.LineString.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getLastCoordinate', + ol.geom.LineString.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getLayout', + ol.geom.LineString.prototype.getLayout); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'rotate', + ol.geom.LineString.prototype.rotate); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'scale', + ol.geom.LineString.prototype.scale); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getClosestPoint', + ol.geom.LineString.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'intersectsCoordinate', + ol.geom.LineString.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getExtent', + ol.geom.LineString.prototype.getExtent); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'simplify', + ol.geom.LineString.prototype.simplify); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'transform', + ol.geom.LineString.prototype.transform); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'get', + ol.geom.LineString.prototype.get); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getKeys', + ol.geom.LineString.prototype.getKeys); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getProperties', + ol.geom.LineString.prototype.getProperties); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'set', + ol.geom.LineString.prototype.set); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'setProperties', + ol.geom.LineString.prototype.setProperties); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'unset', + ol.geom.LineString.prototype.unset); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'changed', + ol.geom.LineString.prototype.changed); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'dispatchEvent', + ol.geom.LineString.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getRevision', + ol.geom.LineString.prototype.getRevision); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'on', + ol.geom.LineString.prototype.on); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'once', + ol.geom.LineString.prototype.once); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'un', + ol.geom.LineString.prototype.un); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'unByKey', + ol.geom.LineString.prototype.unByKey); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getFirstCoordinate', + ol.geom.MultiLineString.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getLastCoordinate', + ol.geom.MultiLineString.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getLayout', + ol.geom.MultiLineString.prototype.getLayout); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'rotate', + ol.geom.MultiLineString.prototype.rotate); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'scale', + ol.geom.MultiLineString.prototype.scale); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getClosestPoint', + ol.geom.MultiLineString.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'intersectsCoordinate', + ol.geom.MultiLineString.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getExtent', + ol.geom.MultiLineString.prototype.getExtent); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'simplify', + ol.geom.MultiLineString.prototype.simplify); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'transform', + ol.geom.MultiLineString.prototype.transform); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'get', + ol.geom.MultiLineString.prototype.get); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getKeys', + ol.geom.MultiLineString.prototype.getKeys); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getProperties', + ol.geom.MultiLineString.prototype.getProperties); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'set', + ol.geom.MultiLineString.prototype.set); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'setProperties', + ol.geom.MultiLineString.prototype.setProperties); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'unset', + ol.geom.MultiLineString.prototype.unset); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'changed', + ol.geom.MultiLineString.prototype.changed); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'dispatchEvent', + ol.geom.MultiLineString.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getRevision', + ol.geom.MultiLineString.prototype.getRevision); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'on', + ol.geom.MultiLineString.prototype.on); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'once', + ol.geom.MultiLineString.prototype.once); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'un', + ol.geom.MultiLineString.prototype.un); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'unByKey', + ol.geom.MultiLineString.prototype.unByKey); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getFirstCoordinate', + ol.geom.MultiPoint.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getLastCoordinate', + ol.geom.MultiPoint.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getLayout', + ol.geom.MultiPoint.prototype.getLayout); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'rotate', + ol.geom.MultiPoint.prototype.rotate); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'scale', + ol.geom.MultiPoint.prototype.scale); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getClosestPoint', + ol.geom.MultiPoint.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'intersectsCoordinate', + ol.geom.MultiPoint.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getExtent', + ol.geom.MultiPoint.prototype.getExtent); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'simplify', + ol.geom.MultiPoint.prototype.simplify); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'transform', + ol.geom.MultiPoint.prototype.transform); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'get', + ol.geom.MultiPoint.prototype.get); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getKeys', + ol.geom.MultiPoint.prototype.getKeys); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getProperties', + ol.geom.MultiPoint.prototype.getProperties); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'set', + ol.geom.MultiPoint.prototype.set); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'setProperties', + ol.geom.MultiPoint.prototype.setProperties); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'unset', + ol.geom.MultiPoint.prototype.unset); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'changed', + ol.geom.MultiPoint.prototype.changed); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'dispatchEvent', + ol.geom.MultiPoint.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getRevision', + ol.geom.MultiPoint.prototype.getRevision); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'on', + ol.geom.MultiPoint.prototype.on); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'once', + ol.geom.MultiPoint.prototype.once); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'un', + ol.geom.MultiPoint.prototype.un); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'unByKey', + ol.geom.MultiPoint.prototype.unByKey); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getFirstCoordinate', + ol.geom.MultiPolygon.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getLastCoordinate', + ol.geom.MultiPolygon.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getLayout', + ol.geom.MultiPolygon.prototype.getLayout); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'rotate', + ol.geom.MultiPolygon.prototype.rotate); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'scale', + ol.geom.MultiPolygon.prototype.scale); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getClosestPoint', + ol.geom.MultiPolygon.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'intersectsCoordinate', + ol.geom.MultiPolygon.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getExtent', + ol.geom.MultiPolygon.prototype.getExtent); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'simplify', + ol.geom.MultiPolygon.prototype.simplify); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'transform', + ol.geom.MultiPolygon.prototype.transform); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'get', + ol.geom.MultiPolygon.prototype.get); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getKeys', + ol.geom.MultiPolygon.prototype.getKeys); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getProperties', + ol.geom.MultiPolygon.prototype.getProperties); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'set', + ol.geom.MultiPolygon.prototype.set); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'setProperties', + ol.geom.MultiPolygon.prototype.setProperties); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'unset', + ol.geom.MultiPolygon.prototype.unset); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'changed', + ol.geom.MultiPolygon.prototype.changed); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'dispatchEvent', + ol.geom.MultiPolygon.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getRevision', + ol.geom.MultiPolygon.prototype.getRevision); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'on', + ol.geom.MultiPolygon.prototype.on); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'once', + ol.geom.MultiPolygon.prototype.once); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'un', + ol.geom.MultiPolygon.prototype.un); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'unByKey', + ol.geom.MultiPolygon.prototype.unByKey); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getFirstCoordinate', + ol.geom.Point.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getLastCoordinate', + ol.geom.Point.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getLayout', + ol.geom.Point.prototype.getLayout); + +goog.exportProperty( + ol.geom.Point.prototype, + 'rotate', + ol.geom.Point.prototype.rotate); + +goog.exportProperty( + ol.geom.Point.prototype, + 'scale', + ol.geom.Point.prototype.scale); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getClosestPoint', + ol.geom.Point.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.Point.prototype, + 'intersectsCoordinate', + ol.geom.Point.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getExtent', + ol.geom.Point.prototype.getExtent); + +goog.exportProperty( + ol.geom.Point.prototype, + 'simplify', + ol.geom.Point.prototype.simplify); + +goog.exportProperty( + ol.geom.Point.prototype, + 'transform', + ol.geom.Point.prototype.transform); + +goog.exportProperty( + ol.geom.Point.prototype, + 'get', + ol.geom.Point.prototype.get); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getKeys', + ol.geom.Point.prototype.getKeys); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getProperties', + ol.geom.Point.prototype.getProperties); + +goog.exportProperty( + ol.geom.Point.prototype, + 'set', + ol.geom.Point.prototype.set); + +goog.exportProperty( + ol.geom.Point.prototype, + 'setProperties', + ol.geom.Point.prototype.setProperties); + +goog.exportProperty( + ol.geom.Point.prototype, + 'unset', + ol.geom.Point.prototype.unset); + +goog.exportProperty( + ol.geom.Point.prototype, + 'changed', + ol.geom.Point.prototype.changed); + +goog.exportProperty( + ol.geom.Point.prototype, + 'dispatchEvent', + ol.geom.Point.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getRevision', + ol.geom.Point.prototype.getRevision); + +goog.exportProperty( + ol.geom.Point.prototype, + 'on', + ol.geom.Point.prototype.on); + +goog.exportProperty( + ol.geom.Point.prototype, + 'once', + ol.geom.Point.prototype.once); + +goog.exportProperty( + ol.geom.Point.prototype, + 'un', + ol.geom.Point.prototype.un); + +goog.exportProperty( + ol.geom.Point.prototype, + 'unByKey', + ol.geom.Point.prototype.unByKey); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getFirstCoordinate', + ol.geom.Polygon.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getLastCoordinate', + ol.geom.Polygon.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getLayout', + ol.geom.Polygon.prototype.getLayout); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'rotate', + ol.geom.Polygon.prototype.rotate); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'scale', + ol.geom.Polygon.prototype.scale); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getClosestPoint', + ol.geom.Polygon.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'intersectsCoordinate', + ol.geom.Polygon.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getExtent', + ol.geom.Polygon.prototype.getExtent); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'simplify', + ol.geom.Polygon.prototype.simplify); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'transform', + ol.geom.Polygon.prototype.transform); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'get', + ol.geom.Polygon.prototype.get); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getKeys', + ol.geom.Polygon.prototype.getKeys); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getProperties', + ol.geom.Polygon.prototype.getProperties); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'set', + ol.geom.Polygon.prototype.set); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'setProperties', + ol.geom.Polygon.prototype.setProperties); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'unset', + ol.geom.Polygon.prototype.unset); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'changed', + ol.geom.Polygon.prototype.changed); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'dispatchEvent', + ol.geom.Polygon.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getRevision', + ol.geom.Polygon.prototype.getRevision); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'on', + ol.geom.Polygon.prototype.on); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'once', + ol.geom.Polygon.prototype.once); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'un', + ol.geom.Polygon.prototype.un); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'unByKey', + ol.geom.Polygon.prototype.unByKey); + +goog.exportProperty( + ol.format.GML.prototype, + 'readFeatures', + ol.format.GML.prototype.readFeatures); + +goog.exportProperty( + ol.format.GML2.prototype, + 'readFeatures', + ol.format.GML2.prototype.readFeatures); + +goog.exportProperty( + ol.format.GML3.prototype, + 'readFeatures', + ol.format.GML3.prototype.readFeatures); + +goog.exportProperty( + ol.control.Control.prototype, + 'get', + ol.control.Control.prototype.get); + +goog.exportProperty( + ol.control.Control.prototype, + 'getKeys', + ol.control.Control.prototype.getKeys); + +goog.exportProperty( + ol.control.Control.prototype, + 'getProperties', + ol.control.Control.prototype.getProperties); + +goog.exportProperty( + ol.control.Control.prototype, + 'set', + ol.control.Control.prototype.set); + +goog.exportProperty( + ol.control.Control.prototype, + 'setProperties', + ol.control.Control.prototype.setProperties); + +goog.exportProperty( + ol.control.Control.prototype, + 'unset', + ol.control.Control.prototype.unset); + +goog.exportProperty( + ol.control.Control.prototype, + 'changed', + ol.control.Control.prototype.changed); + +goog.exportProperty( + ol.control.Control.prototype, + 'dispatchEvent', + ol.control.Control.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.Control.prototype, + 'getRevision', + ol.control.Control.prototype.getRevision); + +goog.exportProperty( + ol.control.Control.prototype, + 'on', + ol.control.Control.prototype.on); + +goog.exportProperty( + ol.control.Control.prototype, + 'once', + ol.control.Control.prototype.once); + +goog.exportProperty( + ol.control.Control.prototype, + 'un', + ol.control.Control.prototype.un); + +goog.exportProperty( + ol.control.Control.prototype, + 'unByKey', + ol.control.Control.prototype.unByKey); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'getMap', + ol.control.Attribution.prototype.getMap); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'setMap', + ol.control.Attribution.prototype.setMap); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'setTarget', + ol.control.Attribution.prototype.setTarget); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'get', + ol.control.Attribution.prototype.get); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'getKeys', + ol.control.Attribution.prototype.getKeys); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'getProperties', + ol.control.Attribution.prototype.getProperties); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'set', + ol.control.Attribution.prototype.set); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'setProperties', + ol.control.Attribution.prototype.setProperties); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'unset', + ol.control.Attribution.prototype.unset); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'changed', + ol.control.Attribution.prototype.changed); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'dispatchEvent', + ol.control.Attribution.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'getRevision', + ol.control.Attribution.prototype.getRevision); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'on', + ol.control.Attribution.prototype.on); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'once', + ol.control.Attribution.prototype.once); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'un', + ol.control.Attribution.prototype.un); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'unByKey', + ol.control.Attribution.prototype.unByKey); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'getMap', + ol.control.FullScreen.prototype.getMap); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'setMap', + ol.control.FullScreen.prototype.setMap); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'setTarget', + ol.control.FullScreen.prototype.setTarget); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'get', + ol.control.FullScreen.prototype.get); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'getKeys', + ol.control.FullScreen.prototype.getKeys); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'getProperties', + ol.control.FullScreen.prototype.getProperties); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'set', + ol.control.FullScreen.prototype.set); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'setProperties', + ol.control.FullScreen.prototype.setProperties); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'unset', + ol.control.FullScreen.prototype.unset); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'changed', + ol.control.FullScreen.prototype.changed); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'dispatchEvent', + ol.control.FullScreen.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'getRevision', + ol.control.FullScreen.prototype.getRevision); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'on', + ol.control.FullScreen.prototype.on); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'once', + ol.control.FullScreen.prototype.once); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'un', + ol.control.FullScreen.prototype.un); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'unByKey', + ol.control.FullScreen.prototype.unByKey); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'getMap', + ol.control.MousePosition.prototype.getMap); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'setMap', + ol.control.MousePosition.prototype.setMap); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'setTarget', + ol.control.MousePosition.prototype.setTarget); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'get', + ol.control.MousePosition.prototype.get); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'getKeys', + ol.control.MousePosition.prototype.getKeys); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'getProperties', + ol.control.MousePosition.prototype.getProperties); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'set', + ol.control.MousePosition.prototype.set); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'setProperties', + ol.control.MousePosition.prototype.setProperties); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'unset', + ol.control.MousePosition.prototype.unset); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'changed', + ol.control.MousePosition.prototype.changed); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'dispatchEvent', + ol.control.MousePosition.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'getRevision', + ol.control.MousePosition.prototype.getRevision); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'on', + ol.control.MousePosition.prototype.on); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'once', + ol.control.MousePosition.prototype.once); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'un', + ol.control.MousePosition.prototype.un); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'unByKey', + ol.control.MousePosition.prototype.unByKey); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getMap', + ol.control.OverviewMap.prototype.getMap); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'setMap', + ol.control.OverviewMap.prototype.setMap); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'setTarget', + ol.control.OverviewMap.prototype.setTarget); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'get', + ol.control.OverviewMap.prototype.get); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getKeys', + ol.control.OverviewMap.prototype.getKeys); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getProperties', + ol.control.OverviewMap.prototype.getProperties); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'set', + ol.control.OverviewMap.prototype.set); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'setProperties', + ol.control.OverviewMap.prototype.setProperties); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'unset', + ol.control.OverviewMap.prototype.unset); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'changed', + ol.control.OverviewMap.prototype.changed); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'dispatchEvent', + ol.control.OverviewMap.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getRevision', + ol.control.OverviewMap.prototype.getRevision); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'on', + ol.control.OverviewMap.prototype.on); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'once', + ol.control.OverviewMap.prototype.once); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'un', + ol.control.OverviewMap.prototype.un); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'unByKey', + ol.control.OverviewMap.prototype.unByKey); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'getMap', + ol.control.Rotate.prototype.getMap); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'setMap', + ol.control.Rotate.prototype.setMap); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'setTarget', + ol.control.Rotate.prototype.setTarget); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'get', + ol.control.Rotate.prototype.get); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'getKeys', + ol.control.Rotate.prototype.getKeys); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'getProperties', + ol.control.Rotate.prototype.getProperties); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'set', + ol.control.Rotate.prototype.set); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'setProperties', + ol.control.Rotate.prototype.setProperties); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'unset', + ol.control.Rotate.prototype.unset); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'changed', + ol.control.Rotate.prototype.changed); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'dispatchEvent', + ol.control.Rotate.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'getRevision', + ol.control.Rotate.prototype.getRevision); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'on', + ol.control.Rotate.prototype.on); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'once', + ol.control.Rotate.prototype.once); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'un', + ol.control.Rotate.prototype.un); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'unByKey', + ol.control.Rotate.prototype.unByKey); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'getMap', + ol.control.ScaleLine.prototype.getMap); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'setMap', + ol.control.ScaleLine.prototype.setMap); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'setTarget', + ol.control.ScaleLine.prototype.setTarget); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'get', + ol.control.ScaleLine.prototype.get); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'getKeys', + ol.control.ScaleLine.prototype.getKeys); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'getProperties', + ol.control.ScaleLine.prototype.getProperties); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'set', + ol.control.ScaleLine.prototype.set); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'setProperties', + ol.control.ScaleLine.prototype.setProperties); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'unset', + ol.control.ScaleLine.prototype.unset); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'changed', + ol.control.ScaleLine.prototype.changed); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'dispatchEvent', + ol.control.ScaleLine.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'getRevision', + ol.control.ScaleLine.prototype.getRevision); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'on', + ol.control.ScaleLine.prototype.on); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'once', + ol.control.ScaleLine.prototype.once); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'un', + ol.control.ScaleLine.prototype.un); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'unByKey', + ol.control.ScaleLine.prototype.unByKey); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'getMap', + ol.control.Zoom.prototype.getMap); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'setMap', + ol.control.Zoom.prototype.setMap); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'setTarget', + ol.control.Zoom.prototype.setTarget); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'get', + ol.control.Zoom.prototype.get); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'getKeys', + ol.control.Zoom.prototype.getKeys); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'getProperties', + ol.control.Zoom.prototype.getProperties); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'set', + ol.control.Zoom.prototype.set); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'setProperties', + ol.control.Zoom.prototype.setProperties); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'unset', + ol.control.Zoom.prototype.unset); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'changed', + ol.control.Zoom.prototype.changed); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'dispatchEvent', + ol.control.Zoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'getRevision', + ol.control.Zoom.prototype.getRevision); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'on', + ol.control.Zoom.prototype.on); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'once', + ol.control.Zoom.prototype.once); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'un', + ol.control.Zoom.prototype.un); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'unByKey', + ol.control.Zoom.prototype.unByKey); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'getMap', + ol.control.ZoomSlider.prototype.getMap); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'setMap', + ol.control.ZoomSlider.prototype.setMap); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'setTarget', + ol.control.ZoomSlider.prototype.setTarget); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'get', + ol.control.ZoomSlider.prototype.get); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'getKeys', + ol.control.ZoomSlider.prototype.getKeys); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'getProperties', + ol.control.ZoomSlider.prototype.getProperties); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'set', + ol.control.ZoomSlider.prototype.set); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'setProperties', + ol.control.ZoomSlider.prototype.setProperties); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'unset', + ol.control.ZoomSlider.prototype.unset); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'changed', + ol.control.ZoomSlider.prototype.changed); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'dispatchEvent', + ol.control.ZoomSlider.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'getRevision', + ol.control.ZoomSlider.prototype.getRevision); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'on', + ol.control.ZoomSlider.prototype.on); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'once', + ol.control.ZoomSlider.prototype.once); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'un', + ol.control.ZoomSlider.prototype.un); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'unByKey', + ol.control.ZoomSlider.prototype.unByKey); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'getMap', + ol.control.ZoomToExtent.prototype.getMap); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'setMap', + ol.control.ZoomToExtent.prototype.setMap); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'setTarget', + ol.control.ZoomToExtent.prototype.setTarget); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'get', + ol.control.ZoomToExtent.prototype.get); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'getKeys', + ol.control.ZoomToExtent.prototype.getKeys); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'getProperties', + ol.control.ZoomToExtent.prototype.getProperties); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'set', + ol.control.ZoomToExtent.prototype.set); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'setProperties', + ol.control.ZoomToExtent.prototype.setProperties); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'unset', + ol.control.ZoomToExtent.prototype.unset); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'changed', + ol.control.ZoomToExtent.prototype.changed); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'dispatchEvent', + ol.control.ZoomToExtent.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'getRevision', + ol.control.ZoomToExtent.prototype.getRevision); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'on', + ol.control.ZoomToExtent.prototype.on); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'once', + ol.control.ZoomToExtent.prototype.once); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'un', + ol.control.ZoomToExtent.prototype.un); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'unByKey', + ol.control.ZoomToExtent.prototype.unByKey); +ol.VERSION = 'v3.20.1'; +OPENLAYERS.ol = ol; + + return OPENLAYERS.ol; +})); + +var map; +var extent; +var overlays = []; +var vectorSource = new ol.source.Vector(); +var lastClick; +var popupOverlay; +var vectorLayer; +var id = null; +var userPositionMarker = null; +var lockViewToPosition = true; +var gps = false; +var gpsLocation = null; +var featureStyle = new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: 'rgb(153,39,208)', + lineDash: [4, 8] + }), + fill: new ol.style.Fill({ + color: 'rgba(153,39,208,.03)' + }) +}); +var mapClickFunction = function(evt) { + var pos = ol.proj.transform(evt["coordinate"], 'EPSG:3857', 'EPSG:4326'); + lastClick = pos; + getNearest(pos[0], pos[1]); +}; +var moveFunction = function() { + var q = $("#search input[name=q]").val(); + if (q !== "" && $("#search input[name=q]").attr("data-move-search") === "") { + updateMapExtent(); + var q = $("#search input[name=q]").val(); + q = encodeURI(q); + $("#clearInput").html("<img src=\"/img/ajax-loader.gif\" />"); + var url = '/' + q + '/' + encodeURI(extent[0]) + '/' + encodeURI(extent[1]) + '/' + encodeURI(extent[2]) + '/' + encodeURI(extent[3] + '/' + false + '/50'); + $.getScript(url); + } +}; +var clearInputFunction = function() { + $("#search input[name=q]").val(''); + $("#search input[name=q]").focus(); + clearPOIS(); + $.each(overlays, function(index, value) { + map.removeOverlay(value); + $("#popup-closer").click(); + }); + deinitResults(); + $("#clearInput").off(); +}; +$(document).ready(function() { + // Initialize the Map + initMap(); + checkGPS(startApplication); + $("#closer").click(function() { + toggleResults(); + }); + map.on('singleclick', mapClickFunction); + $(window).resize(function() { + updateResultsPosition(); + updateCloserPosition(); + }); + $("#follow-location > span.button").click(function() { + followLocation(); + }); + $("#lock-location > span.button").click(function() { + toggleViewLock(); + }); +}); +var options = { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0 +}; + +function success(pos) { + var crd = pos.coords; + map.getView().setCenter(ol.proj.transform([crd.longitude, crd.latitude], 'EPSG:4326', 'EPSG:3857')); + map.getView().setZoom(12); + updateMapExtent(); +} + +function error(err) { + console.warn('ERROR(' + err.code + '): ' + err.message); +} + +function receiveLocation() { + navigator.geolocation.getCurrentPosition(success, error, options); +} + +function updateMapExtent() { + var tmpExtent = map.getView().calculateExtent([$("#map").width(), $("#map").height()]); + extent = ol.proj.transform([tmpExtent[0], tmpExtent[1]], 'EPSG:3857', 'EPSG:4326').concat(ol.proj.transform([tmpExtent[2], tmpExtent[3]], 'EPSG:3857', 'EPSG:4326')); +} + +function numberWithPoints(x) { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, "."); +} + +function deinitSearchBox() { + $("#search").addClass("hidden"); +} + +function initResults() { + if ($("#results").hasClass("hidden")) { + $('#results').removeClass("hidden"); + } + if ($("#closer").hasClass("hidden")) { + $("#closer").removeClass("hidden"); + updateCloserPosition(); + } + updateMapSize(); + toggleResults("out"); +} + +function initStartNavigation() { + $("#clearInput").html('<a href="/route/start/foot" target="_self"><img src="/img/navigation-arrow.svg" height="20px"></a>'); + $("#clearInput").off(); + $("#clearInput").attr("title", "Routenplaner starten"); +} + +function initClearInput() { + $("#clearInput").html('<span class="font-bold">X</span>'); + $("#clearInput").off(); + $("#clearInput").click(clearInputFunction); + $("#clearInput").attr("title", "Sucheingabe löschen"); +} + +function toggleResults(status) { + if (status === undefined) { + status = $("#results").attr("data-status"); + } else if (status !== "in" && status !== "out") { + status = "in"; + } + if (status === "in") { + $("#closer").html("<"); + $("#results").attr("data-status", "out"); + $("#closer").attr("title", "Ergebnisse ausklappen"); + updateResultsPosition(); + updateCloserPosition(); + } else { + $("#closer").html(">"); + $("#results").attr("data-status", "in"); + $("#closer").attr("title", "Ergebnisse einklappen"); + updateResultsPosition(); + updateCloserPosition(); + } + updateMapSize(); +} + +function updateMapSize() { + var resultsWidth = parseInt($("#results").width()); + if ($("#results").hasClass("hidden")) { + resultsWidth = 0; + } + $("#search input[name=q]").attr("data-move-search", "false"); + var displayWidth = $(window).width(); + // Change Map Width + $("#map").width(displayWidth - resultsWidth); + var navBarHeight = $("nav").height(); + if($("nav").hasClass("hidden")){ + navBarHeight = 0; + } + var displayHeight = $(window).height(); + // Change The Map Height + $("#map").height(displayHeight - navBarHeight); + $("#map").css("margin-top", navBarHeight); + map.updateSize(); + setTimeout(function() { + $("#search input[name=q]").attr("data-move-search", ""); + }, 1500); +} + +function updateResultsPosition() { + if ($("#results").attr("data-status") === "out") { + $("#results").addClass("hidden"); + } else { + $("#results").removeClass("hidden"); + } +} + +function updateCloserPosition() { + if ($("#results").attr("data-status") === "out") { + $("#closer").css("right", "0px"); + } else { + var screenWidth = $(window).width(); + var resultsWidth = $("#results").width() - 1; + var closerWidth = $("#closer").width(); + if (screenWidth > (resultsWidth + closerWidth)) { + $("#closer").css("right", resultsWidth + "px"); + } else { + $("#closer").css("right", (resultsWidth - closerWidth) + "px"); + } + } +} + +function adjustView(results) { + if (results.length <= 0) return; + var minPosition = []; + var maxPosition = []; + for (var i = 0; i < results.length; i++) { + if (typeof minPosition[0] === 'undefined' || minPosition[0] > parseFloat(results[i]["lon"])) { + minPosition[0] = parseFloat(results[i]["lon"]); + } + if (typeof minPosition[0] === 'undefined' || (typeof results[i]["boundingbox"] !== 'undefined' && minPosition[0] > parseFloat(results[i]["boundingbox"][2]))) { + minPosition[0] = parseFloat(results[i]["boundingbox"][2]); + } + if (typeof minPosition[1] === 'undefined' || minPosition[1] > parseFloat(results[i]["lat"])) { + minPosition[1] = parseFloat(results[i]["lat"]); + } + if (typeof minPosition[1] === 'undefined' || (typeof results[i]["boundingbox"] !== 'undefined' && minPosition[1] > parseFloat(results[i]["boundingbox"][0]))) { + minPosition[1] = parseFloat(results[i]["boundingbox"][0]); + } + if (typeof maxPosition[0] === 'undefined' || maxPosition[0] < parseFloat(results[i]["lon"])) { + maxPosition[0] = parseFloat(results[i]["lon"]); + } + if (typeof maxPosition[0] === 'undefined' || (typeof results[i]["boundingbox"] !== 'undefined' && maxPosition[0] < parseFloat(results[i]["boundingbox"][3]))) { + maxPosition[0] = parseFloat(results[i]["boundingbox"][3]); + } + if (typeof maxPosition[1] === 'undefined' || maxPosition[1] < parseFloat(results[i]["lat"])) { + maxPosition[1] = parseFloat(results[i]["lat"]); + } + if (typeof maxPosition[1] === 'undefined' || (typeof results[i]["boundingbox"] !== 'undefined' && maxPosition[1] < parseFloat(results[i]["boundingbox"][1]))) { + maxPosition[1] = parseFloat(results[i]["boundingbox"][1]); + } + if (typeof results[i]["type"] !== 'undefined' && (results[i]["type"] === 'city' || results[i]["type"] === 'administrative' || results[i]["type"] === 'river')) { + break; + } + } + minPosition = ol.proj.transform(minPosition, 'EPSG:4326', 'EPSG:3857'); + maxPosition = ol.proj.transform(maxPosition, 'EPSG:4326', 'EPSG:3857'); + map.getView().fit([minPosition[0], minPosition[1], maxPosition[0], maxPosition[1]], map.getSize()); + updateMapExtent(); +} +/** + * Parsesan OSM-Address-Object for the Road-Name + * @param {Array} address + * @return {String} roadname + */ +function getRoad(address) { + var road = ""; + if (typeof address["road"] !== 'undefined') { + road = address["road"]; + } else if (typeof address["pedestrian"] !== 'undefined') { + road = address["pedestrian"]; + } else if (typeof address["path"] !== 'undefined') { + road = address["path"]; + } else if (typeof address["footway"] !== 'undefined') { + road = address["footway"]; + } + return road; +} +/** + * Parse an OSM-Address-Object for the House Number + * @param {Array} address + * @return {String} Housenumber + */ +function getHouseNumber(address) { + var house_number = typeof address["house_number"] !== 'undefined' ? address["house_number"] : ""; + return house_number; +} +/** + * Parse an OSM-Address-Object for the City (including Zip-Code) + * @param {Array} address + * @return {String} City + */ +function getCity(address) { + var city = typeof address["postcode"] !== 'undefined' ? address["postcode"] + " " : ""; + if (typeof address["city"] !== "undefined") { + city += address["city"]; + } else if (typeof address["town"] !== "undefined") { + city += address["town"]; + } else if (typeof address["village"] !== "undefined") { + city += address["village"]; + } + return city; +} + +function adjustViewBoundingBox(minpos, maxpos) { + minPosition = ol.proj.transform(minpos, 'EPSG:4326', 'EPSG:3857'); + maxPosition = ol.proj.transform(maxpos, 'EPSG:4326', 'EPSG:3857'); + map.getView().fit([minPosition[0], minPosition[1], maxPosition[0], maxPosition[1]], map.getSize()); + updateMapExtent(); +} +/* + * This Function takes an array of Positions and adjusts the view of the map so everything is visible + * @param positions{Array} - Array with Position Objects ([lon,lat]) + */ +function adjustViewPosList(positions) { + var minpos = [null, null]; + var maxpos = [null, null]; + $.each(positions, function(index, value) { + if(value === ""){ + return; + } + if (minpos[0] === null || value[0] < minpos[0]) { + minpos[0] = value[0]; + } + if (maxpos[0] === null || value[0] > maxpos[0]) { + maxpos[0] = value[0]; + } + if (minpos[1] === null || value[1] < minpos[1]) { + minpos[1] = value[1]; + } + if (maxpos[1] === null || value[1] > maxpos[1]) { + maxpos[1] = value[1]; + } + }); + adjustViewBoundingBox(minpos, maxpos); +} + +function clearPOIS() { + // Remove All Existing Overlays + $.each(overlays, function(index, value) { + map.removeOverlay(value); + }); + map.removeLayer(vectorLayer); + vectorSource = new ol.source.Vector(); + // Remove Existing Results + $("#results > .result").remove(); + $("#results > h4").remove(); + overlays = []; +} + +function centerMap(longitude, latitude) { + var point = ol.proj.transform([longitude, latitude], 'EPSG:4326', 'EPSG:3857') + map.getView().setCenter(point); +} +/** + * Fügt einen Marker auf die Karte hinzu + * Parameter: + * el: HTML-Code für das Element, welches den Marker definiert + * pos: Position, auf der sich der Marker befinden soll int[2] (Lat, Long) + **/ +function addMarker(el, pos) { + var overlay = new ol.Overlay({ + position: pos, + element: el.get(0), + offset: [-12, -45], + stopEvent: false, + }); + map.addOverlay(overlay); + return overlay; +} + +function checkGPS(callback) { + + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition(function(position){ + if(position.coords.accuracy > 500){ + gps = false; + toggleGPSLocator(false); + }else{ + gps = gps = true; + lon = parseFloat(position.coords.longitude); + lat = parseFloat(position.coords.latitude); + gpsLocation = [lon, lat]; + toggleGPSLocator(true); + } + if(typeof callback === "function"){ + callback(); + } + }, function(error){ + gps = false; + toggleGPSLocator(false); + if(typeof callback === "function"){ + callback(); + } + }); + } else { + gps = false; + toggleGPSLocator(false); + if(typeof callback === "function"){ + callback(); + } + } +} + +function startApplication(){ + if(typeof start === "function"){ + start(); + } +} + +function toggleGPSLocator(visible){ + if(visible){ + $("#location-tool").removeClass("hidden"); + }else{ + $("#location-tool").addClass("hidden"); + } +} + +function followLocation() { + // Element to be displayed at the user-location + var el = $('<span id="user-position" class="glyphicon glyphicon-record" style="color: #2881cc;"></span>'); + if (lockViewToPosition) $("#lock-location").addClass("active"); + else $("#lock-location").removeClass("active"); + if (id === null) { + id = navigator.geolocation.watchPosition(function(position) { + // Remove possibly existing User-Location Marker: + if (userPositionMarker !== null) { + map.removeLayer(userPositionMarker); + userPositionMarker = null; + } + // Create User Position + var point_geom = new ol.geom.Point(ol.proj.transform([position.coords.longitude, position.coords.latitude], 'EPSG:4326', 'EPSG:3857')); + var point_feature = new ol.Feature({ + name: "Position", + geometry: point_geom + }); + // Create the accuracy Circle: + var circle = new ol.geom.Circle(ol.proj.transform([position.coords.longitude, position.coords.latitude], 'EPSG:4326', 'EPSG:3857'), position.coords.accuracy); + var accuracy_feature = new ol.Feature({ + name: "Accuracy", + geometry: circle + }); + userPositionMarker = new ol.layer.Vector({ + source: new ol.source.Vector({ + features: [point_feature, accuracy_feature] + }) + }); + map.addLayer(userPositionMarker); + if (lockViewToPosition) { + // Fit the Extent of the Map to Fit the new Features Exactly + map.getView().fit(userPositionMarker.getSource().getExtent(), map.getSize()); + } + // Change the color of the Icon so the user knows that the position is tracked: + $("#follow-location").addClass("active"); + }, function(error) {}, options); + // Show the Lock View to Position Button + $("#lock-location").removeClass("hidden"); + $("#lock-location > span.info").fadeOut(2000); + } else { + map.removeLayer(userPositionMarker); + userPositionMarker = null; + navigator.geolocation.clearWatch(id); + id = null; + // Clear the color of the Icon so the user knows that the position is no longer tracked + $("#follow-location").removeClass("active"); + // Hide the lock View to Position Button + $("#lock-location").addClass("hidden"); + $("#lock-location > span.info").css("display", ""); + } +} + +function updateCurrentLocation(callback) { + var lon = ""; + var lat = ""; + if (gps) { + navigator.geolocation.getCurrentPosition(function(position) { + lon = parseFloat(position.coords.longitude); + lat = parseFloat(position.coords.latitude); + gpsLocation = [lon, lat]; + if(typeof callback === "function"){ + callback(); + } + }, function(error) { + checkGPS(callback); + }); + + + } else { + return null; + } +} + +function toggleViewLock() { + if (lockViewToPosition) { + lockViewToPosition = false; + $("#lock-location").removeClass("active"); + $("#lock-location > span.info").html("Ansicht freigegeben"); + $("#lock-location > span.info").css("display", ""); + $("#lock-location > span.info").fadeOut(2000); + } else { + lockViewToPosition = true; + $("#lock-location").addClass("active"); + $("#lock-location > span.info").html("Ansicht zentriert"); + $("#lock-location > span.info").css("display", ""); + $("#lock-location > span.info").fadeOut(2000); + } +} + +function createPopup(lon, lat, html) { + var pos = ol.proj.transform([parseFloat(lon), parseFloat(lat)], 'EPSG:4326', 'EPSG:3857'); + $("#popup-content").html(html); + popupOverlay.setPosition(pos); +} + + +$(document).ready(function() { + initStartNavigation(); + if (!boundings && getPosition) receiveLocation(); + if (boundings) { + adjustViewBoundingBox(minPos, maxPos); + } + $("#search input[name=q]").on("keydown", function(event) { + if (event.which == 13) $("#doSearch").click(); + }); + $("#doSearch").click(function() { + updateMapExtent(); + var q = $("#search input[name=q]").val(); + q = encodeURI(q); + $("#clearInput").html("<img src=\"/img/ajax-loader.gif\" />"); + var url = '/' + q + '/' + encodeURI(extent[0]) + '/' + encodeURI(extent[1]) + '/' + encodeURI(extent[2]) + '/' + encodeURI(extent[3]); + $.getScript(url).fail(function(jqxhr, settings, exception) { + console.log(exception); + }); + $("#search input[name=q]").blur(); + }); +}); + +function initMap() { + popupOverlay = new ol.Overlay( /** @type {olx.OverlayOptions} */ ({ + element: document.getElementById("popup"), + autoPan: true, + autoPanAnimation: { + duration: 250 + } + })); + map = new ol.Map({ + layers: [ + new ol.layer.Tile({ + preload: Infinity, + source: new ol.source.OSM({ + attributions: [ + new ol.Attribution({ + html: '<a href="https://metager.de/impressum">Impressum</a>' + }), + new ol.Attribution({ + html: 'All search results © ' + '<a href="http://nominatim.openstreetmap.org/">Nominatim</a>' + }), + ol.source.OSM.ATTRIBUTION, + ], + url: 'https://maps.metager.de/osm_tiles/{z}/{x}/{y}.png' + }) + }) + ], + target: 'map', + controls: ol.control.defaults({ + attributionOptions: /** @type {olx.control.AttributionOptions} */ ({ + collapsible: true + }) + }), + overlays: [popupOverlay], + view: new ol.View({ + maxZoom: 18, + minZoom: 6, + center: ol.proj.transform( + [10.06897, 51.37247], 'EPSG:4326', 'EPSG:3857'), + zoom: 6 + }), + loadTilesWhileAnimating: true, + loadTilesWhileInteracting: true + }); + map.addControl(new ol.control.ZoomSlider()); + $("#popup-closer").click(function() { + popupOverlay.setPosition(undefined); + $(this).blur(); + return false; + }); +} +/** + * This function sends a request to our Nominatim instance and evaluates the given coordinates to an adress + * @param {Float} lon + * @param {Float} lat + * @return {Array} adress + */ +function getNearest(lon, lat) { + var url = "https://maps.metager.de/nominatim/reverse.php?format=json&lat=" + lat + "&lon=" + lon + "&zoom=18"; + // Send the Request + $.get(url, function(data) { + if (typeof data !== "undefined" && typeof data["address"] !== "undefined") { + // Success we have an address + var address = data["address"]; + + var road = getRoad(address); + var house_number = getHouseNumber(address); + var city = getCity(address); + var id = data["place_id"]; + + var url = "/route/start/foot/"+lon+","+lat; + + var popup = $("\ + <div class=\"result col-xs-12\">\ + <p class=\"address\">" + road + " " + house_number + "</p>\ + <p class=\"city\">" + city + "</p>\ + <p class=\"address\">Longitude: " + lon + "</p>\ + <p class=\"address\">Latitude: " + lat + "</p>\ + <a href=\"https://maps.metager.de/nominatim/details.php?place_id=" + id + "\" target=\"_blank\" class=\"btn btn-default btn-xs\">Details</a>\ + <a href=\""+url+"\" class=\"btn btn-default btn-xs\">Route berechnen</a>\ + </div>"); + + // And now we can show the Popup where the user clicked + createPopup(lon, lat, popup); + } + }); +} + +function deinitResults() { + toggleResults("out"); + $("#results").addClass("hidden"); + $("#closer").addClass("hidden"); + $("#results").html(""); + updateMapSize(); + initStartNavigation(); +} +var routeLineStyle = new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: 'rgb(255,0,0)', + width: 5 + }), + fill: new ol.style.Fill({ + color: 'rgba(255,0,0,.03)' + }) +}); +var route = {}; +var routeLayer = null; +var routeMarkers = []; +function start(){ + var pointString = points; + if(points.match(/gps/) !== null){ + var pos = gpsLocation; + pointString = points; + if(pos !== null){ + pointString = pointString.replace(/gps/, pos.toString()); + }else{ + pointString = pointString.replace(/;{0,1}gps;{0,1}/, ';'); + } + } + var url = '/route/find/' + vehicle + '/' + pointString; + $.getJSON(url, function(response) { + route = response; + }).success(function() { + // If the Route could be loaded and there is a route between the points we can show it: + if (typeof route["code"] !== 'undefined' && route["code"] === "Ok" && route["routes"].length >= 1) { + deinitSearchBox(); + addGraphics(); + addResults(); + initResults(); + } + }); +}; + +function addResults() { + $("#results").html(""); + var vehicleChooser = $("<div id=\"vehicle-chooser\">\ + <label class=\"radio-inline\">\ + <input type=\"radio\" name=\"vehicle\" value=\"foot\"> <div><img src=\"/img/silhouette-walk.png\" height=\"20px\" /></div>\ + </label>\ + <label class=\"radio-inline\" >\ + <input type=\"radio\" name=\"vehicle\" value=\"bicycle\"> <div><img src=\"/img/bike.png\" height=\"20px\" /></div>\ + </label>\ + <label class=\"radio-inline\">\ + <input type=\"radio\" name=\"vehicle\" value=\"car\"> <div><img src=\"/img/car.png\" height=\"20px\" /></div>\ + </label>\ + </div>\ + <div id=\"route-content\">\ + </div>\ + "); + $("#results").append(vehicleChooser); + $(vehicleChooser).find("input[value=" + vehicle + "]").prop("checked", true); + // Add the changed Listener to the vehicle Chooser: + $(vehicleChooser).find("input[type=radio]").change(function() { + vehicle = $(vehicleChooser).find("input[type=radio]:checked").val(); + var url = '/route/start/' + vehicle + '/'; + $.each(route["waypoints"], function(index, value) { + url += value["location"].toString() + ";"; + }); + url = url.replace(/;+$/, ''); + document.location.href = url; + }); + // We should add a Place to display Informations About the Route + var routeInformation = $('<div id="route-information" class="row"><div id="length" class="col-md-6"></div><div id="duration" class="col-md-6"></div></div>') + $("#route-content").prepend(routeInformation); + // Add Button for starting the route assistent + if(points.match(/^gps/) !== null){ + var routeAssistent = $('<div class="container-fluid"><div class="row"><div class="col-xs-12"><a id="route-assistent" href="javascript:updateCurrentLocation(startAssistent);">Routenführung starten</a></div></div></div>'); + $("#route-content").prepend(routeAssistent); + } + addRouteMetaData(); + insertSteps(); +} + +function insertSteps() { + var takenRoute = route["routes"][0]; + var stepList = $("<table id=\"routing-steps\" class=\"table\"></table>"); + // Parse Each Leg + $.each(takenRoute["legs"], function(legIndex, leg) { + // Parse all steps for the Leg + $.each(leg["steps"], function(stepIndex, step) { + var lon = step["maneuver"]["location"][0]; + var lat = step["maneuver"]["location"][1]; + var maneuver = $("<tr class=\"step\" data-lon=\"" + lon + "\" data-lat=\"" + lat + "\"></tr>"); + var directionImg = $("<td class=\"step-image\"></td>"); + var img = parseImg(step); + if (img !== "") { + $(directionImg).append("<img height=\"35px\" src=\"" + img + "\" />"); + } + $(maneuver).append(directionImg); + var stepString = parseManeuver(step["maneuver"], takenRoute, legIndex, stepIndex); + //$(maneuver).append($("<div class=\"col-xs-8\">" + stepString + "</p>")); + $(maneuver).append($("<td>" + stepString + "</td>")); + var distance = parseFloat(step["distance"]); + distance = Math.ceil(distance); + if (distance > 1000) { + distance /= 1000; + distance = Math.round(distance * 10) / 10; + distance = distance + " km"; + } else { + distance = distance + " m"; + } + if (step["maneuver"]["type"] === "arrive") { + distance = ""; + } + $(maneuver).append($("<td>" + distance + "</td>")); + $(maneuver).mouseover(function() { + var lon = parseFloat($(this).attr("data-lon")); + var lat = parseFloat($(this).attr("data-lat")); + var layer = addPoint(lon, lat); + $(maneuver).mouseout(function() { + map.removeLayer(layer); + }); + $(maneuver).click(function() { + var point = ol.proj.transform([lon, lat], 'EPSG:4326', 'EPSG:3857'); + map.getView().setCenter(point); + map.getView().setZoom(16); + }); + }); + $(stepList).append(maneuver); + }); + }); + $("#route-content").append(stepList); +} + +function parseImg(step) { + switch (step["maneuver"]["type"]) { + case "depart": + case "new name": + return "/img/straight.png"; + case "roundabout turn": + case "continue": + case "end of road": + case "turn": + switch (step["maneuver"]["modifier"]) { + case "left": + return "/img/turn-left.png"; + case "sharp left": + return "/img/turn-sharp-left.png"; + case "right": + return "/img/turn-right.png"; + case "sharp right": + return "/img/turn-sharp-right.png"; + case "uturn": + return "/img/uturn.png"; + case "slight right": + return "/img/fork-slight-right.png"; + case "slight left": + return "/img/fork-slight-left.png"; + case "straight": + return "/img/straight.png"; + default: + // console.log(step); + } + break; + case "roundabout": + case "rotary": + return "/img/roundabout.png"; + case "merge": + case "off ramp": + case "fork": + switch (step["maneuver"]["modifier"]) { + case "left": + return "/img/fork-left.png"; + case "right": + return "/img/fork-right.png"; + case "slight right": + return "/img/fork-slight-right.png"; + case "slight left": + return "/img/fork-slight-left.png"; + case "straight": + return "/img/straight.png"; + default: + // console.log(step); + } + default: + // console.log(step); + } + return ""; +} + +function addPoint(lon, lat) { + var layer = new ol.layer.Vector({ + source: new ol.source.Vector({ + features: [new ol.Feature({ + geometry: new ol.geom.Point(ol.proj.transform([lon, lat], 'EPSG:4326', 'EPSG:3857')) + })] + }) + }); + map.addLayer(layer); + return layer; +} + +function addRouteMetaData() { + var distance = route["routes"][0]["distance"]; + var duration = route["routes"][0]["duration"]; + $("#route-information #length").html(parseDistance(distance)); + $("#route-information #duration").html(parseDuration(duration)); +} + +function parseManeuver(maneuver, takenRoute, legIndex, stepIndex) { + //console.log(maneuver); + var stepString = ""; + var type = maneuver["type"]; + var modifier = maneuver["modifier"]; + var targetStreet = takenRoute["legs"][legIndex]["steps"][stepIndex]["name"]; + var destinations = takenRoute["legs"][legIndex]["steps"][stepIndex]["destinations"]; + if (!destinations) { + destinations = takenRoute["legs"][legIndex]["steps"][stepIndex]["ref"] + } + switch (type) { + case "depart": + var direction = parseBearing(maneuver["bearing_after"]); + var start = takenRoute["legs"][legIndex]["steps"][stepIndex]["name"]; + stepString = "Auf " + start + " nach " + direction; + var nextStreet = takenRoute["legs"][legIndex]["steps"][stepIndex + 1]["name"]; + if (nextStreet !== start) { + stepString += " Richtung " + nextStreet; + } + break; + case "roundabout turn": + case "continue": + case "end of road": + case "turn": + var direction = ""; + if (maneuver["modifier"] === "uturn") { + stepString = "Bei " + targetStreet + " wenden"; + } else { + var modifier = parseModifier(maneuver["modifier"]); + if (modifier !== "Weiter") { + modifier += " abbiegen"; + } + stepString = modifier; + } + if (targetStreet !== "") { + stepString += " auf " + targetStreet; + } + break; + case "roundabout": + case "rotary": + stepString = "Im Kreisverkehr "; + if (maneuver["exit"] !== null) { + stepString += "die "; + switch (parseInt(maneuver["exit"])) { + case 1: + stepString += "erste "; + break; + case 2: + stepString += "zweite "; + break; + case 3: + stepString += "dritte "; + break; + case 4: + stepString += "vierte "; + break; + case 5: + stepString += "fünfte "; + break; + case 6: + stepString += "sechste "; + break; + case 7: + stepString += "siebte "; + break; + case 8: + stepString += "achte "; + break; + case 9: + stepString += "neunte "; + break; + } + stepString += "Ausfahrt nehmen." + if (destinations) { + stepString += " <nobr>(" + destinations + ")</nobr>"; + } + } + break; + case "arrive": + var mod = parseModifier(modifier); + if (mod === undefined) { + stepString = "Sie haben das Ziel erreicht."; + } else { + stepString = "Das Ziel befindet sich " + mod; + } + break; + case "new name": + stepString = "Weiter auf " + targetStreet; + break; + case "merge": + var mod = parseModifier(modifier); + stepString = mod + " auffahren."; + if (targetStreet && !destinations) { + stepString = mod + " auffahren auf " + targetStreet + "."; + } else if (targetStreet && destinations) { + stepString = mod + " halten um auf " + targetStreet + " (" + destinations + ") aufzufahren."; + } + break; + case "off ramp": + case "fork": + var mod = parseModifier(modifier); + stepString = "An der Gabelung " + mod + " halten."; + if (targetStreet && !destinations) { + stepString = "An der Gabelung " + mod + " halten. Richtung " + targetStreet; + } else if (!targetStreet && destinations) { + stepString = "An der Gabelung " + mod + " halten. (" + destinations + ")"; + } else if (targetStreet && destinations) { + stepString = "An der Gabelung " + mod + " halten. Richtung " + targetStreet + " (" + destinations + ")"; + } + break; + default: + console.log(takenRoute["legs"][legIndex]["steps"][stepIndex]); + stepString = "Konnte diesen Schritt nicht zu einem String auswerten"; + } + return stepString; +} + +function parseModifier(modifier) { + var direction = ""; + switch (modifier) { + case undefined: + direction = undefined; + break; + case "sharp right": + direction = "Scharf rechts"; + break; + case "right": + direction = "Rechts"; + break; + case "slight right": + direction = "Leicht Rechts"; + break; + case "straight": + direction = "Weiter"; + break; + case "slight left": + direction = "Leicht Links"; + break; + case "left": + direction = "Links"; + break; + case "sharp left": + direction = "Scharf Links"; + break; + default: + direction = "Konnte Richtungs-Modifizierer nicht auswerten: " + modifier; + } + return direction; +} + +function parseBearing(bearing) { + bearing = parseFloat(bearing); + if ((bearing >= 0 && bearing < 22.5) || bearing >= 337.5) { + return "Norden"; + } else if (bearing >= 22.5 && bearing < 67.5) { + return "Nordosten"; + } else if (bearing >= 67.5 && bearing < 112.5) { + return "Osten"; + } else if (bearing >= 112.5 && bearing < 157.5) { + return "Südosten"; + } else if (bearing >= 157.5 && bearing < 202.5) { + return "Süden"; + } else if (bearing >= 202.5 && bearing < 247.5) { + return "Südwesten"; + } else if (bearing >= 247.5 && bearing < 292.5) { + return "Westen"; + } else if (bearing >= 292.5 && bearing < 337.5) { + return "Nordwesten"; + } +} + +function parseDistance(distance) { + distance = parseFloat(distance); + distance /= 1000; + var km = Math.round(distance * 10) / 10; + return km + " km"; +} + +function parseDuration(duration) { + duration = Math.floor(parseFloat(duration)); + var hours = 0; + if (duration > 3600) { + hours = Math.floor(duration / 3600); + duration = duration % 3600; + } + var minute = 0; + if (duration > 60) { + minute = Math.round(duration / 60); + duration = duration % 60; + } + var result = ""; + if (hours > 0) { + result += hours + " Std."; + } + if (minute > 0) { + result += " " + minute + " Min."; + } + return result; +} + +function addGraphics() { // We collect the minimal Position and the maximum Position within the route so we can Adjust the View: + var minPos = []; + var maxPos = []; + var vectorS = new ol.source.Vector(); + // We will show the first Route: + $.each(route["routes"][0]["legs"], function(index, value) { + // For each leg we collect all the steps: + $.each(value["steps"], function(index, value) { + var geojson = value["geometry"]; + // Let's look through the Points to find the minimal + $.each(geojson["coordinates"], function(index, value) { + if (typeof minPos[0] === "undefined" || parseFloat(value[0]) < minPos[0]) { + minPos[0] = value[0]; + } else if (typeof maxPos[0] === "undefined" || parseFloat(value[0]) > maxPos[0]) { + maxPos[0] = value[0]; + } + if (typeof minPos[1] === "undefined" || parseFloat(value[1]) < minPos[1]) { + minPos[1] = value[1]; + } else if (typeof maxPos[1] === "undefined" || parseFloat(value[1]) > maxPos[1]) { + maxPos[1] = value[1]; + } + }); + var geom = (new ol.format.GeoJSON()).readGeometry(geojson, { + 'dataProjection': 'EPSG:4326', + 'featureProjection': 'EPSG:3857' + }); + var feature = new ol.Feature({ + 'geometry': geom + }); + feature.setStyle(routeLineStyle); + // feature.setId(index); + vectorS.addFeature(feature); + }); + }); + adjustViewBoundingBox(minPos, maxPos); + if(routeLayer !== null){ + // Remove old Features + map.removeLayer(routeLayer); + } + // Remove old Markers + $.each(routeMarkers, function(index, value){ + map.removeOverlay(value); + }); + // add Features + + routeLayer = new ol.layer.Vector({ + source: vectorS + }); + map.addLayer(routeLayer); + // We should add some Pins to the Waypoint Locations + $.each(route["waypoints"], function(index, value) { + // This will work upto an index of 25 + // Caharacter Representation of the index: + var chr = String.fromCharCode(65 + index); + // So now the Pin + var el = $('<span id="' + chr + '" class="marker">' + chr + '</span>'); + var pos = ol.proj.transform([parseFloat(value["location"][0]), parseFloat(value["location"][1])], 'EPSG:4326', 'EPSG:3857'); + routeMarkers.push(addMarker(el, pos)); + }); +} + +function startAssistent() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/js/routeAssistent.js', { + scope: '/' + }).then(function(reg) { + // registration worked + console.log('Registration succeeded. Scope is ' + reg.scope); + }).catch(function(error) { + // registration failed + console.error('Registration failed with ' + error); + }); + }; +} +var followingId = null; +var positions = []; +var drivenRoute = null; +var routeAssistentVectorSource = new ol.source.Vector(); +var waypoints = []; + +function startAssistent() { + if (gps && points.match(/^gps;/) !== null) { + positions = []; + initWaypoints(); + prepareInterface(); + initAssistentGraphics(); + reloadRoute(); + startLocationFollowing(); + } +} + +function initWaypoints() { + var result = points.split(';'); + $.each(result, function(index, value) { + if (value === 'gps') { + waypoints.push('gps'); + } else { + waypoints.push({ + hint: '', + lon: parseFloat(value.split(',')[0]), + lat: parseFloat(value.split(',')[1]) + }); + } + }); +} + +function initAssistentGraphics() { + // Set The Route Layer to the new one: + routeLayer.setSource(routeAssistentVectorSource); + // Remove old Markers + $.each(routeMarkers, function(index, value) { + map.removeOverlay(value); + }); +} + +function prepareInterface() { + // Change Navigation-Bar to make it show the next Steps + // Hide Navigation Bar + $("nav").removeClass("navbar"); + $("nav").removeClass("navbar-default"); + // Create the Layout for the next Step: + var nextStep = $('\ + <div id="route-content">\ + <div id="route-information" class="row">\ + <div id="length" class="col-xs-6"></div>\ + <div id="duration" class="col-xs-6"></div>\ + </div>\ + <table id="routing-steps" class="table">\ + <tbody>\ + <tr class="step">\ + <td class="step-image"><img height="35px" src=""></td>\ + <td class="step-string"></td>\ + <td class="step-length"></td>\ + </tr>\ + </tbody>\ + </table>\ + </div>\ + '); + $("nav").html(nextStep); + //Hide Results + deinitResults(); + // Remove Zoom Bar + $(".ol-zoom, .ol-zoomslider, #location-tool").addClass("hidden"); + //Update Map Size + updateMapSize(); +} + +function deinitAssistent() { + window.location.reload(); +} +var currentPosition = {}; +var calculating = false; + +function startLocationFollowing() { + if (followingId === null) { + options = { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0 + }; + followingId = navigator.geolocation.watchPosition(function(position) { + var timestamp = Math.floor(position.timestamp / 1000); + var lon = parseFloat(position.coords.longitude); + var lat = parseFloat(position.coords.latitude); + var accuracy = Math.max(position.coords.accuracy, 1.0); + var newPosition = { + timestamp: timestamp, + lon: lon, + lat: lat, + accuracy: accuracy + }; + var dist = getDistance(currentPosition, newPosition); + var minDist = 0; + switch (vehicle) { + case "car": + minDist = 20; + break; + case "bicycle": + minDist = 10; + break; + case "foot": + minDist = 5; + break; + } + if (dist <= minDist || calculating) { + return; + } else { + calculating = true; + } + currentPosition = { + timestamp: timestamp, + lon: lon, + lat: lat, + accuracy: accuracy + }; + positions.push({ + timestamp: timestamp, + lon: lon, + lat: lat, + accuracy: accuracy + }); + if (positions.length >= 2) { + // Generate url to retrieve + var positionstring = ""; + var timestampstring = ""; + var radiusstring = ""; + $.each(positions, function(index, value) { + positionstring += value.lon + "," + value.lat + ";"; + timestampstring += value.timestamp + ";"; + radiusstring += value.accuracy + ";"; + }); + positionstring = positionstring.replace(/;+$/, ''); + timestampstring = timestampstring.replace(/;+$/, ''); + radiusstring = radiusstring.replace(/;+$/, ''); + var url = '/route/match/' + vehicle + '/' + positionstring + '/' + timestampstring + '/' + radiusstring; + $.getJSON(url, function(r) { + // Aus den "gesnappten" Koordinaten berechnen wir nun die gefahrene Route + if (r.code === "Ok" && r.tracepoints !== null && r.tracepoints[r.tracepoints.length - 1] !== null) { + var position = { + hint: r.tracepoints[r.tracepoints.length - 1].hint, + lon: parseFloat(r.tracepoints[r.tracepoints.length - 1].location[0]), + lat: parseFloat(r.tracepoints[r.tracepoints.length - 1].location[1]) + } + // Wir nehmen vorerst immer das Beste Matching für die gefahrene Route. + // Vielleicht fällt mir später etwas besseres ein + if (r.matchings !== null && r.matchings[0] !== undefined) { + // Clear the shown Route + routeAssistentVectorSource.clear(); + // Add the driven Route + drawGeojson(r.matchings[0].geometry, 'grey'); + // Get the Route until the target + var hintsComplete = true; + var pointString = ""; + var hintString = ""; + $.each(waypoints, function(index, value) { + if (value === 'gps') { + pointString += position.lon + "," + position.lat + ";"; + hintString += position.hint + ";"; + } else { + pointString += value.lon + "," + value.lat + ";"; + if (value.hint !== "") { + hintString += value.hint + ";"; + } else { + hintsComplete = false; + } + } + }); + pointString = pointString.replace(/;$/, ''); + hintString = hintString.replace(/;$/, ''); + var url = '/route/find/' + vehicle + '/' + pointString; + if (hintsComplete) { + url += "/" + hintString; + } + $.getJSON(url, function(data) { + if (data.code === "Ok") { + drawGeojson(data.routes[0].geometry, 'red'); + // Add the information for the next steps and route meta data + // Route Metadata + var duration = parseFloat(data.routes[0].duration); + duration = parseDuration(duration) + $("nav #route-information #duration").html(duration); + var distance = parseFloat(data.routes[0].distance) + distance = parseDistance(distance); + $("nav #route-information #length").html(distance); + // Next Step + var step = data.routes[0].legs[0].steps[1]; + var stepString = parseManeuver(step["maneuver"], data.routes[0], 0, 1); + $("nav #routing-steps .step-string").html(stepString); + var img = parseImg(step); + $("nav #routing-steps .step-image img").attr('src', img); + var stepDistance = parseDistance(parseFloat(data.routes[0].legs[0].steps[0].distance)); + $("nav #routing-steps .step-length").html(stepDistance); + // So now that everything is Drawn we adjust the Map View + // The new Rotation is The bearing of the first step of the route: + var rotation = parseInt(data.routes[0].legs[0].steps[0].maneuver.bearing_after); + rotation = 360 - rotation; + // The Value needs to be in Radians + rotation *= Math.PI; + rotation /= 180; + map.getView().setRotation(rotation); + map.getView().setZoom(18); + map.getView().setCenter(ol.proj.transform([position.lon, position.lat], 'EPSG:4326', 'EPSG:3857')); + } + }).always(function() { + calculating = false; + }); + }else{ + calculating = false; + } + }else{ + calculating = false; + } + }); + }else{ + calculating = false; + } + }, function(error) { + // Follow Location couldn't be started. Abort now + deinitAssistent(); + }, options); + } +} +var rad = function(x) { + return x * Math.PI / 180; +}; +var getDistance = function(p1, p2) { + var R = 6378137; // Earth’s mean radius in meter + var dLat = rad(p2.lat - p1.lat); + var dLong = rad(p2.lon - p1.lon); + var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(rad(p1.lat)) * Math.cos(rad(p2.lat)) * Math.sin(dLong / 2) * Math.sin(dLong / 2); + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + var d = R * c; + return d; // returns the distance in meter +}; + +function removeRoute() { + if (routeLayer !== null) { + // Remove old Features + map.removeLayer(routeLayer); + } + // Remove old Markers + $.each(routeMarkers, function(index, value) { + map.removeOverlay(value); + }); + vectorS = new ol.source.Vector(); +} + +function drawGeojson(geojson, lineColor) { + var drivenRouteLineStyle = new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: lineColor, + width: 5 + }), + fill: new ol.style.Fill({ + color: lineColor + }) + }); + var geom = (new ol.format.GeoJSON()).readGeometry(geojson, { + 'dataProjection': 'EPSG:4326', + 'featureProjection': 'EPSG:3857' + }); + var feature = new ol.Feature({ + 'geometry': geom + }); + feature.setStyle(drivenRouteLineStyle); + routeAssistentVectorSource.addFeature(feature); +} + +function reloadRoute() { + if (points.match(/gps/) !== null) { + var pos = gpsLocation; + var pointString = points; + if (pos !== null) { + pointString = pointString.replace(/gps/, pos.toString()); + } else { + pointString = pointString.replace(/;{0,1}gps;{0,1}/, ';'); + } + } + var url = '/route/find/' + vehicle + '/' + pointString; + $.getJSON(url, function(response) { + route = response; + // Add The Hints to the waypoints: + $.each(route.waypoints, function(index, value) { + if (waypoints[index] !== 'gps') { + waypoints[index].hint = value.hint; + } + }); + }).success(function() { + //addGraphics(); + }); +} +//# sourceMappingURL=routing.js.map diff --git a/public/build/js/routing-fd33c92d44.js b/public/build/js/routing-fd33c92d44.js new file mode 100644 index 0000000..ecb8d41 --- /dev/null +++ b/public/build/js/routing-fd33c92d44.js @@ -0,0 +1,93950 @@ +/*! jQuery v1.11.2 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.2",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=hb(),z=hb(),A=hb(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},eb=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fb){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function gb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+rb(o[l]);w=ab.test(a)&&pb(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function hb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ib(a){return a[u]=!0,a}function jb(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function kb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function lb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function nb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function ob(a){return ib(function(b){return b=+b,ib(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pb(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=gb.support={},f=gb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=gb.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",eb,!1):e.attachEvent&&e.attachEvent("onunload",eb)),p=!f(g),c.attributes=jb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=jb(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=jb(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(jb(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\f]' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),jb(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&jb(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return lb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?lb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},gb.matches=function(a,b){return gb(a,null,null,b)},gb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return gb(b,n,null,[a]).length>0},gb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},gb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},gb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},gb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=gb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=gb.selectors={cacheLength:50,createPseudo:ib,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||gb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&gb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=gb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||gb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ib(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ib(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ib(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ib(function(a){return function(b){return gb(a,b).length>0}}),contains:ib(function(a){return a=a.replace(cb,db),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ib(function(a){return W.test(a||"")||gb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:ob(function(){return[0]}),last:ob(function(a,b){return[b-1]}),eq:ob(function(a,b,c){return[0>c?c+b:c]}),even:ob(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:ob(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:ob(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:ob(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=mb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=nb(b);function qb(){}qb.prototype=d.filters=d.pseudos,d.setFilters=new qb,g=gb.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?gb.error(a):z(a,i).slice(0)};function rb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function sb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function tb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ub(a,b,c){for(var d=0,e=b.length;e>d;d++)gb(a,b[d],c);return c}function vb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wb(a,b,c,d,e,f){return d&&!d[u]&&(d=wb(d)),e&&!e[u]&&(e=wb(e,f)),ib(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ub(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:vb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=vb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=vb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sb(function(a){return a===b},h,!0),l=sb(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sb(tb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wb(i>1&&tb(m),i>1&&rb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xb(a.slice(i,e)),f>e&&xb(a=a.slice(e)),f>e&&rb(a))}m.push(c)}return tb(m)}function yb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=vb(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&gb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ib(f):f}return h=gb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,yb(e,d)),f.selector=a}return f},i=gb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&pb(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&rb(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&pb(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=jb(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),jb(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||kb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&jb(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||kb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),jb(function(a){return null==a.getAttribute("disabled")})||kb(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),gb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1; +return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},fix:function(a){if(a[m.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=Z.test(e)?this.mouseHooks:Y.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new m.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=f.srcElement||y),3===a.target.nodeType&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,g.filter?g.filter(a,f):a},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button,g=b.fromElement;return null==a.pageX&&null!=b.clientX&&(d=a.target.ownerDocument||y,e=d.documentElement,c=d.body,a.pageX=b.clientX+(e&&e.scrollLeft||c&&c.scrollLeft||0)-(e&&e.clientLeft||c&&c.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||c&&c.scrollTop||0)-(e&&e.clientTop||c&&c.clientTop||0)),!a.relatedTarget&&g&&(a.relatedTarget=g===a.target?b.toElement:g),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==cb()&&this.focus)try{return this.focus(),!1}catch(a){}},delegateType:"focusin"},blur:{trigger:function(){return this===cb()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return m.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):void 0},_default:function(a){return m.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=m.extend(new m.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?m.event.trigger(e,null,b):m.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},m.removeEvent=y.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]===K&&(a[d]=null),a.detachEvent(d,c))},m.Event=function(a,b){return this instanceof m.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ab:bb):this.type=a,b&&m.extend(this,b),this.timeStamp=a&&a.timeStamp||m.now(),void(this[m.expando]=!0)):new m.Event(a,b)},m.Event.prototype={isDefaultPrevented:bb,isPropagationStopped:bb,isImmediatePropagationStopped:bb,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ab,a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ab,a&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ab,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},m.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){m.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!m.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.submitBubbles||(m.event.special.submit={setup:function(){return m.nodeName(this,"form")?!1:void m.event.add(this,"click._submit keypress._submit",function(a){var b=a.target,c=m.nodeName(b,"input")||m.nodeName(b,"button")?b.form:void 0;c&&!m._data(c,"submitBubbles")&&(m.event.add(c,"submit._submit",function(a){a._submit_bubble=!0}),m._data(c,"submitBubbles",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&m.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){return m.nodeName(this,"form")?!1:void m.event.remove(this,"._submit")}}),k.changeBubbles||(m.event.special.change={setup:function(){return X.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(m.event.add(this,"propertychange._change",function(a){"checked"===a.originalEvent.propertyName&&(this._just_changed=!0)}),m.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),m.event.simulate("change",this,a,!0)})),!1):void m.event.add(this,"beforeactivate._change",function(a){var b=a.target;X.test(b.nodeName)&&!m._data(b,"changeBubbles")&&(m.event.add(b,"change._change",function(a){!this.parentNode||a.isSimulated||a.isTrigger||m.event.simulate("change",this.parentNode,a,!0)}),m._data(b,"changeBubbles",!0))})},handle:function(a){var b=a.target;return this!==b||a.isSimulated||a.isTrigger||"radio"!==b.type&&"checkbox"!==b.type?a.handleObj.handler.apply(this,arguments):void 0},teardown:function(){return m.event.remove(this,"._change"),!X.test(this.nodeName)}}),k.focusinBubbles||m.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){m.event.simulate(b,a.target,m.event.fix(a),!0)};m.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=m._data(d,b);e||d.addEventListener(a,c,!0),m._data(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=m._data(d,b)-1;e?m._data(d,b,e):(d.removeEventListener(a,c,!0),m._removeData(d,b))}}}),m.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(f in a)this.on(f,b,c,a[f],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=bb;else if(!d)return this;return 1===e&&(g=d,d=function(a){return m().off(a),g.apply(this,arguments)},d.guid=g.guid||(g.guid=m.guid++)),this.each(function(){m.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,m(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=bb),this.each(function(){m.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){m.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?m.event.trigger(a,b,c,!0):void 0}});function db(a){var b=eb.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}var eb="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",fb=/ jQuery\d+="(?:null|\d+)"/g,gb=new RegExp("<(?:"+eb+")[\\s/>]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/<tbody/i,lb=/<|&#?\w+;/,mb=/<(?:script|style|link)/i,nb=/checked\s*(?:[^=]|=\s*.checked.)/i,ob=/^$|\/(?:java|ecma)script/i,pb=/^true\/(.*)/,qb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,rb={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:k.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1></$2>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?"<table>"!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=(Cb[0].contentWindow||Cb[0].contentDocument).document,b.write(),b.close(),c=Eb(a,b),Cb.detach()),Db[a]=c),c}!function(){var a;k.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,d;return c=y.getElementsByTagName("body")[0],c&&c.style?(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(y.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(d),a):void 0}}();var Gb=/^margin/,Hb=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ib,Jb,Kb=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ib=function(b){return b.ownerDocument.defaultView.opener?b.ownerDocument.defaultView.getComputedStyle(b,null):a.getComputedStyle(b,null)},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||m.contains(a.ownerDocument,a)||(g=m.style(a,b)),Hb.test(g)&&Gb.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):y.documentElement.currentStyle&&(Ib=function(a){return a.currentStyle},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Hb.test(g)&&!Kb.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function Lb(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h;if(b=y.createElement("div"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=d&&d.style){c.cssText="float:left;opacity:.5",k.opacity="0.5"===c.opacity,k.cssFloat=!!c.cssFloat,b.style.backgroundClip="content-box",b.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===b.style.backgroundClip,k.boxSizing=""===c.boxSizing||""===c.MozBoxSizing||""===c.WebkitBoxSizing,m.extend(k,{reliableHiddenOffsets:function(){return null==g&&i(),g},boxSizingReliable:function(){return null==f&&i(),f},pixelPosition:function(){return null==e&&i(),e},reliableMarginRight:function(){return null==h&&i(),h}});function i(){var b,c,d,i;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),b.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",e=f=!1,h=!0,a.getComputedStyle&&(e="1%"!==(a.getComputedStyle(b,null)||{}).top,f="4px"===(a.getComputedStyle(b,null)||{width:"4px"}).width,i=b.appendChild(y.createElement("div")),i.style.cssText=b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",b.style.width="1px",h=!parseFloat((a.getComputedStyle(i,null)||{}).marginRight),b.removeChild(i)),b.innerHTML="<table><tr><td></td><td>t</td></tr></table>",i=b.getElementsByTagName("td"),i[0].style.cssText="margin:0;border:0;padding:0;display:none",g=0===i[0].offsetHeight,g&&(i[0].style.display="",i[1].style.display="none",g=0===i[0].offsetHeight),c.removeChild(d))}}}(),m.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Mb=/alpha\([^)]*\)/i,Nb=/opacity\s*=\s*([^)]*)/,Ob=/^(none|table(?!-c[ea]).+)/,Pb=new RegExp("^("+S+")(.*)$","i"),Qb=new RegExp("^([+-])=("+S+")","i"),Rb={position:"absolute",visibility:"hidden",display:"block"},Sb={letterSpacing:"0",fontWeight:"400"},Tb=["Webkit","O","Moz","ms"];function Ub(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Tb.length;while(e--)if(b=Tb[e]+c,b in a)return b;return d}function Vb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=m._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&U(d)&&(f[g]=m._data(d,"olddisplay",Fb(d.nodeName)))):(e=U(d),(c&&"none"!==c||!e)&&m._data(d,"olddisplay",e?c:m.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Wb(a,b,c){var d=Pb.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Xb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=m.css(a,c+T[f],!0,e)),d?("content"===c&&(g-=m.css(a,"padding"+T[f],!0,e)),"margin"!==c&&(g-=m.css(a,"border"+T[f]+"Width",!0,e))):(g+=m.css(a,"padding"+T[f],!0,e),"padding"!==c&&(g+=m.css(a,"border"+T[f]+"Width",!0,e)));return g}function Yb(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ib(a),g=k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Jb(a,b,f),(0>e||null==e)&&(e=a.style[b]),Hb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Xb(a,b,c||(g?"border":"content"),d,f)+"px"}m.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Jb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":k.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=m.camelCase(b),i=a.style;if(b=m.cssProps[h]||(m.cssProps[h]=Ub(i,h)),g=m.cssHooks[b]||m.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Qb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(m.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||m.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=m.camelCase(b);return b=m.cssProps[h]||(m.cssProps[h]=Ub(a.style,h)),g=m.cssHooks[b]||m.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Jb(a,b,d)),"normal"===f&&b in Sb&&(f=Sb[b]),""===c||c?(e=parseFloat(f),c===!0||m.isNumeric(e)?e||0:f):f}}),m.each(["height","width"],function(a,b){m.cssHooks[b]={get:function(a,c,d){return c?Ob.test(m.css(a,"display"))&&0===a.offsetWidth?m.swap(a,Rb,function(){return Yb(a,b,d)}):Yb(a,b,d):void 0},set:function(a,c,d){var e=d&&Ib(a);return Wb(a,c,d?Xb(a,b,d,k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,e),e):0)}}}),k.opacity||(m.cssHooks.opacity={get:function(a,b){return Nb.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=m.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===m.trim(f.replace(Mb,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Mb.test(f)?f.replace(Mb,e):f+" "+e)}}),m.cssHooks.marginRight=Lb(k.reliableMarginRight,function(a,b){return b?m.swap(a,{display:"inline-block"},Jb,[a,"marginRight"]):void 0}),m.each({margin:"",padding:"",border:"Width"},function(a,b){m.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+T[d]+b]=f[d]||f[d-2]||f[0];return e}},Gb.test(a)||(m.cssHooks[a+b].set=Wb)}),m.fn.extend({css:function(a,b){return V(this,function(a,b,c){var d,e,f={},g=0;if(m.isArray(b)){for(d=Ib(a),e=b.length;e>g;g++)f[b[g]]=m.css(a,b[g],!1,d);return f}return void 0!==c?m.style(a,b,c):m.css(a,b)},a,b,arguments.length>1)},show:function(){return Vb(this,!0)},hide:function(){return Vb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){U(this)?m(this).show():m(this).hide()})}});function Zb(a,b,c,d,e){return new Zb.prototype.init(a,b,c,d,e) +}m.Tween=Zb,Zb.prototype={constructor:Zb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")},cur:function(){var a=Zb.propHooks[this.prop];return a&&a.get?a.get(this):Zb.propHooks._default.get(this)},run:function(a){var b,c=Zb.propHooks[this.prop];return this.pos=b=this.options.duration?m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Zb.propHooks._default.set(this),this}},Zb.prototype.init.prototype=Zb.prototype,Zb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Zb.propHooks.scrollTop=Zb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Zb.prototype.init,m.fx.step={};var $b,_b,ac=/^(?:toggle|show|hide)$/,bc=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cc=/queueHooks$/,dc=[ic],ec={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bc.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bc.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fc(){return setTimeout(function(){$b=void 0}),$b=m.now()}function gc(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hc(a,b,c){for(var d,e=(ec[b]||[]).concat(ec["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ic(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fb(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fb(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ac.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fb(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hc(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jc(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kc(a,b,c){var d,e,f=0,g=dc.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$b||fc(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$b||fc(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jc(k,j.opts.specialEasing);g>f;f++)if(d=dc[f].call(j,a,k,j.opts))return d;return m.map(k,hc,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kc,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],ec[c]=ec[c]||[],ec[c].unshift(b)},prefilter:function(a,b){b?dc.unshift(a):dc.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in m.fx.speeds?m.fx.speeds[d.duration]:m.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){m.isFunction(d.old)&&d.old.call(this),d.queue&&m.dequeue(this,d.queue)},d},m.fn.extend({fadeTo:function(a,b,c,d){return this.filter(U).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=m.isEmptyObject(a),f=m.speed(b,c,d),g=function(){var b=kc(this,m.extend({},a),f);(e||m._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=m.timers,g=m._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&cc.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&m.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=m._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=m.timers,g=d?d.length:0;for(c.finish=!0,m.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),m.each(["toggle","show","hide"],function(a,b){var c=m.fn[b];m.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gc(b,!0),a,d,e)}}),m.each({slideDown:gc("show"),slideUp:gc("hide"),slideToggle:gc("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){m.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),m.timers=[],m.fx.tick=function(){var a,b=m.timers,c=0;for($b=m.now();c<b.length;c++)a=b[c],a()||b[c]!==a||b.splice(c--,1);b.length||m.fx.stop(),$b=void 0},m.fx.timer=function(a){m.timers.push(a),a()?m.fx.start():m.timers.pop()},m.fx.interval=13,m.fx.start=function(){_b||(_b=setInterval(m.fx.tick,m.fx.interval))},m.fx.stop=function(){clearInterval(_b),_b=null},m.fx.speeds={slow:600,fast:200,_default:400},m.fn.delay=function(a,b){return a=m.fx?m.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a,b,c,d,e;b=y.createElement("div"),b.setAttribute("className","t"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=y.createElement("select"),e=c.appendChild(y.createElement("option")),a=b.getElementsByTagName("input")[0],d.style.cssText="top:1px",k.getSetAttribute="t"!==b.className,k.style=/top/.test(d.getAttribute("style")),k.hrefNormalized="/a"===d.getAttribute("href"),k.checkOn=!!a.value,k.optSelected=e.selected,k.enctype=!!y.createElement("form").enctype,c.disabled=!0,k.optDisabled=!e.disabled,a=y.createElement("input"),a.setAttribute("value",""),k.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),k.radioValue="t"===a.value}();var lc=/\r/g;m.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=m.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,m(this).val()):a,null==e?e="":"number"==typeof e?e+="":m.isArray(e)&&(e=m.map(e,function(a){return null==a?"":a+""})),b=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=m.valHooks[e.type]||m.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(lc,""):null==c?"":c)}}}),m.extend({valHooks:{option:{get:function(a){var b=m.find.attr(a,"value");return null!=b?b:m.trim(m.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&m.nodeName(c.parentNode,"optgroup"))){if(b=m(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=m.makeArray(b),g=e.length;while(g--)if(d=e[g],m.inArray(m.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(a,b){return m.isArray(b)?a.checked=m.inArray(m(a).val(),b)>=0:void 0}},k.checkOn||(m.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var mc,nc,oc=m.expr.attrHandle,pc=/^(?:checked|selected)$/i,qc=k.getSetAttribute,rc=k.input;m.fn.extend({attr:function(a,b){return V(this,m.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){m.removeAttr(this,a)})}}),m.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===K?m.prop(a,b,c):(1===f&&m.isXMLDoc(a)||(b=b.toLowerCase(),d=m.attrHooks[b]||(m.expr.match.bool.test(b)?nc:mc)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=m.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void m.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=m.propFix[c]||c,m.expr.match.bool.test(c)?rc&&qc||!pc.test(c)?a[d]=!1:a[m.camelCase("default-"+c)]=a[d]=!1:m.attr(a,c,""),a.removeAttribute(qc?c:d)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&m.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),nc={set:function(a,b,c){return b===!1?m.removeAttr(a,c):rc&&qc||!pc.test(c)?a.setAttribute(!qc&&m.propFix[c]||c,c):a[m.camelCase("default-"+c)]=a[c]=!0,c}},m.each(m.expr.match.bool.source.match(/\w+/g),function(a,b){var c=oc[b]||m.find.attr;oc[b]=rc&&qc||!pc.test(b)?function(a,b,d){var e,f;return d||(f=oc[b],oc[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,oc[b]=f),e}:function(a,b,c){return c?void 0:a[m.camelCase("default-"+b)]?b.toLowerCase():null}}),rc&&qc||(m.attrHooks.value={set:function(a,b,c){return m.nodeName(a,"input")?void(a.defaultValue=b):mc&&mc.set(a,b,c)}}),qc||(mc={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},oc.id=oc.name=oc.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},m.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:mc.set},m.attrHooks.contenteditable={set:function(a,b,c){mc.set(a,""===b?!1:b,c)}},m.each(["width","height"],function(a,b){m.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),k.style||(m.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var sc=/^(?:input|select|textarea|button|object)$/i,tc=/^(?:a|area)$/i;m.fn.extend({prop:function(a,b){return V(this,m.prop,a,b,arguments.length>1)},removeProp:function(a){return a=m.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),m.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!m.isXMLDoc(a),f&&(b=m.propFix[b]||b,e=m.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=m.find.attr(a,"tabindex");return b?parseInt(b,10):sc.test(a.nodeName)||tc.test(a.nodeName)&&a.href?0:-1}}}}),k.hrefNormalized||m.each(["href","src"],function(a,b){m.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),k.optSelected||(m.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),k.enctype||(m.propFix.enctype="encoding");var uc=/[\t\r\n\f]/g;m.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?m.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(m.isFunction(a)?function(c){m(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=m(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===K||"boolean"===c)&&(this.className&&m._data(this,"__className__",this.className),this.className=this.className||a===!1?"":m._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(uc," ").indexOf(b)>=0)return!0;return!1}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){m.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),m.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var vc=m.now(),wc=/\?/,xc=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;m.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=m.trim(b+"");return e&&!m.trim(e.replace(xc,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():m.error("Invalid JSON: "+b)},m.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+b),c};var yc,zc,Ac=/#.*$/,Bc=/([?&])_=[^&]*/,Cc=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Dc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ec=/^(?:GET|HEAD)$/,Fc=/^\/\//,Gc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hc={},Ic={},Jc="*/".concat("*");try{zc=location.href}catch(Kc){zc=y.createElement("a"),zc.href="",zc=zc.href}yc=Gc.exec(zc.toLowerCase())||[];function Lc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(m.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Mc(a,b,c,d){var e={},f=a===Ic;function g(h){var i;return e[h]=!0,m.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Nc(a,b){var c,d,e=m.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&m.extend(!0,a,c),a}function Oc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Pc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:zc,type:"GET",isLocal:Dc.test(yc[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Jc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Nc(Nc(a,m.ajaxSettings),b):Nc(m.ajaxSettings,a)},ajaxPrefilter:Lc(Hc),ajaxTransport:Lc(Ic),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=m.ajaxSetup({},b),l=k.context||k,n=k.context&&(l.nodeType||l.jquery)?m(l):m.event,o=m.Deferred(),p=m.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Cc.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||zc)+"").replace(Ac,"").replace(Fc,yc[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=m.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(c=Gc.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===yc[1]&&c[2]===yc[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(yc[3]||("http:"===yc[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=m.param(k.data,k.traditional)),Mc(Hc,k,b,v),2===t)return v;h=m.event&&k.global,h&&0===m.active++&&m.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Ec.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(wc.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Bc.test(e)?e.replace(Bc,"$1_="+vc++):e+(wc.test(e)?"&":"?")+"_="+vc++)),k.ifModified&&(m.lastModified[e]&&v.setRequestHeader("If-Modified-Since",m.lastModified[e]),m.etag[e]&&v.setRequestHeader("If-None-Match",m.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Jc+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Mc(Ic,k,b,v)){v.readyState=1,h&&n.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Oc(k,v,c)),u=Pc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(m.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(m.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&n.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(n.trigger("ajaxComplete",[v,k]),--m.active||m.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return m.get(a,b,c,"json")},getScript:function(a,b){return m.get(a,void 0,b,"script")}}),m.each(["get","post"],function(a,b){m[b]=function(a,c,d,e){return m.isFunction(c)&&(e=e||d,d=c,c=void 0),m.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),m._evalUrl=function(a){return m.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},m.fn.extend({wrapAll:function(a){if(m.isFunction(a))return this.each(function(b){m(this).wrapAll(a.call(this,b))});if(this[0]){var b=m(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(m.isFunction(a)?function(b){m(this).wrapInner(a.call(this,b))}:function(){var b=m(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=m.isFunction(a);return this.each(function(c){m(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!k.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||m.css(a,"display"))},m.expr.filters.visible=function(a){return!m.expr.filters.hidden(a)};var Qc=/%20/g,Rc=/\[\]$/,Sc=/\r?\n/g,Tc=/^(?:submit|button|image|reset|file)$/i,Uc=/^(?:input|select|textarea|keygen)/i;function Vc(a,b,c,d){var e;if(m.isArray(b))m.each(b,function(b,e){c||Rc.test(a)?d(a,e):Vc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==m.type(b))d(a,b);else for(e in b)Vc(a+"["+e+"]",b[e],c,d)}m.param=function(a,b){var c,d=[],e=function(a,b){b=m.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(a)||a.jquery&&!m.isPlainObject(a))m.each(a,function(){e(this.name,this.value)});else for(c in a)Vc(c,a[c],b,e);return d.join("&").replace(Qc,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=m.prop(this,"elements");return a?m.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!m(this).is(":disabled")&&Uc.test(this.nodeName)&&!Tc.test(a)&&(this.checked||!W.test(a))}).map(function(a,b){var c=m(this).val();return null==c?null:m.isArray(c)?m.map(c,function(a){return{name:b.name,value:a.replace(Sc,"\r\n")}}):{name:b.name,value:c.replace(Sc,"\r\n")}}).get()}}),m.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zc()||$c()}:Zc;var Wc=0,Xc={},Yc=m.ajaxSettings.xhr();a.attachEvent&&a.attachEvent("onunload",function(){for(var a in Xc)Xc[a](void 0,!0)}),k.cors=!!Yc&&"withCredentials"in Yc,Yc=k.ajax=!!Yc,Yc&&m.ajaxTransport(function(a){if(!a.crossDomain||k.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Wc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Xc[g],b=void 0,f.onreadystatechange=m.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Xc[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function Zc(){try{return new a.XMLHttpRequest}catch(b){}}function $c(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return m.globalEval(a),a}}}),m.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),m.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=y.head||m("head")[0]||y.documentElement;return{send:function(d,e){b=y.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var _c=[],ad=/(=)\?(?=&|$)|\?\?/;m.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=_c.pop()||m.expando+"_"+vc++;return this[a]=!0,a}}),m.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(ad.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&ad.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=m.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(ad,"$1"+e):b.jsonp!==!1&&(b.url+=(wc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||m.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,_c.push(e)),g&&m.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),m.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||y;var d=u.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=m.buildFragment([a],b,e),e&&e.length&&m(e).remove(),m.merge([],d.childNodes))};var bd=m.fn.load;m.fn.load=function(a,b,c){if("string"!=typeof a&&bd)return bd.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=m.trim(a.slice(h,a.length)),a=a.slice(0,h)),m.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&m.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?m("<div>").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cd=a.document.documentElement;function dd(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dd(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cd;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cd})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dd(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=Lb(k.pixelPosition,function(a,c){return c?(c=Jb(a,b),Hb.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ed=a.jQuery,fd=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fd),b&&a.jQuery===m&&(a.jQuery=ed),m},typeof b===K&&(a.jQuery=a.$=m),m}); + +/*! + * Bootstrap v3.3.4 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.4",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.4",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.4",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.4",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27|32)/.test(b.which)&&!/input|textarea/i.test(b.target.tagName)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g&&27!=b.which||g&&27==b.which)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(b.target);38==b.which&&j>0&&j--,40==b.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="menu"]',g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="listbox"]',g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in").attr("aria-hidden",!1),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a('<div class="modal-backdrop '+e+'" />').appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.init("tooltip",a,b)};c.VERSION="3.3.4",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport),this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-m<p.top?"bottom":"right"==h&&k.right+l>p.width?"left":"left"==h&&k.left-l<p.left?"right":h,f.removeClass(n).addClass(h)}var q=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(q,h);var r=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",r).emulateTransitionEnd(c.TRANSITION_DURATION):r()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top=b.top+g,b.left=b.left+h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);return this.$element.trigger(g),g.isDefaultPrevented()?void 0:(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this)},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.4",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.4",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.4",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){ +var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.4",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a(document.body).height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); +// OpenLayers 3. See https://openlayers.org/ +// License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md +// Version: v3.20.1 +;(function (root, factory) { + if (typeof exports === "object") { + module.exports = factory(); + } else if (typeof define === "function" && define.amd) { + define([], factory); + } else { + root.ol = factory(); + } +}(this, function () { + var OPENLAYERS = {}; + var goog = this.goog = {}; +this.CLOSURE_NO_DEPS = true; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Bootstrap for the Google JS Library (Closure). + * + * In uncompiled mode base.js will write out Closure's deps file, unless the + * global <code>CLOSURE_NO_DEPS</code> is set to true. This allows projects to + * include their own deps file(s) from different locations. + * + * @author arv@google.com (Erik Arvidsson) + * + * @provideGoog + */ + + +/** + * @define {boolean} Overridden to true by the compiler when + * --process_closure_primitives is specified. + */ +var COMPILED = false; + + +/** + * Base namespace for the Closure library. Checks to see goog is already + * defined in the current scope before assigning to prevent clobbering if + * base.js is loaded more than once. + * + * @const + */ +var goog = goog || {}; + + +/** + * Reference to the global context. In most cases this will be 'window'. + */ +goog.global = this; + + +/** + * A hook for overriding the define values in uncompiled mode. + * + * In uncompiled mode, {@code CLOSURE_UNCOMPILED_DEFINES} may be defined before + * loading base.js. If a key is defined in {@code CLOSURE_UNCOMPILED_DEFINES}, + * {@code goog.define} will use the value instead of the default value. This + * allows flags to be overwritten without compilation (this is normally + * accomplished with the compiler's "define" flag). + * + * Example: + * <pre> + * var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false}; + * </pre> + * + * @type {Object<string, (string|number|boolean)>|undefined} + */ +goog.global.CLOSURE_UNCOMPILED_DEFINES; + + +/** + * A hook for overriding the define values in uncompiled or compiled mode, + * like CLOSURE_UNCOMPILED_DEFINES but effective in compiled code. In + * uncompiled code CLOSURE_UNCOMPILED_DEFINES takes precedence. + * + * Also unlike CLOSURE_UNCOMPILED_DEFINES the values must be number, boolean or + * string literals or the compiler will emit an error. + * + * While any @define value may be set, only those set with goog.define will be + * effective for uncompiled code. + * + * Example: + * <pre> + * var CLOSURE_DEFINES = {'goog.DEBUG': false} ; + * </pre> + * + * @type {Object<string, (string|number|boolean)>|undefined} + */ +goog.global.CLOSURE_DEFINES; + + +/** + * Returns true if the specified value is not undefined. + * WARNING: Do not use this to test if an object has a property. Use the in + * operator instead. + * + * @param {?} val Variable to test. + * @return {boolean} Whether variable is defined. + */ +goog.isDef = function(val) { + // void 0 always evaluates to undefined and hence we do not need to depend on + // the definition of the global variable named 'undefined'. + return val !== void 0; +}; + + +/** + * Builds an object structure for the provided namespace path, ensuring that + * names that already exist are not overwritten. For example: + * "a.b.c" -> a = {};a.b={};a.b.c={}; + * Used by goog.provide and goog.exportSymbol. + * @param {string} name name of the object that this file defines. + * @param {*=} opt_object the object to expose at the end of the path. + * @param {Object=} opt_objectToExportTo The object to add the path to; default + * is |goog.global|. + * @private + */ +goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) { + var parts = name.split('.'); + var cur = opt_objectToExportTo || goog.global; + + // Internet Explorer exhibits strange behavior when throwing errors from + // methods externed in this manner. See the testExportSymbolExceptions in + // base_test.html for an example. + if (!(parts[0] in cur) && cur.execScript) { + cur.execScript('var ' + parts[0]); + } + + // Certain browsers cannot parse code in the form for((a in b); c;); + // This pattern is produced by the JSCompiler when it collapses the + // statement above into the conditional loop below. To prevent this from + // happening, use a for-loop and reserve the init logic as below. + + // Parentheses added to eliminate strict JS warning in Firefox. + for (var part; parts.length && (part = parts.shift());) { + if (!parts.length && goog.isDef(opt_object)) { + // last part and we have an object; use it + cur[part] = opt_object; + } else if (cur[part]) { + cur = cur[part]; + } else { + cur = cur[part] = {}; + } + } +}; + + +/** + * Defines a named value. In uncompiled mode, the value is retrieved from + * CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and + * has the property specified, and otherwise used the defined defaultValue. + * When compiled the default can be overridden using the compiler + * options or the value set in the CLOSURE_DEFINES object. + * + * @param {string} name The distinguished name to provide. + * @param {string|number|boolean} defaultValue + */ +goog.define = function(name, defaultValue) { + var value = defaultValue; + if (!COMPILED) { + if (goog.global.CLOSURE_UNCOMPILED_DEFINES && + Object.prototype.hasOwnProperty.call( + goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) { + value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name]; + } else if ( + goog.global.CLOSURE_DEFINES && + Object.prototype.hasOwnProperty.call( + goog.global.CLOSURE_DEFINES, name)) { + value = goog.global.CLOSURE_DEFINES[name]; + } + } + goog.exportPath_(name, value); +}; + + +/** + * @define {boolean} DEBUG is provided as a convenience so that debugging code + * that should not be included in a production js_binary can be easily stripped + * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most + * toString() methods should be declared inside an "if (goog.DEBUG)" conditional + * because they are generally used for debugging purposes and it is difficult + * for the JSCompiler to statically determine whether they are used. + */ +goog.define('goog.DEBUG', true); + + +/** + * @define {string} LOCALE defines the locale being used for compilation. It is + * used to select locale specific data to be compiled in js binary. BUILD rule + * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler + * option. + * + * Take into account that the locale code format is important. You should use + * the canonical Unicode format with hyphen as a delimiter. Language must be + * lowercase, Language Script - Capitalized, Region - UPPERCASE. + * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN. + * + * See more info about locale codes here: + * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers + * + * For language codes you should use values defined by ISO 693-1. See it here + * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from + * this rule: the Hebrew language. For legacy reasons the old code (iw) should + * be used instead of the new code (he), see http://wiki/Main/IIISynonyms. + */ +goog.define('goog.LOCALE', 'en'); // default to en + + +/** + * @define {boolean} Whether this code is running on trusted sites. + * + * On untrusted sites, several native functions can be defined or overridden by + * external libraries like Prototype, Datejs, and JQuery and setting this flag + * to false forces closure to use its own implementations when possible. + * + * If your JavaScript can be loaded by a third party site and you are wary about + * relying on non-standard implementations, specify + * "--define goog.TRUSTED_SITE=false" to the JSCompiler. + */ +goog.define('goog.TRUSTED_SITE', true); + + +/** + * @define {boolean} Whether a project is expected to be running in strict mode. + * + * This define can be used to trigger alternate implementations compatible with + * running in EcmaScript Strict mode or warn about unavailable functionality. + * @see https://goo.gl/PudQ4y + * + */ +goog.define('goog.STRICT_MODE_COMPATIBLE', false); + + +/** + * @define {boolean} Whether code that calls {@link goog.setTestOnly} should + * be disallowed in the compilation unit. + */ +goog.define('goog.DISALLOW_TEST_ONLY_CODE', COMPILED && !goog.DEBUG); + + +/** + * @define {boolean} Whether to use a Chrome app CSP-compliant method for + * loading scripts via goog.require. @see appendScriptSrcNode_. + */ +goog.define('goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING', false); + + +/** + * Defines a namespace in Closure. + * + * A namespace may only be defined once in a codebase. It may be defined using + * goog.provide() or goog.module(). + * + * The presence of one or more goog.provide() calls in a file indicates + * that the file defines the given objects/namespaces. + * Provided symbols must not be null or undefined. + * + * In addition, goog.provide() creates the object stubs for a namespace + * (for example, goog.provide("goog.foo.bar") will create the object + * goog.foo.bar if it does not already exist). + * + * Build tools also scan for provide/require/module statements + * to discern dependencies, build dependency files (see deps.js), etc. + * + * @see goog.require + * @see goog.module + * @param {string} name Namespace provided by this file in the form + * "goog.package.part". + */ +goog.provide = function(name) { + if (goog.isInModuleLoader_()) { + throw Error('goog.provide can not be used within a goog.module.'); + } + if (!COMPILED) { + // Ensure that the same namespace isn't provided twice. + // A goog.module/goog.provide maps a goog.require to a specific file + if (goog.isProvided_(name)) { + throw Error('Namespace "' + name + '" already declared.'); + } + } + + goog.constructNamespace_(name); +}; + + +/** + * @param {string} name Namespace provided by this file in the form + * "goog.package.part". + * @param {Object=} opt_obj The object to embed in the namespace. + * @private + */ +goog.constructNamespace_ = function(name, opt_obj) { + if (!COMPILED) { + delete goog.implicitNamespaces_[name]; + + var namespace = name; + while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) { + if (goog.getObjectByName(namespace)) { + break; + } + goog.implicitNamespaces_[namespace] = true; + } + } + + goog.exportPath_(name, opt_obj); +}; + + +/** + * Module identifier validation regexp. + * Note: This is a conservative check, it is very possible to be more lenient, + * the primary exclusion here is "/" and "\" and a leading ".", these + * restrictions are intended to leave the door open for using goog.require + * with relative file paths rather than module identifiers. + * @private + */ +goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/; + + +/** + * Defines a module in Closure. + * + * Marks that this file must be loaded as a module and claims the namespace. + * + * A namespace may only be defined once in a codebase. It may be defined using + * goog.provide() or goog.module(). + * + * goog.module() has three requirements: + * - goog.module may not be used in the same file as goog.provide. + * - goog.module must be the first statement in the file. + * - only one goog.module is allowed per file. + * + * When a goog.module annotated file is loaded, it is enclosed in + * a strict function closure. This means that: + * - any variables declared in a goog.module file are private to the file + * (not global), though the compiler is expected to inline the module. + * - The code must obey all the rules of "strict" JavaScript. + * - the file will be marked as "use strict" + * + * NOTE: unlike goog.provide, goog.module does not declare any symbols by + * itself. If declared symbols are desired, use + * goog.module.declareLegacyNamespace(). + * + * + * See the public goog.module proposal: http://goo.gl/Va1hin + * + * @param {string} name Namespace provided by this file in the form + * "goog.package.part", is expected but not required. + */ +goog.module = function(name) { + if (!goog.isString(name) || !name || + name.search(goog.VALID_MODULE_RE_) == -1) { + throw Error('Invalid module identifier'); + } + if (!goog.isInModuleLoader_()) { + throw Error('Module ' + name + ' has been loaded incorrectly.'); + } + if (goog.moduleLoaderState_.moduleName) { + throw Error('goog.module may only be called once per module.'); + } + + // Store the module name for the loader. + goog.moduleLoaderState_.moduleName = name; + if (!COMPILED) { + // Ensure that the same namespace isn't provided twice. + // A goog.module/goog.provide maps a goog.require to a specific file + if (goog.isProvided_(name)) { + throw Error('Namespace "' + name + '" already declared.'); + } + delete goog.implicitNamespaces_[name]; + } +}; + + +/** + * @param {string} name The module identifier. + * @return {?} The module exports for an already loaded module or null. + * + * Note: This is not an alternative to goog.require, it does not + * indicate a hard dependency, instead it is used to indicate + * an optional dependency or to access the exports of a module + * that has already been loaded. + * @suppress {missingProvide} + */ +goog.module.get = function(name) { + return goog.module.getInternal_(name); +}; + + +/** + * @param {string} name The module identifier. + * @return {?} The module exports for an already loaded module or null. + * @private + */ +goog.module.getInternal_ = function(name) { + if (!COMPILED) { + if (goog.isProvided_(name)) { + // goog.require only return a value with-in goog.module files. + return name in goog.loadedModules_ ? goog.loadedModules_[name] : + goog.getObjectByName(name); + } else { + return null; + } + } +}; + + +/** + * @private {?{moduleName: (string|undefined), declareLegacyNamespace:boolean}} + */ +goog.moduleLoaderState_ = null; + + +/** + * @private + * @return {boolean} Whether a goog.module is currently being initialized. + */ +goog.isInModuleLoader_ = function() { + return goog.moduleLoaderState_ != null; +}; + + +/** + * Provide the module's exports as a globally accessible object under the + * module's declared name. This is intended to ease migration to goog.module + * for files that have existing usages. + * @suppress {missingProvide} + */ +goog.module.declareLegacyNamespace = function() { + if (!COMPILED && !goog.isInModuleLoader_()) { + throw new Error( + 'goog.module.declareLegacyNamespace must be called from ' + + 'within a goog.module'); + } + if (!COMPILED && !goog.moduleLoaderState_.moduleName) { + throw Error( + 'goog.module must be called prior to ' + + 'goog.module.declareLegacyNamespace.'); + } + goog.moduleLoaderState_.declareLegacyNamespace = true; +}; + + +/** + * Marks that the current file should only be used for testing, and never for + * live code in production. + * + * In the case of unit tests, the message may optionally be an exact namespace + * for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra + * provide (if not explicitly defined in the code). + * + * @param {string=} opt_message Optional message to add to the error that's + * raised when used in production code. + */ +goog.setTestOnly = function(opt_message) { + if (goog.DISALLOW_TEST_ONLY_CODE) { + opt_message = opt_message || ''; + throw Error( + 'Importing test-only code into non-debug environment' + + (opt_message ? ': ' + opt_message : '.')); + } +}; + + +/** + * Forward declares a symbol. This is an indication to the compiler that the + * symbol may be used in the source yet is not required and may not be provided + * in compilation. + * + * The most common usage of forward declaration is code that takes a type as a + * function parameter but does not need to require it. By forward declaring + * instead of requiring, no hard dependency is made, and (if not required + * elsewhere) the namespace may never be required and thus, not be pulled + * into the JavaScript binary. If it is required elsewhere, it will be type + * checked as normal. + * + * + * @param {string} name The namespace to forward declare in the form of + * "goog.package.part". + */ +goog.forwardDeclare = function(name) {}; + + +/** + * Forward declare type information. Used to assign types to goog.global + * referenced object that would otherwise result in unknown type references + * and thus block property disambiguation. + */ +goog.forwardDeclare('Document'); +goog.forwardDeclare('HTMLScriptElement'); +goog.forwardDeclare('XMLHttpRequest'); + + +if (!COMPILED) { + /** + * Check if the given name has been goog.provided. This will return false for + * names that are available only as implicit namespaces. + * @param {string} name name of the object to look for. + * @return {boolean} Whether the name has been provided. + * @private + */ + goog.isProvided_ = function(name) { + return (name in goog.loadedModules_) || + (!goog.implicitNamespaces_[name] && + goog.isDefAndNotNull(goog.getObjectByName(name))); + }; + + /** + * Namespaces implicitly defined by goog.provide. For example, + * goog.provide('goog.events.Event') implicitly declares that 'goog' and + * 'goog.events' must be namespaces. + * + * @type {!Object<string, (boolean|undefined)>} + * @private + */ + goog.implicitNamespaces_ = {'goog.module': true}; + + // NOTE: We add goog.module as an implicit namespace as goog.module is defined + // here and because the existing module package has not been moved yet out of + // the goog.module namespace. This satisifies both the debug loader and + // ahead-of-time dependency management. +} + + +/** + * Returns an object based on its fully qualified external name. The object + * is not found if null or undefined. If you are using a compilation pass that + * renames property names beware that using this function will not find renamed + * properties. + * + * @param {string} name The fully qualified name. + * @param {Object=} opt_obj The object within which to look; default is + * |goog.global|. + * @return {?} The value (object or primitive) or, if not found, null. + */ +goog.getObjectByName = function(name, opt_obj) { + var parts = name.split('.'); + var cur = opt_obj || goog.global; + for (var part; part = parts.shift();) { + if (goog.isDefAndNotNull(cur[part])) { + cur = cur[part]; + } else { + return null; + } + } + return cur; +}; + + +/** + * Globalizes a whole namespace, such as goog or goog.lang. + * + * @param {!Object} obj The namespace to globalize. + * @param {Object=} opt_global The object to add the properties to. + * @deprecated Properties may be explicitly exported to the global scope, but + * this should no longer be done in bulk. + */ +goog.globalize = function(obj, opt_global) { + var global = opt_global || goog.global; + for (var x in obj) { + global[x] = obj[x]; + } +}; + + +/** + * Adds a dependency from a file to the files it requires. + * @param {string} relPath The path to the js file. + * @param {!Array<string>} provides An array of strings with + * the names of the objects this file provides. + * @param {!Array<string>} requires An array of strings with + * the names of the objects this file requires. + * @param {boolean|!Object<string>=} opt_loadFlags Parameters indicating + * how the file must be loaded. The boolean 'true' is equivalent + * to {'module': 'goog'} for backwards-compatibility. Valid properties + * and values include {'module': 'goog'} and {'lang': 'es6'}. + */ +goog.addDependency = function(relPath, provides, requires, opt_loadFlags) { + if (goog.DEPENDENCIES_ENABLED) { + var provide, require; + var path = relPath.replace(/\\/g, '/'); + var deps = goog.dependencies_; + if (!opt_loadFlags || typeof opt_loadFlags === 'boolean') { + opt_loadFlags = opt_loadFlags ? {'module': 'goog'} : {}; + } + for (var i = 0; provide = provides[i]; i++) { + deps.nameToPath[provide] = path; + deps.loadFlags[path] = opt_loadFlags; + } + for (var j = 0; require = requires[j]; j++) { + if (!(path in deps.requires)) { + deps.requires[path] = {}; + } + deps.requires[path][require] = true; + } + } +}; + + + + +// NOTE(nnaze): The debug DOM loader was included in base.js as an original way +// to do "debug-mode" development. The dependency system can sometimes be +// confusing, as can the debug DOM loader's asynchronous nature. +// +// With the DOM loader, a call to goog.require() is not blocking -- the script +// will not load until some point after the current script. If a namespace is +// needed at runtime, it needs to be defined in a previous script, or loaded via +// require() with its registered dependencies. +// +// User-defined namespaces may need their own deps file. For a reference on +// creating a deps file, see: +// Externally: https://developers.google.com/closure/library/docs/depswriter +// +// Because of legacy clients, the DOM loader can't be easily removed from +// base.js. Work is being done to make it disableable or replaceable for +// different environments (DOM-less JavaScript interpreters like Rhino or V8, +// for example). See bootstrap/ for more information. + + +/** + * @define {boolean} Whether to enable the debug loader. + * + * If enabled, a call to goog.require() will attempt to load the namespace by + * appending a script tag to the DOM (if the namespace has been registered). + * + * If disabled, goog.require() will simply assert that the namespace has been + * provided (and depend on the fact that some outside tool correctly ordered + * the script). + */ +goog.define('goog.ENABLE_DEBUG_LOADER', true); + + +/** + * @param {string} msg + * @private + */ +goog.logToConsole_ = function(msg) { + if (goog.global.console) { + goog.global.console['error'](msg); + } +}; + + +/** + * Implements a system for the dynamic resolution of dependencies that works in + * parallel with the BUILD system. Note that all calls to goog.require will be + * stripped by the JSCompiler when the --process_closure_primitives option is + * used. + * @see goog.provide + * @param {string} name Namespace to include (as was given in goog.provide()) in + * the form "goog.package.part". + * @return {?} If called within a goog.module file, the associated namespace or + * module otherwise null. + */ +goog.require = function(name) { + // If the object already exists we do not need do do anything. + if (!COMPILED) { + if (goog.ENABLE_DEBUG_LOADER && goog.IS_OLD_IE_) { + goog.maybeProcessDeferredDep_(name); + } + + if (goog.isProvided_(name)) { + if (goog.isInModuleLoader_()) { + return goog.module.getInternal_(name); + } else { + return null; + } + } + + if (goog.ENABLE_DEBUG_LOADER) { + var path = goog.getPathFromDeps_(name); + if (path) { + goog.writeScripts_(path); + return null; + } + } + + var errorMessage = 'goog.require could not find: ' + name; + goog.logToConsole_(errorMessage); + + throw Error(errorMessage); + } +}; + + +/** + * Path for included scripts. + * @type {string} + */ +goog.basePath = ''; + + +/** + * A hook for overriding the base path. + * @type {string|undefined} + */ +goog.global.CLOSURE_BASE_PATH; + + +/** + * Whether to write out Closure's deps file. By default, the deps are written. + * @type {boolean|undefined} + */ +goog.global.CLOSURE_NO_DEPS; + + +/** + * A function to import a single script. This is meant to be overridden when + * Closure is being run in non-HTML contexts, such as web workers. It's defined + * in the global scope so that it can be set before base.js is loaded, which + * allows deps.js to be imported properly. + * + * The function is passed the script source, which is a relative URI. It should + * return true if the script was imported, false otherwise. + * @type {(function(string): boolean)|undefined} + */ +goog.global.CLOSURE_IMPORT_SCRIPT; + + +/** + * Null function used for default values of callbacks, etc. + * @return {void} Nothing. + */ +goog.nullFunction = function() {}; + + +/** + * When defining a class Foo with an abstract method bar(), you can do: + * Foo.prototype.bar = goog.abstractMethod + * + * Now if a subclass of Foo fails to override bar(), an error will be thrown + * when bar() is invoked. + * + * Note: This does not take the name of the function to override as an argument + * because that would make it more difficult to obfuscate our JavaScript code. + * + * @type {!Function} + * @throws {Error} when invoked to indicate the method should be overridden. + */ +goog.abstractMethod = function() { + throw Error('unimplemented abstract method'); +}; + + +/** + * Adds a {@code getInstance} static method that always returns the same + * instance object. + * @param {!Function} ctor The constructor for the class to add the static + * method to. + */ +goog.addSingletonGetter = function(ctor) { + ctor.getInstance = function() { + if (ctor.instance_) { + return ctor.instance_; + } + if (goog.DEBUG) { + // NOTE: JSCompiler can't optimize away Array#push. + goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor; + } + return ctor.instance_ = new ctor; + }; +}; + + +/** + * All singleton classes that have been instantiated, for testing. Don't read + * it directly, use the {@code goog.testing.singleton} module. The compiler + * removes this variable if unused. + * @type {!Array<!Function>} + * @private + */ +goog.instantiatedSingletons_ = []; + + +/** + * @define {boolean} Whether to load goog.modules using {@code eval} when using + * the debug loader. This provides a better debugging experience as the + * source is unmodified and can be edited using Chrome Workspaces or similar. + * However in some environments the use of {@code eval} is banned + * so we provide an alternative. + */ +goog.define('goog.LOAD_MODULE_USING_EVAL', true); + + +/** + * @define {boolean} Whether the exports of goog.modules should be sealed when + * possible. + */ +goog.define('goog.SEAL_MODULE_EXPORTS', goog.DEBUG); + + +/** + * The registry of initialized modules: + * the module identifier to module exports map. + * @private @const {!Object<string, ?>} + */ +goog.loadedModules_ = {}; + + +/** + * True if goog.dependencies_ is available. + * @const {boolean} + */ +goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER; + + +/** + * @define {string} How to decide whether to transpile. Valid values + * are 'always', 'never', and 'detect'. The default ('detect') is to + * use feature detection to determine which language levels need + * transpilation. + */ +// NOTE(user): we could expand this to accept a language level to bypass +// detection: e.g. goog.TRANSPILE == 'es5' would transpile ES6 files but +// would leave ES3 and ES5 files alone. +goog.define('goog.TRANSPILE', 'detect'); + + +/** + * @define {string} Path to the transpiler. Executing the script at this + * path (relative to base.js) should define a function $jscomp.transpile. + */ +goog.define('goog.TRANSPILER', 'transpile.js'); + + +if (goog.DEPENDENCIES_ENABLED) { + /** + * This object is used to keep track of dependencies and other data that is + * used for loading scripts. + * @private + * @type {{ + * loadFlags: !Object<string, !Object<string, string>>, + * nameToPath: !Object<string, string>, + * requires: !Object<string, !Object<string, boolean>>, + * visited: !Object<string, boolean>, + * written: !Object<string, boolean>, + * deferred: !Object<string, string> + * }} + */ + goog.dependencies_ = { + loadFlags: {}, // 1 to 1 + + nameToPath: {}, // 1 to 1 + + requires: {}, // 1 to many + + // Used when resolving dependencies to prevent us from visiting file twice. + visited: {}, + + written: {}, // Used to keep track of script files we have written. + + deferred: {} // Used to track deferred module evaluations in old IEs + }; + + + /** + * Tries to detect whether is in the context of an HTML document. + * @return {boolean} True if it looks like HTML document. + * @private + */ + goog.inHtmlDocument_ = function() { + /** @type {Document} */ + var doc = goog.global.document; + return doc != null && 'write' in doc; // XULDocument misses write. + }; + + + /** + * Tries to detect the base path of base.js script that bootstraps Closure. + * @private + */ + goog.findBasePath_ = function() { + if (goog.isDef(goog.global.CLOSURE_BASE_PATH)) { + goog.basePath = goog.global.CLOSURE_BASE_PATH; + return; + } else if (!goog.inHtmlDocument_()) { + return; + } + /** @type {Document} */ + var doc = goog.global.document; + var scripts = doc.getElementsByTagName('SCRIPT'); + // Search backwards since the current script is in almost all cases the one + // that has base.js. + for (var i = scripts.length - 1; i >= 0; --i) { + var script = /** @type {!HTMLScriptElement} */ (scripts[i]); + var src = script.src; + var qmark = src.lastIndexOf('?'); + var l = qmark == -1 ? src.length : qmark; + if (src.substr(l - 7, 7) == 'base.js') { + goog.basePath = src.substr(0, l - 7); + return; + } + } + }; + + + /** + * Imports a script if, and only if, that script hasn't already been imported. + * (Must be called at execution time) + * @param {string} src Script source. + * @param {string=} opt_sourceText The optionally source text to evaluate + * @private + */ + goog.importScript_ = function(src, opt_sourceText) { + var importScript = + goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_; + if (importScript(src, opt_sourceText)) { + goog.dependencies_.written[src] = true; + } + }; + + + /** + * Whether the browser is IE9 or earlier, which needs special handling + * for deferred modules. + * @const @private {boolean} + */ + goog.IS_OLD_IE_ = + !!(!goog.global.atob && goog.global.document && goog.global.document.all); + + + /** + * Given a URL initiate retrieval and execution of a script that needs + * pre-processing. + * @param {string} src Script source URL. + * @param {boolean} isModule Whether this is a goog.module. + * @param {boolean} needsTranspile Whether this source needs transpilation. + * @private + */ + goog.importProcessedScript_ = function(src, isModule, needsTranspile) { + // In an attempt to keep browsers from timing out loading scripts using + // synchronous XHRs, put each load in its own script block. + var bootstrap = 'goog.retrieveAndExec_("' + src + '", ' + isModule + ', ' + + needsTranspile + ');'; + + goog.importScript_('', bootstrap); + }; + + + /** @private {!Array<string>} */ + goog.queuedModules_ = []; + + + /** + * Return an appropriate module text. Suitable to insert into + * a script tag (that is unescaped). + * @param {string} srcUrl + * @param {string} scriptText + * @return {string} + * @private + */ + goog.wrapModule_ = function(srcUrl, scriptText) { + if (!goog.LOAD_MODULE_USING_EVAL || !goog.isDef(goog.global.JSON)) { + return '' + + 'goog.loadModule(function(exports) {' + + '"use strict";' + scriptText + + '\n' + // terminate any trailing single line comment. + ';return exports' + + '});' + + '\n//# sourceURL=' + srcUrl + '\n'; + } else { + return '' + + 'goog.loadModule(' + + goog.global.JSON.stringify( + scriptText + '\n//# sourceURL=' + srcUrl + '\n') + + ');'; + } + }; + + // On IE9 and earlier, it is necessary to handle + // deferred module loads. In later browsers, the + // code to be evaluated is simply inserted as a script + // block in the correct order. To eval deferred + // code at the right time, we piggy back on goog.require to call + // goog.maybeProcessDeferredDep_. + // + // The goog.requires are used both to bootstrap + // the loading process (when no deps are available) and + // declare that they should be available. + // + // Here we eval the sources, if all the deps are available + // either already eval'd or goog.require'd. This will + // be the case when all the dependencies have already + // been loaded, and the dependent module is loaded. + // + // But this alone isn't sufficient because it is also + // necessary to handle the case where there is no root + // that is not deferred. For that there we register for an event + // and trigger goog.loadQueuedModules_ handle any remaining deferred + // evaluations. + + /** + * Handle any remaining deferred goog.module evals. + * @private + */ + goog.loadQueuedModules_ = function() { + var count = goog.queuedModules_.length; + if (count > 0) { + var queue = goog.queuedModules_; + goog.queuedModules_ = []; + for (var i = 0; i < count; i++) { + var path = queue[i]; + goog.maybeProcessDeferredPath_(path); + } + } + }; + + + /** + * Eval the named module if its dependencies are + * available. + * @param {string} name The module to load. + * @private + */ + goog.maybeProcessDeferredDep_ = function(name) { + if (goog.isDeferredModule_(name) && goog.allDepsAreAvailable_(name)) { + var path = goog.getPathFromDeps_(name); + goog.maybeProcessDeferredPath_(goog.basePath + path); + } + }; + + /** + * @param {string} name The module to check. + * @return {boolean} Whether the name represents a + * module whose evaluation has been deferred. + * @private + */ + goog.isDeferredModule_ = function(name) { + var path = goog.getPathFromDeps_(name); + var loadFlags = path && goog.dependencies_.loadFlags[path] || {}; + if (path && (loadFlags['module'] == 'goog' || + goog.needsTranspile_(loadFlags['lang']))) { + var abspath = goog.basePath + path; + return (abspath) in goog.dependencies_.deferred; + } + return false; + }; + + /** + * @param {string} name The module to check. + * @return {boolean} Whether the name represents a + * module whose declared dependencies have all been loaded + * (eval'd or a deferred module load) + * @private + */ + goog.allDepsAreAvailable_ = function(name) { + var path = goog.getPathFromDeps_(name); + if (path && (path in goog.dependencies_.requires)) { + for (var requireName in goog.dependencies_.requires[path]) { + if (!goog.isProvided_(requireName) && + !goog.isDeferredModule_(requireName)) { + return false; + } + } + } + return true; + }; + + + /** + * @param {string} abspath + * @private + */ + goog.maybeProcessDeferredPath_ = function(abspath) { + if (abspath in goog.dependencies_.deferred) { + var src = goog.dependencies_.deferred[abspath]; + delete goog.dependencies_.deferred[abspath]; + goog.globalEval(src); + } + }; + + + /** + * Load a goog.module from the provided URL. This is not a general purpose + * code loader and does not support late loading code, that is it should only + * be used during page load. This method exists to support unit tests and + * "debug" loaders that would otherwise have inserted script tags. Under the + * hood this needs to use a synchronous XHR and is not recommeneded for + * production code. + * + * The module's goog.requires must have already been satisified; an exception + * will be thrown if this is not the case. This assumption is that no + * "deps.js" file exists, so there is no way to discover and locate the + * module-to-be-loaded's dependencies and no attempt is made to do so. + * + * There should only be one attempt to load a module. If + * "goog.loadModuleFromUrl" is called for an already loaded module, an + * exception will be throw. + * + * @param {string} url The URL from which to attempt to load the goog.module. + */ + goog.loadModuleFromUrl = function(url) { + // Because this executes synchronously, we don't need to do any additional + // bookkeeping. When "goog.loadModule" the namespace will be marked as + // having been provided which is sufficient. + goog.retrieveAndExec_(url, true, false); + }; + + + /** + * Writes a new script pointing to {@code src} directly into the DOM. + * + * NOTE: This method is not CSP-compliant. @see goog.appendScriptSrcNode_ for + * the fallback mechanism. + * + * @param {string} src The script URL. + * @private + */ + goog.writeScriptSrcNode_ = function(src) { + goog.global.document.write( + '<script type="text/javascript" src="' + src + '"></' + + 'script>'); + }; + + + /** + * Appends a new script node to the DOM using a CSP-compliant mechanism. This + * method exists as a fallback for document.write (which is not allowed in a + * strict CSP context, e.g., Chrome apps). + * + * NOTE: This method is not analogous to using document.write to insert a + * <script> tag; specifically, the user agent will execute a script added by + * document.write immediately after the current script block finishes + * executing, whereas the DOM-appended script node will not be executed until + * the entire document is parsed and executed. That is to say, this script is + * added to the end of the script execution queue. + * + * The page must not attempt to call goog.required entities until after the + * document has loaded, e.g., in or after the window.onload callback. + * + * @param {string} src The script URL. + * @private + */ + goog.appendScriptSrcNode_ = function(src) { + /** @type {Document} */ + var doc = goog.global.document; + var scriptEl = + /** @type {HTMLScriptElement} */ (doc.createElement('script')); + scriptEl.type = 'text/javascript'; + scriptEl.src = src; + scriptEl.defer = false; + scriptEl.async = false; + doc.head.appendChild(scriptEl); + }; + + + /** + * The default implementation of the import function. Writes a script tag to + * import the script. + * + * @param {string} src The script url. + * @param {string=} opt_sourceText The optionally source text to evaluate + * @return {boolean} True if the script was imported, false otherwise. + * @private + */ + goog.writeScriptTag_ = function(src, opt_sourceText) { + if (goog.inHtmlDocument_()) { + /** @type {!HTMLDocument} */ + var doc = goog.global.document; + + // If the user tries to require a new symbol after document load, + // something has gone terribly wrong. Doing a document.write would + // wipe out the page. This does not apply to the CSP-compliant method + // of writing script tags. + if (!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING && + doc.readyState == 'complete') { + // Certain test frameworks load base.js multiple times, which tries + // to write deps.js each time. If that happens, just fail silently. + // These frameworks wipe the page between each load of base.js, so this + // is OK. + var isDeps = /\bdeps.js$/.test(src); + if (isDeps) { + return false; + } else { + throw Error('Cannot write "' + src + '" after document load'); + } + } + + if (opt_sourceText === undefined) { + if (!goog.IS_OLD_IE_) { + if (goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING) { + goog.appendScriptSrcNode_(src); + } else { + goog.writeScriptSrcNode_(src); + } + } else { + var state = " onreadystatechange='goog.onScriptLoad_(this, " + + ++goog.lastNonModuleScriptIndex_ + ")' "; + doc.write( + '<script type="text/javascript" src="' + src + '"' + state + + '></' + + 'script>'); + } + } else { + doc.write( + '<script type="text/javascript">' + opt_sourceText + '</' + + 'script>'); + } + return true; + } else { + return false; + } + }; + + + /** + * Determines whether the given language needs to be transpiled. + * @param {string} lang + * @return {boolean} + * @private + */ + goog.needsTranspile_ = function(lang) { + if (goog.TRANSPILE == 'always') { + return true; + } else if (goog.TRANSPILE == 'never') { + return false; + } else if (!goog.transpiledLanguages_) { + goog.transpiledLanguages_ = {'es5': true, 'es6': true, 'es6-impl': true}; + /** @preserveTry */ + try { + // Perform some quick conformance checks, to distinguish + // between browsers that support es5, es6-impl, or es6. + + // Identify ES3-only browsers by their incorrect treatment of commas. + goog.transpiledLanguages_['es5'] = eval('[1,].length!=1'); + + // As browsers mature, features will be moved from the full test + // into the impl test. This must happen before the corresponding + // features are changed in the Closure Compiler's FeatureSet object. + + // Test 1: es6-impl [FF49, Edge 13, Chrome 49] + // (a) let/const keyword, (b) class expressions, (c) Map object, + // (d) iterable arguments, (e) spread operator + var es6implTest = + 'let a={};const X=class{constructor(){}x(z){return new Map([' + + '...arguments]).get(z[0])==3}};return new X().x([a,3])'; + + // Test 2: es6 [FF50 (?), Edge 14 (?), Chrome 50] + // (a) default params (specifically shadowing locals), + // (b) destructuring, (c) block-scoped functions, + // (d) for-of (const), (e) new.target/Reflect.construct + var es6fullTest = + 'class X{constructor(){if(new.target!=String)throw 1;this.x=42}}' + + 'let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof ' + + 'String))throw 1;for(const a of[2,3]){if(a==2)continue;function ' + + 'f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()' + + '==3}'; + + if (eval('(()=>{"use strict";' + es6implTest + '})()')) { + goog.transpiledLanguages_['es6-impl'] = false; + } + if (eval('(()=>{"use strict";' + es6fullTest + '})()')) { + goog.transpiledLanguages_['es6'] = false; + } + } catch (err) { + } + } + return !!goog.transpiledLanguages_[lang]; + }; + + + /** @private {?Object<string, boolean>} */ + goog.transpiledLanguages_ = null; + + + /** @private {number} */ + goog.lastNonModuleScriptIndex_ = 0; + + + /** + * A readystatechange handler for legacy IE + * @param {!HTMLScriptElement} script + * @param {number} scriptIndex + * @return {boolean} + * @private + */ + goog.onScriptLoad_ = function(script, scriptIndex) { + // for now load the modules when we reach the last script, + // later allow more inter-mingling. + if (script.readyState == 'complete' && + goog.lastNonModuleScriptIndex_ == scriptIndex) { + goog.loadQueuedModules_(); + } + return true; + }; + + /** + * Resolves dependencies based on the dependencies added using addDependency + * and calls importScript_ in the correct order. + * @param {string} pathToLoad The path from which to start discovering + * dependencies. + * @private + */ + goog.writeScripts_ = function(pathToLoad) { + /** @type {!Array<string>} The scripts we need to write this time. */ + var scripts = []; + var seenScript = {}; + var deps = goog.dependencies_; + + /** @param {string} path */ + function visitNode(path) { + if (path in deps.written) { + return; + } + + // We have already visited this one. We can get here if we have cyclic + // dependencies. + if (path in deps.visited) { + return; + } + + deps.visited[path] = true; + + if (path in deps.requires) { + for (var requireName in deps.requires[path]) { + // If the required name is defined, we assume that it was already + // bootstrapped by other means. + if (!goog.isProvided_(requireName)) { + if (requireName in deps.nameToPath) { + visitNode(deps.nameToPath[requireName]); + } else { + throw Error('Undefined nameToPath for ' + requireName); + } + } + } + } + + if (!(path in seenScript)) { + seenScript[path] = true; + scripts.push(path); + } + } + + visitNode(pathToLoad); + + // record that we are going to load all these scripts. + for (var i = 0; i < scripts.length; i++) { + var path = scripts[i]; + goog.dependencies_.written[path] = true; + } + + // If a module is loaded synchronously then we need to + // clear the current inModuleLoader value, and restore it when we are + // done loading the current "requires". + var moduleState = goog.moduleLoaderState_; + goog.moduleLoaderState_ = null; + + for (var i = 0; i < scripts.length; i++) { + var path = scripts[i]; + if (path) { + var loadFlags = deps.loadFlags[path] || {}; + var needsTranspile = goog.needsTranspile_(loadFlags['lang']); + if (loadFlags['module'] == 'goog' || needsTranspile) { + goog.importProcessedScript_( + goog.basePath + path, loadFlags['module'] == 'goog', + needsTranspile); + } else { + goog.importScript_(goog.basePath + path); + } + } else { + goog.moduleLoaderState_ = moduleState; + throw Error('Undefined script input'); + } + } + + // restore the current "module loading state" + goog.moduleLoaderState_ = moduleState; + }; + + + /** + * Looks at the dependency rules and tries to determine the script file that + * fulfills a particular rule. + * @param {string} rule In the form goog.namespace.Class or project.script. + * @return {?string} Url corresponding to the rule, or null. + * @private + */ + goog.getPathFromDeps_ = function(rule) { + if (rule in goog.dependencies_.nameToPath) { + return goog.dependencies_.nameToPath[rule]; + } else { + return null; + } + }; + + goog.findBasePath_(); + + // Allow projects to manage the deps files themselves. + if (!goog.global.CLOSURE_NO_DEPS) { + goog.importScript_(goog.basePath + 'deps.js'); + } +} + + +/** + * @param {function(?):?|string} moduleDef The module definition. + */ +goog.loadModule = function(moduleDef) { + // NOTE: we allow function definitions to be either in the from + // of a string to eval (which keeps the original source intact) or + // in a eval forbidden environment (CSP) we allow a function definition + // which in its body must call {@code goog.module}, and return the exports + // of the module. + var previousState = goog.moduleLoaderState_; + try { + goog.moduleLoaderState_ = { + moduleName: undefined, + declareLegacyNamespace: false + }; + var exports; + if (goog.isFunction(moduleDef)) { + exports = moduleDef.call(undefined, {}); + } else if (goog.isString(moduleDef)) { + exports = goog.loadModuleFromSource_.call(undefined, moduleDef); + } else { + throw Error('Invalid module definition'); + } + + var moduleName = goog.moduleLoaderState_.moduleName; + if (!goog.isString(moduleName) || !moduleName) { + throw Error('Invalid module name \"' + moduleName + '\"'); + } + + // Don't seal legacy namespaces as they may be uses as a parent of + // another namespace + if (goog.moduleLoaderState_.declareLegacyNamespace) { + goog.constructNamespace_(moduleName, exports); + } else if (goog.SEAL_MODULE_EXPORTS && Object.seal) { + Object.seal(exports); + } + + goog.loadedModules_[moduleName] = exports; + } finally { + goog.moduleLoaderState_ = previousState; + } +}; + + +/** + * @private @const {function(string):?} + * + * The new type inference warns because this function has no formal + * parameters, but its jsdoc says that it takes one argument. + * (The argument is used via arguments[0], but NTI does not detect this.) + * @suppress {newCheckTypes} + */ +goog.loadModuleFromSource_ = function() { + // NOTE: we avoid declaring parameters or local variables here to avoid + // masking globals or leaking values into the module definition. + 'use strict'; + var exports = {}; + eval(arguments[0]); + return exports; +}; + + +/** + * Normalize a file path by removing redundant ".." and extraneous "." file + * path components. + * @param {string} path + * @return {string} + * @private + */ +goog.normalizePath_ = function(path) { + var components = path.split('/'); + var i = 0; + while (i < components.length) { + if (components[i] == '.') { + components.splice(i, 1); + } else if ( + i && components[i] == '..' && components[i - 1] && + components[i - 1] != '..') { + components.splice(--i, 2); + } else { + i++; + } + } + return components.join('/'); +}; + + +/** + * Loads file by synchronous XHR. Should not be used in production environments. + * @param {string} src Source URL. + * @return {?string} File contents, or null if load failed. + * @private + */ +goog.loadFileSync_ = function(src) { + if (goog.global.CLOSURE_LOAD_FILE_SYNC) { + return goog.global.CLOSURE_LOAD_FILE_SYNC(src); + } else { + try { + /** @type {XMLHttpRequest} */ + var xhr = new goog.global['XMLHttpRequest'](); + xhr.open('get', src, false); + xhr.send(); + // NOTE: Successful http: requests have a status of 200, but successful + // file: requests may have a status of zero. Any other status, or a + // thrown exception (particularly in case of file: requests) indicates + // some sort of error, which we treat as a missing or unavailable file. + return xhr.status == 0 || xhr.status == 200 ? xhr.responseText : null; + } catch (err) { + // No need to rethrow or log, since errors should show up on their own. + return null; + } + } +}; + + +/** + * Retrieve and execute a script that needs some sort of wrapping. + * @param {string} src Script source URL. + * @param {boolean} isModule Whether to load as a module. + * @param {boolean} needsTranspile Whether to transpile down to ES3. + * @private + */ +goog.retrieveAndExec_ = function(src, isModule, needsTranspile) { + if (!COMPILED) { + // The full but non-canonicalized URL for later use. + var originalPath = src; + // Canonicalize the path, removing any /./ or /../ since Chrome's debugging + // console doesn't auto-canonicalize XHR loads as it does <script> srcs. + src = goog.normalizePath_(src); + + var importScript = + goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_; + + var scriptText = goog.loadFileSync_(src); + if (scriptText == null) { + throw new Error('Load of "' + src + '" failed'); + } + + if (needsTranspile) { + scriptText = goog.transpile_.call(goog.global, scriptText, src); + } + + if (isModule) { + scriptText = goog.wrapModule_(src, scriptText); + } else { + scriptText += '\n//# sourceURL=' + src; + } + var isOldIE = goog.IS_OLD_IE_; + if (isOldIE) { + goog.dependencies_.deferred[originalPath] = scriptText; + goog.queuedModules_.push(originalPath); + } else { + importScript(src, scriptText); + } + } +}; + + +/** + * Lazily retrieves the transpiler and applies it to the source. + * @param {string} code JS code. + * @param {string} path Path to the code. + * @return {string} The transpiled code. + * @private + */ +goog.transpile_ = function(code, path) { + var jscomp = goog.global['$jscomp']; + if (!jscomp) { + goog.global['$jscomp'] = jscomp = {}; + } + var transpile = jscomp.transpile; + if (!transpile) { + var transpilerPath = goog.basePath + goog.TRANSPILER; + var transpilerCode = goog.loadFileSync_(transpilerPath); + if (transpilerCode) { + // This must be executed synchronously, since by the time we know we + // need it, we're about to load and write the ES6 code synchronously, + // so a normal script-tag load will be too slow. + eval(transpilerCode + '\n//# sourceURL=' + transpilerPath); + // Note: transpile.js reassigns goog.global['$jscomp'] so pull it again. + jscomp = goog.global['$jscomp']; + transpile = jscomp.transpile; + } + } + if (!transpile) { + // The transpiler is an optional component. If it's not available then + // replace it with a pass-through function that simply logs. + var suffix = ' requires transpilation but no transpiler was found.'; + transpile = jscomp.transpile = function(code, path) { + // TODO(user): figure out some way to get this error to show up + // in test results, noting that the failure may occur in many + // different ways, including in loadModule() before the test + // runner even comes up. + goog.logToConsole_(path + suffix); + return code; + }; + } + // Note: any transpilation errors/warnings will be logged to the console. + return transpile(code, path); +}; + + +//============================================================================== +// Language Enhancements +//============================================================================== + + +/** + * This is a "fixed" version of the typeof operator. It differs from the typeof + * operator in such a way that null returns 'null' and arrays return 'array'. + * @param {?} value The value to get the type of. + * @return {string} The name of the type. + */ +goog.typeOf = function(value) { + var s = typeof value; + if (s == 'object') { + if (value) { + // Check these first, so we can avoid calling Object.prototype.toString if + // possible. + // + // IE improperly marshals typeof across execution contexts, but a + // cross-context object will still return false for "instanceof Object". + if (value instanceof Array) { + return 'array'; + } else if (value instanceof Object) { + return s; + } + + // HACK: In order to use an Object prototype method on the arbitrary + // value, the compiler requires the value be cast to type Object, + // even though the ECMA spec explicitly allows it. + var className = Object.prototype.toString.call( + /** @type {!Object} */ (value)); + // In Firefox 3.6, attempting to access iframe window objects' length + // property throws an NS_ERROR_FAILURE, so we need to special-case it + // here. + if (className == '[object Window]') { + return 'object'; + } + + // We cannot always use constructor == Array or instanceof Array because + // different frames have different Array objects. In IE6, if the iframe + // where the array was created is destroyed, the array loses its + // prototype. Then dereferencing val.splice here throws an exception, so + // we can't use goog.isFunction. Calling typeof directly returns 'unknown' + // so that will work. In this case, this function will return false and + // most array functions will still work because the array is still + // array-like (supports length and []) even though it has lost its + // prototype. + // Mark Miller noticed that Object.prototype.toString + // allows access to the unforgeable [[Class]] property. + // 15.2.4.2 Object.prototype.toString ( ) + // When the toString method is called, the following steps are taken: + // 1. Get the [[Class]] property of this object. + // 2. Compute a string value by concatenating the three strings + // "[object ", Result(1), and "]". + // 3. Return Result(2). + // and this behavior survives the destruction of the execution context. + if ((className == '[object Array]' || + // In IE all non value types are wrapped as objects across window + // boundaries (not iframe though) so we have to do object detection + // for this edge case. + typeof value.length == 'number' && + typeof value.splice != 'undefined' && + typeof value.propertyIsEnumerable != 'undefined' && + !value.propertyIsEnumerable('splice') + + )) { + return 'array'; + } + // HACK: There is still an array case that fails. + // function ArrayImpostor() {} + // ArrayImpostor.prototype = []; + // var impostor = new ArrayImpostor; + // this can be fixed by getting rid of the fast path + // (value instanceof Array) and solely relying on + // (value && Object.prototype.toString.vall(value) === '[object Array]') + // but that would require many more function calls and is not warranted + // unless closure code is receiving objects from untrusted sources. + + // IE in cross-window calls does not correctly marshal the function type + // (it appears just as an object) so we cannot use just typeof val == + // 'function'. However, if the object has a call property, it is a + // function. + if ((className == '[object Function]' || + typeof value.call != 'undefined' && + typeof value.propertyIsEnumerable != 'undefined' && + !value.propertyIsEnumerable('call'))) { + return 'function'; + } + + } else { + return 'null'; + } + + } else if (s == 'function' && typeof value.call == 'undefined') { + // In Safari typeof nodeList returns 'function', and on Firefox typeof + // behaves similarly for HTML{Applet,Embed,Object}, Elements and RegExps. We + // would like to return object for those and we can detect an invalid + // function by making sure that the function object has a call method. + return 'object'; + } + return s; +}; + + +/** + * Returns true if the specified value is null. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is null. + */ +goog.isNull = function(val) { + return val === null; +}; + + +/** + * Returns true if the specified value is defined and not null. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is defined and not null. + */ +goog.isDefAndNotNull = function(val) { + // Note that undefined == null. + return val != null; +}; + + +/** + * Returns true if the specified value is an array. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is an array. + */ +goog.isArray = function(val) { + return goog.typeOf(val) == 'array'; +}; + + +/** + * Returns true if the object looks like an array. To qualify as array like + * the value needs to be either a NodeList or an object with a Number length + * property. As a special case, a function value is not array like, because its + * length property is fixed to correspond to the number of expected arguments. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is an array. + */ +goog.isArrayLike = function(val) { + var type = goog.typeOf(val); + // We do not use goog.isObject here in order to exclude function values. + return type == 'array' || type == 'object' && typeof val.length == 'number'; +}; + + +/** + * Returns true if the object looks like a Date. To qualify as Date-like the + * value needs to be an object and have a getFullYear() function. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a like a Date. + */ +goog.isDateLike = function(val) { + return goog.isObject(val) && typeof val.getFullYear == 'function'; +}; + + +/** + * Returns true if the specified value is a string. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a string. + */ +goog.isString = function(val) { + return typeof val == 'string'; +}; + + +/** + * Returns true if the specified value is a boolean. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is boolean. + */ +goog.isBoolean = function(val) { + return typeof val == 'boolean'; +}; + + +/** + * Returns true if the specified value is a number. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a number. + */ +goog.isNumber = function(val) { + return typeof val == 'number'; +}; + + +/** + * Returns true if the specified value is a function. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a function. + */ +goog.isFunction = function(val) { + return goog.typeOf(val) == 'function'; +}; + + +/** + * Returns true if the specified value is an object. This includes arrays and + * functions. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is an object. + */ +goog.isObject = function(val) { + var type = typeof val; + return type == 'object' && val != null || type == 'function'; + // return Object(val) === val also works, but is slower, especially if val is + // not an object. +}; + + +/** + * Gets a unique ID for an object. This mutates the object so that further calls + * with the same object as a parameter returns the same value. The unique ID is + * guaranteed to be unique across the current session amongst objects that are + * passed into {@code getUid}. There is no guarantee that the ID is unique or + * consistent across sessions. It is unsafe to generate unique ID for function + * prototypes. + * + * @param {Object} obj The object to get the unique ID for. + * @return {number} The unique ID for the object. + */ +goog.getUid = function(obj) { + // TODO(arv): Make the type stricter, do not accept null. + + // In Opera window.hasOwnProperty exists but always returns false so we avoid + // using it. As a consequence the unique ID generated for BaseClass.prototype + // and SubClass.prototype will be the same. + return obj[goog.UID_PROPERTY_] || + (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_); +}; + + +/** + * Whether the given object is already assigned a unique ID. + * + * This does not modify the object. + * + * @param {!Object} obj The object to check. + * @return {boolean} Whether there is an assigned unique id for the object. + */ +goog.hasUid = function(obj) { + return !!obj[goog.UID_PROPERTY_]; +}; + + +/** + * Removes the unique ID from an object. This is useful if the object was + * previously mutated using {@code goog.getUid} in which case the mutation is + * undone. + * @param {Object} obj The object to remove the unique ID field from. + */ +goog.removeUid = function(obj) { + // TODO(arv): Make the type stricter, do not accept null. + + // In IE, DOM nodes are not instances of Object and throw an exception if we + // try to delete. Instead we try to use removeAttribute. + if (obj !== null && 'removeAttribute' in obj) { + obj.removeAttribute(goog.UID_PROPERTY_); + } + /** @preserveTry */ + try { + delete obj[goog.UID_PROPERTY_]; + } catch (ex) { + } +}; + + +/** + * Name for unique ID property. Initialized in a way to help avoid collisions + * with other closure JavaScript on the same page. + * @type {string} + * @private + */ +goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0); + + +/** + * Counter for UID. + * @type {number} + * @private + */ +goog.uidCounter_ = 0; + + +/** + * Adds a hash code field to an object. The hash code is unique for the + * given object. + * @param {Object} obj The object to get the hash code for. + * @return {number} The hash code for the object. + * @deprecated Use goog.getUid instead. + */ +goog.getHashCode = goog.getUid; + + +/** + * Removes the hash code field from an object. + * @param {Object} obj The object to remove the field from. + * @deprecated Use goog.removeUid instead. + */ +goog.removeHashCode = goog.removeUid; + + +/** + * Clones a value. The input may be an Object, Array, or basic type. Objects and + * arrays will be cloned recursively. + * + * WARNINGS: + * <code>goog.cloneObject</code> does not detect reference loops. Objects that + * refer to themselves will cause infinite recursion. + * + * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies + * UIDs created by <code>getUid</code> into cloned results. + * + * @param {*} obj The value to clone. + * @return {*} A clone of the input value. + * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods. + */ +goog.cloneObject = function(obj) { + var type = goog.typeOf(obj); + if (type == 'object' || type == 'array') { + if (obj.clone) { + return obj.clone(); + } + var clone = type == 'array' ? [] : {}; + for (var key in obj) { + clone[key] = goog.cloneObject(obj[key]); + } + return clone; + } + + return obj; +}; + + +/** + * A native implementation of goog.bind. + * @param {Function} fn A function to partially apply. + * @param {Object|undefined} selfObj Specifies the object which this should + * point to when the function is run. + * @param {...*} var_args Additional arguments that are partially applied to the + * function. + * @return {!Function} A partially-applied form of the function bind() was + * invoked as a method of. + * @private + * @suppress {deprecated} The compiler thinks that Function.prototype.bind is + * deprecated because some people have declared a pure-JS version. + * Only the pure-JS version is truly deprecated. + */ +goog.bindNative_ = function(fn, selfObj, var_args) { + return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments)); +}; + + +/** + * A pure-JS implementation of goog.bind. + * @param {Function} fn A function to partially apply. + * @param {Object|undefined} selfObj Specifies the object which this should + * point to when the function is run. + * @param {...*} var_args Additional arguments that are partially applied to the + * function. + * @return {!Function} A partially-applied form of the function bind() was + * invoked as a method of. + * @private + */ +goog.bindJs_ = function(fn, selfObj, var_args) { + if (!fn) { + throw new Error(); + } + + if (arguments.length > 2) { + var boundArgs = Array.prototype.slice.call(arguments, 2); + return function() { + // Prepend the bound arguments to the current arguments. + var newArgs = Array.prototype.slice.call(arguments); + Array.prototype.unshift.apply(newArgs, boundArgs); + return fn.apply(selfObj, newArgs); + }; + + } else { + return function() { return fn.apply(selfObj, arguments); }; + } +}; + + +/** + * Partially applies this function to a particular 'this object' and zero or + * more arguments. The result is a new function with some arguments of the first + * function pre-filled and the value of this 'pre-specified'. + * + * Remaining arguments specified at call-time are appended to the pre-specified + * ones. + * + * Also see: {@link #partial}. + * + * Usage: + * <pre>var barMethBound = goog.bind(myFunction, myObj, 'arg1', 'arg2'); + * barMethBound('arg3', 'arg4');</pre> + * + * @param {?function(this:T, ...)} fn A function to partially apply. + * @param {T} selfObj Specifies the object which this should point to when the + * function is run. + * @param {...*} var_args Additional arguments that are partially applied to the + * function. + * @return {!Function} A partially-applied form of the function goog.bind() was + * invoked as a method of. + * @template T + * @suppress {deprecated} See above. + */ +goog.bind = function(fn, selfObj, var_args) { + // TODO(nicksantos): narrow the type signature. + if (Function.prototype.bind && + // NOTE(nicksantos): Somebody pulled base.js into the default Chrome + // extension environment. This means that for Chrome extensions, they get + // the implementation of Function.prototype.bind that calls goog.bind + // instead of the native one. Even worse, we don't want to introduce a + // circular dependency between goog.bind and Function.prototype.bind, so + // we have to hack this to make sure it works correctly. + Function.prototype.bind.toString().indexOf('native code') != -1) { + goog.bind = goog.bindNative_; + } else { + goog.bind = goog.bindJs_; + } + return goog.bind.apply(null, arguments); +}; + + +/** + * Like goog.bind(), except that a 'this object' is not required. Useful when + * the target function is already bound. + * + * Usage: + * var g = goog.partial(f, arg1, arg2); + * g(arg3, arg4); + * + * @param {Function} fn A function to partially apply. + * @param {...*} var_args Additional arguments that are partially applied to fn. + * @return {!Function} A partially-applied form of the function goog.partial() + * was invoked as a method of. + */ +goog.partial = function(fn, var_args) { + var args = Array.prototype.slice.call(arguments, 1); + return function() { + // Clone the array (with slice()) and append additional arguments + // to the existing arguments. + var newArgs = args.slice(); + newArgs.push.apply(newArgs, arguments); + return fn.apply(this, newArgs); + }; +}; + + +/** + * Copies all the members of a source object to a target object. This method + * does not work on all browsers for all objects that contain keys such as + * toString or hasOwnProperty. Use goog.object.extend for this purpose. + * @param {Object} target Target. + * @param {Object} source Source. + */ +goog.mixin = function(target, source) { + for (var x in source) { + target[x] = source[x]; + } + + // For IE7 or lower, the for-in-loop does not contain any properties that are + // not enumerable on the prototype object (for example, isPrototypeOf from + // Object.prototype) but also it will not include 'replace' on objects that + // extend String and change 'replace' (not that it is common for anyone to + // extend anything except Object). +}; + + +/** + * @return {number} An integer value representing the number of milliseconds + * between midnight, January 1, 1970 and the current time. + */ +goog.now = (goog.TRUSTED_SITE && Date.now) || (function() { + // Unary plus operator converts its operand to a number which in + // the case of + // a date is done by calling getTime(). + return +new Date(); + }); + + +/** + * Evals JavaScript in the global scope. In IE this uses execScript, other + * browsers use goog.global.eval. If goog.global.eval does not evaluate in the + * global scope (for example, in Safari), appends a script tag instead. + * Throws an exception if neither execScript or eval is defined. + * @param {string} script JavaScript string. + */ +goog.globalEval = function(script) { + if (goog.global.execScript) { + goog.global.execScript(script, 'JavaScript'); + } else if (goog.global.eval) { + // Test to see if eval works + if (goog.evalWorksForGlobals_ == null) { + goog.global.eval('var _evalTest_ = 1;'); + if (typeof goog.global['_evalTest_'] != 'undefined') { + try { + delete goog.global['_evalTest_']; + } catch (ignore) { + // Microsoft edge fails the deletion above in strict mode. + } + goog.evalWorksForGlobals_ = true; + } else { + goog.evalWorksForGlobals_ = false; + } + } + + if (goog.evalWorksForGlobals_) { + goog.global.eval(script); + } else { + /** @type {Document} */ + var doc = goog.global.document; + var scriptElt = + /** @type {!HTMLScriptElement} */ (doc.createElement('SCRIPT')); + scriptElt.type = 'text/javascript'; + scriptElt.defer = false; + // Note(user): can't use .innerHTML since "t('<test>')" will fail and + // .text doesn't work in Safari 2. Therefore we append a text node. + scriptElt.appendChild(doc.createTextNode(script)); + doc.body.appendChild(scriptElt); + doc.body.removeChild(scriptElt); + } + } else { + throw Error('goog.globalEval not available'); + } +}; + + +/** + * Indicates whether or not we can call 'eval' directly to eval code in the + * global scope. Set to a Boolean by the first call to goog.globalEval (which + * empirically tests whether eval works for globals). @see goog.globalEval + * @type {?boolean} + * @private + */ +goog.evalWorksForGlobals_ = null; + + +/** + * Optional map of CSS class names to obfuscated names used with + * goog.getCssName(). + * @private {!Object<string, string>|undefined} + * @see goog.setCssNameMapping + */ +goog.cssNameMapping_; + + +/** + * Optional obfuscation style for CSS class names. Should be set to either + * 'BY_WHOLE' or 'BY_PART' if defined. + * @type {string|undefined} + * @private + * @see goog.setCssNameMapping + */ +goog.cssNameMappingStyle_; + + +/** + * Handles strings that are intended to be used as CSS class names. + * + * This function works in tandem with @see goog.setCssNameMapping. + * + * Without any mapping set, the arguments are simple joined with a hyphen and + * passed through unaltered. + * + * When there is a mapping, there are two possible styles in which these + * mappings are used. In the BY_PART style, each part (i.e. in between hyphens) + * of the passed in css name is rewritten according to the map. In the BY_WHOLE + * style, the full css name is looked up in the map directly. If a rewrite is + * not specified by the map, the compiler will output a warning. + * + * When the mapping is passed to the compiler, it will replace calls to + * goog.getCssName with the strings from the mapping, e.g. + * var x = goog.getCssName('foo'); + * var y = goog.getCssName(this.baseClass, 'active'); + * becomes: + * var x = 'foo'; + * var y = this.baseClass + '-active'; + * + * If one argument is passed it will be processed, if two are passed only the + * modifier will be processed, as it is assumed the first argument was generated + * as a result of calling goog.getCssName. + * + * @param {string} className The class name. + * @param {string=} opt_modifier A modifier to be appended to the class name. + * @return {string} The class name or the concatenation of the class name and + * the modifier. + */ +goog.getCssName = function(className, opt_modifier) { + var getMapping = function(cssName) { + return goog.cssNameMapping_[cssName] || cssName; + }; + + var renameByParts = function(cssName) { + // Remap all the parts individually. + var parts = cssName.split('-'); + var mapped = []; + for (var i = 0; i < parts.length; i++) { + mapped.push(getMapping(parts[i])); + } + return mapped.join('-'); + }; + + var rename; + if (goog.cssNameMapping_) { + rename = + goog.cssNameMappingStyle_ == 'BY_WHOLE' ? getMapping : renameByParts; + } else { + rename = function(a) { return a; }; + } + + if (opt_modifier) { + return className + '-' + rename(opt_modifier); + } else { + return rename(className); + } +}; + + +/** + * Sets the map to check when returning a value from goog.getCssName(). Example: + * <pre> + * goog.setCssNameMapping({ + * "goog": "a", + * "disabled": "b", + * }); + * + * var x = goog.getCssName('goog'); + * // The following evaluates to: "a a-b". + * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled') + * </pre> + * When declared as a map of string literals to string literals, the JSCompiler + * will replace all calls to goog.getCssName() using the supplied map if the + * --process_closure_primitives flag is set. + * + * @param {!Object} mapping A map of strings to strings where keys are possible + * arguments to goog.getCssName() and values are the corresponding values + * that should be returned. + * @param {string=} opt_style The style of css name mapping. There are two valid + * options: 'BY_PART', and 'BY_WHOLE'. + * @see goog.getCssName for a description. + */ +goog.setCssNameMapping = function(mapping, opt_style) { + goog.cssNameMapping_ = mapping; + goog.cssNameMappingStyle_ = opt_style; +}; + + +/** + * To use CSS renaming in compiled mode, one of the input files should have a + * call to goog.setCssNameMapping() with an object literal that the JSCompiler + * can extract and use to replace all calls to goog.getCssName(). In uncompiled + * mode, JavaScript code should be loaded before this base.js file that declares + * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is + * to ensure that the mapping is loaded before any calls to goog.getCssName() + * are made in uncompiled mode. + * + * A hook for overriding the CSS name mapping. + * @type {!Object<string, string>|undefined} + */ +goog.global.CLOSURE_CSS_NAME_MAPPING; + + +if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) { + // This does not call goog.setCssNameMapping() because the JSCompiler + // requires that goog.setCssNameMapping() be called with an object literal. + goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING; +} + + +/** + * Gets a localized message. + * + * This function is a compiler primitive. If you give the compiler a localized + * message bundle, it will replace the string at compile-time with a localized + * version, and expand goog.getMsg call to a concatenated string. + * + * Messages must be initialized in the form: + * <code> + * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'}); + * </code> + * + * This function produces a string which should be treated as plain text. Use + * {@link goog.html.SafeHtmlFormatter} in conjunction with goog.getMsg to + * produce SafeHtml. + * + * @param {string} str Translatable string, places holders in the form {$foo}. + * @param {Object<string, string>=} opt_values Maps place holder name to value. + * @return {string} message with placeholders filled. + */ +goog.getMsg = function(str, opt_values) { + if (opt_values) { + str = str.replace(/\{\$([^}]+)}/g, function(match, key) { + return (opt_values != null && key in opt_values) ? opt_values[key] : + match; + }); + } + return str; +}; + + +/** + * Gets a localized message. If the message does not have a translation, gives a + * fallback message. + * + * This is useful when introducing a new message that has not yet been + * translated into all languages. + * + * This function is a compiler primitive. Must be used in the form: + * <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code> + * where MSG_A and MSG_B were initialized with goog.getMsg. + * + * @param {string} a The preferred message. + * @param {string} b The fallback message. + * @return {string} The best translated message. + */ +goog.getMsgWithFallback = function(a, b) { + return a; +}; + + +/** + * Exposes an unobfuscated global namespace path for the given object. + * Note that fields of the exported object *will* be obfuscated, unless they are + * exported in turn via this function or goog.exportProperty. + * + * Also handy for making public items that are defined in anonymous closures. + * + * ex. goog.exportSymbol('public.path.Foo', Foo); + * + * ex. goog.exportSymbol('public.path.Foo.staticFunction', Foo.staticFunction); + * public.path.Foo.staticFunction(); + * + * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod', + * Foo.prototype.myMethod); + * new public.path.Foo().myMethod(); + * + * @param {string} publicPath Unobfuscated name to export. + * @param {*} object Object the name should point to. + * @param {Object=} opt_objectToExportTo The object to add the path to; default + * is goog.global. + */ +goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) { + goog.exportPath_(publicPath, object, opt_objectToExportTo); +}; + + +/** + * Exports a property unobfuscated into the object's namespace. + * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction); + * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod); + * @param {Object} object Object whose static property is being exported. + * @param {string} publicName Unobfuscated name to export. + * @param {*} symbol Object the name should point to. + */ +goog.exportProperty = function(object, publicName, symbol) { + object[publicName] = symbol; +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * Usage: + * <pre> + * function ParentClass(a, b) { } + * ParentClass.prototype.foo = function(a) { }; + * + * function ChildClass(a, b, c) { + * ChildClass.base(this, 'constructor', a, b); + * } + * goog.inherits(ChildClass, ParentClass); + * + * var child = new ChildClass('a', 'b', 'see'); + * child.foo(); // This works. + * </pre> + * + * @param {!Function} childCtor Child class. + * @param {!Function} parentCtor Parent class. + */ +goog.inherits = function(childCtor, parentCtor) { + /** @constructor */ + function tempCtor() {} + tempCtor.prototype = parentCtor.prototype; + childCtor.superClass_ = parentCtor.prototype; + childCtor.prototype = new tempCtor(); + /** @override */ + childCtor.prototype.constructor = childCtor; + + /** + * Calls superclass constructor/method. + * + * This function is only available if you use goog.inherits to + * express inheritance relationships between classes. + * + * NOTE: This is a replacement for goog.base and for superClass_ + * property defined in childCtor. + * + * @param {!Object} me Should always be "this". + * @param {string} methodName The method name to call. Calling + * superclass constructor can be done with the special string + * 'constructor'. + * @param {...*} var_args The arguments to pass to superclass + * method/constructor. + * @return {*} The return value of the superclass method/constructor. + */ + childCtor.base = function(me, methodName, var_args) { + // Copying using loop to avoid deop due to passing arguments object to + // function. This is faster in many JS engines as of late 2014. + var args = new Array(arguments.length - 2); + for (var i = 2; i < arguments.length; i++) { + args[i - 2] = arguments[i]; + } + return parentCtor.prototype[methodName].apply(me, args); + }; +}; + + +/** + * Call up to the superclass. + * + * If this is called from a constructor, then this calls the superclass + * constructor with arguments 1-N. + * + * If this is called from a prototype method, then you must pass the name of the + * method as the second argument to this function. If you do not, you will get a + * runtime error. This calls the superclass' method with arguments 2-N. + * + * This function only works if you use goog.inherits to express inheritance + * relationships between your classes. + * + * This function is a compiler primitive. At compile-time, the compiler will do + * macro expansion to remove a lot of the extra overhead that this function + * introduces. The compiler will also enforce a lot of the assumptions that this + * function makes, and treat it as a compiler error if you break them. + * + * @param {!Object} me Should always be "this". + * @param {*=} opt_methodName The method name if calling a super method. + * @param {...*} var_args The rest of the arguments. + * @return {*} The return value of the superclass method. + * @suppress {es5Strict} This method can not be used in strict mode, but + * all Closure Library consumers must depend on this file. + */ +goog.base = function(me, opt_methodName, var_args) { + var caller = arguments.callee.caller; + + if (goog.STRICT_MODE_COMPATIBLE || (goog.DEBUG && !caller)) { + throw Error( + 'arguments.caller not defined. goog.base() cannot be used ' + + 'with strict mode code. See ' + + 'http://www.ecma-international.org/ecma-262/5.1/#sec-C'); + } + + if (caller.superClass_) { + // Copying using loop to avoid deop due to passing arguments object to + // function. This is faster in many JS engines as of late 2014. + var ctorArgs = new Array(arguments.length - 1); + for (var i = 1; i < arguments.length; i++) { + ctorArgs[i - 1] = arguments[i]; + } + // This is a constructor. Call the superclass constructor. + return caller.superClass_.constructor.apply(me, ctorArgs); + } + + // Copying using loop to avoid deop due to passing arguments object to + // function. This is faster in many JS engines as of late 2014. + var args = new Array(arguments.length - 2); + for (var i = 2; i < arguments.length; i++) { + args[i - 2] = arguments[i]; + } + var foundCaller = false; + for (var ctor = me.constructor; ctor; + ctor = ctor.superClass_ && ctor.superClass_.constructor) { + if (ctor.prototype[opt_methodName] === caller) { + foundCaller = true; + } else if (foundCaller) { + return ctor.prototype[opt_methodName].apply(me, args); + } + } + + // If we did not find the caller in the prototype chain, then one of two + // things happened: + // 1) The caller is an instance method. + // 2) This method was not called by the right caller. + if (me[opt_methodName] === caller) { + return me.constructor.prototype[opt_methodName].apply(me, args); + } else { + throw Error( + 'goog.base called from a method of one name ' + + 'to a method of a different name'); + } +}; + + +/** + * Allow for aliasing within scope functions. This function exists for + * uncompiled code - in compiled code the calls will be inlined and the aliases + * applied. In uncompiled code the function is simply run since the aliases as + * written are valid JavaScript. + * + * + * @param {function()} fn Function to call. This function can contain aliases + * to namespaces (e.g. "var dom = goog.dom") or classes + * (e.g. "var Timer = goog.Timer"). + */ +goog.scope = function(fn) { + if (goog.isInModuleLoader_()) { + throw Error('goog.scope is not supported within a goog.module.'); + } + fn.call(goog.global); +}; + + +/* + * To support uncompiled, strict mode bundles that use eval to divide source + * like so: + * eval('someSource;//# sourceUrl sourcefile.js'); + * We need to export the globally defined symbols "goog" and "COMPILED". + * Exporting "goog" breaks the compiler optimizations, so we required that + * be defined externally. + * NOTE: We don't use goog.exportSymbol here because we don't want to trigger + * extern generation when that compiler option is enabled. + */ +if (!COMPILED) { + goog.global['COMPILED'] = COMPILED; +} + + +//============================================================================== +// goog.defineClass implementation +//============================================================================== + + +/** + * Creates a restricted form of a Closure "class": + * - from the compiler's perspective, the instance returned from the + * constructor is sealed (no new properties may be added). This enables + * better checks. + * - the compiler will rewrite this definition to a form that is optimal + * for type checking and optimization (initially this will be a more + * traditional form). + * + * @param {Function} superClass The superclass, Object or null. + * @param {goog.defineClass.ClassDescriptor} def + * An object literal describing + * the class. It may have the following properties: + * "constructor": the constructor function + * "statics": an object literal containing methods to add to the constructor + * as "static" methods or a function that will receive the constructor + * function as its only parameter to which static properties can + * be added. + * all other properties are added to the prototype. + * @return {!Function} The class constructor. + */ +goog.defineClass = function(superClass, def) { + // TODO(johnlenz): consider making the superClass an optional parameter. + var constructor = def.constructor; + var statics = def.statics; + // Wrap the constructor prior to setting up the prototype and static methods. + if (!constructor || constructor == Object.prototype.constructor) { + constructor = function() { + throw Error('cannot instantiate an interface (no constructor defined).'); + }; + } + + var cls = goog.defineClass.createSealingConstructor_(constructor, superClass); + if (superClass) { + goog.inherits(cls, superClass); + } + + // Remove all the properties that should not be copied to the prototype. + delete def.constructor; + delete def.statics; + + goog.defineClass.applyProperties_(cls.prototype, def); + if (statics != null) { + if (statics instanceof Function) { + statics(cls); + } else { + goog.defineClass.applyProperties_(cls, statics); + } + } + + return cls; +}; + + +/** + * @typedef {{ + * constructor: (!Function|undefined), + * statics: (Object|undefined|function(Function):void) + * }} + * @suppress {missingProvide} + */ +goog.defineClass.ClassDescriptor; + + +/** + * @define {boolean} Whether the instances returned by goog.defineClass should + * be sealed when possible. + * + * When sealing is disabled the constructor function will not be wrapped by + * goog.defineClass, making it incompatible with ES6 class methods. + */ +goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG); + + +/** + * If goog.defineClass.SEAL_CLASS_INSTANCES is enabled and Object.seal is + * defined, this function will wrap the constructor in a function that seals the + * results of the provided constructor function. + * + * @param {!Function} ctr The constructor whose results maybe be sealed. + * @param {Function} superClass The superclass constructor. + * @return {!Function} The replacement constructor. + * @private + */ +goog.defineClass.createSealingConstructor_ = function(ctr, superClass) { + if (!goog.defineClass.SEAL_CLASS_INSTANCES) { + // Do now wrap the constructor when sealing is disabled. Angular code + // depends on this for injection to work properly. + return ctr; + } + + // Compute whether the constructor is sealable at definition time, rather + // than when the instance is being constructed. + var superclassSealable = !goog.defineClass.isUnsealable_(superClass); + + /** + * @this {Object} + * @return {?} + */ + var wrappedCtr = function() { + // Don't seal an instance of a subclass when it calls the constructor of + // its super class as there is most likely still setup to do. + var instance = ctr.apply(this, arguments) || this; + instance[goog.UID_PROPERTY_] = instance[goog.UID_PROPERTY_]; + + if (this.constructor === wrappedCtr && superclassSealable && + Object.seal instanceof Function) { + Object.seal(instance); + } + return instance; + }; + + return wrappedCtr; +}; + + +/** + * @param {Function} ctr The constructor to test. + * @returns {boolean} Whether the constructor has been tagged as unsealable + * using goog.tagUnsealableClass. + * @private + */ +goog.defineClass.isUnsealable_ = function(ctr) { + return ctr && ctr.prototype && + ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]; +}; + + +// TODO(johnlenz): share these values with the goog.object +/** + * The names of the fields that are defined on Object.prototype. + * @type {!Array<string>} + * @private + * @const + */ +goog.defineClass.OBJECT_PROTOTYPE_FIELDS_ = [ + 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', + 'toLocaleString', 'toString', 'valueOf' +]; + + +// TODO(johnlenz): share this function with the goog.object +/** + * @param {!Object} target The object to add properties to. + * @param {!Object} source The object to copy properties from. + * @private + */ +goog.defineClass.applyProperties_ = function(target, source) { + // TODO(johnlenz): update this to support ES5 getters/setters + + var key; + for (key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + + // For IE the for-in-loop does not contain any properties that are not + // enumerable on the prototype object (for example isPrototypeOf from + // Object.prototype) and it will also not include 'replace' on objects that + // extend String and change 'replace' (not that it is common for anyone to + // extend anything except Object). + for (var i = 0; i < goog.defineClass.OBJECT_PROTOTYPE_FIELDS_.length; i++) { + key = goog.defineClass.OBJECT_PROTOTYPE_FIELDS_[i]; + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } +}; + + +/** + * Sealing classes breaks the older idiom of assigning properties on the + * prototype rather than in the constructor. As such, goog.defineClass + * must not seal subclasses of these old-style classes until they are fixed. + * Until then, this marks a class as "broken", instructing defineClass + * not to seal subclasses. + * @param {!Function} ctr The legacy constructor to tag as unsealable. + */ +goog.tagUnsealableClass = function(ctr) { + if (!COMPILED && goog.defineClass.SEAL_CLASS_INSTANCES) { + ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_] = true; + } +}; + + +/** + * Name for unsealable tag property. + * @const @private {string} + */ +goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_ = 'goog_defineClass_legacy_unsealable'; + +goog.provide('ol'); + + +/** + * Constants defined with the define tag cannot be changed in application + * code, but can be set at compile time. + * Some reduce the size of the build in advanced compile mode. + */ + + +/** + * @define {boolean} Enable debug mode. Default is `true`. + */ +ol.DEBUG = true; + + +/** + * @define {boolean} Assume touch. Default is `false`. + */ +ol.ASSUME_TOUCH = false; + + +/** + * TODO: rename this to something having to do with tile grids + * see https://github.com/openlayers/ol3/issues/2076 + * @define {number} Default maximum zoom for default tile grids. + */ +ol.DEFAULT_MAX_ZOOM = 42; + + +/** + * @define {number} Default min zoom level for the map view. Default is `0`. + */ +ol.DEFAULT_MIN_ZOOM = 0; + + +/** + * @define {number} Default maximum allowed threshold (in pixels) for + * reprojection triangulation. Default is `0.5`. + */ +ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD = 0.5; + + +/** + * @define {number} Default tile size. + */ +ol.DEFAULT_TILE_SIZE = 256; + + +/** + * @define {string} Default WMS version. + */ +ol.DEFAULT_WMS_VERSION = '1.3.0'; + + +/** + * @define {number} Hysteresis pixels. + */ +ol.DRAG_BOX_HYSTERESIS_PIXELS = 8; + + +/** + * @define {boolean} Enable the Canvas renderer. Default is `true`. Setting + * this to false at compile time in advanced mode removes all code + * supporting the Canvas renderer from the build. + */ +ol.ENABLE_CANVAS = true; + + +/** + * @define {boolean} Enable rendering of ol.layer.Image based layers. Default + * is `true`. Setting this to false at compile time in advanced mode removes + * all code supporting Image layers from the build. + */ +ol.ENABLE_IMAGE = true; + + +/** + * @define {boolean} Enable integration with the Proj4js library. Default is + * `true`. + */ +ol.ENABLE_PROJ4JS = true; + + +/** + * @define {boolean} Enable automatic reprojection of raster sources. Default is + * `true`. + */ +ol.ENABLE_RASTER_REPROJECTION = true; + + +/** + * @define {boolean} Enable rendering of ol.layer.Tile based layers. Default is + * `true`. Setting this to false at compile time in advanced mode removes + * all code supporting Tile layers from the build. + */ +ol.ENABLE_TILE = true; + + +/** + * @define {boolean} Enable rendering of ol.layer.Vector based layers. Default + * is `true`. Setting this to false at compile time in advanced mode removes + * all code supporting Vector layers from the build. + */ +ol.ENABLE_VECTOR = true; + + +/** + * @define {boolean} Enable rendering of ol.layer.VectorTile based layers. + * Default is `true`. Setting this to false at compile time in advanced mode + * removes all code supporting VectorTile layers from the build. + */ +ol.ENABLE_VECTOR_TILE = true; + + +/** + * @define {boolean} Enable the WebGL renderer. Default is `true`. Setting + * this to false at compile time in advanced mode removes all code + * supporting the WebGL renderer from the build. + */ +ol.ENABLE_WEBGL = true; + + +/** + * @define {number} The size in pixels of the first atlas image. Default is + * `256`. + */ +ol.INITIAL_ATLAS_SIZE = 256; + + +/** + * @define {number} The maximum size in pixels of atlas images. Default is + * `-1`, meaning it is not used (and `ol.WEBGL_MAX_TEXTURE_SIZE` is + * used instead). + */ +ol.MAX_ATLAS_SIZE = -1; + + +/** + * @define {number} Maximum mouse wheel delta. + */ +ol.MOUSEWHEELZOOM_MAXDELTA = 1; + + +/** + * @define {number} Maximum width and/or height extent ratio that determines + * when the overview map should be zoomed out. + */ +ol.OVERVIEWMAP_MAX_RATIO = 0.75; + + +/** + * @define {number} Minimum width and/or height extent ratio that determines + * when the overview map should be zoomed in. + */ +ol.OVERVIEWMAP_MIN_RATIO = 0.1; + + +/** + * @define {number} Maximum number of source tiles for raster reprojection of + * a single tile. + * If too many source tiles are determined to be loaded to create a single + * reprojected tile the browser can become unresponsive or even crash. + * This can happen if the developer defines projections improperly and/or + * with unlimited extents. + * If too many tiles are required, no tiles are loaded and + * `ol.Tile.State.ERROR` state is set. Default is `100`. + */ +ol.RASTER_REPROJECTION_MAX_SOURCE_TILES = 100; + + +/** + * @define {number} Maximum number of subdivision steps during raster + * reprojection triangulation. Prevents high memory usage and large + * number of proj4 calls (for certain transformations and areas). + * At most `2*(2^this)` triangles are created for each triangulated + * extent (tile/image). Default is `10`. + */ +ol.RASTER_REPROJECTION_MAX_SUBDIVISION = 10; + + +/** + * @define {number} Maximum allowed size of triangle relative to world width. + * When transforming corners of world extent between certain projections, + * the resulting triangulation seems to have zero error and no subdivision + * is performed. + * If the triangle width is more than this (relative to world width; 0-1), + * subdivison is forced (up to `ol.RASTER_REPROJECTION_MAX_SUBDIVISION`). + * Default is `0.25`. + */ +ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH = 0.25; + + +/** + * @define {number} Tolerance for geometry simplification in device pixels. + */ +ol.SIMPLIFY_TOLERANCE = 0.5; + + +/** + * @define {number} Texture cache high water mark. + */ +ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024; + + +/** + * @define {string} OpenLayers version. + */ +ol.VERSION = ''; + + +/** + * The maximum supported WebGL texture size in pixels. If WebGL is not + * supported, the value is set to `undefined`. + * @const + * @type {number|undefined} + */ +ol.WEBGL_MAX_TEXTURE_SIZE; // value is set in `ol.has` + + +/** + * List of supported WebGL extensions. + * @const + * @type {Array.<string>} + */ +ol.WEBGL_EXTENSIONS; // value is set in `ol.has` + + +/** + * Inherit the prototype methods from one constructor into another. + * + * Usage: + * + * function ParentClass(a, b) { } + * ParentClass.prototype.foo = function(a) { } + * + * function ChildClass(a, b, c) { + * // Call parent constructor + * ParentClass.call(this, a, b); + * } + * ol.inherits(ChildClass, ParentClass); + * + * var child = new ChildClass('a', 'b', 'see'); + * child.foo(); // This works. + * + * @param {!Function} childCtor Child constructor. + * @param {!Function} parentCtor Parent constructor. + * @function + * @api + */ +ol.inherits = function(childCtor, parentCtor) { + childCtor.prototype = Object.create(parentCtor.prototype); + childCtor.prototype.constructor = childCtor; +}; + + +/** + * A reusable function, used e.g. as a default for callbacks. + * + * @return {undefined} Nothing. + */ +ol.nullFunction = function() {}; + + +/** + * Gets a unique ID for an object. This mutates the object so that further calls + * with the same object as a parameter returns the same value. Unique IDs are generated + * as a strictly increasing sequence. Adapted from goog.getUid. + * + * @param {Object} obj The object to get the unique ID for. + * @return {number} The unique ID for the object. + */ +ol.getUid = function(obj) { + return obj.ol_uid || + (obj.ol_uid = ++ol.uidCounter_); +}; + + +/** + * Counter for getUid. + * @type {number} + * @private + */ +ol.uidCounter_ = 0; + +goog.provide('ol.AssertionError'); + +goog.require('ol'); + +/** + * Error object thrown when an assertion failed. This is an ECMA-262 Error, + * extended with a `code` property. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error} + * @constructor + * @extends {Error} + * @implements {oli.AssertionError} + * @param {number} code Error code. + */ +ol.AssertionError = function(code) { + + /** + * @type {string} + */ + this.message = 'Assertion failed. See ' + + (ol.VERSION ? 'https://openlayers.org/en/' + ol.VERSION.split('-')[0] : '') + + '/doc/errors/#' + code + ' for details.'; + + /** + * Error code. The meaning of the code can be found on + * {@link https://openlayers.org/en/latest/doc/errors/} (replace `latest` with + * the version found in the OpenLayers script's header comment if a version + * other than the latest is used). + * @type {number} + * @api + */ + this.code = code; + + this.name = 'AssertionError'; + +}; +ol.inherits(ol.AssertionError, Error); + +goog.provide('ol.asserts'); + +goog.require('ol.AssertionError'); + + +/** + * @param {*} assertion Assertion we expected to be truthy. + * @param {number} errorCode Error code. + */ +ol.asserts.assert = function(assertion, errorCode) { + if (!assertion) { + throw new ol.AssertionError(errorCode); + } +}; + +goog.provide('ol.math'); + +goog.require('ol'); +goog.require('ol.asserts'); + + +/** + * Takes a number and clamps it to within the provided bounds. + * @param {number} value The input number. + * @param {number} min The minimum value to return. + * @param {number} max The maximum value to return. + * @return {number} The input number if it is within bounds, or the nearest + * number within the bounds. + */ +ol.math.clamp = function(value, min, max) { + return Math.min(Math.max(value, min), max); +}; + + +/** + * Return the hyperbolic cosine of a given number. The method will use the + * native `Math.cosh` function if it is available, otherwise the hyperbolic + * cosine will be calculated via the reference implementation of the Mozilla + * developer network. + * + * @param {number} x X. + * @return {number} Hyperbolic cosine of x. + */ +ol.math.cosh = (function() { + // Wrapped in a iife, to save the overhead of checking for the native + // implementation on every invocation. + var cosh; + if ('cosh' in Math) { + // The environment supports the native Math.cosh function, use it… + cosh = Math.cosh; + } else { + // … else, use the reference implementation of MDN: + cosh = function(x) { + var y = Math.exp(x); + return (y + 1 / y) / 2; + }; + } + return cosh; +}()); + + +/** + * @param {number} x X. + * @return {number} The smallest power of two greater than or equal to x. + */ +ol.math.roundUpToPowerOfTwo = function(x) { + ol.asserts.assert(0 < x, 29); // `x` must be greater than `0` + return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2)); +}; + + +/** + * Returns the square of the closest distance between the point (x, y) and the + * line segment (x1, y1) to (x2, y2). + * @param {number} x X. + * @param {number} y Y. + * @param {number} x1 X1. + * @param {number} y1 Y1. + * @param {number} x2 X2. + * @param {number} y2 Y2. + * @return {number} Squared distance. + */ +ol.math.squaredSegmentDistance = function(x, y, x1, y1, x2, y2) { + var dx = x2 - x1; + var dy = y2 - y1; + if (dx !== 0 || dy !== 0) { + var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy); + if (t > 1) { + x1 = x2; + y1 = y2; + } else if (t > 0) { + x1 += dx * t; + y1 += dy * t; + } + } + return ol.math.squaredDistance(x, y, x1, y1); +}; + + +/** + * Returns the square of the distance between the points (x1, y1) and (x2, y2). + * @param {number} x1 X1. + * @param {number} y1 Y1. + * @param {number} x2 X2. + * @param {number} y2 Y2. + * @return {number} Squared distance. + */ +ol.math.squaredDistance = function(x1, y1, x2, y2) { + var dx = x2 - x1; + var dy = y2 - y1; + return dx * dx + dy * dy; +}; + + +/** + * Solves system of linear equations using Gaussian elimination method. + * + * @param {Array.<Array.<number>>} mat Augmented matrix (n x n + 1 column) + * in row-major order. + * @return {Array.<number>} The resulting vector. + */ +ol.math.solveLinearSystem = function(mat) { + var n = mat.length; + + if (ol.DEBUG) { + for (var row = 0; row < n; row++) { + console.assert(mat[row].length == n + 1, + 'every row should have correct number of columns'); + } + } + + for (var i = 0; i < n; i++) { + // Find max in the i-th column (ignoring i - 1 first rows) + var maxRow = i; + var maxEl = Math.abs(mat[i][i]); + for (var r = i + 1; r < n; r++) { + var absValue = Math.abs(mat[r][i]); + if (absValue > maxEl) { + maxEl = absValue; + maxRow = r; + } + } + + if (maxEl === 0) { + return null; // matrix is singular + } + + // Swap max row with i-th (current) row + var tmp = mat[maxRow]; + mat[maxRow] = mat[i]; + mat[i] = tmp; + + // Subtract the i-th row to make all the remaining rows 0 in the i-th column + for (var j = i + 1; j < n; j++) { + var coef = -mat[j][i] / mat[i][i]; + for (var k = i; k < n + 1; k++) { + if (i == k) { + mat[j][k] = 0; + } else { + mat[j][k] += coef * mat[i][k]; + } + } + } + } + + // Solve Ax=b for upper triangular matrix A (mat) + var x = new Array(n); + for (var l = n - 1; l >= 0; l--) { + x[l] = mat[l][n] / mat[l][l]; + for (var m = l - 1; m >= 0; m--) { + mat[m][n] -= mat[m][l] * x[l]; + } + } + return x; +}; + + +/** + * Converts radians to to degrees. + * + * @param {number} angleInRadians Angle in radians. + * @return {number} Angle in degrees. + */ +ol.math.toDegrees = function(angleInRadians) { + return angleInRadians * 180 / Math.PI; +}; + + +/** + * Converts degrees to radians. + * + * @param {number} angleInDegrees Angle in degrees. + * @return {number} Angle in radians. + */ +ol.math.toRadians = function(angleInDegrees) { + return angleInDegrees * Math.PI / 180; +}; + +/** + * Returns the modulo of a / b, depending on the sign of b. + * + * @param {number} a Dividend. + * @param {number} b Divisor. + * @return {number} Modulo. + */ +ol.math.modulo = function(a, b) { + var r = a % b; + return r * b < 0 ? r + b : r; +}; + +/** + * Calculates the linearly interpolated value of x between a and b. + * + * @param {number} a Number + * @param {number} b Number + * @param {number} x Value to be interpolated. + * @return {number} Interpolated value. + */ +ol.math.lerp = function(a, b, x) { + return a + x * (b - a); +}; + +goog.provide('ol.CenterConstraint'); + +goog.require('ol.math'); + + +/** + * @param {ol.Extent} extent Extent. + * @return {ol.CenterConstraintType} The constraint. + */ +ol.CenterConstraint.createExtent = function(extent) { + return ( + /** + * @param {ol.Coordinate|undefined} center Center. + * @return {ol.Coordinate|undefined} Center. + */ + function(center) { + if (center) { + return [ + ol.math.clamp(center[0], extent[0], extent[2]), + ol.math.clamp(center[1], extent[1], extent[3]) + ]; + } else { + return undefined; + } + }); +}; + + +/** + * @param {ol.Coordinate|undefined} center Center. + * @return {ol.Coordinate|undefined} Center. + */ +ol.CenterConstraint.none = function(center) { + return center; +}; + +goog.provide('ol.Constraints'); + + +/** + * @constructor + * @param {ol.CenterConstraintType} centerConstraint Center constraint. + * @param {ol.ResolutionConstraintType} resolutionConstraint + * Resolution constraint. + * @param {ol.RotationConstraintType} rotationConstraint + * Rotation constraint. + */ +ol.Constraints = function(centerConstraint, resolutionConstraint, rotationConstraint) { + + /** + * @type {ol.CenterConstraintType} + */ + this.center = centerConstraint; + + /** + * @type {ol.ResolutionConstraintType} + */ + this.resolution = resolutionConstraint; + + /** + * @type {ol.RotationConstraintType} + */ + this.rotation = rotationConstraint; + +}; + +goog.provide('ol.obj'); + + +/** + * Polyfill for Object.assign(). Assigns enumerable and own properties from + * one or more source objects to a target object. + * + * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign + * @param {!Object} target The target object. + * @param {...Object} var_sources The source object(s). + * @return {!Object} The modified target object. + */ +ol.obj.assign = (typeof Object.assign === 'function') ? Object.assign : function(target, var_sources) { + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + var output = Object(target); + for (var i = 1, ii = arguments.length; i < ii; ++i) { + var source = arguments[i]; + if (source !== undefined && source !== null) { + for (var key in source) { + if (source.hasOwnProperty(key)) { + output[key] = source[key]; + } + } + } + } + return output; +}; + + +/** + * Removes all properties from an object. + * @param {Object} object The object to clear. + */ +ol.obj.clear = function(object) { + for (var property in object) { + delete object[property]; + } +}; + + +/** + * Get an array of property values from an object. + * @param {Object<K,V>} object The object from which to get the values. + * @return {!Array<V>} The property values. + * @template K,V + */ +ol.obj.getValues = function(object) { + var values = []; + for (var property in object) { + values.push(object[property]); + } + return values; +}; + + +/** + * Determine if an object has any properties. + * @param {Object} object The object to check. + * @return {boolean} The object is empty. + */ +ol.obj.isEmpty = function(object) { + var property; + for (property in object) { + return false; + } + return !property; +}; + +goog.provide('ol.events'); + +goog.require('ol.obj'); + + +/** + * @param {ol.EventsKey} listenerObj Listener object. + * @return {ol.EventsListenerFunctionType} Bound listener. + */ +ol.events.bindListener_ = function(listenerObj) { + var boundListener = function(evt) { + var listener = listenerObj.listener; + var bindTo = listenerObj.bindTo || listenerObj.target; + if (listenerObj.callOnce) { + ol.events.unlistenByKey(listenerObj); + } + return listener.call(bindTo, evt); + }; + listenerObj.boundListener = boundListener; + return boundListener; +}; + + +/** + * Finds the matching {@link ol.EventsKey} in the given listener + * array. + * + * @param {!Array<!ol.EventsKey>} listeners Array of listeners. + * @param {!Function} listener The listener function. + * @param {Object=} opt_this The `this` value inside the listener. + * @param {boolean=} opt_setDeleteIndex Set the deleteIndex on the matching + * listener, for {@link ol.events.unlistenByKey}. + * @return {ol.EventsKey|undefined} The matching listener object. + * @private + */ +ol.events.findListener_ = function(listeners, listener, opt_this, + opt_setDeleteIndex) { + var listenerObj; + for (var i = 0, ii = listeners.length; i < ii; ++i) { + listenerObj = listeners[i]; + if (listenerObj.listener === listener && + listenerObj.bindTo === opt_this) { + if (opt_setDeleteIndex) { + listenerObj.deleteIndex = i; + } + return listenerObj; + } + } + return undefined; +}; + + +/** + * @param {ol.EventTargetLike} target Target. + * @param {string} type Type. + * @return {Array.<ol.EventsKey>|undefined} Listeners. + */ +ol.events.getListeners = function(target, type) { + var listenerMap = target.ol_lm; + return listenerMap ? listenerMap[type] : undefined; +}; + + +/** + * Get the lookup of listeners. If one does not exist on the target, it is + * created. + * @param {ol.EventTargetLike} target Target. + * @return {!Object.<string, Array.<ol.EventsKey>>} Map of + * listeners by event type. + * @private + */ +ol.events.getListenerMap_ = function(target) { + var listenerMap = target.ol_lm; + if (!listenerMap) { + listenerMap = target.ol_lm = {}; + } + return listenerMap; +}; + + +/** + * Clean up all listener objects of the given type. All properties on the + * listener objects will be removed, and if no listeners remain in the listener + * map, it will be removed from the target. + * @param {ol.EventTargetLike} target Target. + * @param {string} type Type. + * @private + */ +ol.events.removeListeners_ = function(target, type) { + var listeners = ol.events.getListeners(target, type); + if (listeners) { + for (var i = 0, ii = listeners.length; i < ii; ++i) { + target.removeEventListener(type, listeners[i].boundListener); + ol.obj.clear(listeners[i]); + } + listeners.length = 0; + var listenerMap = target.ol_lm; + if (listenerMap) { + delete listenerMap[type]; + if (Object.keys(listenerMap).length === 0) { + delete target.ol_lm; + } + } + } +}; + + +/** + * Registers an event listener on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * This function efficiently binds a `listener` to a `this` object, and returns + * a key for use with {@link ol.events.unlistenByKey}. + * + * @param {ol.EventTargetLike} target Event target. + * @param {string} type Event type. + * @param {ol.EventsListenerFunctionType} listener Listener. + * @param {Object=} opt_this Object referenced by the `this` keyword in the + * listener. Default is the `target`. + * @param {boolean=} opt_once If true, add the listener as one-off listener. + * @return {ol.EventsKey} Unique key for the listener. + */ +ol.events.listen = function(target, type, listener, opt_this, opt_once) { + var listenerMap = ol.events.getListenerMap_(target); + var listeners = listenerMap[type]; + if (!listeners) { + listeners = listenerMap[type] = []; + } + var listenerObj = ol.events.findListener_(listeners, listener, opt_this, + false); + if (listenerObj) { + if (!opt_once) { + // Turn one-off listener into a permanent one. + listenerObj.callOnce = false; + } + } else { + listenerObj = /** @type {ol.EventsKey} */ ({ + bindTo: opt_this, + callOnce: !!opt_once, + listener: listener, + target: target, + type: type + }); + target.addEventListener(type, ol.events.bindListener_(listenerObj)); + listeners.push(listenerObj); + } + + return listenerObj; +}; + + +/** + * Registers a one-off event listener on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * This function efficiently binds a `listener` as self-unregistering listener + * to a `this` object, and returns a key for use with + * {@link ol.events.unlistenByKey} in case the listener needs to be unregistered + * before it is called. + * + * When {@link ol.events.listen} is called with the same arguments after this + * function, the self-unregistering listener will be turned into a permanent + * listener. + * + * @param {ol.EventTargetLike} target Event target. + * @param {string} type Event type. + * @param {ol.EventsListenerFunctionType} listener Listener. + * @param {Object=} opt_this Object referenced by the `this` keyword in the + * listener. Default is the `target`. + * @return {ol.EventsKey} Key for unlistenByKey. + */ +ol.events.listenOnce = function(target, type, listener, opt_this) { + return ol.events.listen(target, type, listener, opt_this, true); +}; + + +/** + * Unregisters an event listener on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * To return a listener, this function needs to be called with the exact same + * arguments that were used for a previous {@link ol.events.listen} call. + * + * @param {ol.EventTargetLike} target Event target. + * @param {string} type Event type. + * @param {ol.EventsListenerFunctionType} listener Listener. + * @param {Object=} opt_this Object referenced by the `this` keyword in the + * listener. Default is the `target`. + */ +ol.events.unlisten = function(target, type, listener, opt_this) { + var listeners = ol.events.getListeners(target, type); + if (listeners) { + var listenerObj = ol.events.findListener_(listeners, listener, opt_this, + true); + if (listenerObj) { + ol.events.unlistenByKey(listenerObj); + } + } +}; + + +/** + * Unregisters event listeners on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * The argument passed to this function is the key returned from + * {@link ol.events.listen} or {@link ol.events.listenOnce}. + * + * @param {ol.EventsKey} key The key. + */ +ol.events.unlistenByKey = function(key) { + if (key && key.target) { + key.target.removeEventListener(key.type, key.boundListener); + var listeners = ol.events.getListeners(key.target, key.type); + if (listeners) { + var i = 'deleteIndex' in key ? key.deleteIndex : listeners.indexOf(key); + if (i !== -1) { + listeners.splice(i, 1); + } + if (listeners.length === 0) { + ol.events.removeListeners_(key.target, key.type); + } + } + ol.obj.clear(key); + } +}; + + +/** + * Unregisters all event listeners on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * @param {ol.EventTargetLike} target Target. + */ +ol.events.unlistenAll = function(target) { + var listenerMap = ol.events.getListenerMap_(target); + for (var type in listenerMap) { + ol.events.removeListeners_(target, type); + } +}; + +goog.provide('ol.Disposable'); + +goog.require('ol'); + +/** + * Objects that need to clean up after themselves. + * @constructor + */ +ol.Disposable = function() {}; + +/** + * The object has already been disposed. + * @type {boolean} + * @private + */ +ol.Disposable.prototype.disposed_ = false; + +/** + * Clean up. + */ +ol.Disposable.prototype.dispose = function() { + if (!this.disposed_) { + this.disposed_ = true; + this.disposeInternal(); + } +}; + +/** + * Extension point for disposable objects. + * @protected + */ +ol.Disposable.prototype.disposeInternal = ol.nullFunction; + +goog.provide('ol.events.Event'); + + +/** + * @classdesc + * Stripped down implementation of the W3C DOM Level 2 Event interface. + * @see {@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface} + * + * This implementation only provides `type` and `target` properties, and + * `stopPropagation` and `preventDefault` methods. It is meant as base class + * for higher level events defined in the library, and works with + * {@link ol.events.EventTarget}. + * + * @constructor + * @implements {oli.events.Event} + * @param {string} type Type. + */ +ol.events.Event = function(type) { + + /** + * @type {boolean} + */ + this.propagationStopped; + + /** + * The event type. + * @type {string} + * @api stable + */ + this.type = type; + + /** + * The event target. + * @type {Object} + * @api stable + */ + this.target = null; + +}; + + +/** + * Stop event propagation. + * @function + * @api stable + */ +ol.events.Event.prototype.preventDefault = + +/** + * Stop event propagation. + * @function + * @api stable + */ +ol.events.Event.prototype.stopPropagation = function() { + this.propagationStopped = true; +}; + + +/** + * @param {Event|ol.events.Event} evt Event + */ +ol.events.Event.stopPropagation = function(evt) { + evt.stopPropagation(); +}; + + +/** + * @param {Event|ol.events.Event} evt Event + */ +ol.events.Event.preventDefault = function(evt) { + evt.preventDefault(); +}; + +goog.provide('ol.events.EventTarget'); + +goog.require('ol'); +goog.require('ol.Disposable'); +goog.require('ol.events'); +goog.require('ol.events.Event'); + + +/** + * @classdesc + * A simplified implementation of the W3C DOM Level 2 EventTarget interface. + * @see {@link https://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html#Events-EventTarget} + * + * There are two important simplifications compared to the specification: + * + * 1. The handling of `useCapture` in `addEventListener` and + * `removeEventListener`. There is no real capture model. + * 2. The handling of `stopPropagation` and `preventDefault` on `dispatchEvent`. + * There is no event target hierarchy. When a listener calls + * `stopPropagation` or `preventDefault` on an event object, it means that no + * more listeners after this one will be called. Same as when the listener + * returns false. + * + * @constructor + * @extends {ol.Disposable} + */ +ol.events.EventTarget = function() { + + ol.Disposable.call(this); + + /** + * @private + * @type {!Object.<string, number>} + */ + this.pendingRemovals_ = {}; + + /** + * @private + * @type {!Object.<string, number>} + */ + this.dispatching_ = {}; + + /** + * @private + * @type {!Object.<string, Array.<ol.EventsListenerFunctionType>>} + */ + this.listeners_ = {}; + +}; +ol.inherits(ol.events.EventTarget, ol.Disposable); + + +/** + * @param {string} type Type. + * @param {ol.EventsListenerFunctionType} listener Listener. + */ +ol.events.EventTarget.prototype.addEventListener = function(type, listener) { + var listeners = this.listeners_[type]; + if (!listeners) { + listeners = this.listeners_[type] = []; + } + if (listeners.indexOf(listener) === -1) { + listeners.push(listener); + } +}; + + +/** + * @param {{type: string, + * target: (EventTarget|ol.events.EventTarget|undefined)}|ol.events.Event| + * string} event Event or event type. + * @return {boolean|undefined} `false` if anyone called preventDefault on the + * event object or if any of the listeners returned false. + */ +ol.events.EventTarget.prototype.dispatchEvent = function(event) { + var evt = typeof event === 'string' ? new ol.events.Event(event) : event; + var type = evt.type; + evt.target = this; + var listeners = this.listeners_[type]; + var propagate; + if (listeners) { + if (!(type in this.dispatching_)) { + this.dispatching_[type] = 0; + this.pendingRemovals_[type] = 0; + } + ++this.dispatching_[type]; + for (var i = 0, ii = listeners.length; i < ii; ++i) { + if (listeners[i].call(this, evt) === false || evt.propagationStopped) { + propagate = false; + break; + } + } + --this.dispatching_[type]; + if (this.dispatching_[type] === 0) { + var pendingRemovals = this.pendingRemovals_[type]; + delete this.pendingRemovals_[type]; + while (pendingRemovals--) { + this.removeEventListener(type, ol.nullFunction); + } + delete this.dispatching_[type]; + } + return propagate; + } +}; + + +/** + * @inheritDoc + */ +ol.events.EventTarget.prototype.disposeInternal = function() { + ol.events.unlistenAll(this); +}; + + +/** + * Get the listeners for a specified event type. Listeners are returned in the + * order that they will be called in. + * + * @param {string} type Type. + * @return {Array.<ol.EventsListenerFunctionType>} Listeners. + */ +ol.events.EventTarget.prototype.getListeners = function(type) { + return this.listeners_[type]; +}; + + +/** + * @param {string=} opt_type Type. If not provided, + * `true` will be returned if this EventTarget has any listeners. + * @return {boolean} Has listeners. + */ +ol.events.EventTarget.prototype.hasListener = function(opt_type) { + return opt_type ? + opt_type in this.listeners_ : + Object.keys(this.listeners_).length > 0; +}; + + +/** + * @param {string} type Type. + * @param {ol.EventsListenerFunctionType} listener Listener. + */ +ol.events.EventTarget.prototype.removeEventListener = function(type, listener) { + var listeners = this.listeners_[type]; + if (listeners) { + var index = listeners.indexOf(listener); + ol.DEBUG && console.assert(index != -1, 'listener not found'); + if (type in this.pendingRemovals_) { + // make listener a no-op, and remove later in #dispatchEvent() + listeners[index] = ol.nullFunction; + ++this.pendingRemovals_[type]; + } else { + listeners.splice(index, 1); + if (listeners.length === 0) { + delete this.listeners_[type]; + } + } + } +}; + +goog.provide('ol.events.EventType'); + +/** + * @enum {string} + * @const + */ +ol.events.EventType = { + /** + * Generic change event. Triggered when the revision counter is increased. + * @event ol.events.Event#change + * @api + */ + CHANGE: 'change', + + CLICK: 'click', + DBLCLICK: 'dblclick', + DRAGENTER: 'dragenter', + DRAGOVER: 'dragover', + DROP: 'drop', + ERROR: 'error', + KEYDOWN: 'keydown', + KEYPRESS: 'keypress', + LOAD: 'load', + MOUSEDOWN: 'mousedown', + MOUSEMOVE: 'mousemove', + MOUSEOUT: 'mouseout', + MOUSEUP: 'mouseup', + MOUSEWHEEL: 'mousewheel', + MSPOINTERDOWN: 'mspointerdown', + RESIZE: 'resize', + TOUCHSTART: 'touchstart', + TOUCHMOVE: 'touchmove', + TOUCHEND: 'touchend', + WHEEL: 'wheel' +}; + +goog.provide('ol.Observable'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); +goog.require('ol.events.EventType'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * An event target providing convenient methods for listener registration + * and unregistration. A generic `change` event is always available through + * {@link ol.Observable#changed}. + * + * @constructor + * @extends {ol.events.EventTarget} + * @fires ol.events.Event + * @struct + * @api stable + */ +ol.Observable = function() { + + ol.events.EventTarget.call(this); + + /** + * @private + * @type {number} + */ + this.revision_ = 0; + +}; +ol.inherits(ol.Observable, ol.events.EventTarget); + + +/** + * Removes an event listener using the key returned by `on()` or `once()`. + * @param {ol.EventsKey|Array.<ol.EventsKey>} key The key returned by `on()` + * or `once()` (or an array of keys). + * @api stable + */ +ol.Observable.unByKey = function(key) { + if (Array.isArray(key)) { + for (var i = 0, ii = key.length; i < ii; ++i) { + ol.events.unlistenByKey(key[i]); + } + } else { + ol.events.unlistenByKey(/** @type {ol.EventsKey} */ (key)); + } +}; + + +/** + * Increases the revision counter and dispatches a 'change' event. + * @api + */ +ol.Observable.prototype.changed = function() { + ++this.revision_; + this.dispatchEvent(ol.events.EventType.CHANGE); +}; + + +/** + * Dispatches an event and calls all listeners listening for events + * of this type. The event parameter can either be a string or an + * Object with a `type` property. + * + * @param {{type: string, + * target: (EventTarget|ol.events.EventTarget|undefined)}|ol.events.Event| + * string} event Event object. + * @function + * @api + */ +ol.Observable.prototype.dispatchEvent; + + +/** + * Get the version number for this object. Each time the object is modified, + * its version number will be incremented. + * @return {number} Revision. + * @api + */ +ol.Observable.prototype.getRevision = function() { + return this.revision_; +}; + + +/** + * Listen for a certain type of event. + * @param {string|Array.<string>} type The event type or array of event types. + * @param {function(?): ?} listener The listener function. + * @param {Object=} opt_this The object to use as `this` in `listener`. + * @return {ol.EventsKey|Array.<ol.EventsKey>} Unique key for the listener. If + * called with an array of event types as the first argument, the return + * will be an array of keys. + * @api stable + */ +ol.Observable.prototype.on = function(type, listener, opt_this) { + if (Array.isArray(type)) { + var len = type.length; + var keys = new Array(len); + for (var i = 0; i < len; ++i) { + keys[i] = ol.events.listen(this, type[i], listener, opt_this); + } + return keys; + } else { + return ol.events.listen( + this, /** @type {string} */ (type), listener, opt_this); + } +}; + + +/** + * Listen once for a certain type of event. + * @param {string|Array.<string>} type The event type or array of event types. + * @param {function(?): ?} listener The listener function. + * @param {Object=} opt_this The object to use as `this` in `listener`. + * @return {ol.EventsKey|Array.<ol.EventsKey>} Unique key for the listener. If + * called with an array of event types as the first argument, the return + * will be an array of keys. + * @api stable + */ +ol.Observable.prototype.once = function(type, listener, opt_this) { + if (Array.isArray(type)) { + var len = type.length; + var keys = new Array(len); + for (var i = 0; i < len; ++i) { + keys[i] = ol.events.listenOnce(this, type[i], listener, opt_this); + } + return keys; + } else { + return ol.events.listenOnce( + this, /** @type {string} */ (type), listener, opt_this); + } +}; + + +/** + * Unlisten for a certain type of event. + * @param {string|Array.<string>} type The event type or array of event types. + * @param {function(?): ?} listener The listener function. + * @param {Object=} opt_this The object which was used as `this` by the + * `listener`. + * @api stable + */ +ol.Observable.prototype.un = function(type, listener, opt_this) { + if (Array.isArray(type)) { + for (var i = 0, ii = type.length; i < ii; ++i) { + ol.events.unlisten(this, type[i], listener, opt_this); + } + return; + } else { + ol.events.unlisten(this, /** @type {string} */ (type), listener, opt_this); + } +}; + + +/** + * Removes an event listener using the key returned by `on()` or `once()`. + * Note that using the {@link ol.Observable.unByKey} static function is to + * be preferred. + * @param {ol.EventsKey|Array.<ol.EventsKey>} key The key returned by `on()` + * or `once()` (or an array of keys). + * @function + * @api stable + */ +ol.Observable.prototype.unByKey = ol.Observable.unByKey; + +goog.provide('ol.Object'); + +goog.require('ol'); +goog.require('ol.Observable'); +goog.require('ol.events.Event'); +goog.require('ol.obj'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Most non-trivial classes inherit from this. + * + * This extends {@link ol.Observable} with observable properties, where each + * property is observable as well as the object as a whole. + * + * Classes that inherit from this have pre-defined properties, to which you can + * add your owns. The pre-defined properties are listed in this documentation as + * 'Observable Properties', and have their own accessors; for example, + * {@link ol.Map} has a `target` property, accessed with `getTarget()` and + * changed with `setTarget()`. Not all properties are however settable. There + * are also general-purpose accessors `get()` and `set()`. For example, + * `get('target')` is equivalent to `getTarget()`. + * + * The `set` accessors trigger a change event, and you can monitor this by + * registering a listener. For example, {@link ol.View} has a `center` + * property, so `view.on('change:center', function(evt) {...});` would call the + * function whenever the value of the center property changes. Within the + * function, `evt.target` would be the view, so `evt.target.getCenter()` would + * return the new center. + * + * You can add your own observable properties with + * `object.set('prop', 'value')`, and retrieve that with `object.get('prop')`. + * You can listen for changes on that property value with + * `object.on('change:prop', listener)`. You can get a list of all + * properties with {@link ol.Object#getProperties object.getProperties()}. + * + * Note that the observable properties are separate from standard JS properties. + * You can, for example, give your map object a title with + * `map.title='New title'` and with `map.set('title', 'Another title')`. The + * first will be a `hasOwnProperty`; the second will appear in + * `getProperties()`. Only the second is observable. + * + * Properties can be deleted by using the unset method. E.g. + * object.unset('foo'). + * + * @constructor + * @extends {ol.Observable} + * @param {Object.<string, *>=} opt_values An object with key-value pairs. + * @fires ol.Object.Event + * @api + */ +ol.Object = function(opt_values) { + ol.Observable.call(this); + + // Call ol.getUid to ensure that the order of objects' ids is the same as + // the order in which they were created. This also helps to ensure that + // object properties are always added in the same order, which helps many + // JavaScript engines generate faster code. + ol.getUid(this); + + /** + * @private + * @type {!Object.<string, *>} + */ + this.values_ = {}; + + if (opt_values !== undefined) { + this.setProperties(opt_values); + } +}; +ol.inherits(ol.Object, ol.Observable); + + +/** + * @private + * @type {Object.<string, string>} + */ +ol.Object.changeEventTypeCache_ = {}; + + +/** + * @param {string} key Key name. + * @return {string} Change name. + */ +ol.Object.getChangeEventType = function(key) { + return ol.Object.changeEventTypeCache_.hasOwnProperty(key) ? + ol.Object.changeEventTypeCache_[key] : + (ol.Object.changeEventTypeCache_[key] = 'change:' + key); +}; + + +/** + * Gets a value. + * @param {string} key Key name. + * @return {*} Value. + * @api stable + */ +ol.Object.prototype.get = function(key) { + var value; + if (this.values_.hasOwnProperty(key)) { + value = this.values_[key]; + } + return value; +}; + + +/** + * Get a list of object property names. + * @return {Array.<string>} List of property names. + * @api stable + */ +ol.Object.prototype.getKeys = function() { + return Object.keys(this.values_); +}; + + +/** + * Get an object of all property names and values. + * @return {Object.<string, *>} Object. + * @api stable + */ +ol.Object.prototype.getProperties = function() { + return ol.obj.assign({}, this.values_); +}; + + +/** + * @param {string} key Key name. + * @param {*} oldValue Old value. + */ +ol.Object.prototype.notify = function(key, oldValue) { + var eventType; + eventType = ol.Object.getChangeEventType(key); + this.dispatchEvent(new ol.Object.Event(eventType, key, oldValue)); + eventType = ol.Object.EventType.PROPERTYCHANGE; + this.dispatchEvent(new ol.Object.Event(eventType, key, oldValue)); +}; + + +/** + * Sets a value. + * @param {string} key Key name. + * @param {*} value Value. + * @param {boolean=} opt_silent Update without triggering an event. + * @api stable + */ +ol.Object.prototype.set = function(key, value, opt_silent) { + if (opt_silent) { + this.values_[key] = value; + } else { + var oldValue = this.values_[key]; + this.values_[key] = value; + if (oldValue !== value) { + this.notify(key, oldValue); + } + } +}; + + +/** + * Sets a collection of key-value pairs. Note that this changes any existing + * properties and adds new ones (it does not remove any existing properties). + * @param {Object.<string, *>} values Values. + * @param {boolean=} opt_silent Update without triggering an event. + * @api stable + */ +ol.Object.prototype.setProperties = function(values, opt_silent) { + var key; + for (key in values) { + this.set(key, values[key], opt_silent); + } +}; + + +/** + * Unsets a property. + * @param {string} key Key name. + * @param {boolean=} opt_silent Unset without triggering an event. + * @api stable + */ +ol.Object.prototype.unset = function(key, opt_silent) { + if (key in this.values_) { + var oldValue = this.values_[key]; + delete this.values_[key]; + if (!opt_silent) { + this.notify(key, oldValue); + } + } +}; + + +/** + * @enum {string} + */ +ol.Object.EventType = { + /** + * Triggered when a property is changed. + * @event ol.Object.Event#propertychange + * @api stable + */ + PROPERTYCHANGE: 'propertychange' +}; + + +/** + * @classdesc + * Events emitted by {@link ol.Object} instances are instances of this type. + * + * @param {string} type The event type. + * @param {string} key The property name. + * @param {*} oldValue The old value for `key`. + * @extends {ol.events.Event} + * @implements {oli.Object.Event} + * @constructor + */ +ol.Object.Event = function(type, key, oldValue) { + ol.events.Event.call(this, type); + + /** + * The name of the property whose value is changing. + * @type {string} + * @api stable + */ + this.key = key; + + /** + * The old value. To get the new value use `e.target.get(e.key)` where + * `e` is the event object. + * @type {*} + * @api stable + */ + this.oldValue = oldValue; + +}; +ol.inherits(ol.Object.Event, ol.events.Event); + +goog.provide('ol.array'); + +goog.require('ol'); + + +/** + * Performs a binary search on the provided sorted list and returns the index of the item if found. If it can't be found it'll return -1. + * https://github.com/darkskyapp/binary-search + * + * @param {Array.<*>} haystack Items to search through. + * @param {*} needle The item to look for. + * @param {Function=} opt_comparator Comparator function. + * @return {number} The index of the item if found, -1 if not. + */ +ol.array.binarySearch = function(haystack, needle, opt_comparator) { + var mid, cmp; + var comparator = opt_comparator || ol.array.numberSafeCompareFunction; + var low = 0; + var high = haystack.length; + var found = false; + + while (low < high) { + /* Note that "(low + high) >>> 1" may overflow, and results in a typecast + * to double (which gives the wrong results). */ + mid = low + (high - low >> 1); + cmp = +comparator(haystack[mid], needle); + + if (cmp < 0.0) { /* Too low. */ + low = mid + 1; + + } else { /* Key found or too high */ + high = mid; + found = !cmp; + } + } + + /* Key not found. */ + return found ? low : ~low; +}; + + +/** + * Compare function for array sort that is safe for numbers. + * @param {*} a The first object to be compared. + * @param {*} b The second object to be compared. + * @return {number} A negative number, zero, or a positive number as the first + * argument is less than, equal to, or greater than the second. + */ +ol.array.numberSafeCompareFunction = function(a, b) { + return a > b ? 1 : a < b ? -1 : 0; +}; + + +/** + * Whether the array contains the given object. + * @param {Array.<*>} arr The array to test for the presence of the element. + * @param {*} obj The object for which to test. + * @return {boolean} The object is in the array. + */ +ol.array.includes = function(arr, obj) { + return arr.indexOf(obj) >= 0; +}; + + +/** + * @param {Array.<number>} arr Array. + * @param {number} target Target. + * @param {number} direction 0 means return the nearest, > 0 + * means return the largest nearest, < 0 means return the + * smallest nearest. + * @return {number} Index. + */ +ol.array.linearFindNearest = function(arr, target, direction) { + var n = arr.length; + if (arr[0] <= target) { + return 0; + } else if (target <= arr[n - 1]) { + return n - 1; + } else { + var i; + if (direction > 0) { + for (i = 1; i < n; ++i) { + if (arr[i] < target) { + return i - 1; + } + } + } else if (direction < 0) { + for (i = 1; i < n; ++i) { + if (arr[i] <= target) { + return i; + } + } + } else { + for (i = 1; i < n; ++i) { + if (arr[i] == target) { + return i; + } else if (arr[i] < target) { + if (arr[i - 1] - target < target - arr[i]) { + return i - 1; + } else { + return i; + } + } + } + } + return n - 1; + } +}; + + +/** + * @param {Array.<*>} arr Array. + * @param {number} begin Begin index. + * @param {number} end End index. + */ +ol.array.reverseSubArray = function(arr, begin, end) { + ol.DEBUG && console.assert(begin >= 0, + 'Array begin index should be equal to or greater than 0'); + ol.DEBUG && console.assert(end < arr.length, + 'Array end index should be less than the array length'); + while (begin < end) { + var tmp = arr[begin]; + arr[begin] = arr[end]; + arr[end] = tmp; + ++begin; + --end; + } +}; + + +/** + * @param {Array.<*>} arr Array. + * @return {!Array.<?>} Flattened Array. + */ +ol.array.flatten = function(arr) { + var data = arr.reduce(function(flattened, value) { + if (Array.isArray(value)) { + return flattened.concat(ol.array.flatten(value)); + } else { + return flattened.concat(value); + } + }, []); + return data; +}; + + +/** + * @param {Array.<VALUE>} arr The array to modify. + * @param {Array.<VALUE>|VALUE} data The elements or arrays of elements + * to add to arr. + * @template VALUE + */ +ol.array.extend = function(arr, data) { + var i; + var extension = Array.isArray(data) ? data : [data]; + var length = extension.length; + for (i = 0; i < length; i++) { + arr[arr.length] = extension[i]; + } +}; + + +/** + * @param {Array.<VALUE>} arr The array to modify. + * @param {VALUE} obj The element to remove. + * @template VALUE + * @return {boolean} If the element was removed. + */ +ol.array.remove = function(arr, obj) { + var i = arr.indexOf(obj); + var found = i > -1; + if (found) { + arr.splice(i, 1); + } + return found; +}; + + +/** + * @param {Array.<VALUE>} arr The array to search in. + * @param {function(VALUE, number, ?) : boolean} func The function to compare. + * @template VALUE + * @return {VALUE} The element found. + */ +ol.array.find = function(arr, func) { + var length = arr.length >>> 0; + var value; + + for (var i = 0; i < length; i++) { + value = arr[i]; + if (func(value, i, arr)) { + return value; + } + } + return null; +}; + + +/** + * @param {Array|Uint8ClampedArray} arr1 The first array to compare. + * @param {Array|Uint8ClampedArray} arr2 The second array to compare. + * @return {boolean} Whether the two arrays are equal. + */ +ol.array.equals = function(arr1, arr2) { + var len1 = arr1.length; + if (len1 !== arr2.length) { + return false; + } + for (var i = 0; i < len1; i++) { + if (arr1[i] !== arr2[i]) { + return false; + } + } + return true; +}; + + +/** + * @param {Array.<*>} arr The array to sort (modifies original). + * @param {Function} compareFnc Comparison function. + */ +ol.array.stableSort = function(arr, compareFnc) { + var length = arr.length; + var tmp = Array(arr.length); + var i; + for (i = 0; i < length; i++) { + tmp[i] = {index: i, value: arr[i]}; + } + tmp.sort(function(a, b) { + return compareFnc(a.value, b.value) || a.index - b.index; + }); + for (i = 0; i < arr.length; i++) { + arr[i] = tmp[i].value; + } +}; + + +/** + * @param {Array.<*>} arr The array to search in. + * @param {Function} func Comparison function. + * @return {number} Return index. + */ +ol.array.findIndex = function(arr, func) { + var index; + var found = !arr.every(function(el, idx) { + index = idx; + return !func(el, idx, arr); + }); + return found ? index : -1; +}; + + +/** + * @param {Array.<*>} arr The array to test. + * @param {Function=} opt_func Comparison function. + * @param {boolean=} opt_strict Strictly sorted (default false). + * @return {boolean} Return index. + */ +ol.array.isSorted = function(arr, opt_func, opt_strict) { + var compare = opt_func || ol.array.numberSafeCompareFunction; + return arr.every(function(currentVal, index) { + if (index === 0) { + return true; + } + var res = compare(arr[index - 1], currentVal); + return !(res > 0 || opt_strict && res === 0); + }); +}; + +goog.provide('ol.ResolutionConstraint'); + +goog.require('ol.array'); +goog.require('ol.math'); + + +/** + * @param {Array.<number>} resolutions Resolutions. + * @return {ol.ResolutionConstraintType} Zoom function. + */ +ol.ResolutionConstraint.createSnapToResolutions = function(resolutions) { + return ( + /** + * @param {number|undefined} resolution Resolution. + * @param {number} delta Delta. + * @param {number} direction Direction. + * @return {number|undefined} Resolution. + */ + function(resolution, delta, direction) { + if (resolution !== undefined) { + var z = + ol.array.linearFindNearest(resolutions, resolution, direction); + z = ol.math.clamp(z + delta, 0, resolutions.length - 1); + var index = Math.floor(z); + if (z != index && index < resolutions.length - 1) { + var power = resolutions[index] / resolutions[index + 1]; + return resolutions[index] / Math.pow(power, z - index); + } else { + return resolutions[index]; + } + } else { + return undefined; + } + }); +}; + + +/** + * @param {number} power Power. + * @param {number} maxResolution Maximum resolution. + * @param {number=} opt_maxLevel Maximum level. + * @return {ol.ResolutionConstraintType} Zoom function. + */ +ol.ResolutionConstraint.createSnapToPower = function(power, maxResolution, opt_maxLevel) { + return ( + /** + * @param {number|undefined} resolution Resolution. + * @param {number} delta Delta. + * @param {number} direction Direction. + * @return {number|undefined} Resolution. + */ + function(resolution, delta, direction) { + if (resolution !== undefined) { + var offset = -direction / 2 + 0.5; + var oldLevel = Math.floor( + Math.log(maxResolution / resolution) / Math.log(power) + offset); + var newLevel = Math.max(oldLevel + delta, 0); + if (opt_maxLevel !== undefined) { + newLevel = Math.min(newLevel, opt_maxLevel); + } + return maxResolution / Math.pow(power, newLevel); + } else { + return undefined; + } + }); +}; + +goog.provide('ol.RotationConstraint'); + +goog.require('ol.math'); + + +/** + * @param {number|undefined} rotation Rotation. + * @param {number} delta Delta. + * @return {number|undefined} Rotation. + */ +ol.RotationConstraint.disable = function(rotation, delta) { + if (rotation !== undefined) { + return 0; + } else { + return undefined; + } +}; + + +/** + * @param {number|undefined} rotation Rotation. + * @param {number} delta Delta. + * @return {number|undefined} Rotation. + */ +ol.RotationConstraint.none = function(rotation, delta) { + if (rotation !== undefined) { + return rotation + delta; + } else { + return undefined; + } +}; + + +/** + * @param {number} n N. + * @return {ol.RotationConstraintType} Rotation constraint. + */ +ol.RotationConstraint.createSnapToN = function(n) { + var theta = 2 * Math.PI / n; + return ( + /** + * @param {number|undefined} rotation Rotation. + * @param {number} delta Delta. + * @return {number|undefined} Rotation. + */ + function(rotation, delta) { + if (rotation !== undefined) { + rotation = Math.floor((rotation + delta) / theta + 0.5) * theta; + return rotation; + } else { + return undefined; + } + }); +}; + + +/** + * @param {number=} opt_tolerance Tolerance. + * @return {ol.RotationConstraintType} Rotation constraint. + */ +ol.RotationConstraint.createSnapToZero = function(opt_tolerance) { + var tolerance = opt_tolerance || ol.math.toRadians(5); + return ( + /** + * @param {number|undefined} rotation Rotation. + * @param {number} delta Delta. + * @return {number|undefined} Rotation. + */ + function(rotation, delta) { + if (rotation !== undefined) { + if (Math.abs(rotation + delta) <= tolerance) { + return 0; + } else { + return rotation + delta; + } + } else { + return undefined; + } + }); +}; + +goog.provide('ol.string'); + +/** + * @param {number} number Number to be formatted + * @param {number} width The desired width + * @param {number=} opt_precision Precision of the output string (i.e. number of decimal places) + * @returns {string} Formatted string +*/ +ol.string.padNumber = function(number, width, opt_precision) { + var numberString = opt_precision !== undefined ? number.toFixed(opt_precision) : '' + number; + var decimal = numberString.indexOf('.'); + decimal = decimal === -1 ? numberString.length : decimal; + return decimal > width ? numberString : new Array(1 + width - decimal).join('0') + numberString; +}; + +/** + * Adapted from https://github.com/omichelsen/compare-versions/blob/master/index.js + * @param {string|number} v1 First version + * @param {string|number} v2 Second version + * @returns {number} Value + */ +ol.string.compareVersions = function(v1, v2) { + var s1 = ('' + v1).split('.'); + var s2 = ('' + v2).split('.'); + + for (var i = 0; i < Math.max(s1.length, s2.length); i++) { + var n1 = parseInt(s1[i] || '0', 10); + var n2 = parseInt(s2[i] || '0', 10); + + if (n1 > n2) { + return 1; + } + if (n2 > n1) { + return -1; + } + } + + return 0; +}; + +goog.provide('ol.coordinate'); + +goog.require('ol.math'); +goog.require('ol.string'); + + +/** + * Add `delta` to `coordinate`. `coordinate` is modified in place and returned + * by the function. + * + * Example: + * + * var coord = [7.85, 47.983333]; + * ol.coordinate.add(coord, [-2, 4]); + * // coord is now [5.85, 51.983333] + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.Coordinate} delta Delta. + * @return {ol.Coordinate} The input coordinate adjusted by the given delta. + * @api stable + */ +ol.coordinate.add = function(coordinate, delta) { + coordinate[0] += delta[0]; + coordinate[1] += delta[1]; + return coordinate; +}; + + +/** + * Calculates the point closest to the passed coordinate on the passed segment. + * This is the foot of the perpendicular of the coordinate to the segment when + * the foot is on the segment, or the closest segment coordinate when the foot + * is outside the segment. + * + * @param {ol.Coordinate} coordinate The coordinate. + * @param {Array.<ol.Coordinate>} segment The two coordinates of the segment. + * @return {ol.Coordinate} The foot of the perpendicular of the coordinate to + * the segment. + */ +ol.coordinate.closestOnSegment = function(coordinate, segment) { + var x0 = coordinate[0]; + var y0 = coordinate[1]; + var start = segment[0]; + var end = segment[1]; + var x1 = start[0]; + var y1 = start[1]; + var x2 = end[0]; + var y2 = end[1]; + var dx = x2 - x1; + var dy = y2 - y1; + var along = (dx === 0 && dy === 0) ? 0 : + ((dx * (x0 - x1)) + (dy * (y0 - y1))) / ((dx * dx + dy * dy) || 0); + var x, y; + if (along <= 0) { + x = x1; + y = y1; + } else if (along >= 1) { + x = x2; + y = y2; + } else { + x = x1 + along * dx; + y = y1 + along * dy; + } + return [x, y]; +}; + + +/** + * Returns a {@link ol.CoordinateFormatType} function that can be used to format + * a {ol.Coordinate} to a string. + * + * Example without specifying the fractional digits: + * + * var coord = [7.85, 47.983333]; + * var stringifyFunc = ol.coordinate.createStringXY(); + * var out = stringifyFunc(coord); + * // out is now '8, 48' + * + * Example with explicitly specifying 2 fractional digits: + * + * var coord = [7.85, 47.983333]; + * var stringifyFunc = ol.coordinate.createStringXY(2); + * var out = stringifyFunc(coord); + * // out is now '7.85, 47.98' + * + * @param {number=} opt_fractionDigits The number of digits to include + * after the decimal point. Default is `0`. + * @return {ol.CoordinateFormatType} Coordinate format. + * @api stable + */ +ol.coordinate.createStringXY = function(opt_fractionDigits) { + return ( + /** + * @param {ol.Coordinate|undefined} coordinate Coordinate. + * @return {string} String XY. + */ + function(coordinate) { + return ol.coordinate.toStringXY(coordinate, opt_fractionDigits); + }); +}; + + +/** + * @private + * @param {number} degrees Degrees. + * @param {string} hemispheres Hemispheres. + * @param {number=} opt_fractionDigits The number of digits to include + * after the decimal point. Default is `0`. + * @return {string} String. + */ +ol.coordinate.degreesToStringHDMS_ = function(degrees, hemispheres, opt_fractionDigits) { + var normalizedDegrees = ol.math.modulo(degrees + 180, 360) - 180; + var x = Math.abs(3600 * normalizedDegrees); + var dflPrecision = opt_fractionDigits || 0; + return Math.floor(x / 3600) + '\u00b0 ' + + ol.string.padNumber(Math.floor((x / 60) % 60), 2) + '\u2032 ' + + ol.string.padNumber((x % 60), 2, dflPrecision) + '\u2033 ' + + hemispheres.charAt(normalizedDegrees < 0 ? 1 : 0); +}; + + +/** + * Transforms the given {@link ol.Coordinate} to a string using the given string + * template. The strings `{x}` and `{y}` in the template will be replaced with + * the first and second coordinate values respectively. + * + * Example without specifying the fractional digits: + * + * var coord = [7.85, 47.983333]; + * var template = 'Coordinate is ({x}|{y}).'; + * var out = ol.coordinate.format(coord, template); + * // out is now 'Coordinate is (8|48).' + * + * Example explicitly specifying the fractional digits: + * + * var coord = [7.85, 47.983333]; + * var template = 'Coordinate is ({x}|{y}).'; + * var out = ol.coordinate.format(coord, template, 2); + * // out is now 'Coordinate is (7.85|47.98).' + * + * @param {ol.Coordinate|undefined} coordinate Coordinate. + * @param {string} template A template string with `{x}` and `{y}` placeholders + * that will be replaced by first and second coordinate values. + * @param {number=} opt_fractionDigits The number of digits to include + * after the decimal point. Default is `0`. + * @return {string} Formatted coordinate. + * @api stable + */ +ol.coordinate.format = function(coordinate, template, opt_fractionDigits) { + if (coordinate) { + return template + .replace('{x}', coordinate[0].toFixed(opt_fractionDigits)) + .replace('{y}', coordinate[1].toFixed(opt_fractionDigits)); + } else { + return ''; + } +}; + + +/** + * @param {ol.Coordinate} coordinate1 First coordinate. + * @param {ol.Coordinate} coordinate2 Second coordinate. + * @return {boolean} Whether the passed coordinates are equal. + */ +ol.coordinate.equals = function(coordinate1, coordinate2) { + var equals = true; + for (var i = coordinate1.length - 1; i >= 0; --i) { + if (coordinate1[i] != coordinate2[i]) { + equals = false; + break; + } + } + return equals; +}; + + +/** + * Rotate `coordinate` by `angle`. `coordinate` is modified in place and + * returned by the function. + * + * Example: + * + * var coord = [7.85, 47.983333]; + * var rotateRadians = Math.PI / 2; // 90 degrees + * ol.coordinate.rotate(coord, rotateRadians); + * // coord is now [-47.983333, 7.85] + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} angle Angle in radian. + * @return {ol.Coordinate} Coordinate. + * @api stable + */ +ol.coordinate.rotate = function(coordinate, angle) { + var cosAngle = Math.cos(angle); + var sinAngle = Math.sin(angle); + var x = coordinate[0] * cosAngle - coordinate[1] * sinAngle; + var y = coordinate[1] * cosAngle + coordinate[0] * sinAngle; + coordinate[0] = x; + coordinate[1] = y; + return coordinate; +}; + + +/** + * Scale `coordinate` by `scale`. `coordinate` is modified in place and returned + * by the function. + * + * Example: + * + * var coord = [7.85, 47.983333]; + * var scale = 1.2; + * ol.coordinate.scale(coord, scale); + * // coord is now [9.42, 57.5799996] + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} scale Scale factor. + * @return {ol.Coordinate} Coordinate. + */ +ol.coordinate.scale = function(coordinate, scale) { + coordinate[0] *= scale; + coordinate[1] *= scale; + return coordinate; +}; + + +/** + * Subtract `delta` to `coordinate`. `coordinate` is modified in place and + * returned by the function. + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.Coordinate} delta Delta. + * @return {ol.Coordinate} Coordinate. + */ +ol.coordinate.sub = function(coordinate, delta) { + coordinate[0] -= delta[0]; + coordinate[1] -= delta[1]; + return coordinate; +}; + + +/** + * @param {ol.Coordinate} coord1 First coordinate. + * @param {ol.Coordinate} coord2 Second coordinate. + * @return {number} Squared distance between coord1 and coord2. + */ +ol.coordinate.squaredDistance = function(coord1, coord2) { + var dx = coord1[0] - coord2[0]; + var dy = coord1[1] - coord2[1]; + return dx * dx + dy * dy; +}; + + +/** + * Calculate the squared distance from a coordinate to a line segment. + * + * @param {ol.Coordinate} coordinate Coordinate of the point. + * @param {Array.<ol.Coordinate>} segment Line segment (2 coordinates). + * @return {number} Squared distance from the point to the line segment. + */ +ol.coordinate.squaredDistanceToSegment = function(coordinate, segment) { + return ol.coordinate.squaredDistance(coordinate, + ol.coordinate.closestOnSegment(coordinate, segment)); +}; + + +/** + * Format a geographic coordinate with the hemisphere, degrees, minutes, and + * seconds. + * + * Example without specifying fractional digits: + * + * var coord = [7.85, 47.983333]; + * var out = ol.coordinate.toStringHDMS(coord); + * // out is now '47° 58′ 60″ N 7° 50′ 60″ E' + * + * Example explicitly specifying 1 fractional digit: + * + * var coord = [7.85, 47.983333]; + * var out = ol.coordinate.toStringHDMS(coord, 1); + * // out is now '47° 58′ 60.0″ N 7° 50′ 60.0″ E' + * + * @param {ol.Coordinate|undefined} coordinate Coordinate. + * @param {number=} opt_fractionDigits The number of digits to include + * after the decimal point. Default is `0`. + * @return {string} Hemisphere, degrees, minutes and seconds. + * @api stable + */ +ol.coordinate.toStringHDMS = function(coordinate, opt_fractionDigits) { + if (coordinate) { + return ol.coordinate.degreesToStringHDMS_(coordinate[1], 'NS', opt_fractionDigits) + ' ' + + ol.coordinate.degreesToStringHDMS_(coordinate[0], 'EW', opt_fractionDigits); + } else { + return ''; + } +}; + + +/** + * Format a coordinate as a comma delimited string. + * + * Example without specifying fractional digits: + * + * var coord = [7.85, 47.983333]; + * var out = ol.coordinate.toStringXY(coord); + * // out is now '8, 48' + * + * Example explicitly specifying 1 fractional digit: + * + * var coord = [7.85, 47.983333]; + * var out = ol.coordinate.toStringXY(coord, 1); + * // out is now '7.8, 48.0' + * + * @param {ol.Coordinate|undefined} coordinate Coordinate. + * @param {number=} opt_fractionDigits The number of digits to include + * after the decimal point. Default is `0`. + * @return {string} XY. + * @api stable + */ +ol.coordinate.toStringXY = function(coordinate, opt_fractionDigits) { + return ol.coordinate.format(coordinate, '{x}, {y}', opt_fractionDigits); +}; + +goog.provide('ol.easing'); + + +/** + * Start slow and speed up. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api + */ +ol.easing.easeIn = function(t) { + return Math.pow(t, 3); +}; + + +/** + * Start fast and slow down. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api + */ +ol.easing.easeOut = function(t) { + return 1 - ol.easing.easeIn(1 - t); +}; + + +/** + * Start slow, speed up, and then slow down again. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api + */ +ol.easing.inAndOut = function(t) { + return 3 * t * t - 2 * t * t * t; +}; + + +/** + * Maintain a constant speed over time. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api + */ +ol.easing.linear = function(t) { + return t; +}; + + +/** + * Start slow, speed up, and at the very end slow down again. This has the + * same general behavior as {@link ol.easing.inAndOut}, but the final slowdown + * is delayed. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api + */ +ol.easing.upAndDown = function(t) { + if (t < 0.5) { + return ol.easing.inAndOut(2 * t); + } else { + return 1 - ol.easing.inAndOut(2 * (t - 0.5)); + } +}; + +goog.provide('ol.extent.Corner'); + +/** + * Extent corner. + * @enum {string} + */ +ol.extent.Corner = { + BOTTOM_LEFT: 'bottom-left', + BOTTOM_RIGHT: 'bottom-right', + TOP_LEFT: 'top-left', + TOP_RIGHT: 'top-right' +}; + +goog.provide('ol.extent.Relationship'); + + +/** + * Relationship to an extent. + * @enum {number} + */ +ol.extent.Relationship = { + UNKNOWN: 0, + INTERSECTING: 1, + ABOVE: 2, + RIGHT: 4, + BELOW: 8, + LEFT: 16 +}; + +goog.provide('ol.extent'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.extent.Corner'); +goog.require('ol.extent.Relationship'); + + +/** + * Build an extent that includes all given coordinates. + * + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @return {ol.Extent} Bounding extent. + * @api stable + */ +ol.extent.boundingExtent = function(coordinates) { + var extent = ol.extent.createEmpty(); + for (var i = 0, ii = coordinates.length; i < ii; ++i) { + ol.extent.extendCoordinate(extent, coordinates[i]); + } + return extent; +}; + + +/** + * @param {Array.<number>} xs Xs. + * @param {Array.<number>} ys Ys. + * @param {ol.Extent=} opt_extent Destination extent. + * @private + * @return {ol.Extent} Extent. + */ +ol.extent.boundingExtentXYs_ = function(xs, ys, opt_extent) { + ol.DEBUG && console.assert(xs.length > 0, 'xs length should be larger than 0'); + ol.DEBUG && console.assert(ys.length > 0, 'ys length should be larger than 0'); + var minX = Math.min.apply(null, xs); + var minY = Math.min.apply(null, ys); + var maxX = Math.max.apply(null, xs); + var maxY = Math.max.apply(null, ys); + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); +}; + + +/** + * Return extent increased by the provided value. + * @param {ol.Extent} extent Extent. + * @param {number} value The amount by which the extent should be buffered. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + * @api stable + */ +ol.extent.buffer = function(extent, value, opt_extent) { + if (opt_extent) { + opt_extent[0] = extent[0] - value; + opt_extent[1] = extent[1] - value; + opt_extent[2] = extent[2] + value; + opt_extent[3] = extent[3] + value; + return opt_extent; + } else { + return [ + extent[0] - value, + extent[1] - value, + extent[2] + value, + extent[3] + value + ]; + } +}; + + +/** + * Creates a clone of an extent. + * + * @param {ol.Extent} extent Extent to clone. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} The clone. + */ +ol.extent.clone = function(extent, opt_extent) { + if (opt_extent) { + opt_extent[0] = extent[0]; + opt_extent[1] = extent[1]; + opt_extent[2] = extent[2]; + opt_extent[3] = extent[3]; + return opt_extent; + } else { + return extent.slice(); + } +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} x X. + * @param {number} y Y. + * @return {number} Closest squared distance. + */ +ol.extent.closestSquaredDistanceXY = function(extent, x, y) { + var dx, dy; + if (x < extent[0]) { + dx = extent[0] - x; + } else if (extent[2] < x) { + dx = x - extent[2]; + } else { + dx = 0; + } + if (y < extent[1]) { + dy = extent[1] - y; + } else if (extent[3] < y) { + dy = y - extent[3]; + } else { + dy = 0; + } + return dx * dx + dy * dy; +}; + + +/** + * Check if the passed coordinate is contained or on the edge of the extent. + * + * @param {ol.Extent} extent Extent. + * @param {ol.Coordinate} coordinate Coordinate. + * @return {boolean} The coordinate is contained in the extent. + * @api stable + */ +ol.extent.containsCoordinate = function(extent, coordinate) { + return ol.extent.containsXY(extent, coordinate[0], coordinate[1]); +}; + + +/** + * Check if one extent contains another. + * + * An extent is deemed contained if it lies completely within the other extent, + * including if they share one or more edges. + * + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @return {boolean} The second extent is contained by or on the edge of the + * first. + * @api stable + */ +ol.extent.containsExtent = function(extent1, extent2) { + return extent1[0] <= extent2[0] && extent2[2] <= extent1[2] && + extent1[1] <= extent2[1] && extent2[3] <= extent1[3]; +}; + + +/** + * Check if the passed coordinate is contained or on the edge of the extent. + * + * @param {ol.Extent} extent Extent. + * @param {number} x X coordinate. + * @param {number} y Y coordinate. + * @return {boolean} The x, y values are contained in the extent. + * @api stable + */ +ol.extent.containsXY = function(extent, x, y) { + return extent[0] <= x && x <= extent[2] && extent[1] <= y && y <= extent[3]; +}; + + +/** + * Get the relationship between a coordinate and extent. + * @param {ol.Extent} extent The extent. + * @param {ol.Coordinate} coordinate The coordinate. + * @return {number} The relationship (bitwise compare with + * ol.extent.Relationship). + */ +ol.extent.coordinateRelationship = function(extent, coordinate) { + var minX = extent[0]; + var minY = extent[1]; + var maxX = extent[2]; + var maxY = extent[3]; + var x = coordinate[0]; + var y = coordinate[1]; + var relationship = ol.extent.Relationship.UNKNOWN; + if (x < minX) { + relationship = relationship | ol.extent.Relationship.LEFT; + } else if (x > maxX) { + relationship = relationship | ol.extent.Relationship.RIGHT; + } + if (y < minY) { + relationship = relationship | ol.extent.Relationship.BELOW; + } else if (y > maxY) { + relationship = relationship | ol.extent.Relationship.ABOVE; + } + if (relationship === ol.extent.Relationship.UNKNOWN) { + relationship = ol.extent.Relationship.INTERSECTING; + } + return relationship; +}; + + +/** + * Create an empty extent. + * @return {ol.Extent} Empty extent. + * @api stable + */ +ol.extent.createEmpty = function() { + return [Infinity, Infinity, -Infinity, -Infinity]; +}; + + +/** + * Create a new extent or update the provided extent. + * @param {number} minX Minimum X. + * @param {number} minY Minimum Y. + * @param {number} maxX Maximum X. + * @param {number} maxY Maximum Y. + * @param {ol.Extent=} opt_extent Destination extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdate = function(minX, minY, maxX, maxY, opt_extent) { + if (opt_extent) { + opt_extent[0] = minX; + opt_extent[1] = minY; + opt_extent[2] = maxX; + opt_extent[3] = maxY; + return opt_extent; + } else { + return [minX, minY, maxX, maxY]; + } +}; + + +/** + * Create a new empty extent or make the provided one empty. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateEmpty = function(opt_extent) { + return ol.extent.createOrUpdate( + Infinity, Infinity, -Infinity, -Infinity, opt_extent); +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromCoordinate = function(coordinate, opt_extent) { + var x = coordinate[0]; + var y = coordinate[1]; + return ol.extent.createOrUpdate(x, y, x, y, opt_extent); +}; + + +/** + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromCoordinates = function(coordinates, opt_extent) { + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + return ol.extent.extendCoordinates(extent, coordinates); +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromFlatCoordinates = function(flatCoordinates, offset, end, stride, opt_extent) { + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + return ol.extent.extendFlatCoordinates( + extent, flatCoordinates, offset, end, stride); +}; + + +/** + * @param {Array.<Array.<ol.Coordinate>>} rings Rings. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromRings = function(rings, opt_extent) { + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + return ol.extent.extendRings(extent, rings); +}; + + +/** + * Determine if two extents are equivalent. + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @return {boolean} The two extents are equivalent. + * @api stable + */ +ol.extent.equals = function(extent1, extent2) { + return extent1[0] == extent2[0] && extent1[2] == extent2[2] && + extent1[1] == extent2[1] && extent1[3] == extent2[3]; +}; + + +/** + * Modify an extent to include another extent. + * @param {ol.Extent} extent1 The extent to be modified. + * @param {ol.Extent} extent2 The extent that will be included in the first. + * @return {ol.Extent} A reference to the first (extended) extent. + * @api stable + */ +ol.extent.extend = function(extent1, extent2) { + if (extent2[0] < extent1[0]) { + extent1[0] = extent2[0]; + } + if (extent2[2] > extent1[2]) { + extent1[2] = extent2[2]; + } + if (extent2[1] < extent1[1]) { + extent1[1] = extent2[1]; + } + if (extent2[3] > extent1[3]) { + extent1[3] = extent2[3]; + } + return extent1; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Coordinate} coordinate Coordinate. + */ +ol.extent.extendCoordinate = function(extent, coordinate) { + if (coordinate[0] < extent[0]) { + extent[0] = coordinate[0]; + } + if (coordinate[0] > extent[2]) { + extent[2] = coordinate[0]; + } + if (coordinate[1] < extent[1]) { + extent[1] = coordinate[1]; + } + if (coordinate[1] > extent[3]) { + extent[3] = coordinate[1]; + } +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @return {ol.Extent} Extent. + */ +ol.extent.extendCoordinates = function(extent, coordinates) { + var i, ii; + for (i = 0, ii = coordinates.length; i < ii; ++i) { + ol.extent.extendCoordinate(extent, coordinates[i]); + } + return extent; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {ol.Extent} Extent. + */ +ol.extent.extendFlatCoordinates = function(extent, flatCoordinates, offset, end, stride) { + for (; offset < end; offset += stride) { + ol.extent.extendXY( + extent, flatCoordinates[offset], flatCoordinates[offset + 1]); + } + return extent; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {Array.<Array.<ol.Coordinate>>} rings Rings. + * @return {ol.Extent} Extent. + */ +ol.extent.extendRings = function(extent, rings) { + var i, ii; + for (i = 0, ii = rings.length; i < ii; ++i) { + ol.extent.extendCoordinates(extent, rings[i]); + } + return extent; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} x X. + * @param {number} y Y. + */ +ol.extent.extendXY = function(extent, x, y) { + extent[0] = Math.min(extent[0], x); + extent[1] = Math.min(extent[1], y); + extent[2] = Math.max(extent[2], x); + extent[3] = Math.max(extent[3], y); +}; + + +/** + * This function calls `callback` for each corner of the extent. If the + * callback returns a truthy value the function returns that value + * immediately. Otherwise the function returns `false`. + * @param {ol.Extent} extent Extent. + * @param {function(this:T, ol.Coordinate): S} callback Callback. + * @param {T=} opt_this Value to use as `this` when executing `callback`. + * @return {S|boolean} Value. + * @template S, T + */ +ol.extent.forEachCorner = function(extent, callback, opt_this) { + var val; + val = callback.call(opt_this, ol.extent.getBottomLeft(extent)); + if (val) { + return val; + } + val = callback.call(opt_this, ol.extent.getBottomRight(extent)); + if (val) { + return val; + } + val = callback.call(opt_this, ol.extent.getTopRight(extent)); + if (val) { + return val; + } + val = callback.call(opt_this, ol.extent.getTopLeft(extent)); + if (val) { + return val; + } + return false; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @return {number} Area. + */ +ol.extent.getArea = function(extent) { + var area = 0; + if (!ol.extent.isEmpty(extent)) { + area = ol.extent.getWidth(extent) * ol.extent.getHeight(extent); + } + return area; +}; + + +/** + * Get the bottom left coordinate of an extent. + * @param {ol.Extent} extent Extent. + * @return {ol.Coordinate} Bottom left coordinate. + * @api stable + */ +ol.extent.getBottomLeft = function(extent) { + return [extent[0], extent[1]]; +}; + + +/** + * Get the bottom right coordinate of an extent. + * @param {ol.Extent} extent Extent. + * @return {ol.Coordinate} Bottom right coordinate. + * @api stable + */ +ol.extent.getBottomRight = function(extent) { + return [extent[2], extent[1]]; +}; + + +/** + * Get the center coordinate of an extent. + * @param {ol.Extent} extent Extent. + * @return {ol.Coordinate} Center. + * @api stable + */ +ol.extent.getCenter = function(extent) { + return [(extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2]; +}; + + +/** + * Get a corner coordinate of an extent. + * @param {ol.Extent} extent Extent. + * @param {ol.extent.Corner} corner Corner. + * @return {ol.Coordinate} Corner coordinate. + */ +ol.extent.getCorner = function(extent, corner) { + var coordinate; + if (corner === ol.extent.Corner.BOTTOM_LEFT) { + coordinate = ol.extent.getBottomLeft(extent); + } else if (corner === ol.extent.Corner.BOTTOM_RIGHT) { + coordinate = ol.extent.getBottomRight(extent); + } else if (corner === ol.extent.Corner.TOP_LEFT) { + coordinate = ol.extent.getTopLeft(extent); + } else if (corner === ol.extent.Corner.TOP_RIGHT) { + coordinate = ol.extent.getTopRight(extent); + } else { + ol.asserts.assert(false, 13); // Invalid corner + } + return /** @type {!ol.Coordinate} */ (coordinate); +}; + + +/** + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @return {number} Enlarged area. + */ +ol.extent.getEnlargedArea = function(extent1, extent2) { + var minX = Math.min(extent1[0], extent2[0]); + var minY = Math.min(extent1[1], extent2[1]); + var maxX = Math.max(extent1[2], extent2[2]); + var maxY = Math.max(extent1[3], extent2[3]); + return (maxX - minX) * (maxY - minY); +}; + + +/** + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {ol.Extent=} opt_extent Destination extent. + * @return {ol.Extent} Extent. + */ +ol.extent.getForViewAndSize = function(center, resolution, rotation, size, opt_extent) { + var dx = resolution * size[0] / 2; + var dy = resolution * size[1] / 2; + var cosRotation = Math.cos(rotation); + var sinRotation = Math.sin(rotation); + var xCos = dx * cosRotation; + var xSin = dx * sinRotation; + var yCos = dy * cosRotation; + var ySin = dy * sinRotation; + var x = center[0]; + var y = center[1]; + var x0 = x - xCos + ySin; + var x1 = x - xCos - ySin; + var x2 = x + xCos - ySin; + var x3 = x + xCos + ySin; + var y0 = y - xSin - yCos; + var y1 = y - xSin + yCos; + var y2 = y + xSin + yCos; + var y3 = y + xSin - yCos; + return ol.extent.createOrUpdate( + Math.min(x0, x1, x2, x3), Math.min(y0, y1, y2, y3), + Math.max(x0, x1, x2, x3), Math.max(y0, y1, y2, y3), + opt_extent); +}; + + +/** + * Get the height of an extent. + * @param {ol.Extent} extent Extent. + * @return {number} Height. + * @api stable + */ +ol.extent.getHeight = function(extent) { + return extent[3] - extent[1]; +}; + + +/** + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @return {number} Intersection area. + */ +ol.extent.getIntersectionArea = function(extent1, extent2) { + var intersection = ol.extent.getIntersection(extent1, extent2); + return ol.extent.getArea(intersection); +}; + + +/** + * Get the intersection of two extents. + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @param {ol.Extent=} opt_extent Optional extent to populate with intersection. + * @return {ol.Extent} Intersecting extent. + * @api stable + */ +ol.extent.getIntersection = function(extent1, extent2, opt_extent) { + var intersection = opt_extent ? opt_extent : ol.extent.createEmpty(); + if (ol.extent.intersects(extent1, extent2)) { + if (extent1[0] > extent2[0]) { + intersection[0] = extent1[0]; + } else { + intersection[0] = extent2[0]; + } + if (extent1[1] > extent2[1]) { + intersection[1] = extent1[1]; + } else { + intersection[1] = extent2[1]; + } + if (extent1[2] < extent2[2]) { + intersection[2] = extent1[2]; + } else { + intersection[2] = extent2[2]; + } + if (extent1[3] < extent2[3]) { + intersection[3] = extent1[3]; + } else { + intersection[3] = extent2[3]; + } + } + return intersection; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @return {number} Margin. + */ +ol.extent.getMargin = function(extent) { + return ol.extent.getWidth(extent) + ol.extent.getHeight(extent); +}; + + +/** + * Get the size (width, height) of an extent. + * @param {ol.Extent} extent The extent. + * @return {ol.Size} The extent size. + * @api stable + */ +ol.extent.getSize = function(extent) { + return [extent[2] - extent[0], extent[3] - extent[1]]; +}; + + +/** + * Get the top left coordinate of an extent. + * @param {ol.Extent} extent Extent. + * @return {ol.Coordinate} Top left coordinate. + * @api stable + */ +ol.extent.getTopLeft = function(extent) { + return [extent[0], extent[3]]; +}; + + +/** + * Get the top right coordinate of an extent. + * @param {ol.Extent} extent Extent. + * @return {ol.Coordinate} Top right coordinate. + * @api stable + */ +ol.extent.getTopRight = function(extent) { + return [extent[2], extent[3]]; +}; + + +/** + * Get the width of an extent. + * @param {ol.Extent} extent Extent. + * @return {number} Width. + * @api stable + */ +ol.extent.getWidth = function(extent) { + return extent[2] - extent[0]; +}; + + +/** + * Determine if one extent intersects another. + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent. + * @return {boolean} The two extents intersect. + * @api stable + */ +ol.extent.intersects = function(extent1, extent2) { + return extent1[0] <= extent2[2] && + extent1[2] >= extent2[0] && + extent1[1] <= extent2[3] && + extent1[3] >= extent2[1]; +}; + + +/** + * Determine if an extent is empty. + * @param {ol.Extent} extent Extent. + * @return {boolean} Is empty. + * @api stable + */ +ol.extent.isEmpty = function(extent) { + return extent[2] < extent[0] || extent[3] < extent[1]; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.returnOrUpdate = function(extent, opt_extent) { + if (opt_extent) { + opt_extent[0] = extent[0]; + opt_extent[1] = extent[1]; + opt_extent[2] = extent[2]; + opt_extent[3] = extent[3]; + return opt_extent; + } else { + return extent; + } +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} value Value. + */ +ol.extent.scaleFromCenter = function(extent, value) { + var deltaX = ((extent[2] - extent[0]) / 2) * (value - 1); + var deltaY = ((extent[3] - extent[1]) / 2) * (value - 1); + extent[0] -= deltaX; + extent[2] += deltaX; + extent[1] -= deltaY; + extent[3] += deltaY; +}; + + +/** + * Determine if the segment between two coordinates intersects (crosses, + * touches, or is contained by) the provided extent. + * @param {ol.Extent} extent The extent. + * @param {ol.Coordinate} start Segment start coordinate. + * @param {ol.Coordinate} end Segment end coordinate. + * @return {boolean} The segment intersects the extent. + */ +ol.extent.intersectsSegment = function(extent, start, end) { + var intersects = false; + var startRel = ol.extent.coordinateRelationship(extent, start); + var endRel = ol.extent.coordinateRelationship(extent, end); + if (startRel === ol.extent.Relationship.INTERSECTING || + endRel === ol.extent.Relationship.INTERSECTING) { + intersects = true; + } else { + var minX = extent[0]; + var minY = extent[1]; + var maxX = extent[2]; + var maxY = extent[3]; + var startX = start[0]; + var startY = start[1]; + var endX = end[0]; + var endY = end[1]; + var slope = (endY - startY) / (endX - startX); + var x, y; + if (!!(endRel & ol.extent.Relationship.ABOVE) && + !(startRel & ol.extent.Relationship.ABOVE)) { + // potentially intersects top + x = endX - ((endY - maxY) / slope); + intersects = x >= minX && x <= maxX; + } + if (!intersects && !!(endRel & ol.extent.Relationship.RIGHT) && + !(startRel & ol.extent.Relationship.RIGHT)) { + // potentially intersects right + y = endY - ((endX - maxX) * slope); + intersects = y >= minY && y <= maxY; + } + if (!intersects && !!(endRel & ol.extent.Relationship.BELOW) && + !(startRel & ol.extent.Relationship.BELOW)) { + // potentially intersects bottom + x = endX - ((endY - minY) / slope); + intersects = x >= minX && x <= maxX; + } + if (!intersects && !!(endRel & ol.extent.Relationship.LEFT) && + !(startRel & ol.extent.Relationship.LEFT)) { + // potentially intersects left + y = endY - ((endX - minX) * slope); + intersects = y >= minY && y <= maxY; + } + + } + return intersects; +}; + + +/** + * Apply a transform function to the extent. + * @param {ol.Extent} extent Extent. + * @param {ol.TransformFunction} transformFn Transform function. Called with + * [minX, minY, maxX, maxY] extent coordinates. + * @param {ol.Extent=} opt_extent Destination extent. + * @return {ol.Extent} Extent. + * @api stable + */ +ol.extent.applyTransform = function(extent, transformFn, opt_extent) { + var coordinates = [ + extent[0], extent[1], + extent[0], extent[3], + extent[2], extent[1], + extent[2], extent[3] + ]; + transformFn(coordinates, coordinates, 2); + var xs = [coordinates[0], coordinates[2], coordinates[4], coordinates[6]]; + var ys = [coordinates[1], coordinates[3], coordinates[5], coordinates[7]]; + return ol.extent.boundingExtentXYs_(xs, ys, opt_extent); +}; + +goog.provide('ol.geom.GeometryLayout'); + + +/** + * The coordinate layout for geometries, indicating whether a 3rd or 4th z ('Z') + * or measure ('M') coordinate is available. Supported values are `'XY'`, + * `'XYZ'`, `'XYM'`, `'XYZM'`. + * @enum {string} + */ +ol.geom.GeometryLayout = { + XY: 'XY', + XYZ: 'XYZ', + XYM: 'XYM', + XYZM: 'XYZM' +}; + +goog.provide('ol.geom.GeometryType'); + + +/** + * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`, + * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`, + * `'GeometryCollection'`, `'Circle'`. + * @enum {string} + */ +ol.geom.GeometryType = { + POINT: 'Point', + LINE_STRING: 'LineString', + LINEAR_RING: 'LinearRing', + POLYGON: 'Polygon', + MULTI_POINT: 'MultiPoint', + MULTI_LINE_STRING: 'MultiLineString', + MULTI_POLYGON: 'MultiPolygon', + GEOMETRY_COLLECTION: 'GeometryCollection', + CIRCLE: 'Circle' +}; + +goog.provide('ol.functions'); + +/** + * Always returns true. + * @returns {boolean} true. + */ +ol.functions.TRUE = function() { + return true; +}; + +/** + * Always returns false. + * @returns {boolean} false. + */ +ol.functions.FALSE = function() { + return false; +}; + +/** + * @license + * Latitude/longitude spherical geodesy formulae taken from + * http://www.movable-type.co.uk/scripts/latlong.html + * Licensed under CC-BY-3.0. + */ + +goog.provide('ol.Sphere'); + +goog.require('ol.math'); + + +/** + * @classdesc + * Class to create objects that can be used with {@link + * ol.geom.Polygon.circular}. + * + * For example to create a sphere whose radius is equal to the semi-major + * axis of the WGS84 ellipsoid: + * + * ```js + * var wgs84Sphere= new ol.Sphere(6378137); + * ``` + * + * @constructor + * @param {number} radius Radius. + * @api + */ +ol.Sphere = function(radius) { + + /** + * @type {number} + */ + this.radius = radius; + +}; + + +/** + * Returns the geodesic area for a list of coordinates. + * + * [Reference](http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409) + * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for + * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion + * Laboratory, Pasadena, CA, June 2007 + * + * @param {Array.<ol.Coordinate>} coordinates List of coordinates of a linear + * ring. If the ring is oriented clockwise, the area will be positive, + * otherwise it will be negative. + * @return {number} Area. + * @api + */ +ol.Sphere.prototype.geodesicArea = function(coordinates) { + var area = 0, len = coordinates.length; + var x1 = coordinates[len - 1][0]; + var y1 = coordinates[len - 1][1]; + for (var i = 0; i < len; i++) { + var x2 = coordinates[i][0], y2 = coordinates[i][1]; + area += ol.math.toRadians(x2 - x1) * + (2 + Math.sin(ol.math.toRadians(y1)) + + Math.sin(ol.math.toRadians(y2))); + x1 = x2; + y1 = y2; + } + return area * this.radius * this.radius / 2.0; +}; + + +/** + * Returns the distance from c1 to c2 using the haversine formula. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {number} Haversine distance. + * @api + */ +ol.Sphere.prototype.haversineDistance = function(c1, c2) { + var lat1 = ol.math.toRadians(c1[1]); + var lat2 = ol.math.toRadians(c2[1]); + var deltaLatBy2 = (lat2 - lat1) / 2; + var deltaLonBy2 = ol.math.toRadians(c2[0] - c1[0]) / 2; + var a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) + + Math.sin(deltaLonBy2) * Math.sin(deltaLonBy2) * + Math.cos(lat1) * Math.cos(lat2); + return 2 * this.radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); +}; + + +/** + * Returns the coordinate at the given distance and bearing from `c1`. + * + * @param {ol.Coordinate} c1 The origin point (`[lon, lat]` in degrees). + * @param {number} distance The great-circle distance between the origin + * point and the target point. + * @param {number} bearing The bearing (in radians). + * @return {ol.Coordinate} The target point. + */ +ol.Sphere.prototype.offset = function(c1, distance, bearing) { + var lat1 = ol.math.toRadians(c1[1]); + var lon1 = ol.math.toRadians(c1[0]); + var dByR = distance / this.radius; + var lat = Math.asin( + Math.sin(lat1) * Math.cos(dByR) + + Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing)); + var lon = lon1 + Math.atan2( + Math.sin(bearing) * Math.sin(dByR) * Math.cos(lat1), + Math.cos(dByR) - Math.sin(lat1) * Math.sin(lat)); + return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)]; +}; + +goog.provide('ol.sphere.NORMAL'); + +goog.require('ol.Sphere'); + + +/** + * The normal sphere. + * @const + * @type {ol.Sphere} + */ +ol.sphere.NORMAL = new ol.Sphere(6370997); + +goog.provide('ol.proj.Units'); + +goog.require('ol.sphere.NORMAL'); + + +/** + * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, `'tile-pixels'` or + * `'us-ft'`. + * @enum {string} + */ +ol.proj.Units = { + DEGREES: 'degrees', + FEET: 'ft', + METERS: 'm', + PIXELS: 'pixels', + TILE_PIXELS: 'tile-pixels', + USFEET: 'us-ft' +}; + + +/** + * Meters per unit lookup table. + * @const + * @type {Object.<ol.proj.Units, number>} + * @api stable + */ +ol.proj.Units.METERS_PER_UNIT = {}; +ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.DEGREES] = + 2 * Math.PI * ol.sphere.NORMAL.radius / 360; +ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.FEET] = 0.3048; +ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.METERS] = 1; +ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.USFEET] = 1200 / 3937; + +goog.provide('ol.proj.proj4'); + + +/** + * @private + * @type {proj4} + */ +ol.proj.proj4.cache_ = null; + + +/** + * Store the proj4 function. + * @param {proj4} proj4 The proj4 function. + */ +ol.proj.proj4.set = function(proj4) { + ol.proj.proj4.cache_ = proj4; +}; + + +/** + * Get proj4. + * @return {proj4} The proj4 function set above or available globally. + */ +ol.proj.proj4.get = function() { + return ol.proj.proj4.cache_ || window['proj4']; +}; + +goog.provide('ol.proj.Projection'); + +goog.require('ol'); +goog.require('ol.proj.Units'); +goog.require('ol.proj.proj4'); + + +/** + * @classdesc + * Projection definition class. One of these is created for each projection + * supported in the application and stored in the {@link ol.proj} namespace. + * You can use these in applications, but this is not required, as API params + * and options use {@link ol.ProjectionLike} which means the simple string + * code will suffice. + * + * You can use {@link ol.proj.get} to retrieve the object for a particular + * projection. + * + * The library includes definitions for `EPSG:4326` and `EPSG:3857`, together + * with the following aliases: + * * `EPSG:4326`: CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, + * urn:ogc:def:crs:OGC:1.3:CRS84, urn:ogc:def:crs:OGC:2:84, + * http://www.opengis.net/gml/srs/epsg.xml#4326, + * urn:x-ogc:def:crs:EPSG:4326 + * * `EPSG:3857`: EPSG:102100, EPSG:102113, EPSG:900913, + * urn:ogc:def:crs:EPSG:6.18:3:3857, + * http://www.opengis.net/gml/srs/epsg.xml#3857 + * + * If you use proj4js, aliases can be added using `proj4.defs()`; see + * [documentation](https://github.com/proj4js/proj4js). To set an alternative + * namespace for proj4, use {@link ol.proj.setProj4}. + * + * @constructor + * @param {olx.ProjectionOptions} options Projection options. + * @struct + * @api stable + */ +ol.proj.Projection = function(options) { + + /** + * @private + * @type {string} + */ + this.code_ = options.code; + + /** + * @private + * @type {ol.proj.Units} + */ + this.units_ = /** @type {ol.proj.Units} */ (options.units); + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = options.extent !== undefined ? options.extent : null; + + /** + * @private + * @type {ol.Extent} + */ + this.worldExtent_ = options.worldExtent !== undefined ? + options.worldExtent : null; + + /** + * @private + * @type {string} + */ + this.axisOrientation_ = options.axisOrientation !== undefined ? + options.axisOrientation : 'enu'; + + /** + * @private + * @type {boolean} + */ + this.global_ = options.global !== undefined ? options.global : false; + + /** + * @private + * @type {boolean} + */ + this.canWrapX_ = !!(this.global_ && this.extent_); + + /** + * @private + * @type {function(number, ol.Coordinate):number|undefined} + */ + this.getPointResolutionFunc_ = options.getPointResolution; + + /** + * @private + * @type {ol.tilegrid.TileGrid} + */ + this.defaultTileGrid_ = null; + + /** + * @private + * @type {number|undefined} + */ + this.metersPerUnit_ = options.metersPerUnit; + + var code = options.code; + ol.DEBUG && console.assert(code !== undefined, + 'Option "code" is required for constructing instance'); + if (ol.ENABLE_PROJ4JS) { + var proj4js = ol.proj.proj4.get(); + if (typeof proj4js == 'function') { + var def = proj4js.defs(code); + if (def !== undefined) { + if (def.axis !== undefined && options.axisOrientation === undefined) { + this.axisOrientation_ = def.axis; + } + if (options.metersPerUnit === undefined) { + this.metersPerUnit_ = def.to_meter; + } + if (options.units === undefined) { + this.units_ = def.units; + } + } + } + } + +}; + + +/** + * @return {boolean} The projection is suitable for wrapping the x-axis + */ +ol.proj.Projection.prototype.canWrapX = function() { + return this.canWrapX_; +}; + + +/** + * Get the code for this projection, e.g. 'EPSG:4326'. + * @return {string} Code. + * @api stable + */ +ol.proj.Projection.prototype.getCode = function() { + return this.code_; +}; + + +/** + * Get the validity extent for this projection. + * @return {ol.Extent} Extent. + * @api stable + */ +ol.proj.Projection.prototype.getExtent = function() { + return this.extent_; +}; + + +/** + * Get the units of this projection. + * @return {ol.proj.Units} Units. + * @api stable + */ +ol.proj.Projection.prototype.getUnits = function() { + return this.units_; +}; + + +/** + * Get the amount of meters per unit of this projection. If the projection is + * not configured with `metersPerUnit` or a units identifier, the return is + * `undefined`. + * @return {number|undefined} Meters. + * @api stable + */ +ol.proj.Projection.prototype.getMetersPerUnit = function() { + return this.metersPerUnit_ || ol.proj.Units.METERS_PER_UNIT[this.units_]; +}; + + +/** + * Get the world extent for this projection. + * @return {ol.Extent} Extent. + * @api + */ +ol.proj.Projection.prototype.getWorldExtent = function() { + return this.worldExtent_; +}; + + +/** + * Get the axis orientation of this projection. + * Example values are: + * enu - the default easting, northing, elevation. + * neu - northing, easting, up - useful for "lat/long" geographic coordinates, + * or south orientated transverse mercator. + * wnu - westing, northing, up - some planetary coordinate systems have + * "west positive" coordinate systems + * @return {string} Axis orientation. + */ +ol.proj.Projection.prototype.getAxisOrientation = function() { + return this.axisOrientation_; +}; + + +/** + * Is this projection a global projection which spans the whole world? + * @return {boolean} Whether the projection is global. + * @api stable + */ +ol.proj.Projection.prototype.isGlobal = function() { + return this.global_; +}; + + +/** +* Set if the projection is a global projection which spans the whole world +* @param {boolean} global Whether the projection is global. +* @api stable +*/ +ol.proj.Projection.prototype.setGlobal = function(global) { + this.global_ = global; + this.canWrapX_ = !!(global && this.extent_); +}; + + +/** + * @return {ol.tilegrid.TileGrid} The default tile grid. + */ +ol.proj.Projection.prototype.getDefaultTileGrid = function() { + return this.defaultTileGrid_; +}; + + +/** + * @param {ol.tilegrid.TileGrid} tileGrid The default tile grid. + */ +ol.proj.Projection.prototype.setDefaultTileGrid = function(tileGrid) { + this.defaultTileGrid_ = tileGrid; +}; + + +/** + * Set the validity extent for this projection. + * @param {ol.Extent} extent Extent. + * @api stable + */ +ol.proj.Projection.prototype.setExtent = function(extent) { + this.extent_ = extent; + this.canWrapX_ = !!(this.global_ && extent); +}; + + +/** + * Set the world extent for this projection. + * @param {ol.Extent} worldExtent World extent + * [minlon, minlat, maxlon, maxlat]. + * @api + */ +ol.proj.Projection.prototype.setWorldExtent = function(worldExtent) { + this.worldExtent_ = worldExtent; +}; + + +/** + * Set the getPointResolution function for this projection. + * @param {function(number, ol.Coordinate):number} func Function + * @api + */ +ol.proj.Projection.prototype.setGetPointResolution = function(func) { + this.getPointResolutionFunc_ = func; +}; + + +/** + * Get the custom point resolution function for this projection (if set). + * @return {function(number, ol.Coordinate):number|undefined} The custom point + * resolution function (if set). + */ +ol.proj.Projection.prototype.getPointResolutionFunc = function() { + return this.getPointResolutionFunc_; +}; + +goog.provide('ol.proj.projections'); + + +/** + * @private + * @type {Object.<string, ol.proj.Projection>} + */ +ol.proj.projections.cache_ = {}; + + +/** + * Clear the projections cache. + */ +ol.proj.projections.clear = function() { + ol.proj.projections.cache_ = {}; +}; + + +/** + * Get a cached projection by code. + * @param {string} code The code for the projection. + * @return {ol.proj.Projection} The projection (if cached). + */ +ol.proj.projections.get = function(code) { + var projections = ol.proj.projections.cache_; + return projections[code] || null; +}; + + +/** + * Add a projection to the cache. + * @param {string} code The projection code. + * @param {ol.proj.Projection} projection The projection to cache. + */ +ol.proj.projections.add = function(code, projection) { + var projections = ol.proj.projections.cache_; + projections[code] = projection; +}; + +goog.provide('ol.proj.transforms'); + +goog.require('ol'); +goog.require('ol.obj'); + + +/** + * @private + * @type {Object.<string, Object.<string, ol.TransformFunction>>} + */ +ol.proj.transforms.cache_ = {}; + + +/** + * Clear the transform cache. + */ +ol.proj.transforms.clear = function() { + ol.proj.transforms.cache_ = {}; +}; + + +/** + * Registers a conversion function to convert coordinates from the source + * projection to the destination projection. + * + * @param {ol.proj.Projection} source Source. + * @param {ol.proj.Projection} destination Destination. + * @param {ol.TransformFunction} transformFn Transform. + */ +ol.proj.transforms.add = function(source, destination, transformFn) { + var sourceCode = source.getCode(); + var destinationCode = destination.getCode(); + var transforms = ol.proj.transforms.cache_; + if (!(sourceCode in transforms)) { + transforms[sourceCode] = {}; + } + transforms[sourceCode][destinationCode] = transformFn; +}; + + +/** + * Unregisters the conversion function to convert coordinates from the source + * projection to the destination projection. This method is used to clean up + * cached transforms during testing. + * + * @param {ol.proj.Projection} source Source projection. + * @param {ol.proj.Projection} destination Destination projection. + * @return {ol.TransformFunction} transformFn The unregistered transform. + */ +ol.proj.transforms.remove = function(source, destination) { + var sourceCode = source.getCode(); + var destinationCode = destination.getCode(); + var transforms = ol.proj.transforms.cache_; + ol.DEBUG && console.assert(sourceCode in transforms, + 'sourceCode should be in transforms'); + ol.DEBUG && console.assert(destinationCode in transforms[sourceCode], + 'destinationCode should be in transforms of sourceCode'); + var transform = transforms[sourceCode][destinationCode]; + delete transforms[sourceCode][destinationCode]; + if (ol.obj.isEmpty(transforms[sourceCode])) { + delete transforms[sourceCode]; + } + return transform; +}; + + +/** + * Get a transform given a source code and a destination code. + * @param {string} sourceCode The code for the source projection. + * @param {string} destinationCode The code for the destination projection. + * @return {ol.TransformFunction|undefined} The transform function (if found). + */ +ol.proj.transforms.get = function(sourceCode, destinationCode) { + var transform; + var transforms = ol.proj.transforms.cache_; + if (sourceCode in transforms && destinationCode in transforms[sourceCode]) { + transform = transforms[sourceCode][destinationCode]; + } + return transform; +}; + +goog.provide('ol.proj'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); +goog.require('ol.proj.proj4'); +goog.require('ol.proj.projections'); +goog.require('ol.proj.transforms'); +goog.require('ol.sphere.NORMAL'); + + +/** + * Meters per unit lookup table. + * @const + * @type {Object.<ol.proj.Units, number>} + * @api stable + */ +ol.proj.METERS_PER_UNIT = ol.proj.Units.METERS_PER_UNIT; + + +if (ol.ENABLE_PROJ4JS) { + /** + * Register proj4. If not explicitly registered, it will be assumed that + * proj4js will be loaded in the global namespace. For example in a + * browserify ES6 environment you could use: + * + * import ol from 'openlayers'; + * import proj4 from 'proj4'; + * ol.proj.setProj4(proj4); + * + * @param {proj4} proj4 Proj4. + * @api + */ + ol.proj.setProj4 = function(proj4) { + ol.DEBUG && console.assert(typeof proj4 == 'function', + 'proj4 argument should be a function'); + ol.proj.proj4.set(proj4); + }; +} + + +/** + * Get the resolution of the point in degrees or distance units. + * For projections with degrees as the unit this will simply return the + * provided resolution. For other projections the point resolution is + * estimated by transforming the 'point' pixel to EPSG:4326, + * measuring its width and height on the normal sphere, + * and taking the average of the width and height. + * @param {ol.proj.Projection} projection The projection. + * @param {number} resolution Nominal resolution in projection units. + * @param {ol.Coordinate} point Point to find adjusted resolution at. + * @return {number} Point resolution at point in projection units. + * @api + */ +ol.proj.getPointResolution = function(projection, resolution, point) { + var pointResolution; + var getter = projection.getPointResolutionFunc(); + if (getter) { + pointResolution = getter(resolution, point); + } else { + var units = projection.getUnits(); + if (units == ol.proj.Units.DEGREES) { + pointResolution = resolution; + } else { + // Estimate point resolution by transforming the center pixel to EPSG:4326, + // measuring its width and height on the normal sphere, and taking the + // average of the width and height. + var toEPSG4326 = ol.proj.getTransformFromProjections(projection, ol.proj.get('EPSG:4326')); + var vertices = [ + point[0] - resolution / 2, point[1], + point[0] + resolution / 2, point[1], + point[0], point[1] - resolution / 2, + point[0], point[1] + resolution / 2 + ]; + vertices = toEPSG4326(vertices, vertices, 2); + var width = ol.sphere.NORMAL.haversineDistance( + vertices.slice(0, 2), vertices.slice(2, 4)); + var height = ol.sphere.NORMAL.haversineDistance( + vertices.slice(4, 6), vertices.slice(6, 8)); + pointResolution = (width + height) / 2; + var metersPerUnit = projection.getMetersPerUnit(); + if (metersPerUnit !== undefined) { + pointResolution /= metersPerUnit; + } + } + } + return pointResolution; +}; + + +/** + * Registers transformation functions that don't alter coordinates. Those allow + * to transform between projections with equal meaning. + * + * @param {Array.<ol.proj.Projection>} projections Projections. + * @api + */ +ol.proj.addEquivalentProjections = function(projections) { + ol.proj.addProjections(projections); + projections.forEach(function(source) { + projections.forEach(function(destination) { + if (source !== destination) { + ol.proj.transforms.add(source, destination, ol.proj.cloneTransform); + } + }); + }); +}; + + +/** + * Registers transformation functions to convert coordinates in any projection + * in projection1 to any projection in projection2. + * + * @param {Array.<ol.proj.Projection>} projections1 Projections with equal + * meaning. + * @param {Array.<ol.proj.Projection>} projections2 Projections with equal + * meaning. + * @param {ol.TransformFunction} forwardTransform Transformation from any + * projection in projection1 to any projection in projection2. + * @param {ol.TransformFunction} inverseTransform Transform from any projection + * in projection2 to any projection in projection1.. + */ +ol.proj.addEquivalentTransforms = function(projections1, projections2, forwardTransform, inverseTransform) { + projections1.forEach(function(projection1) { + projections2.forEach(function(projection2) { + ol.proj.transforms.add(projection1, projection2, forwardTransform); + ol.proj.transforms.add(projection2, projection1, inverseTransform); + }); + }); +}; + + +/** + * Add a Projection object to the list of supported projections that can be + * looked up by their code. + * + * @param {ol.proj.Projection} projection Projection instance. + * @api stable + */ +ol.proj.addProjection = function(projection) { + ol.proj.projections.add(projection.getCode(), projection); + ol.proj.transforms.add(projection, projection, ol.proj.cloneTransform); +}; + + +/** + * @param {Array.<ol.proj.Projection>} projections Projections. + */ +ol.proj.addProjections = function(projections) { + var addedProjections = []; + projections.forEach(function(projection) { + addedProjections.push(ol.proj.addProjection(projection)); + }); +}; + + +/** + * Clear all cached projections and transforms. + */ +ol.proj.clearAllProjections = function() { + ol.proj.projections.clear(); + ol.proj.transforms.clear(); +}; + + +/** + * @param {ol.proj.Projection|string|undefined} projection Projection. + * @param {string} defaultCode Default code. + * @return {ol.proj.Projection} Projection. + */ +ol.proj.createProjection = function(projection, defaultCode) { + if (!projection) { + return ol.proj.get(defaultCode); + } else if (typeof projection === 'string') { + return ol.proj.get(projection); + } else { + return /** @type {ol.proj.Projection} */ (projection); + } +}; + + +/** + * Registers coordinate transform functions to convert coordinates between the + * source projection and the destination projection. + * The forward and inverse functions convert coordinate pairs; this function + * converts these into the functions used internally which also handle + * extents and coordinate arrays. + * + * @param {ol.ProjectionLike} source Source projection. + * @param {ol.ProjectionLike} destination Destination projection. + * @param {function(ol.Coordinate): ol.Coordinate} forward The forward transform + * function (that is, from the source projection to the destination + * projection) that takes a {@link ol.Coordinate} as argument and returns + * the transformed {@link ol.Coordinate}. + * @param {function(ol.Coordinate): ol.Coordinate} inverse The inverse transform + * function (that is, from the destination projection to the source + * projection) that takes a {@link ol.Coordinate} as argument and returns + * the transformed {@link ol.Coordinate}. + * @api stable + */ +ol.proj.addCoordinateTransforms = function(source, destination, forward, inverse) { + var sourceProj = ol.proj.get(source); + var destProj = ol.proj.get(destination); + ol.proj.transforms.add(sourceProj, destProj, + ol.proj.createTransformFromCoordinateTransform(forward)); + ol.proj.transforms.add(destProj, sourceProj, + ol.proj.createTransformFromCoordinateTransform(inverse)); +}; + + +/** + * Creates a {@link ol.TransformFunction} from a simple 2D coordinate transform + * function. + * @param {function(ol.Coordinate): ol.Coordinate} transform Coordinate + * transform. + * @return {ol.TransformFunction} Transform function. + */ +ol.proj.createTransformFromCoordinateTransform = function(transform) { + return ( + /** + * @param {Array.<number>} input Input. + * @param {Array.<number>=} opt_output Output. + * @param {number=} opt_dimension Dimension. + * @return {Array.<number>} Output. + */ + function(input, opt_output, opt_dimension) { + var length = input.length; + var dimension = opt_dimension !== undefined ? opt_dimension : 2; + var output = opt_output !== undefined ? opt_output : new Array(length); + var point, i, j; + for (i = 0; i < length; i += dimension) { + point = transform([input[i], input[i + 1]]); + output[i] = point[0]; + output[i + 1] = point[1]; + for (j = dimension - 1; j >= 2; --j) { + output[i + j] = input[i + j]; + } + } + return output; + }); +}; + + +/** + * Transforms a coordinate from longitude/latitude to a different projection. + * @param {ol.Coordinate} coordinate Coordinate as longitude and latitude, i.e. + * an array with longitude as 1st and latitude as 2nd element. + * @param {ol.ProjectionLike=} opt_projection Target projection. The + * default is Web Mercator, i.e. 'EPSG:3857'. + * @return {ol.Coordinate} Coordinate projected to the target projection. + * @api stable + */ +ol.proj.fromLonLat = function(coordinate, opt_projection) { + return ol.proj.transform(coordinate, 'EPSG:4326', + opt_projection !== undefined ? opt_projection : 'EPSG:3857'); +}; + + +/** + * Transforms a coordinate to longitude/latitude. + * @param {ol.Coordinate} coordinate Projected coordinate. + * @param {ol.ProjectionLike=} opt_projection Projection of the coordinate. + * The default is Web Mercator, i.e. 'EPSG:3857'. + * @return {ol.Coordinate} Coordinate as longitude and latitude, i.e. an array + * with longitude as 1st and latitude as 2nd element. + * @api stable + */ +ol.proj.toLonLat = function(coordinate, opt_projection) { + return ol.proj.transform(coordinate, + opt_projection !== undefined ? opt_projection : 'EPSG:3857', 'EPSG:4326'); +}; + + +/** + * Fetches a Projection object for the code specified. + * + * @param {ol.ProjectionLike} projectionLike Either a code string which is + * a combination of authority and identifier such as "EPSG:4326", or an + * existing projection object, or undefined. + * @return {ol.proj.Projection} Projection object, or null if not in list. + * @api stable + */ +ol.proj.get = function(projectionLike) { + var projection = null; + if (projectionLike instanceof ol.proj.Projection) { + projection = projectionLike; + } else if (typeof projectionLike === 'string') { + var code = projectionLike; + projection = ol.proj.projections.get(code); + if (ol.ENABLE_PROJ4JS) { + var proj4js = ol.proj.proj4.get(); + if (!projection && typeof proj4js == 'function' && + proj4js.defs(code) !== undefined) { + projection = new ol.proj.Projection({code: code}); + ol.proj.addProjection(projection); + } + } + } + return projection; +}; + + +/** + * Checks if two projections are the same, that is every coordinate in one + * projection does represent the same geographic point as the same coordinate in + * the other projection. + * + * @param {ol.proj.Projection} projection1 Projection 1. + * @param {ol.proj.Projection} projection2 Projection 2. + * @return {boolean} Equivalent. + * @api + */ +ol.proj.equivalent = function(projection1, projection2) { + if (projection1 === projection2) { + return true; + } + var equalUnits = projection1.getUnits() === projection2.getUnits(); + if (projection1.getCode() === projection2.getCode()) { + return equalUnits; + } else { + var transformFn = ol.proj.getTransformFromProjections( + projection1, projection2); + return transformFn === ol.proj.cloneTransform && equalUnits; + } +}; + + +/** + * Given the projection-like objects, searches for a transformation + * function to convert a coordinates array from the source projection to the + * destination projection. + * + * @param {ol.ProjectionLike} source Source. + * @param {ol.ProjectionLike} destination Destination. + * @return {ol.TransformFunction} Transform function. + * @api stable + */ +ol.proj.getTransform = function(source, destination) { + var sourceProjection = ol.proj.get(source); + var destinationProjection = ol.proj.get(destination); + return ol.proj.getTransformFromProjections( + sourceProjection, destinationProjection); +}; + + +/** + * Searches in the list of transform functions for the function for converting + * coordinates from the source projection to the destination projection. + * + * @param {ol.proj.Projection} sourceProjection Source Projection object. + * @param {ol.proj.Projection} destinationProjection Destination Projection + * object. + * @return {ol.TransformFunction} Transform function. + */ +ol.proj.getTransformFromProjections = function(sourceProjection, destinationProjection) { + var sourceCode = sourceProjection.getCode(); + var destinationCode = destinationProjection.getCode(); + var transform = ol.proj.transforms.get(sourceCode, destinationCode); + if (ol.ENABLE_PROJ4JS && !transform) { + var proj4js = ol.proj.proj4.get(); + if (typeof proj4js == 'function') { + var sourceDef = proj4js.defs(sourceCode); + var destinationDef = proj4js.defs(destinationCode); + + if (sourceDef !== undefined && destinationDef !== undefined) { + if (sourceDef === destinationDef) { + ol.proj.addEquivalentProjections([destinationProjection, sourceProjection]); + } else { + var proj4Transform = proj4js(destinationCode, sourceCode); + ol.proj.addCoordinateTransforms(destinationProjection, sourceProjection, + proj4Transform.forward, proj4Transform.inverse); + } + transform = ol.proj.transforms.get(sourceCode, destinationCode); + } + } + } + if (!transform) { + ol.DEBUG && console.assert(transform, 'transform should be defined'); + transform = ol.proj.identityTransform; + } + return transform; +}; + + +/** + * @param {Array.<number>} input Input coordinate array. + * @param {Array.<number>=} opt_output Output array of coordinate values. + * @param {number=} opt_dimension Dimension. + * @return {Array.<number>} Input coordinate array (same array as input). + */ +ol.proj.identityTransform = function(input, opt_output, opt_dimension) { + if (opt_output !== undefined && input !== opt_output) { + // TODO: consider making this a warning instead + ol.DEBUG && console.assert(false, 'This should not be used internally.'); + for (var i = 0, ii = input.length; i < ii; ++i) { + opt_output[i] = input[i]; + } + input = opt_output; + } + return input; +}; + + +/** + * @param {Array.<number>} input Input coordinate array. + * @param {Array.<number>=} opt_output Output array of coordinate values. + * @param {number=} opt_dimension Dimension. + * @return {Array.<number>} Output coordinate array (new array, same coordinate + * values). + */ +ol.proj.cloneTransform = function(input, opt_output, opt_dimension) { + var output; + if (opt_output !== undefined) { + for (var i = 0, ii = input.length; i < ii; ++i) { + opt_output[i] = input[i]; + } + output = opt_output; + } else { + output = input.slice(); + } + return output; +}; + + +/** + * Transforms a coordinate from source projection to destination projection. + * This returns a new coordinate (and does not modify the original). + * + * See {@link ol.proj.transformExtent} for extent transformation. + * See the transform method of {@link ol.geom.Geometry} and its subclasses for + * geometry transforms. + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.ProjectionLike} source Source projection-like. + * @param {ol.ProjectionLike} destination Destination projection-like. + * @return {ol.Coordinate} Coordinate. + * @api stable + */ +ol.proj.transform = function(coordinate, source, destination) { + var transformFn = ol.proj.getTransform(source, destination); + return transformFn(coordinate, undefined, coordinate.length); +}; + + +/** + * Transforms an extent from source projection to destination projection. This + * returns a new extent (and does not modify the original). + * + * @param {ol.Extent} extent The extent to transform. + * @param {ol.ProjectionLike} source Source projection-like. + * @param {ol.ProjectionLike} destination Destination projection-like. + * @return {ol.Extent} The transformed extent. + * @api stable + */ +ol.proj.transformExtent = function(extent, source, destination) { + var transformFn = ol.proj.getTransform(source, destination); + return ol.extent.applyTransform(extent, transformFn); +}; + + +/** + * Transforms the given point to the destination projection. + * + * @param {ol.Coordinate} point Point. + * @param {ol.proj.Projection} sourceProjection Source projection. + * @param {ol.proj.Projection} destinationProjection Destination projection. + * @return {ol.Coordinate} Point. + */ +ol.proj.transformWithProjections = function(point, sourceProjection, destinationProjection) { + var transformFn = ol.proj.getTransformFromProjections( + sourceProjection, destinationProjection); + return transformFn(point); +}; + +goog.provide('ol.geom.Geometry'); + +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.extent'); +goog.require('ol.functions'); +goog.require('ol.proj'); +goog.require('ol.proj.Units'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for vector geometries. + * + * To get notified of changes to the geometry, register a listener for the + * generic `change` event on your geometry instance. + * + * @constructor + * @extends {ol.Object} + * @api stable + */ +ol.geom.Geometry = function() { + + ol.Object.call(this); + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = ol.extent.createEmpty(); + + /** + * @private + * @type {number} + */ + this.extentRevision_ = -1; + + /** + * @protected + * @type {Object.<string, ol.geom.Geometry>} + */ + this.simplifiedGeometryCache = {}; + + /** + * @protected + * @type {number} + */ + this.simplifiedGeometryMaxMinSquaredTolerance = 0; + + /** + * @protected + * @type {number} + */ + this.simplifiedGeometryRevision = 0; + +}; +ol.inherits(ol.geom.Geometry, ol.Object); + + +/** + * Make a complete copy of the geometry. + * @abstract + * @return {!ol.geom.Geometry} Clone. + */ +ol.geom.Geometry.prototype.clone = function() {}; + + +/** + * @abstract + * @param {number} x X. + * @param {number} y Y. + * @param {ol.Coordinate} closestPoint Closest point. + * @param {number} minSquaredDistance Minimum squared distance. + * @return {number} Minimum squared distance. + */ +ol.geom.Geometry.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {}; + + +/** + * Return the closest point of the geometry to the passed point as + * {@link ol.Coordinate coordinate}. + * @param {ol.Coordinate} point Point. + * @param {ol.Coordinate=} opt_closestPoint Closest point. + * @return {ol.Coordinate} Closest point. + * @api stable + */ +ol.geom.Geometry.prototype.getClosestPoint = function(point, opt_closestPoint) { + var closestPoint = opt_closestPoint ? opt_closestPoint : [NaN, NaN]; + this.closestPointXY(point[0], point[1], closestPoint, Infinity); + return closestPoint; +}; + + +/** + * Returns true if this geometry includes the specified coordinate. If the + * coordinate is on the boundary of the geometry, returns false. + * @param {ol.Coordinate} coordinate Coordinate. + * @return {boolean} Contains coordinate. + * @api + */ +ol.geom.Geometry.prototype.intersectsCoordinate = function(coordinate) { + return this.containsXY(coordinate[0], coordinate[1]); +}; + + +/** + * @abstract + * @param {ol.Extent} extent Extent. + * @protected + * @return {ol.Extent} extent Extent. + */ +ol.geom.Geometry.prototype.computeExtent = function(extent) {}; + + +/** + * @param {number} x X. + * @param {number} y Y. + * @return {boolean} Contains (x, y). + */ +ol.geom.Geometry.prototype.containsXY = ol.functions.FALSE; + + +/** + * Get the extent of the geometry. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} extent Extent. + * @api stable + */ +ol.geom.Geometry.prototype.getExtent = function(opt_extent) { + if (this.extentRevision_ != this.getRevision()) { + this.extent_ = this.computeExtent(this.extent_); + this.extentRevision_ = this.getRevision(); + } + return ol.extent.returnOrUpdate(this.extent_, opt_extent); +}; + + +/** + * Rotate the geometry around a given coordinate. This modifies the geometry + * coordinates in place. + * @abstract + * @param {number} angle Rotation angle in radians. + * @param {ol.Coordinate} anchor The rotation center. + * @api + */ +ol.geom.Geometry.prototype.rotate = function(angle, anchor) {}; + + +/** + * Scale the geometry (with an optional origin). This modifies the geometry + * coordinates in place. + * @abstract + * @param {number} sx The scaling factor in the x-direction. + * @param {number=} opt_sy The scaling factor in the y-direction (defaults to + * sx). + * @param {ol.Coordinate=} opt_anchor The scale origin (defaults to the center + * of the geometry extent). + * @api + */ +ol.geom.Geometry.prototype.scale = function(sx, opt_sy, opt_anchor) {}; + + +/** + * Create a simplified version of this geometry. For linestrings, this uses + * the the {@link + * https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm + * Douglas Peucker} algorithm. For polygons, a quantization-based + * simplification is used to preserve topology. + * @function + * @param {number} tolerance The tolerance distance for simplification. + * @return {ol.geom.Geometry} A new, simplified version of the original + * geometry. + * @api + */ +ol.geom.Geometry.prototype.simplify = function(tolerance) { + return this.getSimplifiedGeometry(tolerance * tolerance); +}; + + +/** + * Create a simplified version of this geometry using the Douglas Peucker + * algorithm. + * @see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm + * @abstract + * @param {number} squaredTolerance Squared tolerance. + * @return {ol.geom.Geometry} Simplified geometry. + */ +ol.geom.Geometry.prototype.getSimplifiedGeometry = function(squaredTolerance) {}; + + +/** + * Get the type of this geometry. + * @abstract + * @return {ol.geom.GeometryType} Geometry type. + */ +ol.geom.Geometry.prototype.getType = function() {}; + + +/** + * Apply a transform function to each coordinate of the geometry. + * The geometry is modified in place. + * If you do not want the geometry modified in place, first `clone()` it and + * then use this function on the clone. + * @abstract + * @param {ol.TransformFunction} transformFn Transform. + */ +ol.geom.Geometry.prototype.applyTransform = function(transformFn) {}; + + +/** + * Test if the geometry and the passed extent intersect. + * @abstract + * @param {ol.Extent} extent Extent. + * @return {boolean} `true` if the geometry and the extent intersect. + */ +ol.geom.Geometry.prototype.intersectsExtent = function(extent) {}; + + +/** + * Translate the geometry. This modifies the geometry coordinates in place. If + * instead you want a new geometry, first `clone()` this geometry. + * @abstract + * @param {number} deltaX Delta X. + * @param {number} deltaY Delta Y. + */ +ol.geom.Geometry.prototype.translate = function(deltaX, deltaY) {}; + + +/** + * Transform each coordinate of the geometry from one coordinate reference + * system to another. The geometry is modified in place. + * For example, a line will be transformed to a line and a circle to a circle. + * If you do not want the geometry modified in place, first `clone()` it and + * then use this function on the clone. + * + * @param {ol.ProjectionLike} source The current projection. Can be a + * string identifier or a {@link ol.proj.Projection} object. + * @param {ol.ProjectionLike} destination The desired projection. Can be a + * string identifier or a {@link ol.proj.Projection} object. + * @return {ol.geom.Geometry} This geometry. Note that original geometry is + * modified in place. + * @api stable + */ +ol.geom.Geometry.prototype.transform = function(source, destination) { + ol.DEBUG && console.assert( + ol.proj.get(source).getUnits() !== ol.proj.Units.TILE_PIXELS && + ol.proj.get(destination).getUnits() !== ol.proj.Units.TILE_PIXELS, + 'cannot transform geometries with TILE_PIXELS units'); + this.applyTransform(ol.proj.getTransform(source, destination)); + return this; +}; + +goog.provide('ol.geom.flat.transform'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Transform} transform Transform. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed coordinates. + */ +ol.geom.flat.transform.transform2D = function(flatCoordinates, offset, end, stride, transform, opt_dest) { + var dest = opt_dest ? opt_dest : []; + var i = 0; + var j; + for (j = offset; j < end; j += stride) { + var x = flatCoordinates[j]; + var y = flatCoordinates[j + 1]; + dest[i++] = transform[0] * x + transform[2] * y + transform[4]; + dest[i++] = transform[1] * x + transform[3] * y + transform[5]; + } + if (opt_dest && dest.length != i) { + dest.length = i; + } + return dest; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} angle Angle. + * @param {Array.<number>} anchor Rotation anchor point. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed coordinates. + */ +ol.geom.flat.transform.rotate = function(flatCoordinates, offset, end, stride, angle, anchor, opt_dest) { + var dest = opt_dest ? opt_dest : []; + var cos = Math.cos(angle); + var sin = Math.sin(angle); + var anchorX = anchor[0]; + var anchorY = anchor[1]; + var i = 0; + for (var j = offset; j < end; j += stride) { + var deltaX = flatCoordinates[j] - anchorX; + var deltaY = flatCoordinates[j + 1] - anchorY; + dest[i++] = anchorX + deltaX * cos - deltaY * sin; + dest[i++] = anchorY + deltaX * sin + deltaY * cos; + for (var k = j + 2; k < j + stride; ++k) { + dest[i++] = flatCoordinates[k]; + } + } + if (opt_dest && dest.length != i) { + dest.length = i; + } + return dest; +}; + + +/** + * Scale the coordinates. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} sx Scale factor in the x-direction. + * @param {number} sy Scale factor in the y-direction. + * @param {Array.<number>} anchor Scale anchor point. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed coordinates. + */ +ol.geom.flat.transform.scale = function(flatCoordinates, offset, end, stride, sx, sy, anchor, opt_dest) { + var dest = opt_dest ? opt_dest : []; + var anchorX = anchor[0]; + var anchorY = anchor[1]; + var i = 0; + for (var j = offset; j < end; j += stride) { + var deltaX = flatCoordinates[j] - anchorX; + var deltaY = flatCoordinates[j + 1] - anchorY; + dest[i++] = anchorX + sx * deltaX; + dest[i++] = anchorY + sy * deltaY; + for (var k = j + 2; k < j + stride; ++k) { + dest[i++] = flatCoordinates[k]; + } + } + if (opt_dest && dest.length != i) { + dest.length = i; + } + return dest; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} deltaX Delta X. + * @param {number} deltaY Delta Y. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed coordinates. + */ +ol.geom.flat.transform.translate = function(flatCoordinates, offset, end, stride, deltaX, deltaY, opt_dest) { + var dest = opt_dest ? opt_dest : []; + var i = 0; + var j, k; + for (j = offset; j < end; j += stride) { + dest[i++] = flatCoordinates[j] + deltaX; + dest[i++] = flatCoordinates[j + 1] + deltaY; + for (k = j + 2; k < j + stride; ++k) { + dest[i++] = flatCoordinates[k]; + } + } + if (opt_dest && dest.length != i) { + dest.length = i; + } + return dest; +}; + +goog.provide('ol.geom.SimpleGeometry'); + +goog.require('ol'); +goog.require('ol.functions'); +goog.require('ol.extent'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.obj'); + + +/** + * @classdesc + * Abstract base class; only used for creating subclasses; do not instantiate + * in apps, as cannot be rendered. + * + * @constructor + * @extends {ol.geom.Geometry} + * @api stable + */ +ol.geom.SimpleGeometry = function() { + + ol.geom.Geometry.call(this); + + /** + * @protected + * @type {ol.geom.GeometryLayout} + */ + this.layout = ol.geom.GeometryLayout.XY; + + /** + * @protected + * @type {number} + */ + this.stride = 2; + + /** + * @protected + * @type {Array.<number>} + */ + this.flatCoordinates = null; + +}; +ol.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry); + + +/** + * @param {number} stride Stride. + * @private + * @return {ol.geom.GeometryLayout} layout Layout. + */ +ol.geom.SimpleGeometry.getLayoutForStride_ = function(stride) { + var layout; + if (stride == 2) { + layout = ol.geom.GeometryLayout.XY; + } else if (stride == 3) { + layout = ol.geom.GeometryLayout.XYZ; + } else if (stride == 4) { + layout = ol.geom.GeometryLayout.XYZM; + } + ol.DEBUG && console.assert(layout, 'unsupported stride: ' + stride); + return /** @type {ol.geom.GeometryLayout} */ (layout); +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @return {number} Stride. + */ +ol.geom.SimpleGeometry.getStrideForLayout = function(layout) { + var stride; + if (layout == ol.geom.GeometryLayout.XY) { + stride = 2; + } else if (layout == ol.geom.GeometryLayout.XYZ || layout == ol.geom.GeometryLayout.XYM) { + stride = 3; + } else if (layout == ol.geom.GeometryLayout.XYZM) { + stride = 4; + } + ol.DEBUG && console.assert(stride, 'unsupported layout: ' + layout); + return /** @type {number} */ (stride); +}; + + +/** + * @inheritDoc + */ +ol.geom.SimpleGeometry.prototype.containsXY = ol.functions.FALSE; + + +/** + * @inheritDoc + */ +ol.geom.SimpleGeometry.prototype.computeExtent = function(extent) { + return ol.extent.createOrUpdateFromFlatCoordinates( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + extent); +}; + + +/** + * @abstract + * @return {Array} Coordinates. + */ +ol.geom.SimpleGeometry.prototype.getCoordinates = function() {}; + + +/** + * Return the first coordinate of the geometry. + * @return {ol.Coordinate} First coordinate. + * @api stable + */ +ol.geom.SimpleGeometry.prototype.getFirstCoordinate = function() { + return this.flatCoordinates.slice(0, this.stride); +}; + + +/** + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.SimpleGeometry.prototype.getFlatCoordinates = function() { + return this.flatCoordinates; +}; + + +/** + * Return the last coordinate of the geometry. + * @return {ol.Coordinate} Last point. + * @api stable + */ +ol.geom.SimpleGeometry.prototype.getLastCoordinate = function() { + return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride); +}; + + +/** + * Return the {@link ol.geom.GeometryLayout layout} of the geometry. + * @return {ol.geom.GeometryLayout} Layout. + * @api stable + */ +ol.geom.SimpleGeometry.prototype.getLayout = function() { + return this.layout; +}; + + +/** + * @inheritDoc + */ +ol.geom.SimpleGeometry.prototype.getSimplifiedGeometry = function(squaredTolerance) { + if (this.simplifiedGeometryRevision != this.getRevision()) { + ol.obj.clear(this.simplifiedGeometryCache); + this.simplifiedGeometryMaxMinSquaredTolerance = 0; + this.simplifiedGeometryRevision = this.getRevision(); + } + // If squaredTolerance is negative or if we know that simplification will not + // have any effect then just return this. + if (squaredTolerance < 0 || + (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 && + squaredTolerance <= this.simplifiedGeometryMaxMinSquaredTolerance)) { + return this; + } + var key = squaredTolerance.toString(); + if (this.simplifiedGeometryCache.hasOwnProperty(key)) { + return this.simplifiedGeometryCache[key]; + } else { + var simplifiedGeometry = + this.getSimplifiedGeometryInternal(squaredTolerance); + var simplifiedFlatCoordinates = simplifiedGeometry.getFlatCoordinates(); + if (simplifiedFlatCoordinates.length < this.flatCoordinates.length) { + this.simplifiedGeometryCache[key] = simplifiedGeometry; + return simplifiedGeometry; + } else { + // Simplification did not actually remove any coordinates. We now know + // that any calls to getSimplifiedGeometry with a squaredTolerance less + // than or equal to the current squaredTolerance will also not have any + // effect. This allows us to short circuit simplification (saving CPU + // cycles) and prevents the cache of simplified geometries from filling + // up with useless identical copies of this geometry (saving memory). + this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance; + return this; + } + } +}; + + +/** + * @param {number} squaredTolerance Squared tolerance. + * @return {ol.geom.SimpleGeometry} Simplified geometry. + * @protected + */ +ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) { + return this; +}; + + +/** + * @return {number} Stride. + */ +ol.geom.SimpleGeometry.prototype.getStride = function() { + return this.stride; +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @protected + */ +ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal = function(layout, flatCoordinates) { + this.stride = ol.geom.SimpleGeometry.getStrideForLayout(layout); + this.layout = layout; + this.flatCoordinates = flatCoordinates; +}; + + +/** + * @abstract + * @param {Array} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + */ +ol.geom.SimpleGeometry.prototype.setCoordinates = function(coordinates, opt_layout) {}; + + +/** + * @param {ol.geom.GeometryLayout|undefined} layout Layout. + * @param {Array} coordinates Coordinates. + * @param {number} nesting Nesting. + * @protected + */ +ol.geom.SimpleGeometry.prototype.setLayout = function(layout, coordinates, nesting) { + /** @type {number} */ + var stride; + if (layout) { + stride = ol.geom.SimpleGeometry.getStrideForLayout(layout); + } else { + var i; + for (i = 0; i < nesting; ++i) { + if (coordinates.length === 0) { + this.layout = ol.geom.GeometryLayout.XY; + this.stride = 2; + return; + } else { + coordinates = /** @type {Array} */ (coordinates[0]); + } + } + stride = coordinates.length; + layout = ol.geom.SimpleGeometry.getLayoutForStride_(stride); + } + this.layout = layout; + this.stride = stride; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.SimpleGeometry.prototype.applyTransform = function(transformFn) { + if (this.flatCoordinates) { + transformFn(this.flatCoordinates, this.flatCoordinates, this.stride); + this.changed(); + } +}; + + +/** + * @inheritDoc + * @api + */ +ol.geom.SimpleGeometry.prototype.rotate = function(angle, anchor) { + var flatCoordinates = this.getFlatCoordinates(); + if (flatCoordinates) { + var stride = this.getStride(); + ol.geom.flat.transform.rotate( + flatCoordinates, 0, flatCoordinates.length, + stride, angle, anchor, flatCoordinates); + this.changed(); + } +}; + + +/** + * @inheritDoc + * @api + */ +ol.geom.SimpleGeometry.prototype.scale = function(sx, opt_sy, opt_anchor) { + var sy = opt_sy; + if (sy === undefined) { + sy = sx; + } + var anchor = opt_anchor; + if (!anchor) { + anchor = ol.extent.getCenter(this.getExtent()); + } + var flatCoordinates = this.getFlatCoordinates(); + if (flatCoordinates) { + var stride = this.getStride(); + ol.geom.flat.transform.scale( + flatCoordinates, 0, flatCoordinates.length, + stride, sx, sy, anchor, flatCoordinates); + this.changed(); + } +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.SimpleGeometry.prototype.translate = function(deltaX, deltaY) { + var flatCoordinates = this.getFlatCoordinates(); + if (flatCoordinates) { + var stride = this.getStride(); + ol.geom.flat.transform.translate( + flatCoordinates, 0, flatCoordinates.length, stride, + deltaX, deltaY, flatCoordinates); + this.changed(); + } +}; + + +/** + * @param {ol.geom.SimpleGeometry} simpleGeometry Simple geometry. + * @param {ol.Transform} transform Transform. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed flat coordinates. + */ +ol.geom.SimpleGeometry.transform2D = function(simpleGeometry, transform, opt_dest) { + var flatCoordinates = simpleGeometry.getFlatCoordinates(); + if (!flatCoordinates) { + return null; + } else { + var stride = simpleGeometry.getStride(); + return ol.geom.flat.transform.transform2D( + flatCoordinates, 0, flatCoordinates.length, stride, + transform, opt_dest); + } +}; + +goog.provide('ol.geom.flat.area'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} Area. + */ +ol.geom.flat.area.linearRing = function(flatCoordinates, offset, end, stride) { + var twiceArea = 0; + var x1 = flatCoordinates[end - stride]; + var y1 = flatCoordinates[end - stride + 1]; + for (; offset < end; offset += stride) { + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + twiceArea += y1 * x2 - x1 * y2; + x1 = x2; + y1 = y2; + } + return twiceArea / 2; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @return {number} Area. + */ +ol.geom.flat.area.linearRings = function(flatCoordinates, offset, ends, stride) { + var area = 0; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + area += ol.geom.flat.area.linearRing(flatCoordinates, offset, end, stride); + offset = end; + } + return area; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @return {number} Area. + */ +ol.geom.flat.area.linearRingss = function(flatCoordinates, offset, endss, stride) { + var area = 0; + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + area += + ol.geom.flat.area.linearRings(flatCoordinates, offset, ends, stride); + offset = ends[ends.length - 1]; + } + return area; +}; + +goog.provide('ol.geom.flat.closest'); + +goog.require('ol'); +goog.require('ol.math'); + + +/** + * Returns the point on the 2D line segment flatCoordinates[offset1] to + * flatCoordinates[offset2] that is closest to the point (x, y). Extra + * dimensions are linearly interpolated. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset1 Offset 1. + * @param {number} offset2 Offset 2. + * @param {number} stride Stride. + * @param {number} x X. + * @param {number} y Y. + * @param {Array.<number>} closestPoint Closest point. + */ +ol.geom.flat.closest.point = function(flatCoordinates, offset1, offset2, stride, x, y, closestPoint) { + var x1 = flatCoordinates[offset1]; + var y1 = flatCoordinates[offset1 + 1]; + var dx = flatCoordinates[offset2] - x1; + var dy = flatCoordinates[offset2 + 1] - y1; + var i, offset; + if (dx === 0 && dy === 0) { + offset = offset1; + } else { + var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy); + if (t > 1) { + offset = offset2; + } else if (t > 0) { + for (i = 0; i < stride; ++i) { + closestPoint[i] = ol.math.lerp(flatCoordinates[offset1 + i], + flatCoordinates[offset2 + i], t); + } + closestPoint.length = stride; + return; + } else { + offset = offset1; + } + } + for (i = 0; i < stride; ++i) { + closestPoint[i] = flatCoordinates[offset + i]; + } + closestPoint.length = stride; +}; + + +/** + * Return the squared of the largest distance between any pair of consecutive + * coordinates. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} maxSquaredDelta Max squared delta. + * @return {number} Max squared delta. + */ +ol.geom.flat.closest.getMaxSquaredDelta = function(flatCoordinates, offset, end, stride, maxSquaredDelta) { + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + for (offset += stride; offset < end; offset += stride) { + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + var squaredDelta = ol.math.squaredDistance(x1, y1, x2, y2); + if (squaredDelta > maxSquaredDelta) { + maxSquaredDelta = squaredDelta; + } + x1 = x2; + y1 = y2; + } + return maxSquaredDelta; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} maxSquaredDelta Max squared delta. + * @return {number} Max squared delta. + */ +ol.geom.flat.closest.getsMaxSquaredDelta = function(flatCoordinates, offset, ends, stride, maxSquaredDelta) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + maxSquaredDelta = ol.geom.flat.closest.getMaxSquaredDelta( + flatCoordinates, offset, end, stride, maxSquaredDelta); + offset = end; + } + return maxSquaredDelta; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} maxSquaredDelta Max squared delta. + * @return {number} Max squared delta. + */ +ol.geom.flat.closest.getssMaxSquaredDelta = function(flatCoordinates, offset, endss, stride, maxSquaredDelta) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + maxSquaredDelta = ol.geom.flat.closest.getsMaxSquaredDelta( + flatCoordinates, offset, ends, stride, maxSquaredDelta); + offset = ends[ends.length - 1]; + } + return maxSquaredDelta; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} maxDelta Max delta. + * @param {boolean} isRing Is ring. + * @param {number} x X. + * @param {number} y Y. + * @param {Array.<number>} closestPoint Closest point. + * @param {number} minSquaredDistance Minimum squared distance. + * @param {Array.<number>=} opt_tmpPoint Temporary point object. + * @return {number} Minimum squared distance. + */ +ol.geom.flat.closest.getClosestPoint = function(flatCoordinates, offset, end, + stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, + opt_tmpPoint) { + if (offset == end) { + return minSquaredDistance; + } + var i, squaredDistance; + if (maxDelta === 0) { + // All points are identical, so just test the first point. + squaredDistance = ol.math.squaredDistance( + x, y, flatCoordinates[offset], flatCoordinates[offset + 1]); + if (squaredDistance < minSquaredDistance) { + for (i = 0; i < stride; ++i) { + closestPoint[i] = flatCoordinates[offset + i]; + } + closestPoint.length = stride; + return squaredDistance; + } else { + return minSquaredDistance; + } + } + ol.DEBUG && console.assert(maxDelta > 0, 'maxDelta should be larger than 0'); + var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN]; + var index = offset + stride; + while (index < end) { + ol.geom.flat.closest.point( + flatCoordinates, index - stride, index, stride, x, y, tmpPoint); + squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]); + if (squaredDistance < minSquaredDistance) { + minSquaredDistance = squaredDistance; + for (i = 0; i < stride; ++i) { + closestPoint[i] = tmpPoint[i]; + } + closestPoint.length = stride; + index += stride; + } else { + // Skip ahead multiple points, because we know that all the skipped + // points cannot be any closer than the closest point we have found so + // far. We know this because we know how close the current point is, how + // close the closest point we have found so far is, and the maximum + // distance between consecutive points. For example, if we're currently + // at distance 10, the best we've found so far is 3, and that the maximum + // distance between consecutive points is 2, then we'll need to skip at + // least (10 - 3) / 2 == 3 (rounded down) points to have any chance of + // finding a closer point. We use Math.max(..., 1) to ensure that we + // always advance at least one point, to avoid an infinite loop. + index += stride * Math.max( + ((Math.sqrt(squaredDistance) - + Math.sqrt(minSquaredDistance)) / maxDelta) | 0, 1); + } + } + if (isRing) { + // Check the closing segment. + ol.geom.flat.closest.point( + flatCoordinates, end - stride, offset, stride, x, y, tmpPoint); + squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]); + if (squaredDistance < minSquaredDistance) { + minSquaredDistance = squaredDistance; + for (i = 0; i < stride; ++i) { + closestPoint[i] = tmpPoint[i]; + } + closestPoint.length = stride; + } + } + return minSquaredDistance; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} maxDelta Max delta. + * @param {boolean} isRing Is ring. + * @param {number} x X. + * @param {number} y Y. + * @param {Array.<number>} closestPoint Closest point. + * @param {number} minSquaredDistance Minimum squared distance. + * @param {Array.<number>=} opt_tmpPoint Temporary point object. + * @return {number} Minimum squared distance. + */ +ol.geom.flat.closest.getsClosestPoint = function(flatCoordinates, offset, ends, + stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, + opt_tmpPoint) { + var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN]; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + minSquaredDistance = ol.geom.flat.closest.getClosestPoint( + flatCoordinates, offset, end, stride, + maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint); + offset = end; + } + return minSquaredDistance; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} maxDelta Max delta. + * @param {boolean} isRing Is ring. + * @param {number} x X. + * @param {number} y Y. + * @param {Array.<number>} closestPoint Closest point. + * @param {number} minSquaredDistance Minimum squared distance. + * @param {Array.<number>=} opt_tmpPoint Temporary point object. + * @return {number} Minimum squared distance. + */ +ol.geom.flat.closest.getssClosestPoint = function(flatCoordinates, offset, + endss, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, + opt_tmpPoint) { + var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN]; + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + minSquaredDistance = ol.geom.flat.closest.getsClosestPoint( + flatCoordinates, offset, ends, stride, + maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint); + offset = ends[ends.length - 1]; + } + return minSquaredDistance; +}; + +goog.provide('ol.geom.flat.deflate'); + +goog.require('ol'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} stride Stride. + * @return {number} offset Offset. + */ +ol.geom.flat.deflate.coordinate = function(flatCoordinates, offset, coordinate, stride) { + ol.DEBUG && console.assert(coordinate.length == stride, + 'length of the coordinate array should match stride'); + var i, ii; + for (i = 0, ii = coordinate.length; i < ii; ++i) { + flatCoordinates[offset++] = coordinate[i]; + } + return offset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {number} stride Stride. + * @return {number} offset Offset. + */ +ol.geom.flat.deflate.coordinates = function(flatCoordinates, offset, coordinates, stride) { + var i, ii; + for (i = 0, ii = coordinates.length; i < ii; ++i) { + var coordinate = coordinates[i]; + ol.DEBUG && console.assert(coordinate.length == stride, + 'length of coordinate array should match stride'); + var j; + for (j = 0; j < stride; ++j) { + flatCoordinates[offset++] = coordinate[j]; + } + } + return offset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<ol.Coordinate>>} coordinatess Coordinatess. + * @param {number} stride Stride. + * @param {Array.<number>=} opt_ends Ends. + * @return {Array.<number>} Ends. + */ +ol.geom.flat.deflate.coordinatess = function(flatCoordinates, offset, coordinatess, stride, opt_ends) { + var ends = opt_ends ? opt_ends : []; + var i = 0; + var j, jj; + for (j = 0, jj = coordinatess.length; j < jj; ++j) { + var end = ol.geom.flat.deflate.coordinates( + flatCoordinates, offset, coordinatess[j], stride); + ends[i++] = end; + offset = end; + } + ends.length = i; + return ends; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinatesss Coordinatesss. + * @param {number} stride Stride. + * @param {Array.<Array.<number>>=} opt_endss Endss. + * @return {Array.<Array.<number>>} Endss. + */ +ol.geom.flat.deflate.coordinatesss = function(flatCoordinates, offset, coordinatesss, stride, opt_endss) { + var endss = opt_endss ? opt_endss : []; + var i = 0; + var j, jj; + for (j = 0, jj = coordinatesss.length; j < jj; ++j) { + var ends = ol.geom.flat.deflate.coordinatess( + flatCoordinates, offset, coordinatesss[j], stride, endss[i]); + endss[i++] = ends; + offset = ends[ends.length - 1]; + } + endss.length = i; + return endss; +}; + +goog.provide('ol.geom.flat.inflate'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {Array.<ol.Coordinate>=} opt_coordinates Coordinates. + * @return {Array.<ol.Coordinate>} Coordinates. + */ +ol.geom.flat.inflate.coordinates = function(flatCoordinates, offset, end, stride, opt_coordinates) { + var coordinates = opt_coordinates !== undefined ? opt_coordinates : []; + var i = 0; + var j; + for (j = offset; j < end; j += stride) { + coordinates[i++] = flatCoordinates.slice(j, j + stride); + } + coordinates.length = i; + return coordinates; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {Array.<Array.<ol.Coordinate>>=} opt_coordinatess Coordinatess. + * @return {Array.<Array.<ol.Coordinate>>} Coordinatess. + */ +ol.geom.flat.inflate.coordinatess = function(flatCoordinates, offset, ends, stride, opt_coordinatess) { + var coordinatess = opt_coordinatess !== undefined ? opt_coordinatess : []; + var i = 0; + var j, jj; + for (j = 0, jj = ends.length; j < jj; ++j) { + var end = ends[j]; + coordinatess[i++] = ol.geom.flat.inflate.coordinates( + flatCoordinates, offset, end, stride, coordinatess[i]); + offset = end; + } + coordinatess.length = i; + return coordinatess; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {Array.<Array.<Array.<ol.Coordinate>>>=} opt_coordinatesss + * Coordinatesss. + * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinatesss. + */ +ol.geom.flat.inflate.coordinatesss = function(flatCoordinates, offset, endss, stride, opt_coordinatesss) { + var coordinatesss = opt_coordinatesss !== undefined ? opt_coordinatesss : []; + var i = 0; + var j, jj; + for (j = 0, jj = endss.length; j < jj; ++j) { + var ends = endss[j]; + coordinatesss[i++] = ol.geom.flat.inflate.coordinatess( + flatCoordinates, offset, ends, stride, coordinatesss[i]); + offset = ends[ends.length - 1]; + } + coordinatesss.length = i; + return coordinatesss; +}; + +// Based on simplify-js https://github.com/mourner/simplify-js +// Copyright (c) 2012, Vladimir Agafonkin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.geom.flat.simplify'); + +goog.require('ol.math'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {boolean} highQuality Highest quality. + * @param {Array.<number>=} opt_simplifiedFlatCoordinates Simplified flat + * coordinates. + * @return {Array.<number>} Simplified line string. + */ +ol.geom.flat.simplify.lineString = function(flatCoordinates, offset, end, + stride, squaredTolerance, highQuality, opt_simplifiedFlatCoordinates) { + var simplifiedFlatCoordinates = opt_simplifiedFlatCoordinates !== undefined ? + opt_simplifiedFlatCoordinates : []; + if (!highQuality) { + end = ol.geom.flat.simplify.radialDistance(flatCoordinates, offset, end, + stride, squaredTolerance, + simplifiedFlatCoordinates, 0); + flatCoordinates = simplifiedFlatCoordinates; + offset = 0; + stride = 2; + } + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker( + flatCoordinates, offset, end, stride, squaredTolerance, + simplifiedFlatCoordinates, 0); + return simplifiedFlatCoordinates; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.douglasPeucker = function(flatCoordinates, offset, end, + stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) { + var n = (end - offset) / stride; + if (n < 3) { + for (; offset < end; offset += stride) { + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset]; + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset + 1]; + } + return simplifiedOffset; + } + /** @type {Array.<number>} */ + var markers = new Array(n); + markers[0] = 1; + markers[n - 1] = 1; + /** @type {Array.<number>} */ + var stack = [offset, end - stride]; + var index = 0; + var i; + while (stack.length > 0) { + var last = stack.pop(); + var first = stack.pop(); + var maxSquaredDistance = 0; + var x1 = flatCoordinates[first]; + var y1 = flatCoordinates[first + 1]; + var x2 = flatCoordinates[last]; + var y2 = flatCoordinates[last + 1]; + for (i = first + stride; i < last; i += stride) { + var x = flatCoordinates[i]; + var y = flatCoordinates[i + 1]; + var squaredDistance = ol.math.squaredSegmentDistance( + x, y, x1, y1, x2, y2); + if (squaredDistance > maxSquaredDistance) { + index = i; + maxSquaredDistance = squaredDistance; + } + } + if (maxSquaredDistance > squaredTolerance) { + markers[(index - offset) / stride] = 1; + if (first + stride < index) { + stack.push(first, index); + } + if (index + stride < last) { + stack.push(index, last); + } + } + } + for (i = 0; i < n; ++i) { + if (markers[i]) { + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset + i * stride]; + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset + i * stride + 1]; + } + } + return simplifiedOffset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @param {Array.<number>} simplifiedEnds Simplified ends. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.douglasPeuckers = function(flatCoordinates, offset, + ends, stride, squaredTolerance, simplifiedFlatCoordinates, + simplifiedOffset, simplifiedEnds) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + simplifiedOffset = ol.geom.flat.simplify.douglasPeucker( + flatCoordinates, offset, end, stride, squaredTolerance, + simplifiedFlatCoordinates, simplifiedOffset); + simplifiedEnds.push(simplifiedOffset); + offset = end; + } + return simplifiedOffset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.douglasPeuckerss = function( + flatCoordinates, offset, endss, stride, squaredTolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + var simplifiedEnds = []; + simplifiedOffset = ol.geom.flat.simplify.douglasPeuckers( + flatCoordinates, offset, ends, stride, squaredTolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds); + simplifiedEndss.push(simplifiedEnds); + offset = ends[ends.length - 1]; + } + return simplifiedOffset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.radialDistance = function(flatCoordinates, offset, end, + stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) { + if (end <= offset + stride) { + // zero or one point, no simplification possible, so copy and return + for (; offset < end; offset += stride) { + simplifiedFlatCoordinates[simplifiedOffset++] = flatCoordinates[offset]; + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset + 1]; + } + return simplifiedOffset; + } + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + // copy first point + simplifiedFlatCoordinates[simplifiedOffset++] = x1; + simplifiedFlatCoordinates[simplifiedOffset++] = y1; + var x2 = x1; + var y2 = y1; + for (offset += stride; offset < end; offset += stride) { + x2 = flatCoordinates[offset]; + y2 = flatCoordinates[offset + 1]; + if (ol.math.squaredDistance(x1, y1, x2, y2) > squaredTolerance) { + // copy point at offset + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + x1 = x2; + y1 = y2; + } + } + if (x2 != x1 || y2 != y1) { + // copy last point + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + } + return simplifiedOffset; +}; + + +/** + * @param {number} value Value. + * @param {number} tolerance Tolerance. + * @return {number} Rounded value. + */ +ol.geom.flat.simplify.snap = function(value, tolerance) { + return tolerance * Math.round(value / tolerance); +}; + + +/** + * Simplifies a line string using an algorithm designed by Tim Schaub. + * Coordinates are snapped to the nearest value in a virtual grid and + * consecutive duplicate coordinates are discarded. This effectively preserves + * topology as the simplification of any subsection of a line string is + * independent of the rest of the line string. This means that, for examples, + * the common edge between two polygons will be simplified to the same line + * string independently in both polygons. This implementation uses a single + * pass over the coordinates and eliminates intermediate collinear points. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} tolerance Tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.quantize = function(flatCoordinates, offset, end, stride, + tolerance, simplifiedFlatCoordinates, simplifiedOffset) { + // do nothing if the line is empty + if (offset == end) { + return simplifiedOffset; + } + // snap the first coordinate (P1) + var x1 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance); + var y1 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance); + offset += stride; + // add the first coordinate to the output + simplifiedFlatCoordinates[simplifiedOffset++] = x1; + simplifiedFlatCoordinates[simplifiedOffset++] = y1; + // find the next coordinate that does not snap to the same value as the first + // coordinate (P2) + var x2, y2; + do { + x2 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance); + y2 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance); + offset += stride; + if (offset == end) { + // all coordinates snap to the same value, the line collapses to a point + // push the last snapped value anyway to ensure that the output contains + // at least two points + // FIXME should we really return at least two points anyway? + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + return simplifiedOffset; + } + } while (x2 == x1 && y2 == y1); + while (offset < end) { + var x3, y3; + // snap the next coordinate (P3) + x3 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance); + y3 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance); + offset += stride; + // skip P3 if it is equal to P2 + if (x3 == x2 && y3 == y2) { + continue; + } + // calculate the delta between P1 and P2 + var dx1 = x2 - x1; + var dy1 = y2 - y1; + // calculate the delta between P3 and P1 + var dx2 = x3 - x1; + var dy2 = y3 - y1; + // if P1, P2, and P3 are colinear and P3 is further from P1 than P2 is from + // P1 in the same direction then P2 is on the straight line between P1 and + // P3 + if ((dx1 * dy2 == dy1 * dx2) && + ((dx1 < 0 && dx2 < dx1) || dx1 == dx2 || (dx1 > 0 && dx2 > dx1)) && + ((dy1 < 0 && dy2 < dy1) || dy1 == dy2 || (dy1 > 0 && dy2 > dy1))) { + // discard P2 and set P2 = P3 + x2 = x3; + y2 = y3; + continue; + } + // either P1, P2, and P3 are not colinear, or they are colinear but P3 is + // between P3 and P1 or on the opposite half of the line to P2. add P2, + // and continue with P1 = P2 and P2 = P3 + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + x1 = x2; + y1 = y2; + x2 = x3; + y2 = y3; + } + // add the last point (P2) + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + return simplifiedOffset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} tolerance Tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @param {Array.<number>} simplifiedEnds Simplified ends. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.quantizes = function( + flatCoordinates, offset, ends, stride, + tolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + simplifiedOffset = ol.geom.flat.simplify.quantize( + flatCoordinates, offset, end, stride, + tolerance, + simplifiedFlatCoordinates, simplifiedOffset); + simplifiedEnds.push(simplifiedOffset); + offset = end; + } + return simplifiedOffset; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} tolerance Tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss. + * @return {number} Simplified offset. + */ +ol.geom.flat.simplify.quantizess = function( + flatCoordinates, offset, endss, stride, + tolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + var simplifiedEnds = []; + simplifiedOffset = ol.geom.flat.simplify.quantizes( + flatCoordinates, offset, ends, stride, + tolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds); + simplifiedEndss.push(simplifiedEnds); + offset = ends[ends.length - 1]; + } + return simplifiedOffset; +}; + +goog.provide('ol.geom.LinearRing'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.area'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.simplify'); + + +/** + * @classdesc + * Linear ring geometry. Only used as part of polygon; cannot be rendered + * on its own. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.LinearRing = function(coordinates, opt_layout) { + + ol.geom.SimpleGeometry.call(this); + + /** + * @private + * @type {number} + */ + this.maxDelta_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDeltaRevision_ = -1; + + this.setCoordinates(coordinates, opt_layout); + +}; +ol.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry); + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.LinearRing} Clone. + * @api stable + */ +ol.geom.LinearRing.prototype.clone = function() { + var linearRing = new ol.geom.LinearRing(null); + linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return linearRing; +}; + + +/** + * @inheritDoc + */ +ol.geom.LinearRing.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getClosestPoint( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); +}; + + +/** + * Return the area of the linear ring on projected plane. + * @return {number} Area (on projected plane). + * @api stable + */ +ol.geom.LinearRing.prototype.getArea = function() { + return ol.geom.flat.area.linearRing( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; + + +/** + * Return the coordinates of the linear ring. + * @return {Array.<ol.Coordinate>} Coordinates. + * @api stable + */ +ol.geom.LinearRing.prototype.getCoordinates = function() { + return ol.geom.flat.inflate.coordinates( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; + + +/** + * @inheritDoc + */ +ol.geom.LinearRing.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + squaredTolerance, simplifiedFlatCoordinates, 0); + var simplifiedLinearRing = new ol.geom.LinearRing(null); + simplifiedLinearRing.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates); + return simplifiedLinearRing; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.LinearRing.prototype.getType = function() { + return ol.geom.GeometryType.LINEAR_RING; +}; + + +/** + * Set the coordinates of the linear ring. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.LinearRing.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, coordinates, 1); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + this.flatCoordinates.length = ol.geom.flat.deflate.coordinates( + this.flatCoordinates, 0, coordinates, this.stride); + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + */ +ol.geom.LinearRing.prototype.setFlatCoordinates = function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; + +goog.provide('ol.geom.Point'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.math'); + + +/** + * @classdesc + * Point geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {ol.Coordinate} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.Point = function(coordinates, opt_layout) { + ol.geom.SimpleGeometry.call(this); + this.setCoordinates(coordinates, opt_layout); +}; +ol.inherits(ol.geom.Point, ol.geom.SimpleGeometry); + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.Point} Clone. + * @api stable + */ +ol.geom.Point.prototype.clone = function() { + var point = new ol.geom.Point(null); + point.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return point; +}; + + +/** + * @inheritDoc + */ +ol.geom.Point.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + var flatCoordinates = this.flatCoordinates; + var squaredDistance = ol.math.squaredDistance( + x, y, flatCoordinates[0], flatCoordinates[1]); + if (squaredDistance < minSquaredDistance) { + var stride = this.stride; + var i; + for (i = 0; i < stride; ++i) { + closestPoint[i] = flatCoordinates[i]; + } + closestPoint.length = stride; + return squaredDistance; + } else { + return minSquaredDistance; + } +}; + + +/** + * Return the coordinate of the point. + * @return {ol.Coordinate} Coordinates. + * @api stable + */ +ol.geom.Point.prototype.getCoordinates = function() { + return !this.flatCoordinates ? [] : this.flatCoordinates.slice(); +}; + + +/** + * @inheritDoc + */ +ol.geom.Point.prototype.computeExtent = function(extent) { + return ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates, extent); +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.Point.prototype.getType = function() { + return ol.geom.GeometryType.POINT; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.Point.prototype.intersectsExtent = function(extent) { + return ol.extent.containsXY(extent, + this.flatCoordinates[0], this.flatCoordinates[1]); +}; + + +/** + * Set the coordinate of the point. + * @param {ol.Coordinate} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.Point.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, coordinates, 0); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + this.flatCoordinates.length = ol.geom.flat.deflate.coordinate( + this.flatCoordinates, 0, coordinates, this.stride); + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + */ +ol.geom.Point.prototype.setFlatCoordinates = function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; + +goog.provide('ol.geom.flat.contains'); + +goog.require('ol'); +goog.require('ol.extent'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} Contains extent. + */ +ol.geom.flat.contains.linearRingContainsExtent = function(flatCoordinates, offset, end, stride, extent) { + var outside = ol.extent.forEachCorner(extent, + /** + * @param {ol.Coordinate} coordinate Coordinate. + * @return {boolean} Contains (x, y). + */ + function(coordinate) { + return !ol.geom.flat.contains.linearRingContainsXY(flatCoordinates, + offset, end, stride, coordinate[0], coordinate[1]); + }); + return !outside; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} x X. + * @param {number} y Y. + * @return {boolean} Contains (x, y). + */ +ol.geom.flat.contains.linearRingContainsXY = function(flatCoordinates, offset, end, stride, x, y) { + // http://geomalgorithms.com/a03-_inclusion.html + // Copyright 2000 softSurfer, 2012 Dan Sunday + // This code may be freely used and modified for any purpose + // providing that this copyright notice is included with it. + // SoftSurfer makes no warranty for this code, and cannot be held + // liable for any real or imagined damage resulting from its use. + // Users of this code must verify correctness for their application. + var wn = 0; + var x1 = flatCoordinates[end - stride]; + var y1 = flatCoordinates[end - stride + 1]; + for (; offset < end; offset += stride) { + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + if (y1 <= y) { + if (y2 > y && ((x2 - x1) * (y - y1)) - ((x - x1) * (y2 - y1)) > 0) { + wn++; + } + } else if (y2 <= y && ((x2 - x1) * (y - y1)) - ((x - x1) * (y2 - y1)) < 0) { + wn--; + } + x1 = x2; + y1 = y2; + } + return wn !== 0; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} x X. + * @param {number} y Y. + * @return {boolean} Contains (x, y). + */ +ol.geom.flat.contains.linearRingsContainsXY = function(flatCoordinates, offset, ends, stride, x, y) { + ol.DEBUG && console.assert(ends.length > 0, 'ends should not be an empty array'); + if (ends.length === 0) { + return false; + } + if (!ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, ends[0], stride, x, y)) { + return false; + } + var i, ii; + for (i = 1, ii = ends.length; i < ii; ++i) { + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, ends[i - 1], ends[i], stride, x, y)) { + return false; + } + } + return true; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} x X. + * @param {number} y Y. + * @return {boolean} Contains (x, y). + */ +ol.geom.flat.contains.linearRingssContainsXY = function(flatCoordinates, offset, endss, stride, x, y) { + ol.DEBUG && console.assert(endss.length > 0, 'endss should not be an empty array'); + if (endss.length === 0) { + return false; + } + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + if (ol.geom.flat.contains.linearRingsContainsXY( + flatCoordinates, offset, ends, stride, x, y)) { + return true; + } + offset = ends[ends.length - 1]; + } + return false; +}; + +goog.provide('ol.geom.flat.interiorpoint'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.geom.flat.contains'); + + +/** + * Calculates a point that is likely to lie in the interior of the linear rings. + * Inspired by JTS's com.vividsolutions.jts.geom.Geometry#getInteriorPoint. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {Array.<number>} flatCenters Flat centers. + * @param {number} flatCentersOffset Flat center offset. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Destination. + */ +ol.geom.flat.interiorpoint.linearRings = function(flatCoordinates, offset, + ends, stride, flatCenters, flatCentersOffset, opt_dest) { + var i, ii, x, x1, x2, y1, y2; + var y = flatCenters[flatCentersOffset + 1]; + /** @type {Array.<number>} */ + var intersections = []; + // Calculate intersections with the horizontal line + var end = ends[0]; + x1 = flatCoordinates[end - stride]; + y1 = flatCoordinates[end - stride + 1]; + for (i = offset; i < end; i += stride) { + x2 = flatCoordinates[i]; + y2 = flatCoordinates[i + 1]; + if ((y <= y1 && y2 <= y) || (y1 <= y && y <= y2)) { + x = (y - y1) / (y2 - y1) * (x2 - x1) + x1; + intersections.push(x); + } + x1 = x2; + y1 = y2; + } + // Find the longest segment of the horizontal line that has its center point + // inside the linear ring. + var pointX = NaN; + var maxSegmentLength = -Infinity; + intersections.sort(ol.array.numberSafeCompareFunction); + x1 = intersections[0]; + for (i = 1, ii = intersections.length; i < ii; ++i) { + x2 = intersections[i]; + var segmentLength = Math.abs(x2 - x1); + if (segmentLength > maxSegmentLength) { + x = (x1 + x2) / 2; + if (ol.geom.flat.contains.linearRingsContainsXY( + flatCoordinates, offset, ends, stride, x, y)) { + pointX = x; + maxSegmentLength = segmentLength; + } + } + x1 = x2; + } + if (isNaN(pointX)) { + // There is no horizontal line that has its center point inside the linear + // ring. Use the center of the the linear ring's extent. + pointX = flatCenters[flatCentersOffset]; + } + if (opt_dest) { + opt_dest.push(pointX, y); + return opt_dest; + } else { + return [pointX, y]; + } +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {Array.<number>} flatCenters Flat centers. + * @return {Array.<number>} Interior points. + */ +ol.geom.flat.interiorpoint.linearRingss = function(flatCoordinates, offset, endss, stride, flatCenters) { + ol.DEBUG && console.assert(2 * endss.length == flatCenters.length, + 'endss.length times 2 should be flatCenters.length'); + var interiorPoints = []; + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + interiorPoints = ol.geom.flat.interiorpoint.linearRings(flatCoordinates, + offset, ends, stride, flatCenters, 2 * i, interiorPoints); + offset = ends[ends.length - 1]; + } + return interiorPoints; +}; + +goog.provide('ol.geom.flat.segments'); + + +/** + * This function calls `callback` for each segment of the flat coordinates + * array. If the callback returns a truthy value the function returns that + * value immediately. Otherwise the function returns `false`. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function + * called for each segment. + * @param {S=} opt_this The object to be used as the value of 'this' + * within callback. + * @return {T|boolean} Value. + * @template T,S + */ +ol.geom.flat.segments.forEach = function(flatCoordinates, offset, end, stride, callback, opt_this) { + var point1 = [flatCoordinates[offset], flatCoordinates[offset + 1]]; + var point2 = []; + var ret; + for (; (offset + stride) < end; offset += stride) { + point2[0] = flatCoordinates[offset + stride]; + point2[1] = flatCoordinates[offset + stride + 1]; + ret = callback.call(opt_this, point1, point2); + if (ret) { + return ret; + } + point1[0] = point2[0]; + point1[1] = point2[1]; + } + return false; +}; + +goog.provide('ol.geom.flat.intersectsextent'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.flat.contains'); +goog.require('ol.geom.flat.segments'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. + */ +ol.geom.flat.intersectsextent.lineString = function(flatCoordinates, offset, end, stride, extent) { + var coordinatesExtent = ol.extent.extendFlatCoordinates( + ol.extent.createEmpty(), flatCoordinates, offset, end, stride); + if (!ol.extent.intersects(extent, coordinatesExtent)) { + return false; + } + if (ol.extent.containsExtent(extent, coordinatesExtent)) { + return true; + } + if (coordinatesExtent[0] >= extent[0] && + coordinatesExtent[2] <= extent[2]) { + return true; + } + if (coordinatesExtent[1] >= extent[1] && + coordinatesExtent[3] <= extent[3]) { + return true; + } + return ol.geom.flat.segments.forEach(flatCoordinates, offset, end, stride, + /** + * @param {ol.Coordinate} point1 Start point. + * @param {ol.Coordinate} point2 End point. + * @return {boolean} `true` if the segment and the extent intersect, + * `false` otherwise. + */ + function(point1, point2) { + return ol.extent.intersectsSegment(extent, point1, point2); + }); +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. + */ +ol.geom.flat.intersectsextent.lineStrings = function(flatCoordinates, offset, ends, stride, extent) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + if (ol.geom.flat.intersectsextent.lineString( + flatCoordinates, offset, ends[i], stride, extent)) { + return true; + } + offset = ends[i]; + } + return false; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. + */ +ol.geom.flat.intersectsextent.linearRing = function(flatCoordinates, offset, end, stride, extent) { + if (ol.geom.flat.intersectsextent.lineString( + flatCoordinates, offset, end, stride, extent)) { + return true; + } + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, end, stride, extent[0], extent[1])) { + return true; + } + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, end, stride, extent[0], extent[3])) { + return true; + } + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, end, stride, extent[2], extent[1])) { + return true; + } + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, end, stride, extent[2], extent[3])) { + return true; + } + return false; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. + */ +ol.geom.flat.intersectsextent.linearRings = function(flatCoordinates, offset, ends, stride, extent) { + ol.DEBUG && console.assert(ends.length > 0, 'ends should not be an empty array'); + if (!ol.geom.flat.intersectsextent.linearRing( + flatCoordinates, offset, ends[0], stride, extent)) { + return false; + } + if (ends.length === 1) { + return true; + } + var i, ii; + for (i = 1, ii = ends.length; i < ii; ++i) { + if (ol.geom.flat.contains.linearRingContainsExtent( + flatCoordinates, ends[i - 1], ends[i], stride, extent)) { + return false; + } + } + return true; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. + */ +ol.geom.flat.intersectsextent.linearRingss = function(flatCoordinates, offset, endss, stride, extent) { + ol.DEBUG && console.assert(endss.length > 0, 'endss should not be an empty array'); + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + if (ol.geom.flat.intersectsextent.linearRings( + flatCoordinates, offset, ends, stride, extent)) { + return true; + } + offset = ends[ends.length - 1]; + } + return false; +}; + +goog.provide('ol.geom.flat.reverse'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + */ +ol.geom.flat.reverse.coordinates = function(flatCoordinates, offset, end, stride) { + while (offset < end - stride) { + var i; + for (i = 0; i < stride; ++i) { + var tmp = flatCoordinates[offset + i]; + flatCoordinates[offset + i] = flatCoordinates[end - stride + i]; + flatCoordinates[end - stride + i] = tmp; + } + offset += stride; + end -= stride; + } +}; + +goog.provide('ol.geom.flat.orient'); + +goog.require('ol'); +goog.require('ol.geom.flat.reverse'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {boolean} Is clockwise. + */ +ol.geom.flat.orient.linearRingIsClockwise = function(flatCoordinates, offset, end, stride) { + // http://tinyurl.com/clockwise-method + // https://github.com/OSGeo/gdal/blob/trunk/gdal/ogr/ogrlinearring.cpp + var edge = 0; + var x1 = flatCoordinates[end - stride]; + var y1 = flatCoordinates[end - stride + 1]; + for (; offset < end; offset += stride) { + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + edge += (x2 - x1) * (y2 + y1); + x1 = x2; + y1 = y2; + } + return edge > 0; +}; + + +/** + * Determines if linear rings are oriented. By default, left-hand orientation + * is tested (first ring must be clockwise, remaining rings counter-clockwise). + * To test for right-hand orientation, use the `opt_right` argument. + * + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Array of end indexes. + * @param {number} stride Stride. + * @param {boolean=} opt_right Test for right-hand orientation + * (counter-clockwise exterior ring and clockwise interior rings). + * @return {boolean} Rings are correctly oriented. + */ +ol.geom.flat.orient.linearRingsAreOriented = function(flatCoordinates, offset, ends, stride, opt_right) { + var right = opt_right !== undefined ? opt_right : false; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var isClockwise = ol.geom.flat.orient.linearRingIsClockwise( + flatCoordinates, offset, end, stride); + if (i === 0) { + if ((right && isClockwise) || (!right && !isClockwise)) { + return false; + } + } else { + if ((right && !isClockwise) || (!right && isClockwise)) { + return false; + } + } + offset = end; + } + return true; +}; + + +/** + * Determines if linear rings are oriented. By default, left-hand orientation + * is tested (first ring must be clockwise, remaining rings counter-clockwise). + * To test for right-hand orientation, use the `opt_right` argument. + * + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Array of array of end indexes. + * @param {number} stride Stride. + * @param {boolean=} opt_right Test for right-hand orientation + * (counter-clockwise exterior ring and clockwise interior rings). + * @return {boolean} Rings are correctly oriented. + */ +ol.geom.flat.orient.linearRingssAreOriented = function(flatCoordinates, offset, endss, stride, opt_right) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + if (!ol.geom.flat.orient.linearRingsAreOriented( + flatCoordinates, offset, endss[i], stride, opt_right)) { + return false; + } + } + return true; +}; + + +/** + * Orient coordinates in a flat array of linear rings. By default, rings + * are oriented following the left-hand rule (clockwise for exterior and + * counter-clockwise for interior rings). To orient according to the + * right-hand rule, use the `opt_right` argument. + * + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {boolean=} opt_right Follow the right-hand rule for orientation. + * @return {number} End. + */ +ol.geom.flat.orient.orientLinearRings = function(flatCoordinates, offset, ends, stride, opt_right) { + var right = opt_right !== undefined ? opt_right : false; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var isClockwise = ol.geom.flat.orient.linearRingIsClockwise( + flatCoordinates, offset, end, stride); + var reverse = i === 0 ? + (right && isClockwise) || (!right && !isClockwise) : + (right && !isClockwise) || (!right && isClockwise); + if (reverse) { + ol.geom.flat.reverse.coordinates(flatCoordinates, offset, end, stride); + } + offset = end; + } + return offset; +}; + + +/** + * Orient coordinates in a flat array of linear rings. By default, rings + * are oriented following the left-hand rule (clockwise for exterior and + * counter-clockwise for interior rings). To orient according to the + * right-hand rule, use the `opt_right` argument. + * + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Array of array of end indexes. + * @param {number} stride Stride. + * @param {boolean=} opt_right Follow the right-hand rule for orientation. + * @return {number} End. + */ +ol.geom.flat.orient.orientLinearRingss = function(flatCoordinates, offset, endss, stride, opt_right) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + offset = ol.geom.flat.orient.orientLinearRings( + flatCoordinates, offset, endss[i], stride, opt_right); + } + return offset; +}; + +goog.provide('ol.geom.Polygon'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.area'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.contains'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.interiorpoint'); +goog.require('ol.geom.flat.intersectsextent'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.simplify'); +goog.require('ol.math'); + + +/** + * @classdesc + * Polygon geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.Polygon = function(coordinates, opt_layout) { + + ol.geom.SimpleGeometry.call(this); + + /** + * @type {Array.<number>} + * @private + */ + this.ends_ = []; + + /** + * @private + * @type {number} + */ + this.flatInteriorPointRevision_ = -1; + + /** + * @private + * @type {ol.Coordinate} + */ + this.flatInteriorPoint_ = null; + + /** + * @private + * @type {number} + */ + this.maxDelta_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDeltaRevision_ = -1; + + /** + * @private + * @type {number} + */ + this.orientedRevision_ = -1; + + /** + * @private + * @type {Array.<number>} + */ + this.orientedFlatCoordinates_ = null; + + this.setCoordinates(coordinates, opt_layout); + +}; +ol.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry); + + +/** + * Append the passed linear ring to this polygon. + * @param {ol.geom.LinearRing} linearRing Linear ring. + * @api stable + */ +ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) { + ol.DEBUG && console.assert(linearRing.getLayout() == this.layout, + 'layout of linearRing should match layout'); + if (!this.flatCoordinates) { + this.flatCoordinates = linearRing.getFlatCoordinates().slice(); + } else { + ol.array.extend(this.flatCoordinates, linearRing.getFlatCoordinates()); + } + this.ends_.push(this.flatCoordinates.length); + this.changed(); +}; + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.Polygon} Clone. + * @api stable + */ +ol.geom.Polygon.prototype.clone = function() { + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + this.layout, this.flatCoordinates.slice(), this.ends_.slice()); + return polygon; +}; + + +/** + * @inheritDoc + */ +ol.geom.Polygon.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta( + this.flatCoordinates, 0, this.ends_, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getsClosestPoint( + this.flatCoordinates, 0, this.ends_, this.stride, + this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); +}; + + +/** + * @inheritDoc + */ +ol.geom.Polygon.prototype.containsXY = function(x, y) { + return ol.geom.flat.contains.linearRingsContainsXY( + this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y); +}; + + +/** + * Return the area of the polygon on projected plane. + * @return {number} Area (on projected plane). + * @api stable + */ +ol.geom.Polygon.prototype.getArea = function() { + return ol.geom.flat.area.linearRings( + this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride); +}; + + +/** + * Get the coordinate array for this geometry. This array has the structure + * of a GeoJSON coordinate array for polygons. + * + * @param {boolean=} opt_right Orient coordinates according to the right-hand + * rule (counter-clockwise for exterior and clockwise for interior rings). + * If `false`, coordinates will be oriented according to the left-hand rule + * (clockwise for exterior and counter-clockwise for interior rings). + * By default, coordinate orientation will depend on how the geometry was + * constructed. + * @return {Array.<Array.<ol.Coordinate>>} Coordinates. + * @api stable + */ +ol.geom.Polygon.prototype.getCoordinates = function(opt_right) { + var flatCoordinates; + if (opt_right !== undefined) { + flatCoordinates = this.getOrientedFlatCoordinates().slice(); + ol.geom.flat.orient.orientLinearRings( + flatCoordinates, 0, this.ends_, this.stride, opt_right); + } else { + flatCoordinates = this.flatCoordinates; + } + + return ol.geom.flat.inflate.coordinatess( + flatCoordinates, 0, this.ends_, this.stride); +}; + + +/** + * @return {Array.<number>} Ends. + */ +ol.geom.Polygon.prototype.getEnds = function() { + return this.ends_; +}; + + +/** + * @return {Array.<number>} Interior point. + */ +ol.geom.Polygon.prototype.getFlatInteriorPoint = function() { + if (this.flatInteriorPointRevision_ != this.getRevision()) { + var flatCenter = ol.extent.getCenter(this.getExtent()); + this.flatInteriorPoint_ = ol.geom.flat.interiorpoint.linearRings( + this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, + flatCenter, 0); + this.flatInteriorPointRevision_ = this.getRevision(); + } + return this.flatInteriorPoint_; +}; + + +/** + * Return an interior point of the polygon. + * @return {ol.geom.Point} Interior point. + * @api stable + */ +ol.geom.Polygon.prototype.getInteriorPoint = function() { + return new ol.geom.Point(this.getFlatInteriorPoint()); +}; + + +/** + * Return the number of rings of the polygon, this includes the exterior + * ring and any interior rings. + * + * @return {number} Number of rings. + * @api + */ +ol.geom.Polygon.prototype.getLinearRingCount = function() { + return this.ends_.length; +}; + + +/** + * Return the Nth linear ring of the polygon geometry. Return `null` if the + * given index is out of range. + * The exterior linear ring is available at index `0` and the interior rings + * at index `1` and beyond. + * + * @param {number} index Index. + * @return {ol.geom.LinearRing} Linear ring. + * @api stable + */ +ol.geom.Polygon.prototype.getLinearRing = function(index) { + ol.DEBUG && console.assert(0 <= index && index < this.ends_.length, + 'index should be in between 0 and and length of this.ends_'); + if (index < 0 || this.ends_.length <= index) { + return null; + } + var linearRing = new ol.geom.LinearRing(null); + linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice( + index === 0 ? 0 : this.ends_[index - 1], this.ends_[index])); + return linearRing; +}; + + +/** + * Return the linear rings of the polygon. + * @return {Array.<ol.geom.LinearRing>} Linear rings. + * @api stable + */ +ol.geom.Polygon.prototype.getLinearRings = function() { + var layout = this.layout; + var flatCoordinates = this.flatCoordinates; + var ends = this.ends_; + var linearRings = []; + var offset = 0; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var linearRing = new ol.geom.LinearRing(null); + linearRing.setFlatCoordinates(layout, flatCoordinates.slice(offset, end)); + linearRings.push(linearRing); + offset = end; + } + return linearRings; +}; + + +/** + * @return {Array.<number>} Oriented flat coordinates. + */ +ol.geom.Polygon.prototype.getOrientedFlatCoordinates = function() { + if (this.orientedRevision_ != this.getRevision()) { + var flatCoordinates = this.flatCoordinates; + if (ol.geom.flat.orient.linearRingsAreOriented( + flatCoordinates, 0, this.ends_, this.stride)) { + this.orientedFlatCoordinates_ = flatCoordinates; + } else { + this.orientedFlatCoordinates_ = flatCoordinates.slice(); + this.orientedFlatCoordinates_.length = + ol.geom.flat.orient.orientLinearRings( + this.orientedFlatCoordinates_, 0, this.ends_, this.stride); + } + this.orientedRevision_ = this.getRevision(); + } + return this.orientedFlatCoordinates_; +}; + + +/** + * @inheritDoc + */ +ol.geom.Polygon.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + var simplifiedEnds = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizes( + this.flatCoordinates, 0, this.ends_, this.stride, + Math.sqrt(squaredTolerance), + simplifiedFlatCoordinates, 0, simplifiedEnds); + var simplifiedPolygon = new ol.geom.Polygon(null); + simplifiedPolygon.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds); + return simplifiedPolygon; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.Polygon.prototype.getType = function() { + return ol.geom.GeometryType.POLYGON; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.Polygon.prototype.intersectsExtent = function(extent) { + return ol.geom.flat.intersectsextent.linearRings( + this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent); +}; + + +/** + * Set the coordinates of the polygon. + * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.Polygon.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_); + } else { + this.setLayout(opt_layout, coordinates, 2); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + var ends = ol.geom.flat.deflate.coordinatess( + this.flatCoordinates, 0, coordinates, this.stride, this.ends_); + this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1]; + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<number>} ends Ends. + */ +ol.geom.Polygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) { + if (!flatCoordinates) { + ol.DEBUG && console.assert(ends && ends.length === 0, + 'ends must be an empty array'); + } else if (ends.length === 0) { + ol.DEBUG && console.assert(flatCoordinates.length === 0, + 'flatCoordinates should be an empty array'); + } else { + ol.DEBUG && console.assert(flatCoordinates.length == ends[ends.length - 1], + 'the length of flatCoordinates should be the last entry of ends'); + } + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.ends_ = ends; + this.changed(); +}; + + +/** + * Create an approximation of a circle on the surface of a sphere. + * @param {ol.Sphere} sphere The sphere. + * @param {ol.Coordinate} center Center (`[lon, lat]` in degrees). + * @param {number} radius The great-circle distance from the center to + * the polygon vertices. + * @param {number=} opt_n Optional number of vertices for the resulting + * polygon. Default is `32`. + * @return {ol.geom.Polygon} The "circular" polygon. + * @api stable + */ +ol.geom.Polygon.circular = function(sphere, center, radius, opt_n) { + var n = opt_n ? opt_n : 32; + /** @type {Array.<number>} */ + var flatCoordinates = []; + var i; + for (i = 0; i < n; ++i) { + ol.array.extend( + flatCoordinates, sphere.offset(center, radius, 2 * Math.PI * i / n)); + } + flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]); + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]); + return polygon; +}; + + +/** + * Create a polygon from an extent. The layout used is `XY`. + * @param {ol.Extent} extent The extent. + * @return {ol.geom.Polygon} The polygon. + * @api + */ +ol.geom.Polygon.fromExtent = function(extent) { + var minX = extent[0]; + var minY = extent[1]; + var maxX = extent[2]; + var maxY = extent[3]; + var flatCoordinates = + [minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY]; + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]); + return polygon; +}; + + +/** + * Create a regular polygon from a circle. + * @param {ol.geom.Circle} circle Circle geometry. + * @param {number=} opt_sides Number of sides of the polygon. Default is 32. + * @param {number=} opt_angle Start angle for the first vertex of the polygon in + * radians. Default is 0. + * @return {ol.geom.Polygon} Polygon geometry. + * @api + */ +ol.geom.Polygon.fromCircle = function(circle, opt_sides, opt_angle) { + var sides = opt_sides ? opt_sides : 32; + var stride = circle.getStride(); + var layout = circle.getLayout(); + var polygon = new ol.geom.Polygon(null, layout); + var arrayLength = stride * (sides + 1); + var flatCoordinates = new Array(arrayLength); + for (var i = 0; i < arrayLength; i++) { + flatCoordinates[i] = 0; + } + var ends = [flatCoordinates.length]; + polygon.setFlatCoordinates(layout, flatCoordinates, ends); + ol.geom.Polygon.makeRegular( + polygon, circle.getCenter(), circle.getRadius(), opt_angle); + return polygon; +}; + + +/** + * Modify the coordinates of a polygon to make it a regular polygon. + * @param {ol.geom.Polygon} polygon Polygon geometry. + * @param {ol.Coordinate} center Center of the regular polygon. + * @param {number} radius Radius of the regular polygon. + * @param {number=} opt_angle Start angle for the first vertex of the polygon in + * radians. Default is 0. + */ +ol.geom.Polygon.makeRegular = function(polygon, center, radius, opt_angle) { + var flatCoordinates = polygon.getFlatCoordinates(); + var layout = polygon.getLayout(); + var stride = polygon.getStride(); + var ends = polygon.getEnds(); + ol.DEBUG && console.assert(ends.length === 1, 'only 1 ring is supported'); + var sides = flatCoordinates.length / stride - 1; + var startAngle = opt_angle ? opt_angle : 0; + var angle, offset; + for (var i = 0; i <= sides; ++i) { + offset = i * stride; + angle = startAngle + (ol.math.modulo(i, sides) * 2 * Math.PI / sides); + flatCoordinates[offset] = center[0] + (radius * Math.cos(angle)); + flatCoordinates[offset + 1] = center[1] + (radius * Math.sin(angle)); + } + polygon.setFlatCoordinates(layout, flatCoordinates, ends); +}; + +goog.provide('ol.View'); + +goog.require('ol'); +goog.require('ol.CenterConstraint'); +goog.require('ol.Constraints'); +goog.require('ol.Object'); +goog.require('ol.ResolutionConstraint'); +goog.require('ol.RotationConstraint'); +goog.require('ol.array'); +goog.require('ol.asserts'); +goog.require('ol.coordinate'); +goog.require('ol.easing'); +goog.require('ol.extent'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.proj'); +goog.require('ol.proj.Units'); + + +/** + * @classdesc + * An ol.View object represents a simple 2D view of the map. + * + * This is the object to act upon to change the center, resolution, + * and rotation of the map. + * + * ### The view states + * + * An `ol.View` is determined by three states: `center`, `resolution`, + * and `rotation`. Each state has a corresponding getter and setter, e.g. + * `getCenter` and `setCenter` for the `center` state. + * + * An `ol.View` has a `projection`. The projection determines the + * coordinate system of the center, and its units determine the units of the + * resolution (projection units per pixel). The default projection is + * Spherical Mercator (EPSG:3857). + * + * ### The constraints + * + * `setCenter`, `setResolution` and `setRotation` can be used to change the + * states of the view. Any value can be passed to the setters. And the value + * that is passed to a setter will effectively be the value set in the view, + * and returned by the corresponding getter. + * + * But an `ol.View` object also has a *resolution constraint*, a + * *rotation constraint* and a *center constraint*. + * + * As said above, no constraints are applied when the setters are used to set + * new states for the view. Applying constraints is done explicitly through + * the use of the `constrain*` functions (`constrainResolution` and + * `constrainRotation` and `constrainCenter`). + * + * The main users of the constraints are the interactions and the + * controls. For example, double-clicking on the map changes the view to + * the "next" resolution. And releasing the fingers after pinch-zooming + * snaps to the closest resolution (with an animation). + * + * The *resolution constraint* snaps to specific resolutions. It is + * determined by the following options: `resolutions`, `maxResolution`, + * `maxZoom`, and `zoomFactor`. If `resolutions` is set, the other three + * options are ignored. See documentation for each option for more + * information. + * + * The *rotation constraint* snaps to specific angles. It is determined + * by the following options: `enableRotation` and `constrainRotation`. + * By default the rotation value is snapped to zero when approaching the + * horizontal. + * + * The *center constraint* is determined by the `extent` option. By + * default the center is not constrained at all. + * + * @constructor + * @extends {ol.Object} + * @param {olx.ViewOptions=} opt_options View options. + * @api stable + */ +ol.View = function(opt_options) { + ol.Object.call(this); + var options = opt_options || {}; + + /** + * @private + * @type {Array.<number>} + */ + this.hints_ = [0, 0]; + + /** + * @private + * @type {Array.<Array.<ol.ViewAnimation>>} + */ + this.animations_ = []; + + /** + * @private + * @type {number|undefined} + */ + this.updateAnimationKey_; + + this.updateAnimations_ = this.updateAnimations_.bind(this); + + /** + * @type {Object.<string, *>} + */ + var properties = {}; + properties[ol.View.Property.CENTER] = options.center !== undefined ? + options.center : null; + + /** + * @private + * @const + * @type {ol.proj.Projection} + */ + this.projection_ = ol.proj.createProjection(options.projection, 'EPSG:3857'); + + var resolutionConstraintInfo = ol.View.createResolutionConstraint_( + options); + + /** + * @private + * @type {number} + */ + this.maxResolution_ = resolutionConstraintInfo.maxResolution; + + /** + * @private + * @type {number} + */ + this.minResolution_ = resolutionConstraintInfo.minResolution; + + /** + * @private + * @type {number} + */ + this.zoomFactor_ = resolutionConstraintInfo.zoomFactor; + + /** + * @private + * @type {Array.<number>|undefined} + */ + this.resolutions_ = options.resolutions; + + /** + * @private + * @type {number} + */ + this.minZoom_ = resolutionConstraintInfo.minZoom; + + var centerConstraint = ol.View.createCenterConstraint_(options); + var resolutionConstraint = resolutionConstraintInfo.constraint; + var rotationConstraint = ol.View.createRotationConstraint_(options); + + /** + * @private + * @type {ol.Constraints} + */ + this.constraints_ = new ol.Constraints( + centerConstraint, resolutionConstraint, rotationConstraint); + + if (options.resolution !== undefined) { + properties[ol.View.Property.RESOLUTION] = options.resolution; + } else if (options.zoom !== undefined) { + properties[ol.View.Property.RESOLUTION] = this.constrainResolution( + this.maxResolution_, options.zoom - this.minZoom_); + } + properties[ol.View.Property.ROTATION] = + options.rotation !== undefined ? options.rotation : 0; + this.setProperties(properties); +}; +ol.inherits(ol.View, ol.Object); + + +/** + * Animate the view. The view's center, zoom (or resolution), and rotation + * can be animated for smooth transitions between view states. For example, + * to animate the view to a new zoom level: + * + * view.animate({zoom: view.getZoom() + 1}); + * + * By default, the animation lasts one second and uses in-and-out easing. You + * can customize this behavior by including `duration` (in milliseconds) and + * `easing` options (see {@link ol.easing}). + * + * To chain together multiple animations, call the method with multiple + * animation objects. For example, to first zoom and then pan: + * + * view.animate({zoom: 10}, {center: [0, 0]}); + * + * If you provide a function as the last argument to the animate method, it + * will get called at the end of an animation series. The callback will be + * called with `true` if the animation series completed on its own or `false` + * if it was cancelled. + * + * Animations are cancelled by user interactions (e.g. dragging the map) or by + * calling `view.setCenter()`, `view.setResolution()`, or `view.setRotation()` + * (or another method that calls one of these). + * + * @param {...(olx.AnimationOptions|function(boolean))} var_args Animation + * options. Multiple animations can be run in series by passing multiple + * options objects. To run multiple animations in parallel, call the method + * multiple times. An optional callback can be provided as a final + * argument. The callback will be called with a boolean indicating whether + * the animation completed without being cancelled. + * @api + */ +ol.View.prototype.animate = function(var_args) { + var start = Date.now(); + var center = this.getCenter().slice(); + var resolution = this.getResolution(); + var rotation = this.getRotation(); + var animationCount = arguments.length; + var callback; + if (animationCount > 1 && typeof arguments[animationCount - 1] === 'function') { + callback = arguments[animationCount - 1]; + --animationCount; + } + var series = []; + for (var i = 0; i < animationCount; ++i) { + var options = /** @type olx.AnimationOptions */ (arguments[i]); + + var animation = /** @type {ol.ViewAnimation} */ ({ + start: start, + complete: false, + anchor: options.anchor, + duration: options.duration !== undefined ? options.duration : 1000, + easing: options.easing || ol.easing.inAndOut + }); + + if (options.center) { + animation.sourceCenter = center; + animation.targetCenter = options.center; + center = animation.targetCenter; + } + + if (options.zoom !== undefined) { + animation.sourceResolution = resolution; + animation.targetResolution = this.constrainResolution( + this.maxResolution_, options.zoom - this.minZoom_, 0); + resolution = animation.targetResolution; + } else if (options.resolution) { + animation.sourceResolution = resolution; + animation.targetResolution = options.resolution; + resolution = animation.targetResolution; + } + + if (options.rotation !== undefined) { + animation.sourceRotation = rotation; + animation.targetRotation = options.rotation; + rotation = animation.targetRotation; + } + + animation.callback = callback; + start += animation.duration; + series.push(animation); + } + this.animations_.push(series); + this.setHint(ol.View.Hint.ANIMATING, 1); + this.updateAnimations_(); +}; + + +/** + * Determine if the view is being animated. + * @return {boolean} The view is being animated. + */ +ol.View.prototype.getAnimating = function() { + return this.getHints()[ol.View.Hint.ANIMATING] > 0; +}; + + +/** + * Cancel any ongoing animations. + */ +ol.View.prototype.cancelAnimations = function() { + this.setHint(ol.View.Hint.ANIMATING, -this.getHints()[ol.View.Hint.ANIMATING]); + for (var i = 0, ii = this.animations_.length; i < ii; ++i) { + var series = this.animations_[i]; + if (series[0].callback) { + series[0].callback(false); + } + } + this.animations_.length = 0; +}; + +/** + * Update all animations. + */ +ol.View.prototype.updateAnimations_ = function() { + if (this.updateAnimationKey_ !== undefined) { + cancelAnimationFrame(this.updateAnimationKey_); + this.updateAnimationKey_ = undefined; + } + if (!this.getAnimating()) { + return; + } + var now = Date.now(); + var more = false; + for (var i = this.animations_.length - 1; i >= 0; --i) { + var series = this.animations_[i]; + var seriesComplete = true; + for (var j = 0, jj = series.length; j < jj; ++j) { + var animation = series[j]; + if (animation.complete) { + continue; + } + var elapsed = now - animation.start; + var fraction = animation.duration > 0 ? elapsed / animation.duration : 1; + if (fraction >= 1) { + animation.complete = true; + fraction = 1; + } else { + seriesComplete = false; + } + var progress = animation.easing(fraction); + if (animation.sourceCenter) { + var x0 = animation.sourceCenter[0]; + var y0 = animation.sourceCenter[1]; + var x1 = animation.targetCenter[0]; + var y1 = animation.targetCenter[1]; + var x = x0 + progress * (x1 - x0); + var y = y0 + progress * (y1 - y0); + this.set(ol.View.Property.CENTER, [x, y]); + } + if (animation.sourceResolution) { + var resolution = animation.sourceResolution + + progress * (animation.targetResolution - animation.sourceResolution); + if (animation.anchor) { + this.set(ol.View.Property.CENTER, + this.calculateCenterZoom(resolution, animation.anchor)); + } + this.set(ol.View.Property.RESOLUTION, resolution); + } + if (animation.sourceRotation !== undefined) { + var rotation = animation.sourceRotation + + progress * (animation.targetRotation - animation.sourceRotation); + if (animation.anchor) { + this.set(ol.View.Property.CENTER, + this.calculateCenterRotate(rotation, animation.anchor)); + } + this.set(ol.View.Property.ROTATION, rotation); + } + more = true; + if (!animation.complete) { + break; + } + } + if (seriesComplete) { + this.animations_[i] = null; + this.setHint(ol.View.Hint.ANIMATING, -1); + var callback = series[0].callback; + if (callback) { + callback(true); + } + } + } + // prune completed series + this.animations_ = this.animations_.filter(Boolean); + if (more && this.updateAnimationKey_ === undefined) { + this.updateAnimationKey_ = requestAnimationFrame(this.updateAnimations_); + } +}; + +/** + * @param {number} rotation Target rotation. + * @param {ol.Coordinate} anchor Rotation anchor. + * @return {ol.Coordinate|undefined} Center for rotation and anchor. + */ +ol.View.prototype.calculateCenterRotate = function(rotation, anchor) { + var center; + var currentCenter = this.getCenter(); + if (currentCenter !== undefined) { + center = [currentCenter[0] - anchor[0], currentCenter[1] - anchor[1]]; + ol.coordinate.rotate(center, rotation - this.getRotation()); + ol.coordinate.add(center, anchor); + } + return center; +}; + + +/** + * @param {number} resolution Target resolution. + * @param {ol.Coordinate} anchor Zoom anchor. + * @return {ol.Coordinate|undefined} Center for resolution and anchor. + */ +ol.View.prototype.calculateCenterZoom = function(resolution, anchor) { + var center; + var currentCenter = this.getCenter(); + var currentResolution = this.getResolution(); + if (currentCenter !== undefined && currentResolution !== undefined) { + var x = anchor[0] - + resolution * (anchor[0] - currentCenter[0]) / currentResolution; + var y = anchor[1] - + resolution * (anchor[1] - currentCenter[1]) / currentResolution; + center = [x, y]; + } + return center; +}; + + +/** + * Get the constrained center of this view. + * @param {ol.Coordinate|undefined} center Center. + * @return {ol.Coordinate|undefined} Constrained center. + * @api + */ +ol.View.prototype.constrainCenter = function(center) { + return this.constraints_.center(center); +}; + + +/** + * Get the constrained resolution of this view. + * @param {number|undefined} resolution Resolution. + * @param {number=} opt_delta Delta. Default is `0`. + * @param {number=} opt_direction Direction. Default is `0`. + * @return {number|undefined} Constrained resolution. + * @api + */ +ol.View.prototype.constrainResolution = function( + resolution, opt_delta, opt_direction) { + var delta = opt_delta || 0; + var direction = opt_direction || 0; + return this.constraints_.resolution(resolution, delta, direction); +}; + + +/** + * Get the constrained rotation of this view. + * @param {number|undefined} rotation Rotation. + * @param {number=} opt_delta Delta. Default is `0`. + * @return {number|undefined} Constrained rotation. + * @api + */ +ol.View.prototype.constrainRotation = function(rotation, opt_delta) { + var delta = opt_delta || 0; + return this.constraints_.rotation(rotation, delta); +}; + + +/** + * Get the view center. + * @return {ol.Coordinate|undefined} The center of the view. + * @observable + * @api stable + */ +ol.View.prototype.getCenter = function() { + return /** @type {ol.Coordinate|undefined} */ ( + this.get(ol.View.Property.CENTER)); +}; + + +/** + * @param {Array.<number>=} opt_hints Destination array. + * @return {Array.<number>} Hint. + */ +ol.View.prototype.getHints = function(opt_hints) { + if (opt_hints !== undefined) { + opt_hints[0] = this.hints_[0]; + opt_hints[1] = this.hints_[1]; + return opt_hints; + } else { + return this.hints_.slice(); + } +}; + + +/** + * Calculate the extent for the current view state and the passed size. + * The size is the pixel dimensions of the box into which the calculated extent + * should fit. In most cases you want to get the extent of the entire map, + * that is `map.getSize()`. + * @param {ol.Size} size Box pixel size. + * @return {ol.Extent} Extent. + * @api stable + */ +ol.View.prototype.calculateExtent = function(size) { + var center = /** @type {!ol.Coordinate} */ (this.getCenter()); + ol.asserts.assert(center, 1); // The view center is not defined + var resolution = /** @type {!number} */ (this.getResolution()); + ol.asserts.assert(resolution !== undefined, 2); // The view resolution is not defined + var rotation = /** @type {!number} */ (this.getRotation()); + ol.asserts.assert(rotation !== undefined, 3); // The view rotation is not defined + + return ol.extent.getForViewAndSize(center, resolution, rotation, size); +}; + + +/** + * Get the maximum resolution of the view. + * @return {number} The maximum resolution of the view. + * @api + */ +ol.View.prototype.getMaxResolution = function() { + return this.maxResolution_; +}; + + +/** + * Get the minimum resolution of the view. + * @return {number} The minimum resolution of the view. + * @api + */ +ol.View.prototype.getMinResolution = function() { + return this.minResolution_; +}; + + +/** + * Get the view projection. + * @return {ol.proj.Projection} The projection of the view. + * @api stable + */ +ol.View.prototype.getProjection = function() { + return this.projection_; +}; + + +/** + * Get the view resolution. + * @return {number|undefined} The resolution of the view. + * @observable + * @api stable + */ +ol.View.prototype.getResolution = function() { + return /** @type {number|undefined} */ ( + this.get(ol.View.Property.RESOLUTION)); +}; + + +/** + * Get the resolutions for the view. This returns the array of resolutions + * passed to the constructor of the {ol.View}, or undefined if none were given. + * @return {Array.<number>|undefined} The resolutions of the view. + * @api stable + */ +ol.View.prototype.getResolutions = function() { + return this.resolutions_; +}; + + +/** + * Get the resolution for a provided extent (in map units) and size (in pixels). + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Box pixel size. + * @return {number} The resolution at which the provided extent will render at + * the given size. + */ +ol.View.prototype.getResolutionForExtent = function(extent, size) { + var xResolution = ol.extent.getWidth(extent) / size[0]; + var yResolution = ol.extent.getHeight(extent) / size[1]; + return Math.max(xResolution, yResolution); +}; + + +/** + * Return a function that returns a value between 0 and 1 for a + * resolution. Exponential scaling is assumed. + * @param {number=} opt_power Power. + * @return {function(number): number} Resolution for value function. + */ +ol.View.prototype.getResolutionForValueFunction = function(opt_power) { + var power = opt_power || 2; + var maxResolution = this.maxResolution_; + var minResolution = this.minResolution_; + var max = Math.log(maxResolution / minResolution) / Math.log(power); + return ( + /** + * @param {number} value Value. + * @return {number} Resolution. + */ + function(value) { + var resolution = maxResolution / Math.pow(power, value * max); + ol.DEBUG && console.assert(resolution >= minResolution && + resolution <= maxResolution, + 'calculated resolution outside allowed bounds (%s <= %s <= %s)', + minResolution, resolution, maxResolution); + return resolution; + }); +}; + + +/** + * Get the view rotation. + * @return {number} The rotation of the view in radians. + * @observable + * @api stable + */ +ol.View.prototype.getRotation = function() { + return /** @type {number} */ (this.get(ol.View.Property.ROTATION)); +}; + + +/** + * Return a function that returns a resolution for a value between + * 0 and 1. Exponential scaling is assumed. + * @param {number=} opt_power Power. + * @return {function(number): number} Value for resolution function. + */ +ol.View.prototype.getValueForResolutionFunction = function(opt_power) { + var power = opt_power || 2; + var maxResolution = this.maxResolution_; + var minResolution = this.minResolution_; + var max = Math.log(maxResolution / minResolution) / Math.log(power); + return ( + /** + * @param {number} resolution Resolution. + * @return {number} Value. + */ + function(resolution) { + var value = + (Math.log(maxResolution / resolution) / Math.log(power)) / max; + ol.DEBUG && console.assert(value >= 0 && value <= 1, + 'calculated value (%s) ouside allowed range (0-1)', value); + return value; + }); +}; + + +/** + * @return {olx.ViewState} View state. + */ +ol.View.prototype.getState = function() { + ol.DEBUG && console.assert(this.isDef(), + 'the view was not defined (had no center and/or resolution)'); + var center = /** @type {ol.Coordinate} */ (this.getCenter()); + var projection = this.getProjection(); + var resolution = /** @type {number} */ (this.getResolution()); + var rotation = this.getRotation(); + return /** @type {olx.ViewState} */ ({ + center: center.slice(), + projection: projection !== undefined ? projection : null, + resolution: resolution, + rotation: rotation + }); +}; + + +/** + * Get the current zoom level. Return undefined if the current + * resolution is undefined or not within the "resolution constraints". + * @return {number|undefined} Zoom. + * @api stable + */ +ol.View.prototype.getZoom = function() { + var zoom; + var resolution = this.getResolution(); + if (resolution !== undefined && + resolution >= this.minResolution_ && resolution <= this.maxResolution_) { + var offset = this.minZoom_ || 0; + var max, zoomFactor; + if (this.resolutions_) { + var nearest = ol.array.linearFindNearest(this.resolutions_, resolution, 1); + offset += nearest; + if (nearest == this.resolutions_.length - 1) { + return offset; + } + max = this.resolutions_[nearest]; + zoomFactor = max / this.resolutions_[nearest + 1]; + } else { + max = this.maxResolution_; + zoomFactor = this.zoomFactor_; + } + zoom = offset + Math.log(max / resolution) / Math.log(zoomFactor); + } + return zoom; +}; + + +/** + * Fit the given geometry or extent based on the given map size and border. + * The size is pixel dimensions of the box to fit the extent into. + * In most cases you will want to use the map size, that is `map.getSize()`. + * Takes care of the map angle. + * @param {ol.geom.SimpleGeometry|ol.Extent} geometry Geometry. + * @param {ol.Size} size Box pixel size. + * @param {olx.view.FitOptions=} opt_options Options. + * @api + */ +ol.View.prototype.fit = function(geometry, size, opt_options) { + if (!(geometry instanceof ol.geom.SimpleGeometry)) { + ol.asserts.assert(Array.isArray(geometry), + 24); // Invalid extent or geometry provided as `geometry` + ol.asserts.assert(!ol.extent.isEmpty(geometry), + 25); // Cannot fit empty extent provided as `geometry` + geometry = ol.geom.Polygon.fromExtent(geometry); + } + + var options = opt_options || {}; + + var padding = options.padding !== undefined ? options.padding : [0, 0, 0, 0]; + var constrainResolution = options.constrainResolution !== undefined ? + options.constrainResolution : true; + var nearest = options.nearest !== undefined ? options.nearest : false; + var minResolution; + if (options.minResolution !== undefined) { + minResolution = options.minResolution; + } else if (options.maxZoom !== undefined) { + minResolution = this.constrainResolution( + this.maxResolution_, options.maxZoom - this.minZoom_, 0); + } else { + minResolution = 0; + } + var coords = geometry.getFlatCoordinates(); + + // calculate rotated extent + var rotation = this.getRotation(); + ol.DEBUG && console.assert(rotation !== undefined, 'rotation was not defined'); + var cosAngle = Math.cos(-rotation); + var sinAngle = Math.sin(-rotation); + var minRotX = +Infinity; + var minRotY = +Infinity; + var maxRotX = -Infinity; + var maxRotY = -Infinity; + var stride = geometry.getStride(); + for (var i = 0, ii = coords.length; i < ii; i += stride) { + var rotX = coords[i] * cosAngle - coords[i + 1] * sinAngle; + var rotY = coords[i] * sinAngle + coords[i + 1] * cosAngle; + minRotX = Math.min(minRotX, rotX); + minRotY = Math.min(minRotY, rotY); + maxRotX = Math.max(maxRotX, rotX); + maxRotY = Math.max(maxRotY, rotY); + } + + // calculate resolution + var resolution = this.getResolutionForExtent( + [minRotX, minRotY, maxRotX, maxRotY], + [size[0] - padding[1] - padding[3], size[1] - padding[0] - padding[2]]); + resolution = isNaN(resolution) ? minResolution : + Math.max(resolution, minResolution); + if (constrainResolution) { + var constrainedResolution = this.constrainResolution(resolution, 0, 0); + if (!nearest && constrainedResolution < resolution) { + constrainedResolution = this.constrainResolution( + constrainedResolution, -1, 0); + } + resolution = constrainedResolution; + } + + // calculate center + sinAngle = -sinAngle; // go back to original rotation + var centerRotX = (minRotX + maxRotX) / 2; + var centerRotY = (minRotY + maxRotY) / 2; + centerRotX += (padding[1] - padding[3]) / 2 * resolution; + centerRotY += (padding[0] - padding[2]) / 2 * resolution; + var centerX = centerRotX * cosAngle - centerRotY * sinAngle; + var centerY = centerRotY * cosAngle + centerRotX * sinAngle; + var center = [centerX, centerY]; + + if (options.duration !== undefined) { + this.animate({ + resolution: resolution, + center: center, + duration: options.duration, + easing: options.easing + }); + } else { + this.setResolution(resolution); + this.setCenter(center); + } +}; + + +/** + * Center on coordinate and view position. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.Size} size Box pixel size. + * @param {ol.Pixel} position Position on the view to center on. + * @api + */ +ol.View.prototype.centerOn = function(coordinate, size, position) { + // calculate rotated position + var rotation = this.getRotation(); + var cosAngle = Math.cos(-rotation); + var sinAngle = Math.sin(-rotation); + var rotX = coordinate[0] * cosAngle - coordinate[1] * sinAngle; + var rotY = coordinate[1] * cosAngle + coordinate[0] * sinAngle; + var resolution = this.getResolution(); + rotX += (size[0] / 2 - position[0]) * resolution; + rotY += (position[1] - size[1] / 2) * resolution; + + // go back to original angle + sinAngle = -sinAngle; // go back to original rotation + var centerX = rotX * cosAngle - rotY * sinAngle; + var centerY = rotY * cosAngle + rotX * sinAngle; + + this.setCenter([centerX, centerY]); +}; + + +/** + * @return {boolean} Is defined. + */ +ol.View.prototype.isDef = function() { + return !!this.getCenter() && this.getResolution() !== undefined; +}; + + +/** + * Rotate the view around a given coordinate. + * @param {number} rotation New rotation value for the view. + * @param {ol.Coordinate=} opt_anchor The rotation center. + * @api stable + */ +ol.View.prototype.rotate = function(rotation, opt_anchor) { + if (opt_anchor !== undefined) { + var center = this.calculateCenterRotate(rotation, opt_anchor); + this.setCenter(center); + } + this.setRotation(rotation); +}; + + +/** + * Set the center of the current view. + * @param {ol.Coordinate|undefined} center The center of the view. + * @observable + * @api stable + */ +ol.View.prototype.setCenter = function(center) { + this.set(ol.View.Property.CENTER, center); + if (this.getAnimating()) { + this.cancelAnimations(); + } +}; + + +/** + * @param {ol.View.Hint} hint Hint. + * @param {number} delta Delta. + * @return {number} New value. + */ +ol.View.prototype.setHint = function(hint, delta) { + ol.DEBUG && console.assert(0 <= hint && hint < this.hints_.length, + 'illegal hint (%s), must be between 0 and %s', hint, this.hints_.length); + this.hints_[hint] += delta; + ol.DEBUG && console.assert(this.hints_[hint] >= 0, + 'Hint at %s must be positive, was %s', hint, this.hints_[hint]); + this.changed(); + return this.hints_[hint]; +}; + + +/** + * Set the resolution for this view. + * @param {number|undefined} resolution The resolution of the view. + * @observable + * @api stable + */ +ol.View.prototype.setResolution = function(resolution) { + this.set(ol.View.Property.RESOLUTION, resolution); + if (this.getAnimating()) { + this.cancelAnimations(); + } +}; + + +/** + * Set the rotation for this view. + * @param {number} rotation The rotation of the view in radians. + * @observable + * @api stable + */ +ol.View.prototype.setRotation = function(rotation) { + this.set(ol.View.Property.ROTATION, rotation); + if (this.getAnimating()) { + this.cancelAnimations(); + } +}; + + +/** + * Zoom to a specific zoom level. + * @param {number} zoom Zoom level. + * @api stable + */ +ol.View.prototype.setZoom = function(zoom) { + var resolution = this.constrainResolution( + this.maxResolution_, zoom - this.minZoom_, 0); + this.setResolution(resolution); +}; + + +/** + * @param {olx.ViewOptions} options View options. + * @private + * @return {ol.CenterConstraintType} The constraint. + */ +ol.View.createCenterConstraint_ = function(options) { + if (options.extent !== undefined) { + return ol.CenterConstraint.createExtent(options.extent); + } else { + return ol.CenterConstraint.none; + } +}; + + +/** + * @private + * @param {olx.ViewOptions} options View options. + * @return {{constraint: ol.ResolutionConstraintType, maxResolution: number, + * minResolution: number, zoomFactor: number}} The constraint. + */ +ol.View.createResolutionConstraint_ = function(options) { + var resolutionConstraint; + var maxResolution; + var minResolution; + + // TODO: move these to be ol constants + // see https://github.com/openlayers/ol3/issues/2076 + var defaultMaxZoom = 28; + var defaultZoomFactor = 2; + + var minZoom = options.minZoom !== undefined ? + options.minZoom : ol.DEFAULT_MIN_ZOOM; + + var maxZoom = options.maxZoom !== undefined ? + options.maxZoom : defaultMaxZoom; + + var zoomFactor = options.zoomFactor !== undefined ? + options.zoomFactor : defaultZoomFactor; + + if (options.resolutions !== undefined) { + var resolutions = options.resolutions; + maxResolution = resolutions[0]; + minResolution = resolutions[resolutions.length - 1]; + resolutionConstraint = ol.ResolutionConstraint.createSnapToResolutions( + resolutions); + } else { + // calculate the default min and max resolution + var projection = ol.proj.createProjection(options.projection, 'EPSG:3857'); + var extent = projection.getExtent(); + var size = !extent ? + // use an extent that can fit the whole world if need be + 360 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] / + projection.getMetersPerUnit() : + Math.max(ol.extent.getWidth(extent), ol.extent.getHeight(extent)); + + var defaultMaxResolution = size / ol.DEFAULT_TILE_SIZE / Math.pow( + defaultZoomFactor, ol.DEFAULT_MIN_ZOOM); + + var defaultMinResolution = defaultMaxResolution / Math.pow( + defaultZoomFactor, defaultMaxZoom - ol.DEFAULT_MIN_ZOOM); + + // user provided maxResolution takes precedence + maxResolution = options.maxResolution; + if (maxResolution !== undefined) { + minZoom = 0; + } else { + maxResolution = defaultMaxResolution / Math.pow(zoomFactor, minZoom); + } + + // user provided minResolution takes precedence + minResolution = options.minResolution; + if (minResolution === undefined) { + if (options.maxZoom !== undefined) { + if (options.maxResolution !== undefined) { + minResolution = maxResolution / Math.pow(zoomFactor, maxZoom); + } else { + minResolution = defaultMaxResolution / Math.pow(zoomFactor, maxZoom); + } + } else { + minResolution = defaultMinResolution; + } + } + + // given discrete zoom levels, minResolution may be different than provided + maxZoom = minZoom + Math.floor( + Math.log(maxResolution / minResolution) / Math.log(zoomFactor)); + minResolution = maxResolution / Math.pow(zoomFactor, maxZoom - minZoom); + + resolutionConstraint = ol.ResolutionConstraint.createSnapToPower( + zoomFactor, maxResolution, maxZoom - minZoom); + } + return {constraint: resolutionConstraint, maxResolution: maxResolution, + minResolution: minResolution, minZoom: minZoom, zoomFactor: zoomFactor}; +}; + + +/** + * @private + * @param {olx.ViewOptions} options View options. + * @return {ol.RotationConstraintType} Rotation constraint. + */ +ol.View.createRotationConstraint_ = function(options) { + var enableRotation = options.enableRotation !== undefined ? + options.enableRotation : true; + if (enableRotation) { + var constrainRotation = options.constrainRotation; + if (constrainRotation === undefined || constrainRotation === true) { + return ol.RotationConstraint.createSnapToZero(); + } else if (constrainRotation === false) { + return ol.RotationConstraint.none; + } else if (typeof constrainRotation === 'number') { + return ol.RotationConstraint.createSnapToN(constrainRotation); + } else { + ol.DEBUG && console.assert(false, + 'illegal option for constrainRotation (%s)', constrainRotation); + return ol.RotationConstraint.none; + } + } else { + return ol.RotationConstraint.disable; + } +}; + + +/** + * @enum {string} + */ +ol.View.Property = { + CENTER: 'center', + RESOLUTION: 'resolution', + ROTATION: 'rotation' +}; + + +/** + * @enum {number} + */ +ol.View.Hint = { + ANIMATING: 0, + INTERACTING: 1 +}; + +goog.provide('ol.animation'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.coordinate'); +goog.require('ol.easing'); + + +/** + * Deprecated (use {@link ol.View#animate} instead). + * Generate an animated transition that will "bounce" the resolution as it + * approaches the final value. + * @param {olx.animation.BounceOptions} options Bounce options. + * @return {ol.PreRenderFunction} Pre-render function. + * @api + */ +ol.animation.bounce = function(options) { + ol.DEBUG && console.warn('ol.animation.bounce() is deprecated. Use view.animate() instead.'); + var resolution = options.resolution; + var start = options.start ? options.start : Date.now(); + var duration = options.duration !== undefined ? options.duration : 1000; + var easing = options.easing ? + options.easing : ol.easing.upAndDown; + return ( + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + * @return {boolean} Run this function in the next frame. + */ + function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = easing((frameState.time - start) / duration); + var deltaResolution = resolution - frameState.viewState.resolution; + frameState.animate = true; + frameState.viewState.resolution += delta * deltaResolution; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else { + return false; + } + }); +}; + + +/** + * Deprecated (use {@link ol.View#animate} instead). + * Generate an animated transition while updating the view center. + * @param {olx.animation.PanOptions} options Pan options. + * @return {ol.PreRenderFunction} Pre-render function. + * @api + */ +ol.animation.pan = function(options) { + ol.DEBUG && console.warn('ol.animation.pan() is deprecated. Use view.animate() instead.'); + var source = options.source; + var start = options.start ? options.start : Date.now(); + var sourceX = source[0]; + var sourceY = source[1]; + var duration = options.duration !== undefined ? options.duration : 1000; + var easing = options.easing ? + options.easing : ol.easing.inAndOut; + return ( + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + * @return {boolean} Run this function in the next frame. + */ + function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = 1 - easing((frameState.time - start) / duration); + var deltaX = sourceX - frameState.viewState.center[0]; + var deltaY = sourceY - frameState.viewState.center[1]; + frameState.animate = true; + frameState.viewState.center[0] += delta * deltaX; + frameState.viewState.center[1] += delta * deltaY; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else { + return false; + } + }); +}; + + +/** + * Deprecated (use {@link ol.View#animate} instead). + * Generate an animated transition while updating the view rotation. + * @param {olx.animation.RotateOptions} options Rotate options. + * @return {ol.PreRenderFunction} Pre-render function. + * @api + */ +ol.animation.rotate = function(options) { + ol.DEBUG && console.warn('ol.animation.rotate() is deprecated. Use view.animate() instead.'); + var sourceRotation = options.rotation ? options.rotation : 0; + var start = options.start ? options.start : Date.now(); + var duration = options.duration !== undefined ? options.duration : 1000; + var easing = options.easing ? + options.easing : ol.easing.inAndOut; + var anchor = options.anchor ? + options.anchor : null; + + return ( + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + * @return {boolean} Run this function in the next frame. + */ + function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = 1 - easing((frameState.time - start) / duration); + var deltaRotation = + (sourceRotation - frameState.viewState.rotation) * delta; + frameState.animate = true; + frameState.viewState.rotation += deltaRotation; + if (anchor) { + var center = frameState.viewState.center; + ol.coordinate.sub(center, anchor); + ol.coordinate.rotate(center, deltaRotation); + ol.coordinate.add(center, anchor); + } + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else { + return false; + } + }); +}; + + +/** + * Deprecated (use {@link ol.View#animate} instead). + * Generate an animated transition while updating the view resolution. + * @param {olx.animation.ZoomOptions} options Zoom options. + * @return {ol.PreRenderFunction} Pre-render function. + * @api + */ +ol.animation.zoom = function(options) { + ol.DEBUG && console.warn('ol.animation.zoom() is deprecated. Use view.animate() instead.'); + var sourceResolution = options.resolution; + var start = options.start ? options.start : Date.now(); + var duration = options.duration !== undefined ? options.duration : 1000; + var easing = options.easing ? + options.easing : ol.easing.inAndOut; + return ( + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + * @return {boolean} Run this function in the next frame. + */ + function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = 1 - easing((frameState.time - start) / duration); + var deltaResolution = + sourceResolution - frameState.viewState.resolution; + frameState.animate = true; + frameState.viewState.resolution += delta * deltaResolution; + frameState.viewHints[ol.View.Hint.ANIMATING] += 1; + return true; + } else { + return false; + } + }); +}; + +goog.provide('ol.TileRange'); + + +/** + * A representation of a contiguous block of tiles. A tile range is specified + * by its min/max tile coordinates and is inclusive of coordinates. + * + * @constructor + * @param {number} minX Minimum X. + * @param {number} maxX Maximum X. + * @param {number} minY Minimum Y. + * @param {number} maxY Maximum Y. + * @struct + */ +ol.TileRange = function(minX, maxX, minY, maxY) { + + /** + * @type {number} + */ + this.minX = minX; + + /** + * @type {number} + */ + this.maxX = maxX; + + /** + * @type {number} + */ + this.minY = minY; + + /** + * @type {number} + */ + this.maxY = maxY; + +}; + + +/** + * @param {number} minX Minimum X. + * @param {number} maxX Maximum X. + * @param {number} minY Minimum Y. + * @param {number} maxY Maximum Y. + * @param {ol.TileRange|undefined} tileRange TileRange. + * @return {ol.TileRange} Tile range. + */ +ol.TileRange.createOrUpdate = function(minX, maxX, minY, maxY, tileRange) { + if (tileRange !== undefined) { + tileRange.minX = minX; + tileRange.maxX = maxX; + tileRange.minY = minY; + tileRange.maxY = maxY; + return tileRange; + } else { + return new ol.TileRange(minX, maxX, minY, maxY); + } +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {boolean} Contains tile coordinate. + */ +ol.TileRange.prototype.contains = function(tileCoord) { + return this.containsXY(tileCoord[1], tileCoord[2]); +}; + + +/** + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} Contains. + */ +ol.TileRange.prototype.containsTileRange = function(tileRange) { + return this.minX <= tileRange.minX && tileRange.maxX <= this.maxX && + this.minY <= tileRange.minY && tileRange.maxY <= this.maxY; +}; + + +/** + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @return {boolean} Contains coordinate. + */ +ol.TileRange.prototype.containsXY = function(x, y) { + return this.minX <= x && x <= this.maxX && this.minY <= y && y <= this.maxY; +}; + + +/** + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} Equals. + */ +ol.TileRange.prototype.equals = function(tileRange) { + return this.minX == tileRange.minX && this.minY == tileRange.minY && + this.maxX == tileRange.maxX && this.maxY == tileRange.maxY; +}; + + +/** + * @param {ol.TileRange} tileRange Tile range. + */ +ol.TileRange.prototype.extend = function(tileRange) { + if (tileRange.minX < this.minX) { + this.minX = tileRange.minX; + } + if (tileRange.maxX > this.maxX) { + this.maxX = tileRange.maxX; + } + if (tileRange.minY < this.minY) { + this.minY = tileRange.minY; + } + if (tileRange.maxY > this.maxY) { + this.maxY = tileRange.maxY; + } +}; + + +/** + * @return {number} Height. + */ +ol.TileRange.prototype.getHeight = function() { + return this.maxY - this.minY + 1; +}; + + +/** + * @return {ol.Size} Size. + */ +ol.TileRange.prototype.getSize = function() { + return [this.getWidth(), this.getHeight()]; +}; + + +/** + * @return {number} Width. + */ +ol.TileRange.prototype.getWidth = function() { + return this.maxX - this.minX + 1; +}; + + +/** + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} Intersects. + */ +ol.TileRange.prototype.intersects = function(tileRange) { + return this.minX <= tileRange.maxX && + this.maxX >= tileRange.minX && + this.minY <= tileRange.maxY && + this.maxY >= tileRange.minY; +}; + +goog.provide('ol.size'); + + +/** + * Returns a buffered size. + * @param {ol.Size} size Size. + * @param {number} buffer Buffer. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} The buffered size. + */ +ol.size.buffer = function(size, buffer, opt_size) { + if (opt_size === undefined) { + opt_size = [0, 0]; + } + opt_size[0] = size[0] + 2 * buffer; + opt_size[1] = size[1] + 2 * buffer; + return opt_size; +}; + + +/** + * Determines if a size has a positive area. + * @param {ol.Size} size The size to test. + * @return {boolean} The size has a positive area. + */ +ol.size.hasArea = function(size) { + return size[0] > 0 && size[1] > 0; +}; + + +/** + * Returns a size scaled by a ratio. The result will be an array of integers. + * @param {ol.Size} size Size. + * @param {number} ratio Ratio. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} The scaled size. + */ +ol.size.scale = function(size, ratio, opt_size) { + if (opt_size === undefined) { + opt_size = [0, 0]; + } + opt_size[0] = (size[0] * ratio + 0.5) | 0; + opt_size[1] = (size[1] * ratio + 0.5) | 0; + return opt_size; +}; + + +/** + * Returns an `ol.Size` array for the passed in number (meaning: square) or + * `ol.Size` array. + * (meaning: non-square), + * @param {number|ol.Size} size Width and height. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} Size. + * @api stable + */ +ol.size.toSize = function(size, opt_size) { + if (Array.isArray(size)) { + return size; + } else { + if (opt_size === undefined) { + opt_size = [size, size]; + } else { + opt_size[0] = opt_size[1] = /** @type {number} */ (size); + } + return opt_size; + } +}; + +goog.provide('ol.tilecoord'); + + +/** + * @param {number} z Z. + * @param {number} x X. + * @param {number} y Y. + * @param {ol.TileCoord=} opt_tileCoord Tile coordinate. + * @return {ol.TileCoord} Tile coordinate. + */ +ol.tilecoord.createOrUpdate = function(z, x, y, opt_tileCoord) { + if (opt_tileCoord !== undefined) { + opt_tileCoord[0] = z; + opt_tileCoord[1] = x; + opt_tileCoord[2] = y; + return opt_tileCoord; + } else { + return [z, x, y]; + } +}; + + +/** + * @param {number} z Z. + * @param {number} x X. + * @param {number} y Y. + * @return {string} Key. + */ +ol.tilecoord.getKeyZXY = function(z, x, y) { + return z + '/' + x + '/' + y; +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coord. + * @return {number} Hash. + */ +ol.tilecoord.hash = function(tileCoord) { + return (tileCoord[1] << tileCoord[0]) + tileCoord[2]; +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coord. + * @return {string} Quad key. + */ +ol.tilecoord.quadKey = function(tileCoord) { + var z = tileCoord[0]; + var digits = new Array(z); + var mask = 1 << (z - 1); + var i, charCode; + for (i = 0; i < z; ++i) { + // 48 is charCode for 0 - '0'.charCodeAt(0) + charCode = 48; + if (tileCoord[1] & mask) { + charCode += 1; + } + if (tileCoord[2] & mask) { + charCode += 2; + } + digits[i] = String.fromCharCode(charCode); + mask >>= 1; + } + return digits.join(''); +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid. + * @return {boolean} Tile coordinate is within extent and zoom level range. + */ +ol.tilecoord.withinExtentAndZ = function(tileCoord, tileGrid) { + var z = tileCoord[0]; + var x = tileCoord[1]; + var y = tileCoord[2]; + + if (tileGrid.getMinZoom() > z || z > tileGrid.getMaxZoom()) { + return false; + } + var extent = tileGrid.getExtent(); + var tileRange; + if (!extent) { + tileRange = tileGrid.getFullTileRange(z); + } else { + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + } + if (!tileRange) { + return true; + } else { + return tileRange.containsXY(x, y); + } +}; + +goog.provide('ol.tilegrid.TileGrid'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.TileRange'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.math'); +goog.require('ol.size'); +goog.require('ol.tilecoord'); + + +/** + * @classdesc + * Base class for setting the grid pattern for sources accessing tiled-image + * servers. + * + * @constructor + * @param {olx.tilegrid.TileGridOptions} options Tile grid options. + * @struct + * @api stable + */ +ol.tilegrid.TileGrid = function(options) { + + /** + * @protected + * @type {number} + */ + this.minZoom = options.minZoom !== undefined ? options.minZoom : 0; + + /** + * @private + * @type {!Array.<number>} + */ + this.resolutions_ = options.resolutions; + ol.asserts.assert(ol.array.isSorted(this.resolutions_, function(a, b) { + return b - a; + }, true), 17); // `resolutions` must be sorted in descending order + + /** + * @protected + * @type {number} + */ + this.maxZoom = this.resolutions_.length - 1; + + /** + * @private + * @type {ol.Coordinate} + */ + this.origin_ = options.origin !== undefined ? options.origin : null; + + /** + * @private + * @type {Array.<ol.Coordinate>} + */ + this.origins_ = null; + if (options.origins !== undefined) { + this.origins_ = options.origins; + ol.asserts.assert(this.origins_.length == this.resolutions_.length, + 20); // Number of `origins` and `resolutions` must be equal + } + + var extent = options.extent; + + if (extent !== undefined && + !this.origin_ && !this.origins_) { + this.origin_ = ol.extent.getTopLeft(extent); + } + + ol.asserts.assert( + (!this.origin_ && this.origins_) || (this.origin_ && !this.origins_), + 18); // Either `origin` or `origins` must be configured, never both + + /** + * @private + * @type {Array.<number|ol.Size>} + */ + this.tileSizes_ = null; + if (options.tileSizes !== undefined) { + this.tileSizes_ = options.tileSizes; + ol.asserts.assert(this.tileSizes_.length == this.resolutions_.length, + 19); // Number of `tileSizes` and `resolutions` must be equal + } + + /** + * @private + * @type {number|ol.Size} + */ + this.tileSize_ = options.tileSize !== undefined ? + options.tileSize : + !this.tileSizes_ ? ol.DEFAULT_TILE_SIZE : null; + ol.asserts.assert( + (!this.tileSize_ && this.tileSizes_) || + (this.tileSize_ && !this.tileSizes_), + 22); // Either `tileSize` or `tileSizes` must be configured, never both + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = extent !== undefined ? extent : null; + + + /** + * @private + * @type {Array.<ol.TileRange>} + */ + this.fullTileRanges_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; + + if (options.sizes !== undefined) { + ol.DEBUG && console.assert(options.sizes.length == this.resolutions_.length, + 'number of sizes and resolutions must be equal'); + this.fullTileRanges_ = options.sizes.map(function(size, z) { + ol.DEBUG && console.assert(size[0] !== 0, 'width must not be 0'); + ol.DEBUG && console.assert(size[1] !== 0, 'height must not be 0'); + var tileRange = new ol.TileRange( + Math.min(0, size[0]), Math.max(size[0] - 1, -1), + Math.min(0, size[1]), Math.max(size[1] - 1, -1)); + return tileRange; + }, this); + } else if (extent) { + this.calculateTileRanges_(extent); + } + +}; + + +/** + * @private + * @type {ol.TileCoord} + */ +ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0]; + + +/** + * Call a function with each tile coordinate for a given extent and zoom level. + * + * @param {ol.Extent} extent Extent. + * @param {number} zoom Zoom level. + * @param {function(ol.TileCoord)} callback Function called with each tile coordinate. + * @api + */ +ol.tilegrid.TileGrid.prototype.forEachTileCoord = function(extent, zoom, callback) { + var tileRange = this.getTileRangeForExtentAndZ(extent, zoom); + for (var i = tileRange.minX, ii = tileRange.maxX; i <= ii; ++i) { + for (var j = tileRange.minY, jj = tileRange.maxY; j <= jj; ++j) { + callback([zoom, i, j]); + } + } +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {function(this: T, number, ol.TileRange): boolean} callback Callback. + * @param {T=} opt_this The object to use as `this` in `callback`. + * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. + * @param {ol.Extent=} opt_extent Temporary ol.Extent object. + * @return {boolean} Callback succeeded. + * @template T + */ +ol.tilegrid.TileGrid.prototype.forEachTileCoordParentTileRange = function(tileCoord, callback, opt_this, opt_tileRange, opt_extent) { + var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent); + var z = tileCoord[0] - 1; + while (z >= this.minZoom) { + if (callback.call(opt_this, z, + this.getTileRangeForExtentAndZ(tileCoordExtent, z, opt_tileRange))) { + return true; + } + --z; + } + return false; +}; + + +/** + * Get the extent for this tile grid, if it was configured. + * @return {ol.Extent} Extent. + */ +ol.tilegrid.TileGrid.prototype.getExtent = function() { + return this.extent_; +}; + + +/** + * Get the maximum zoom level for the grid. + * @return {number} Max zoom. + * @api + */ +ol.tilegrid.TileGrid.prototype.getMaxZoom = function() { + return this.maxZoom; +}; + + +/** + * Get the minimum zoom level for the grid. + * @return {number} Min zoom. + * @api + */ +ol.tilegrid.TileGrid.prototype.getMinZoom = function() { + return this.minZoom; +}; + + +/** + * Get the origin for the grid at the given zoom level. + * @param {number} z Z. + * @return {ol.Coordinate} Origin. + * @api stable + */ +ol.tilegrid.TileGrid.prototype.getOrigin = function(z) { + if (this.origin_) { + return this.origin_; + } else { + ol.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom, + 'given z is not in allowed range (%s <= %s <= %s)', + this.minZoom, z, this.maxZoom); + return this.origins_[z]; + } +}; + + +/** + * Get the resolution for the given zoom level. + * @param {number} z Z. + * @return {number} Resolution. + * @api stable + */ +ol.tilegrid.TileGrid.prototype.getResolution = function(z) { + ol.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom, + 'given z is not in allowed range (%s <= %s <= %s)', + this.minZoom, z, this.maxZoom); + return this.resolutions_[z]; +}; + + +/** + * Get the list of resolutions for the tile grid. + * @return {Array.<number>} Resolutions. + * @api stable + */ +ol.tilegrid.TileGrid.prototype.getResolutions = function() { + return this.resolutions_; +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. + * @param {ol.Extent=} opt_extent Temporary ol.Extent object. + * @return {ol.TileRange} Tile range. + */ +ol.tilegrid.TileGrid.prototype.getTileCoordChildTileRange = function(tileCoord, opt_tileRange, opt_extent) { + if (tileCoord[0] < this.maxZoom) { + var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent); + return this.getTileRangeForExtentAndZ( + tileCoordExtent, tileCoord[0] + 1, opt_tileRange); + } else { + return null; + } +}; + + +/** + * @param {number} z Z. + * @param {ol.TileRange} tileRange Tile range. + * @param {ol.Extent=} opt_extent Temporary ol.Extent object. + * @return {ol.Extent} Extent. + */ +ol.tilegrid.TileGrid.prototype.getTileRangeExtent = function(z, tileRange, opt_extent) { + var origin = this.getOrigin(z); + var resolution = this.getResolution(z); + var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); + var minX = origin[0] + tileRange.minX * tileSize[0] * resolution; + var maxX = origin[0] + (tileRange.maxX + 1) * tileSize[0] * resolution; + var minY = origin[1] + tileRange.minY * tileSize[1] * resolution; + var maxY = origin[1] + (tileRange.maxY + 1) * tileSize[1] * resolution; + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {ol.TileRange=} opt_tileRange Temporary tile range object. + * @return {ol.TileRange} Tile range. + */ +ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndResolution = function(extent, resolution, opt_tileRange) { + var tileCoord = ol.tilegrid.TileGrid.tmpTileCoord_; + this.getTileCoordForXYAndResolution_( + extent[0], extent[1], resolution, false, tileCoord); + var minX = tileCoord[1]; + var minY = tileCoord[2]; + this.getTileCoordForXYAndResolution_( + extent[2], extent[3], resolution, true, tileCoord); + return ol.TileRange.createOrUpdate( + minX, tileCoord[1], minY, tileCoord[2], opt_tileRange); +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} z Z. + * @param {ol.TileRange=} opt_tileRange Temporary tile range object. + * @return {ol.TileRange} Tile range. + */ +ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ = function(extent, z, opt_tileRange) { + var resolution = this.getResolution(z); + return this.getTileRangeForExtentAndResolution( + extent, resolution, opt_tileRange); +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {ol.Coordinate} Tile center. + */ +ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) { + var origin = this.getOrigin(tileCoord[0]); + var resolution = this.getResolution(tileCoord[0]); + var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_); + return [ + origin[0] + (tileCoord[1] + 0.5) * tileSize[0] * resolution, + origin[1] + (tileCoord[2] + 0.5) * tileSize[1] * resolution + ]; +}; + + +/** + * Get the extent of a tile coordinate. + * + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Extent=} opt_extent Temporary extent object. + * @return {ol.Extent} Extent. + * @api + */ +ol.tilegrid.TileGrid.prototype.getTileCoordExtent = function(tileCoord, opt_extent) { + var origin = this.getOrigin(tileCoord[0]); + var resolution = this.getResolution(tileCoord[0]); + var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_); + var minX = origin[0] + tileCoord[1] * tileSize[0] * resolution; + var minY = origin[1] + tileCoord[2] * tileSize[1] * resolution; + var maxX = minX + tileSize[0] * resolution; + var maxY = minY + tileSize[1] * resolution; + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); +}; + + +/** + * Get the tile coordinate for the given map coordinate and resolution. This + * method considers that coordinates that intersect tile boundaries should be + * assigned the higher tile coordinate. + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object. + * @return {ol.TileCoord} Tile coordinate. + * @api + */ +ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function(coordinate, resolution, opt_tileCoord) { + return this.getTileCoordForXYAndResolution_( + coordinate[0], coordinate[1], resolution, false, opt_tileCoord); +}; + + +/** + * @param {number} x X. + * @param {number} y Y. + * @param {number} resolution Resolution. + * @param {boolean} reverseIntersectionPolicy Instead of letting edge + * intersections go to the higher tile coordinate, let edge intersections + * go to the lower tile coordinate. + * @param {ol.TileCoord=} opt_tileCoord Temporary ol.TileCoord object. + * @return {ol.TileCoord} Tile coordinate. + * @private + */ +ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function( + x, y, resolution, reverseIntersectionPolicy, opt_tileCoord) { + var z = this.getZForResolution(resolution); + var scale = resolution / this.getResolution(z); + var origin = this.getOrigin(z); + var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); + + var adjustX = reverseIntersectionPolicy ? 0.5 : 0; + var adjustY = reverseIntersectionPolicy ? 0 : 0.5; + var xFromOrigin = Math.floor((x - origin[0]) / resolution + adjustX); + var yFromOrigin = Math.floor((y - origin[1]) / resolution + adjustY); + var tileCoordX = scale * xFromOrigin / tileSize[0]; + var tileCoordY = scale * yFromOrigin / tileSize[1]; + + if (reverseIntersectionPolicy) { + tileCoordX = Math.ceil(tileCoordX) - 1; + tileCoordY = Math.ceil(tileCoordY) - 1; + } else { + tileCoordX = Math.floor(tileCoordX); + tileCoordY = Math.floor(tileCoordY); + } + + return ol.tilecoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord); +}; + + +/** + * Get a tile coordinate given a map coordinate and zoom level. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} z Zoom level. + * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object. + * @return {ol.TileCoord} Tile coordinate. + * @api + */ +ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ = function(coordinate, z, opt_tileCoord) { + var resolution = this.getResolution(z); + return this.getTileCoordForXYAndResolution_( + coordinate[0], coordinate[1], resolution, false, opt_tileCoord); +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {number} Tile resolution. + */ +ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) { + ol.DEBUG && console.assert( + this.minZoom <= tileCoord[0] && tileCoord[0] <= this.maxZoom, + 'z of given tilecoord is not in allowed range (%s <= %s <= %s', + this.minZoom, tileCoord[0], this.maxZoom); + return this.resolutions_[tileCoord[0]]; +}; + + +/** + * Get the tile size for a zoom level. The type of the return value matches the + * `tileSize` or `tileSizes` that the tile grid was configured with. To always + * get an `ol.Size`, run the result through `ol.size.toSize()`. + * @param {number} z Z. + * @return {number|ol.Size} Tile size. + * @api stable + */ +ol.tilegrid.TileGrid.prototype.getTileSize = function(z) { + if (this.tileSize_) { + return this.tileSize_; + } else { + ol.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom, + 'z is not in allowed range (%s <= %s <= %s', + this.minZoom, z, this.maxZoom); + return this.tileSizes_[z]; + } +}; + + +/** + * @param {number} z Zoom level. + * @return {ol.TileRange} Extent tile range for the specified zoom level. + */ +ol.tilegrid.TileGrid.prototype.getFullTileRange = function(z) { + if (!this.fullTileRanges_) { + return null; + } else { + ol.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom, + 'z is not in allowed range (%s <= %s <= %s', + this.minZoom, z, this.maxZoom); + return this.fullTileRanges_[z]; + } +}; + + +/** + * @param {number} resolution Resolution. + * @param {number=} opt_direction If 0, the nearest resolution will be used. + * If 1, the nearest lower resolution will be used. If -1, the nearest + * higher resolution will be used. Default is 0. + * @return {number} Z. + * @api + */ +ol.tilegrid.TileGrid.prototype.getZForResolution = function( + resolution, opt_direction) { + var z = ol.array.linearFindNearest(this.resolutions_, resolution, + opt_direction || 0); + return ol.math.clamp(z, this.minZoom, this.maxZoom); +}; + + +/** + * @param {!ol.Extent} extent Extent for this tile grid. + * @private + */ +ol.tilegrid.TileGrid.prototype.calculateTileRanges_ = function(extent) { + var length = this.resolutions_.length; + var fullTileRanges = new Array(length); + for (var z = this.minZoom; z < length; ++z) { + fullTileRanges[z] = this.getTileRangeForExtentAndZ(extent, z); + } + this.fullTileRanges_ = fullTileRanges; +}; + +goog.provide('ol.tilegrid'); + +goog.require('ol'); +goog.require('ol.size'); +goog.require('ol.extent'); +goog.require('ol.extent.Corner'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.proj.Units'); +goog.require('ol.tilegrid.TileGrid'); + + +/** + * @param {ol.proj.Projection} projection Projection. + * @return {!ol.tilegrid.TileGrid} Default tile grid for the passed projection. + */ +ol.tilegrid.getForProjection = function(projection) { + var tileGrid = projection.getDefaultTileGrid(); + if (!tileGrid) { + tileGrid = ol.tilegrid.createForProjection(projection); + projection.setDefaultTileGrid(tileGrid); + } + return tileGrid; +}; + + +/** + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.TileCoord} Tile coordinate. + */ +ol.tilegrid.wrapX = function(tileGrid, tileCoord, projection) { + var z = tileCoord[0]; + var center = tileGrid.getTileCoordCenter(tileCoord); + var projectionExtent = ol.tilegrid.extentFromProjection(projection); + if (!ol.extent.containsCoordinate(projectionExtent, center)) { + var worldWidth = ol.extent.getWidth(projectionExtent); + var worldsAway = Math.ceil((projectionExtent[0] - center[0]) / worldWidth); + center[0] += worldWidth * worldsAway; + return tileGrid.getTileCoordForCoordAndZ(center, z); + } else { + return tileCoord; + } +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number=} opt_maxZoom Maximum zoom level (default is + * ol.DEFAULT_MAX_ZOOM). + * @param {number|ol.Size=} opt_tileSize Tile size (default uses + * ol.DEFAULT_TILE_SIZE). + * @param {ol.extent.Corner=} opt_corner Extent corner (default is + * ol.extent.Corner.TOP_LEFT). + * @return {!ol.tilegrid.TileGrid} TileGrid instance. + */ +ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_corner) { + var corner = opt_corner !== undefined ? + opt_corner : ol.extent.Corner.TOP_LEFT; + + var resolutions = ol.tilegrid.resolutionsFromExtent( + extent, opt_maxZoom, opt_tileSize); + + return new ol.tilegrid.TileGrid({ + extent: extent, + origin: ol.extent.getCorner(extent, corner), + resolutions: resolutions, + tileSize: opt_tileSize + }); +}; + + +/** + * Creates a tile grid with a standard XYZ tiling scheme. + * @param {olx.tilegrid.XYZOptions=} opt_options Tile grid options. + * @return {ol.tilegrid.TileGrid} Tile grid instance. + * @api + */ +ol.tilegrid.createXYZ = function(opt_options) { + var options = /** @type {olx.tilegrid.TileGridOptions} */ ({}); + ol.obj.assign(options, opt_options !== undefined ? + opt_options : /** @type {olx.tilegrid.XYZOptions} */ ({})); + if (options.extent === undefined) { + options.extent = ol.proj.get('EPSG:3857').getExtent(); + } + options.resolutions = ol.tilegrid.resolutionsFromExtent( + options.extent, options.maxZoom, options.tileSize); + delete options.maxZoom; + + return new ol.tilegrid.TileGrid(options); +}; + + +/** + * Create a resolutions array from an extent. A zoom factor of 2 is assumed. + * @param {ol.Extent} extent Extent. + * @param {number=} opt_maxZoom Maximum zoom level (default is + * ol.DEFAULT_MAX_ZOOM). + * @param {number|ol.Size=} opt_tileSize Tile size (default uses + * ol.DEFAULT_TILE_SIZE). + * @return {!Array.<number>} Resolutions array. + */ +ol.tilegrid.resolutionsFromExtent = function(extent, opt_maxZoom, opt_tileSize) { + var maxZoom = opt_maxZoom !== undefined ? + opt_maxZoom : ol.DEFAULT_MAX_ZOOM; + + var height = ol.extent.getHeight(extent); + var width = ol.extent.getWidth(extent); + + var tileSize = ol.size.toSize(opt_tileSize !== undefined ? + opt_tileSize : ol.DEFAULT_TILE_SIZE); + var maxResolution = Math.max( + width / tileSize[0], height / tileSize[1]); + + var length = maxZoom + 1; + var resolutions = new Array(length); + for (var z = 0; z < length; ++z) { + resolutions[z] = maxResolution / Math.pow(2, z); + } + return resolutions; +}; + + +/** + * @param {ol.ProjectionLike} projection Projection. + * @param {number=} opt_maxZoom Maximum zoom level (default is + * ol.DEFAULT_MAX_ZOOM). + * @param {ol.Size=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). + * @param {ol.extent.Corner=} opt_corner Extent corner (default is + * ol.extent.Corner.BOTTOM_LEFT). + * @return {!ol.tilegrid.TileGrid} TileGrid instance. + */ +ol.tilegrid.createForProjection = function(projection, opt_maxZoom, opt_tileSize, opt_corner) { + var extent = ol.tilegrid.extentFromProjection(projection); + return ol.tilegrid.createForExtent( + extent, opt_maxZoom, opt_tileSize, opt_corner); +}; + + +/** + * Generate a tile grid extent from a projection. If the projection has an + * extent, it is used. If not, a global extent is assumed. + * @param {ol.ProjectionLike} projection Projection. + * @return {ol.Extent} Extent. + */ +ol.tilegrid.extentFromProjection = function(projection) { + projection = ol.proj.get(projection); + var extent = projection.getExtent(); + if (!extent) { + var half = 180 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] / + projection.getMetersPerUnit(); + extent = ol.extent.createOrUpdate(-half, -half, half, half); + } + return extent; +}; + +goog.provide('ol.Attribution'); + +goog.require('ol.TileRange'); +goog.require('ol.math'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * An attribution for a layer source. + * + * Example: + * + * source: new ol.source.OSM({ + * attributions: [ + * new ol.Attribution({ + * html: 'All maps © ' + + * '<a href="https://www.opencyclemap.org/">OpenCycleMap</a>' + * }), + * ol.source.OSM.ATTRIBUTION + * ], + * .. + * + * @constructor + * @param {olx.AttributionOptions} options Attribution options. + * @struct + * @api stable + */ +ol.Attribution = function(options) { + + /** + * @private + * @type {string} + */ + this.html_ = options.html; + + /** + * @private + * @type {Object.<string, Array.<ol.TileRange>>} + */ + this.tileRanges_ = options.tileRanges ? options.tileRanges : null; + +}; + + +/** + * Get the attribution markup. + * @return {string} The attribution HTML. + * @api stable + */ +ol.Attribution.prototype.getHTML = function() { + return this.html_; +}; + + +/** + * @param {Object.<string, ol.TileRange>} tileRanges Tile ranges. + * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {!ol.proj.Projection} projection Projection. + * @return {boolean} Intersects any tile range. + */ +ol.Attribution.prototype.intersectsAnyTileRange = function(tileRanges, tileGrid, projection) { + if (!this.tileRanges_) { + return true; + } + var i, ii, tileRange, zKey; + for (zKey in tileRanges) { + if (!(zKey in this.tileRanges_)) { + continue; + } + tileRange = tileRanges[zKey]; + var testTileRange; + for (i = 0, ii = this.tileRanges_[zKey].length; i < ii; ++i) { + testTileRange = this.tileRanges_[zKey][i]; + if (testTileRange.intersects(tileRange)) { + return true; + } + var extentTileRange = tileGrid.getTileRangeForExtentAndZ( + ol.tilegrid.extentFromProjection(projection), parseInt(zKey, 10)); + var width = extentTileRange.getWidth(); + if (tileRange.minX < extentTileRange.minX || + tileRange.maxX > extentTileRange.maxX) { + if (testTileRange.intersects(new ol.TileRange( + ol.math.modulo(tileRange.minX, width), + ol.math.modulo(tileRange.maxX, width), + tileRange.minY, tileRange.maxY))) { + return true; + } + if (tileRange.getWidth() > width && + testTileRange.intersects(extentTileRange)) { + return true; + } + } + } + } + return false; +}; + +/** + * An implementation of Google Maps' MVCArray. + * @see https://developers.google.com/maps/documentation/javascript/reference + */ + +goog.provide('ol.Collection'); + +goog.require('ol'); +goog.require('ol.events.Event'); +goog.require('ol.Object'); + + +/** + * @classdesc + * An expanded version of standard JS Array, adding convenience methods for + * manipulation. Add and remove changes to the Collection trigger a Collection + * event. Note that this does not cover changes to the objects _within_ the + * Collection; they trigger events on the appropriate object, not on the + * Collection as a whole. + * + * @constructor + * @extends {ol.Object} + * @fires ol.Collection.Event + * @param {!Array.<T>=} opt_array Array. + * @template T + * @api stable + */ +ol.Collection = function(opt_array) { + + ol.Object.call(this); + + /** + * @private + * @type {!Array.<T>} + */ + this.array_ = opt_array ? opt_array : []; + + this.updateLength_(); + +}; +ol.inherits(ol.Collection, ol.Object); + + +/** + * Remove all elements from the collection. + * @api stable + */ +ol.Collection.prototype.clear = function() { + while (this.getLength() > 0) { + this.pop(); + } +}; + + +/** + * Add elements to the collection. This pushes each item in the provided array + * to the end of the collection. + * @param {!Array.<T>} arr Array. + * @return {ol.Collection.<T>} This collection. + * @api stable + */ +ol.Collection.prototype.extend = function(arr) { + var i, ii; + for (i = 0, ii = arr.length; i < ii; ++i) { + this.push(arr[i]); + } + return this; +}; + + +/** + * Iterate over each element, calling the provided callback. + * @param {function(this: S, T, number, Array.<T>): *} f The function to call + * for every element. This function takes 3 arguments (the element, the + * index and the array). The return value is ignored. + * @param {S=} opt_this The object to use as `this` in `f`. + * @template S + * @api stable + */ +ol.Collection.prototype.forEach = function(f, opt_this) { + this.array_.forEach(f, opt_this); +}; + + +/** + * Get a reference to the underlying Array object. Warning: if the array + * is mutated, no events will be dispatched by the collection, and the + * collection's "length" property won't be in sync with the actual length + * of the array. + * @return {!Array.<T>} Array. + * @api stable + */ +ol.Collection.prototype.getArray = function() { + return this.array_; +}; + + +/** + * Get the element at the provided index. + * @param {number} index Index. + * @return {T} Element. + * @api stable + */ +ol.Collection.prototype.item = function(index) { + return this.array_[index]; +}; + + +/** + * Get the length of this collection. + * @return {number} The length of the array. + * @observable + * @api stable + */ +ol.Collection.prototype.getLength = function() { + return /** @type {number} */ (this.get(ol.Collection.Property.LENGTH)); +}; + + +/** + * Insert an element at the provided index. + * @param {number} index Index. + * @param {T} elem Element. + * @api stable + */ +ol.Collection.prototype.insertAt = function(index, elem) { + this.array_.splice(index, 0, elem); + this.updateLength_(); + this.dispatchEvent( + new ol.Collection.Event(ol.Collection.EventType.ADD, elem)); +}; + + +/** + * Remove the last element of the collection and return it. + * Return `undefined` if the collection is empty. + * @return {T|undefined} Element. + * @api stable + */ +ol.Collection.prototype.pop = function() { + return this.removeAt(this.getLength() - 1); +}; + + +/** + * Insert the provided element at the end of the collection. + * @param {T} elem Element. + * @return {number} New length of the collection. + * @api stable + */ +ol.Collection.prototype.push = function(elem) { + var n = this.getLength(); + this.insertAt(n, elem); + return this.getLength(); +}; + + +/** + * Remove the first occurrence of an element from the collection. + * @param {T} elem Element. + * @return {T|undefined} The removed element or undefined if none found. + * @api stable + */ +ol.Collection.prototype.remove = function(elem) { + var arr = this.array_; + var i, ii; + for (i = 0, ii = arr.length; i < ii; ++i) { + if (arr[i] === elem) { + return this.removeAt(i); + } + } + return undefined; +}; + + +/** + * Remove the element at the provided index and return it. + * Return `undefined` if the collection does not contain this index. + * @param {number} index Index. + * @return {T|undefined} Value. + * @api stable + */ +ol.Collection.prototype.removeAt = function(index) { + var prev = this.array_[index]; + this.array_.splice(index, 1); + this.updateLength_(); + this.dispatchEvent( + new ol.Collection.Event(ol.Collection.EventType.REMOVE, prev)); + return prev; +}; + + +/** + * Set the element at the provided index. + * @param {number} index Index. + * @param {T} elem Element. + * @api stable + */ +ol.Collection.prototype.setAt = function(index, elem) { + var n = this.getLength(); + if (index < n) { + var prev = this.array_[index]; + this.array_[index] = elem; + this.dispatchEvent( + new ol.Collection.Event(ol.Collection.EventType.REMOVE, prev)); + this.dispatchEvent( + new ol.Collection.Event(ol.Collection.EventType.ADD, elem)); + } else { + var j; + for (j = n; j < index; ++j) { + this.insertAt(j, undefined); + } + this.insertAt(index, elem); + } +}; + + +/** + * @private + */ +ol.Collection.prototype.updateLength_ = function() { + this.set(ol.Collection.Property.LENGTH, this.array_.length); +}; + + +/** + * @enum {string} + */ +ol.Collection.Property = { + LENGTH: 'length' +}; + + +/** + * @enum {string} + */ +ol.Collection.EventType = { + /** + * Triggered when an item is added to the collection. + * @event ol.Collection.Event#add + * @api stable + */ + ADD: 'add', + /** + * Triggered when an item is removed from the collection. + * @event ol.Collection.Event#remove + * @api stable + */ + REMOVE: 'remove' +}; + + +/** + * @classdesc + * Events emitted by {@link ol.Collection} instances are instances of this + * type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.Collection.Event} + * @param {ol.Collection.EventType} type Type. + * @param {*=} opt_element Element. + */ +ol.Collection.Event = function(type, opt_element) { + + ol.events.Event.call(this, type); + + /** + * The element that is added to or removed from the collection. + * @type {*} + * @api stable + */ + this.element = opt_element; + +}; +ol.inherits(ol.Collection.Event, ol.events.Event); + +goog.provide('ol.color'); + +goog.require('ol.asserts'); +goog.require('ol.math'); + + +/** + * This RegExp matches # followed by 3 or 6 hex digits. + * @const + * @type {RegExp} + * @private + */ +ol.color.HEX_COLOR_RE_ = /^#(?:[0-9a-f]{3}){1,2}$/i; + + +/** + * Regular expression for matching potential named color style strings. + * @const + * @type {RegExp} + * @private + */ +ol.color.NAMED_COLOR_RE_ = /^([a-z]*)$/i; + + +/** + * Return the color as an array. This function maintains a cache of calculated + * arrays which means the result should not be modified. + * @param {ol.Color|string} color Color. + * @return {ol.Color} Color. + * @api + */ +ol.color.asArray = function(color) { + if (Array.isArray(color)) { + return color; + } else { + return ol.color.fromString(/** @type {string} */ (color)); + } +}; + + +/** + * Return the color as an rgba string. + * @param {ol.Color|string} color Color. + * @return {string} Rgba string. + * @api + */ +ol.color.asString = function(color) { + if (typeof color === 'string') { + return color; + } else { + return ol.color.toString(color); + } +}; + +/** + * Return named color as an rgba string. + * @param {string} color Named color. + * @return {string} Rgb string. + */ +ol.color.fromNamed = function(color) { + var el = document.createElement('div'); + el.style.color = color; + document.body.appendChild(el); + var rgb = getComputedStyle(el).color; + document.body.removeChild(el); + return rgb; +}; + + +/** + * @param {string} s String. + * @return {ol.Color} Color. + */ +ol.color.fromString = ( + function() { + + // We maintain a small cache of parsed strings. To provide cheap LRU-like + // semantics, whenever the cache grows too large we simply delete an + // arbitrary 25% of the entries. + + /** + * @const + * @type {number} + */ + var MAX_CACHE_SIZE = 1024; + + /** + * @type {Object.<string, ol.Color>} + */ + var cache = {}; + + /** + * @type {number} + */ + var cacheSize = 0; + + return ( + /** + * @param {string} s String. + * @return {ol.Color} Color. + */ + function(s) { + var color; + if (cache.hasOwnProperty(s)) { + color = cache[s]; + } else { + if (cacheSize >= MAX_CACHE_SIZE) { + var i = 0; + var key; + for (key in cache) { + if ((i++ & 3) === 0) { + delete cache[key]; + --cacheSize; + } + } + } + color = ol.color.fromStringInternal_(s); + cache[s] = color; + ++cacheSize; + } + return color; + }); + + })(); + + +/** + * @param {string} s String. + * @private + * @return {ol.Color} Color. + */ +ol.color.fromStringInternal_ = function(s) { + var r, g, b, a, color, parts; + + if (ol.color.NAMED_COLOR_RE_.exec(s)) { + s = ol.color.fromNamed(s); + } + + if (ol.color.HEX_COLOR_RE_.exec(s)) { // hex + var n = s.length - 1; // number of hex digits + ol.asserts.assert(n == 3 || n == 6, 54); // Hex color should have 3 or 6 digits + var d = n == 3 ? 1 : 2; // number of digits per channel + r = parseInt(s.substr(1 + 0 * d, d), 16); + g = parseInt(s.substr(1 + 1 * d, d), 16); + b = parseInt(s.substr(1 + 2 * d, d), 16); + if (d == 1) { + r = (r << 4) + r; + g = (g << 4) + g; + b = (b << 4) + b; + } + a = 1; + color = [r, g, b, a]; + } else if (s.indexOf('rgba(') == 0) { // rgba() + parts = s.slice(5, -1).split(',').map(Number); + color = ol.color.normalize(parts); + } else if (s.indexOf('rgb(') == 0) { // rgb() + parts = s.slice(4, -1).split(',').map(Number); + parts.push(1); + color = ol.color.normalize(parts); + } else { + ol.asserts.assert(false, 14); // Invalid color + } + return /** @type {ol.Color} */ (color); +}; + + +/** + * @param {ol.Color} color Color. + * @param {ol.Color=} opt_color Color. + * @return {ol.Color} Clamped color. + */ +ol.color.normalize = function(color, opt_color) { + var result = opt_color || []; + result[0] = ol.math.clamp((color[0] + 0.5) | 0, 0, 255); + result[1] = ol.math.clamp((color[1] + 0.5) | 0, 0, 255); + result[2] = ol.math.clamp((color[2] + 0.5) | 0, 0, 255); + result[3] = ol.math.clamp(color[3], 0, 1); + return result; +}; + + +/** + * @param {ol.Color} color Color. + * @return {string} String. + */ +ol.color.toString = function(color) { + var r = color[0]; + if (r != (r | 0)) { + r = (r + 0.5) | 0; + } + var g = color[1]; + if (g != (g | 0)) { + g = (g + 0.5) | 0; + } + var b = color[2]; + if (b != (b | 0)) { + b = (b + 0.5) | 0; + } + var a = color[3] === undefined ? 1 : color[3]; + return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; +}; + +goog.provide('ol.colorlike'); + +goog.require('ol.color'); + + +/** + * @param {ol.Color|ol.ColorLike} color Color. + * @return {ol.ColorLike} The color as an ol.ColorLike + * @api + */ +ol.colorlike.asColorLike = function(color) { + if (ol.colorlike.isColorLike(color)) { + return /** @type {string|CanvasPattern|CanvasGradient} */ (color); + } else { + return ol.color.asString(/** @type {ol.Color} */ (color)); + } +}; + + +/** + * @param {?} color The value that is potentially an ol.ColorLike + * @return {boolean} Whether the color is an ol.ColorLike + */ +ol.colorlike.isColorLike = function(color) { + return ( + typeof color === 'string' || + color instanceof CanvasPattern || + color instanceof CanvasGradient + ); +}; + +goog.provide('ol.dom'); + + +/** + * Create an html canvas element and returns its 2d context. + * @param {number=} opt_width Canvas width. + * @param {number=} opt_height Canvas height. + * @return {CanvasRenderingContext2D} The context. + */ +ol.dom.createCanvasContext2D = function(opt_width, opt_height) { + var canvas = document.createElement('CANVAS'); + if (opt_width) { + canvas.width = opt_width; + } + if (opt_height) { + canvas.height = opt_height; + } + return canvas.getContext('2d'); +}; + + +/** + * Get the current computed width for the given element including margin, + * padding and border. + * Equivalent to jQuery's `$(el).outerWidth(true)`. + * @param {!Element} element Element. + * @return {number} The width. + */ +ol.dom.outerWidth = function(element) { + var width = element.offsetWidth; + var style = element.currentStyle || getComputedStyle(element); + width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10); + + return width; +}; + + +/** + * Get the current computed height for the given element including margin, + * padding and border. + * Equivalent to jQuery's `$(el).outerHeight(true)`. + * @param {!Element} element Element. + * @return {number} The height. + */ +ol.dom.outerHeight = function(element) { + var height = element.offsetHeight; + var style = element.currentStyle || getComputedStyle(element); + height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10); + + return height; +}; + +/** + * @param {Node} newNode Node to replace old node + * @param {Node} oldNode The node to be replaced + */ +ol.dom.replaceNode = function(newNode, oldNode) { + var parent = oldNode.parentNode; + if (parent) { + parent.replaceChild(newNode, oldNode); + } +}; + +/** + * @param {Node} node The node to remove. + * @returns {Node} The node that was removed or null. + */ +ol.dom.removeNode = function(node) { + return node && node.parentNode ? node.parentNode.removeChild(node) : null; +}; + +/** + * @param {Node} node The node to remove the children from. + */ +ol.dom.removeChildren = function(node) { + while (node.lastChild) { + node.removeChild(node.lastChild); + } +}; + +goog.provide('ol.MapEvent'); + +goog.require('ol'); +goog.require('ol.events.Event'); + + +/** + * @classdesc + * Events emitted as map events are instances of this type. + * See {@link ol.Map} for which events trigger a map event. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.MapEvent} + * @param {string} type Event type. + * @param {ol.Map} map Map. + * @param {?olx.FrameState=} opt_frameState Frame state. + */ +ol.MapEvent = function(type, map, opt_frameState) { + + ol.events.Event.call(this, type); + + /** + * The map where the event occurred. + * @type {ol.Map} + * @api stable + */ + this.map = map; + + /** + * The frame state at the time of the event. + * @type {?olx.FrameState} + * @api + */ + this.frameState = opt_frameState !== undefined ? opt_frameState : null; + +}; +ol.inherits(ol.MapEvent, ol.events.Event); + + +/** + * @enum {string} + */ +ol.MapEvent.Type = { + + /** + * Triggered after a map frame is rendered. + * @event ol.MapEvent#postrender + * @api + */ + POSTRENDER: 'postrender', + + /** + * Triggered after the map is moved. + * @event ol.MapEvent#moveend + * @api stable + */ + MOVEEND: 'moveend' + +}; + +goog.provide('ol.control.Control'); + +goog.require('ol.events'); +goog.require('ol'); +goog.require('ol.MapEvent'); +goog.require('ol.Object'); +goog.require('ol.dom'); + + +/** + * @classdesc + * A control is a visible widget with a DOM element in a fixed position on the + * screen. They can involve user input (buttons), or be informational only; + * the position is determined using CSS. By default these are placed in the + * container with CSS class name `ol-overlaycontainer-stopevent`, but can use + * any outside DOM element. + * + * This is the base class for controls. You can use it for simple custom + * controls by creating the element with listeners, creating an instance: + * ```js + * var myControl = new ol.control.Control({element: myElement}); + * ``` + * and then adding this to the map. + * + * The main advantage of having this as a control rather than a simple separate + * DOM element is that preventing propagation is handled for you. Controls + * will also be `ol.Object`s in a `ol.Collection`, so you can use their + * methods. + * + * You can also extend this base for your own control class. See + * examples/custom-controls for an example of how to do this. + * + * @constructor + * @extends {ol.Object} + * @implements {oli.control.Control} + * @param {olx.control.ControlOptions} options Control options. + * @api stable + */ +ol.control.Control = function(options) { + + ol.Object.call(this); + + /** + * @protected + * @type {Element} + */ + this.element = options.element ? options.element : null; + + /** + * @private + * @type {Element} + */ + this.target_ = null; + + /** + * @private + * @type {ol.Map} + */ + this.map_ = null; + + /** + * @protected + * @type {!Array.<ol.EventsKey>} + */ + this.listenerKeys = []; + + /** + * @type {function(ol.MapEvent)} + */ + this.render = options.render ? options.render : ol.nullFunction; + + if (options.target) { + this.setTarget(options.target); + } + +}; +ol.inherits(ol.control.Control, ol.Object); + + +/** + * @inheritDoc + */ +ol.control.Control.prototype.disposeInternal = function() { + ol.dom.removeNode(this.element); + ol.Object.prototype.disposeInternal.call(this); +}; + + +/** + * Get the map associated with this control. + * @return {ol.Map} Map. + * @api stable + */ +ol.control.Control.prototype.getMap = function() { + return this.map_; +}; + + +/** + * Remove the control from its current map and attach it to the new map. + * Subclasses may set up event handlers to get notified about changes to + * the map here. + * @param {ol.Map} map Map. + * @api stable + */ +ol.control.Control.prototype.setMap = function(map) { + if (this.map_) { + ol.dom.removeNode(this.element); + } + for (var i = 0, ii = this.listenerKeys.length; i < ii; ++i) { + ol.events.unlistenByKey(this.listenerKeys[i]); + } + this.listenerKeys.length = 0; + this.map_ = map; + if (this.map_) { + var target = this.target_ ? + this.target_ : map.getOverlayContainerStopEvent(); + target.appendChild(this.element); + if (this.render !== ol.nullFunction) { + this.listenerKeys.push(ol.events.listen(map, + ol.MapEvent.Type.POSTRENDER, this.render, this)); + } + map.render(); + } +}; + + +/** + * This function is used to set a target element for the control. It has no + * effect if it is called after the control has been added to the map (i.e. + * after `setMap` is called on the control). If no `target` is set in the + * options passed to the control constructor and if `setTarget` is not called + * then the control is added to the map's overlay container. + * @param {Element|string} target Target. + * @api + */ +ol.control.Control.prototype.setTarget = function(target) { + this.target_ = typeof target === 'string' ? + document.getElementById(target) : + target; +}; + +goog.provide('ol.css'); + + +/** + * The CSS class for hidden feature. + * + * @const + * @type {string} + */ +ol.css.CLASS_HIDDEN = 'ol-hidden'; + + +/** + * The CSS class that we'll give the DOM elements to have them unselectable. + * + * @const + * @type {string} + */ +ol.css.CLASS_UNSELECTABLE = 'ol-unselectable'; + + +/** + * The CSS class for unsupported feature. + * + * @const + * @type {string} + */ +ol.css.CLASS_UNSUPPORTED = 'ol-unsupported'; + + +/** + * The CSS class for controls. + * + * @const + * @type {string} + */ +ol.css.CLASS_CONTROL = 'ol-control'; + +// FIXME handle date line wrap + +goog.provide('ol.control.Attribution'); + +goog.require('ol'); +goog.require('ol.dom'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.obj'); + + +/** + * @classdesc + * Control to show all the attributions associated with the layer sources + * in the map. This control is one of the default controls included in maps. + * By default it will show in the bottom right portion of the map, but this can + * be changed by using a css selector for `.ol-attribution`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.AttributionOptions=} opt_options Attribution options. + * @api stable + */ +ol.control.Attribution = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {Element} + */ + this.ulElement_ = document.createElement('UL'); + + /** + * @private + * @type {Element} + */ + this.logoLi_ = document.createElement('LI'); + + this.ulElement_.appendChild(this.logoLi_); + this.logoLi_.style.display = 'none'; + + /** + * @private + * @type {boolean} + */ + this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true; + + /** + * @private + * @type {boolean} + */ + this.collapsible_ = options.collapsible !== undefined ? + options.collapsible : true; + + if (!this.collapsible_) { + this.collapsed_ = false; + } + + var className = options.className !== undefined ? options.className : 'ol-attribution'; + + var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Attributions'; + + var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00BB'; + + if (typeof collapseLabel === 'string') { + /** + * @private + * @type {Node} + */ + this.collapseLabel_ = document.createElement('span'); + this.collapseLabel_.textContent = collapseLabel; + } else { + this.collapseLabel_ = collapseLabel; + } + + var label = options.label !== undefined ? options.label : 'i'; + + if (typeof label === 'string') { + /** + * @private + * @type {Node} + */ + this.label_ = document.createElement('span'); + this.label_.textContent = label; + } else { + this.label_ = label; + } + + + var activeLabel = (this.collapsible_ && !this.collapsed_) ? + this.collapseLabel_ : this.label_; + var button = document.createElement('button'); + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild(activeLabel); + + ol.events.listen(button, ol.events.EventType.CLICK, this.handleClick_, this); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL + + (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') + + (this.collapsible_ ? '' : ' ol-uncollapsible'); + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(this.ulElement_); + element.appendChild(button); + + var render = options.render ? options.render : ol.control.Attribution.render; + + ol.control.Control.call(this, { + element: element, + render: render, + target: options.target + }); + + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = true; + + /** + * @private + * @type {Object.<string, Element>} + */ + this.attributionElements_ = {}; + + /** + * @private + * @type {Object.<string, boolean>} + */ + this.attributionElementRenderedVisible_ = {}; + + /** + * @private + * @type {Object.<string, Element>} + */ + this.logoElements_ = {}; + +}; +ol.inherits(ol.control.Attribution, ol.control.Control); + + +/** + * @param {?olx.FrameState} frameState Frame state. + * @return {Array.<Object.<string, ol.Attribution>>} Attributions. + */ +ol.control.Attribution.prototype.getSourceAttributions = function(frameState) { + var i, ii, j, jj, tileRanges, source, sourceAttribution, + sourceAttributionKey, sourceAttributions, sourceKey; + var intersectsTileRange; + var layerStatesArray = frameState.layerStatesArray; + /** @type {Object.<string, ol.Attribution>} */ + var attributions = ol.obj.assign({}, frameState.attributions); + /** @type {Object.<string, ol.Attribution>} */ + var hiddenAttributions = {}; + var uniqueAttributions = {}; + var projection = /** @type {!ol.proj.Projection} */ (frameState.viewState.projection); + for (i = 0, ii = layerStatesArray.length; i < ii; i++) { + source = layerStatesArray[i].layer.getSource(); + if (!source) { + continue; + } + sourceKey = ol.getUid(source).toString(); + sourceAttributions = source.getAttributions(); + if (!sourceAttributions) { + continue; + } + for (j = 0, jj = sourceAttributions.length; j < jj; j++) { + sourceAttribution = sourceAttributions[j]; + sourceAttributionKey = ol.getUid(sourceAttribution).toString(); + if (sourceAttributionKey in attributions) { + continue; + } + tileRanges = frameState.usedTiles[sourceKey]; + if (tileRanges) { + var tileGrid = /** @type {ol.source.Tile} */ (source).getTileGridForProjection(projection); + intersectsTileRange = sourceAttribution.intersectsAnyTileRange( + tileRanges, tileGrid, projection); + } else { + intersectsTileRange = false; + } + if (intersectsTileRange) { + if (sourceAttributionKey in hiddenAttributions) { + delete hiddenAttributions[sourceAttributionKey]; + } + var html = sourceAttribution.getHTML(); + if (!(html in uniqueAttributions)) { + uniqueAttributions[html] = true; + attributions[sourceAttributionKey] = sourceAttribution; + } + } else { + hiddenAttributions[sourceAttributionKey] = sourceAttribution; + } + } + } + return [attributions, hiddenAttributions]; +}; + + +/** + * Update the attribution element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.Attribution} + * @api + */ +ol.control.Attribution.render = function(mapEvent) { + this.updateElement_(mapEvent.frameState); +}; + + +/** + * @private + * @param {?olx.FrameState} frameState Frame state. + */ +ol.control.Attribution.prototype.updateElement_ = function(frameState) { + + if (!frameState) { + if (this.renderedVisible_) { + this.element.style.display = 'none'; + this.renderedVisible_ = false; + } + return; + } + + var attributions = this.getSourceAttributions(frameState); + /** @type {Object.<string, ol.Attribution>} */ + var visibleAttributions = attributions[0]; + /** @type {Object.<string, ol.Attribution>} */ + var hiddenAttributions = attributions[1]; + + var attributionElement, attributionKey; + for (attributionKey in this.attributionElements_) { + if (attributionKey in visibleAttributions) { + if (!this.attributionElementRenderedVisible_[attributionKey]) { + this.attributionElements_[attributionKey].style.display = ''; + this.attributionElementRenderedVisible_[attributionKey] = true; + } + delete visibleAttributions[attributionKey]; + } else if (attributionKey in hiddenAttributions) { + if (this.attributionElementRenderedVisible_[attributionKey]) { + this.attributionElements_[attributionKey].style.display = 'none'; + delete this.attributionElementRenderedVisible_[attributionKey]; + } + delete hiddenAttributions[attributionKey]; + } else { + ol.dom.removeNode(this.attributionElements_[attributionKey]); + delete this.attributionElements_[attributionKey]; + delete this.attributionElementRenderedVisible_[attributionKey]; + } + } + for (attributionKey in visibleAttributions) { + attributionElement = document.createElement('LI'); + attributionElement.innerHTML = + visibleAttributions[attributionKey].getHTML(); + this.ulElement_.appendChild(attributionElement); + this.attributionElements_[attributionKey] = attributionElement; + this.attributionElementRenderedVisible_[attributionKey] = true; + } + for (attributionKey in hiddenAttributions) { + attributionElement = document.createElement('LI'); + attributionElement.innerHTML = + hiddenAttributions[attributionKey].getHTML(); + attributionElement.style.display = 'none'; + this.ulElement_.appendChild(attributionElement); + this.attributionElements_[attributionKey] = attributionElement; + } + + var renderVisible = + !ol.obj.isEmpty(this.attributionElementRenderedVisible_) || + !ol.obj.isEmpty(frameState.logos); + if (this.renderedVisible_ != renderVisible) { + this.element.style.display = renderVisible ? '' : 'none'; + this.renderedVisible_ = renderVisible; + } + if (renderVisible && + ol.obj.isEmpty(this.attributionElementRenderedVisible_)) { + this.element.classList.add('ol-logo-only'); + } else { + this.element.classList.remove('ol-logo-only'); + } + + this.insertLogos_(frameState); + +}; + + +/** + * @param {?olx.FrameState} frameState Frame state. + * @private + */ +ol.control.Attribution.prototype.insertLogos_ = function(frameState) { + + var logo; + var logos = frameState.logos; + var logoElements = this.logoElements_; + + for (logo in logoElements) { + if (!(logo in logos)) { + ol.dom.removeNode(logoElements[logo]); + delete logoElements[logo]; + } + } + + var image, logoElement, logoKey; + for (logoKey in logos) { + var logoValue = logos[logoKey]; + if (logoValue instanceof HTMLElement) { + this.logoLi_.appendChild(logoValue); + logoElements[logoKey] = logoValue; + } + if (!(logoKey in logoElements)) { + image = new Image(); + image.src = logoKey; + if (logoValue === '') { + logoElement = image; + } else { + logoElement = document.createElement('a'); + logoElement.href = logoValue; + logoElement.appendChild(image); + } + this.logoLi_.appendChild(logoElement); + logoElements[logoKey] = logoElement; + } + } + + this.logoLi_.style.display = !ol.obj.isEmpty(logos) ? '' : 'none'; + +}; + + +/** + * @param {Event} event The event to handle + * @private + */ +ol.control.Attribution.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleToggle_(); +}; + + +/** + * @private + */ +ol.control.Attribution.prototype.handleToggle_ = function() { + this.element.classList.toggle('ol-collapsed'); + if (this.collapsed_) { + ol.dom.replaceNode(this.collapseLabel_, this.label_); + } else { + ol.dom.replaceNode(this.label_, this.collapseLabel_); + } + this.collapsed_ = !this.collapsed_; +}; + + +/** + * Return `true` if the attribution is collapsible, `false` otherwise. + * @return {boolean} True if the widget is collapsible. + * @api stable + */ +ol.control.Attribution.prototype.getCollapsible = function() { + return this.collapsible_; +}; + + +/** + * Set whether the attribution should be collapsible. + * @param {boolean} collapsible True if the widget is collapsible. + * @api stable + */ +ol.control.Attribution.prototype.setCollapsible = function(collapsible) { + if (this.collapsible_ === collapsible) { + return; + } + this.collapsible_ = collapsible; + this.element.classList.toggle('ol-uncollapsible'); + if (!collapsible && this.collapsed_) { + this.handleToggle_(); + } +}; + + +/** + * Collapse or expand the attribution according to the passed parameter. Will + * not do anything if the attribution isn't collapsible or if the current + * collapsed state is already the one requested. + * @param {boolean} collapsed True if the widget is collapsed. + * @api stable + */ +ol.control.Attribution.prototype.setCollapsed = function(collapsed) { + if (!this.collapsible_ || this.collapsed_ === collapsed) { + return; + } + this.handleToggle_(); +}; + + +/** + * Return `true` when the attribution is currently collapsed or `false` + * otherwise. + * @return {boolean} True if the widget is collapsed. + * @api stable + */ +ol.control.Attribution.prototype.getCollapsed = function() { + return this.collapsed_; +}; + +goog.provide('ol.control.FullScreen'); + +goog.require('ol'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); + + +/** + * @classdesc + * Provides a button that when clicked fills up the full screen with the map. + * The full screen source element is by default the element containing the map viewport unless + * overriden by providing the `source` option. In which case, the dom + * element introduced using this parameter will be displayed in full screen. + * + * When in full screen mode, a close button is shown to exit full screen mode. + * The [Fullscreen API](http://www.w3.org/TR/fullscreen/) is used to + * toggle the map in full screen mode. + * + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.FullScreenOptions=} opt_options Options. + * @api stable + */ +ol.control.FullScreen = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {string} + */ + this.cssClassName_ = options.className !== undefined ? options.className : + 'ol-full-screen'; + + var label = options.label !== undefined ? options.label : '\u2922'; + + /** + * @private + * @type {Node} + */ + this.labelNode_ = typeof label === 'string' ? + document.createTextNode(label) : label; + + var labelActive = options.labelActive !== undefined ? options.labelActive : '\u00d7'; + + /** + * @private + * @type {Node} + */ + this.labelActiveNode_ = typeof labelActive === 'string' ? + document.createTextNode(labelActive) : labelActive; + + var tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen'; + var button = document.createElement('button'); + button.className = this.cssClassName_ + '-' + ol.control.FullScreen.isFullScreen(); + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild(this.labelNode_); + + ol.events.listen(button, ol.events.EventType.CLICK, + this.handleClick_, this); + + var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE + + ' ' + ol.css.CLASS_CONTROL + ' ' + + (!ol.control.FullScreen.isFullScreenSupported() ? ol.css.CLASS_UNSUPPORTED : ''); + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(button); + + ol.control.Control.call(this, { + element: element, + target: options.target + }); + + /** + * @private + * @type {boolean} + */ + this.keys_ = options.keys !== undefined ? options.keys : false; + + /** + * @private + * @type {Element|string|undefined} + */ + this.source_ = options.source; + +}; +ol.inherits(ol.control.FullScreen, ol.control.Control); + + +/** + * @param {Event} event The event to handle + * @private + */ +ol.control.FullScreen.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleFullScreen_(); +}; + + +/** + * @private + */ +ol.control.FullScreen.prototype.handleFullScreen_ = function() { + if (!ol.control.FullScreen.isFullScreenSupported()) { + return; + } + var map = this.getMap(); + if (!map) { + return; + } + if (ol.control.FullScreen.isFullScreen()) { + ol.control.FullScreen.exitFullScreen(); + } else { + var element; + if (this.source_) { + element = typeof this.source_ === 'string' ? + document.getElementById(this.source_) : + this.source_; + } else { + element = map.getTargetElement(); + } + if (this.keys_) { + ol.control.FullScreen.requestFullScreenWithKeys(element); + + } else { + ol.control.FullScreen.requestFullScreen(element); + } + } +}; + + +/** + * @private + */ +ol.control.FullScreen.prototype.handleFullScreenChange_ = function() { + var button = this.element.firstElementChild; + var map = this.getMap(); + if (ol.control.FullScreen.isFullScreen()) { + button.className = this.cssClassName_ + '-true'; + ol.dom.replaceNode(this.labelActiveNode_, this.labelNode_); + } else { + button.className = this.cssClassName_ + '-false'; + ol.dom.replaceNode(this.labelNode_, this.labelActiveNode_); + } + if (map) { + map.updateSize(); + } +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.control.FullScreen.prototype.setMap = function(map) { + ol.control.Control.prototype.setMap.call(this, map); + if (map) { + this.listenerKeys.push(ol.events.listen(document, + ol.control.FullScreen.getChangeType_(), + this.handleFullScreenChange_, this) + ); + } +}; + +/** + * @return {boolean} Fullscreen is supported by the current platform. + */ +ol.control.FullScreen.isFullScreenSupported = function() { + var body = document.body; + return !!( + body.webkitRequestFullscreen || + (body.mozRequestFullScreen && document.mozFullScreenEnabled) || + (body.msRequestFullscreen && document.msFullscreenEnabled) || + (body.requestFullscreen && document.fullscreenEnabled) + ); +}; + +/** + * @return {boolean} Element is currently in fullscreen. + */ +ol.control.FullScreen.isFullScreen = function() { + return !!( + document.webkitIsFullScreen || document.mozFullScreen || + document.msFullscreenElement || document.fullscreenElement + ); +}; + +/** + * Request to fullscreen an element. + * @param {Node} element Element to request fullscreen + */ +ol.control.FullScreen.requestFullScreen = function(element) { + if (element.requestFullscreen) { + element.requestFullscreen(); + } else if (element.msRequestFullscreen) { + element.msRequestFullscreen(); + } else if (element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if (element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(); + } +}; + +/** + * Request to fullscreen an element with keyboard input. + * @param {Node} element Element to request fullscreen + */ +ol.control.FullScreen.requestFullScreenWithKeys = function(element) { + if (element.mozRequestFullScreenWithKeys) { + element.mozRequestFullScreenWithKeys(); + } else if (element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else { + ol.control.FullScreen.requestFullScreen(element); + } +}; + +/** + * Exit fullscreen. + */ +ol.control.FullScreen.exitFullScreen = function() { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } +}; + +/** + * @return {string} Change type. + * @private + */ +ol.control.FullScreen.getChangeType_ = (function() { + var changeType; + return function() { + if (!changeType) { + var body = document.body; + if (body.webkitRequestFullscreen) { + changeType = 'webkitfullscreenchange'; + } else if (body.mozRequestFullScreen) { + changeType = 'mozfullscreenchange'; + } else if (body.msRequestFullscreen) { + changeType = 'MSFullscreenChange'; + } else if (body.requestFullscreen) { + changeType = 'fullscreenchange'; + } + } + return changeType; + }; +})(); + +goog.provide('ol.control.Rotate'); + +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.easing'); + + +/** + * @classdesc + * A button control to reset rotation to 0. + * To style this control use css selector `.ol-rotate`. A `.ol-hidden` css + * selector is added to the button when the rotation is 0. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.RotateOptions=} opt_options Rotate options. + * @api stable + */ +ol.control.Rotate = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var className = options.className !== undefined ? options.className : 'ol-rotate'; + + var label = options.label !== undefined ? options.label : '\u21E7'; + + /** + * @type {Element} + * @private + */ + this.label_ = null; + + if (typeof label === 'string') { + this.label_ = document.createElement('span'); + this.label_.className = 'ol-compass'; + this.label_.textContent = label; + } else { + this.label_ = label; + this.label_.classList.add('ol-compass'); + } + + var tipLabel = options.tipLabel ? options.tipLabel : 'Reset rotation'; + + var button = document.createElement('button'); + button.className = className + '-reset'; + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild(this.label_); + + ol.events.listen(button, ol.events.EventType.CLICK, + ol.control.Rotate.prototype.handleClick_, this); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(button); + + var render = options.render ? options.render : ol.control.Rotate.render; + + this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined; + + ol.control.Control.call(this, { + element: element, + render: render, + target: options.target + }); + + /** + * @type {number} + * @private + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; + + /** + * @type {boolean} + * @private + */ + this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true; + + /** + * @private + * @type {number|undefined} + */ + this.rotation_ = undefined; + + if (this.autoHide_) { + this.element.classList.add(ol.css.CLASS_HIDDEN); + } + +}; +ol.inherits(ol.control.Rotate, ol.control.Control); + + +/** + * @param {Event} event The event to handle + * @private + */ +ol.control.Rotate.prototype.handleClick_ = function(event) { + event.preventDefault(); + if (this.callResetNorth_ !== undefined) { + this.callResetNorth_(); + } else { + this.resetNorth_(); + } +}; + + +/** + * @private + */ +ol.control.Rotate.prototype.resetNorth_ = function() { + var map = this.getMap(); + var view = map.getView(); + if (!view) { + // the map does not have a view, so we can't act + // upon it + return; + } + var currentRotation = view.getRotation(); + if (currentRotation !== undefined) { + if (this.duration_ > 0) { + currentRotation = currentRotation % (2 * Math.PI); + if (currentRotation < -Math.PI) { + currentRotation += 2 * Math.PI; + } + if (currentRotation > Math.PI) { + currentRotation -= 2 * Math.PI; + } + view.animate({ + rotation: 0, + duration: this.duration_, + easing: ol.easing.easeOut + }); + } else { + view.setRotation(0); + } + } +}; + + +/** + * Update the rotate control element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.Rotate} + * @api + */ +ol.control.Rotate.render = function(mapEvent) { + var frameState = mapEvent.frameState; + if (!frameState) { + return; + } + var rotation = frameState.viewState.rotation; + if (rotation != this.rotation_) { + var transform = 'rotate(' + rotation + 'rad)'; + if (this.autoHide_) { + var contains = this.element.classList.contains(ol.css.CLASS_HIDDEN); + if (!contains && rotation === 0) { + this.element.classList.add(ol.css.CLASS_HIDDEN); + } else if (contains && rotation !== 0) { + this.element.classList.remove(ol.css.CLASS_HIDDEN); + } + } + this.label_.style.msTransform = transform; + this.label_.style.webkitTransform = transform; + this.label_.style.transform = transform; + } + this.rotation_ = rotation; +}; + +goog.provide('ol.control.Zoom'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.easing'); + + +/** + * @classdesc + * A control with 2 buttons, one for zoom in and one for zoom out. + * This control is one of the default controls of a map. To style this control + * use css selectors `.ol-zoom-in` and `.ol-zoom-out`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ZoomOptions=} opt_options Zoom options. + * @api stable + */ +ol.control.Zoom = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var className = options.className !== undefined ? options.className : 'ol-zoom'; + + var delta = options.delta !== undefined ? options.delta : 1; + + var zoomInLabel = options.zoomInLabel !== undefined ? options.zoomInLabel : '+'; + var zoomOutLabel = options.zoomOutLabel !== undefined ? options.zoomOutLabel : '\u2212'; + + var zoomInTipLabel = options.zoomInTipLabel !== undefined ? + options.zoomInTipLabel : 'Zoom in'; + var zoomOutTipLabel = options.zoomOutTipLabel !== undefined ? + options.zoomOutTipLabel : 'Zoom out'; + + var inElement = document.createElement('button'); + inElement.className = className + '-in'; + inElement.setAttribute('type', 'button'); + inElement.title = zoomInTipLabel; + inElement.appendChild( + typeof zoomInLabel === 'string' ? document.createTextNode(zoomInLabel) : zoomInLabel + ); + + ol.events.listen(inElement, ol.events.EventType.CLICK, + ol.control.Zoom.prototype.handleClick_.bind(this, delta)); + + var outElement = document.createElement('button'); + outElement.className = className + '-out'; + outElement.setAttribute('type', 'button'); + outElement.title = zoomOutTipLabel; + outElement.appendChild( + typeof zoomOutLabel === 'string' ? document.createTextNode(zoomOutLabel) : zoomOutLabel + ); + + ol.events.listen(outElement, ol.events.EventType.CLICK, + ol.control.Zoom.prototype.handleClick_.bind(this, -delta)); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(inElement); + element.appendChild(outElement); + + ol.control.Control.call(this, { + element: element, + target: options.target + }); + + /** + * @type {number} + * @private + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; + +}; +ol.inherits(ol.control.Zoom, ol.control.Control); + + +/** + * @param {number} delta Zoom delta. + * @param {Event} event The event to handle + * @private + */ +ol.control.Zoom.prototype.handleClick_ = function(delta, event) { + event.preventDefault(); + this.zoomByDelta_(delta); +}; + + +/** + * @param {number} delta Zoom delta. + * @private + */ +ol.control.Zoom.prototype.zoomByDelta_ = function(delta) { + var map = this.getMap(); + var view = map.getView(); + if (!view) { + // the map does not have a view, so we can't act + // upon it + return; + } + var currentResolution = view.getResolution(); + if (currentResolution) { + var newResolution = view.constrainResolution(currentResolution, delta); + if (this.duration_ > 0) { + if (view.getAnimating()) { + view.cancelAnimations(); + } + view.animate({ + resolution: newResolution, + duration: this.duration_, + easing: ol.easing.easeOut + }); + } else { + view.setResolution(newResolution); + } + } +}; + +goog.provide('ol.control'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.control.Attribution'); +goog.require('ol.control.Rotate'); +goog.require('ol.control.Zoom'); + + +/** + * Set of controls included in maps by default. Unless configured otherwise, + * this returns a collection containing an instance of each of the following + * controls: + * * {@link ol.control.Zoom} + * * {@link ol.control.Rotate} + * * {@link ol.control.Attribution} + * + * @param {olx.control.DefaultsOptions=} opt_options Defaults options. + * @return {ol.Collection.<ol.control.Control>} Controls. + * @api stable + */ +ol.control.defaults = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var controls = new ol.Collection(); + + var zoomControl = options.zoom !== undefined ? options.zoom : true; + if (zoomControl) { + controls.push(new ol.control.Zoom(options.zoomOptions)); + } + + var rotateControl = options.rotate !== undefined ? options.rotate : true; + if (rotateControl) { + controls.push(new ol.control.Rotate(options.rotateOptions)); + } + + var attributionControl = options.attribution !== undefined ? + options.attribution : true; + if (attributionControl) { + controls.push(new ol.control.Attribution(options.attributionOptions)); + } + + return controls; + +}; + +// FIXME should listen on appropriate pane, once it is defined + +goog.provide('ol.control.MousePosition'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.Object'); +goog.require('ol.control.Control'); +goog.require('ol.proj'); + + +/** + * @classdesc + * A control to show the 2D coordinates of the mouse cursor. By default, these + * are in the view projection, but can be in any supported projection. + * By default the control is shown in the top right corner of the map, but this + * can be changed by using the css selector `.ol-mouse-position`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.MousePositionOptions=} opt_options Mouse position + * options. + * @api stable + */ +ol.control.MousePosition = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var element = document.createElement('DIV'); + element.className = options.className !== undefined ? options.className : 'ol-mouse-position'; + + var render = options.render ? + options.render : ol.control.MousePosition.render; + + ol.control.Control.call(this, { + element: element, + render: render, + target: options.target + }); + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.control.MousePosition.Property.PROJECTION), + this.handleProjectionChanged_, this); + + if (options.coordinateFormat) { + this.setCoordinateFormat(options.coordinateFormat); + } + if (options.projection) { + this.setProjection(ol.proj.get(options.projection)); + } + + /** + * @private + * @type {string} + */ + this.undefinedHTML_ = options.undefinedHTML !== undefined ? options.undefinedHTML : ''; + + /** + * @private + * @type {string} + */ + this.renderedHTML_ = element.innerHTML; + + /** + * @private + * @type {ol.proj.Projection} + */ + this.mapProjection_ = null; + + /** + * @private + * @type {?ol.TransformFunction} + */ + this.transform_ = null; + + /** + * @private + * @type {ol.Pixel} + */ + this.lastMouseMovePixel_ = null; + +}; +ol.inherits(ol.control.MousePosition, ol.control.Control); + + +/** + * Update the mouseposition element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.MousePosition} + * @api + */ +ol.control.MousePosition.render = function(mapEvent) { + var frameState = mapEvent.frameState; + if (!frameState) { + this.mapProjection_ = null; + } else { + if (this.mapProjection_ != frameState.viewState.projection) { + this.mapProjection_ = frameState.viewState.projection; + this.transform_ = null; + } + } + this.updateHTML_(this.lastMouseMovePixel_); +}; + + +/** + * @private + */ +ol.control.MousePosition.prototype.handleProjectionChanged_ = function() { + this.transform_ = null; +}; + + +/** + * Return the coordinate format type used to render the current position or + * undefined. + * @return {ol.CoordinateFormatType|undefined} The format to render the current + * position in. + * @observable + * @api stable + */ +ol.control.MousePosition.prototype.getCoordinateFormat = function() { + return /** @type {ol.CoordinateFormatType|undefined} */ ( + this.get(ol.control.MousePosition.Property.COORDINATE_FORMAT)); +}; + + +/** + * Return the projection that is used to report the mouse position. + * @return {ol.proj.Projection|undefined} The projection to report mouse + * position in. + * @observable + * @api stable + */ +ol.control.MousePosition.prototype.getProjection = function() { + return /** @type {ol.proj.Projection|undefined} */ ( + this.get(ol.control.MousePosition.Property.PROJECTION)); +}; + + +/** + * @param {Event} event Browser event. + * @protected + */ +ol.control.MousePosition.prototype.handleMouseMove = function(event) { + var map = this.getMap(); + this.lastMouseMovePixel_ = map.getEventPixel(event); + this.updateHTML_(this.lastMouseMovePixel_); +}; + + +/** + * @param {Event} event Browser event. + * @protected + */ +ol.control.MousePosition.prototype.handleMouseOut = function(event) { + this.updateHTML_(null); + this.lastMouseMovePixel_ = null; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.control.MousePosition.prototype.setMap = function(map) { + ol.control.Control.prototype.setMap.call(this, map); + if (map) { + var viewport = map.getViewport(); + this.listenerKeys.push( + ol.events.listen(viewport, ol.events.EventType.MOUSEMOVE, + this.handleMouseMove, this), + ol.events.listen(viewport, ol.events.EventType.MOUSEOUT, + this.handleMouseOut, this) + ); + } +}; + + +/** + * Set the coordinate format type used to render the current position. + * @param {ol.CoordinateFormatType} format The format to render the current + * position in. + * @observable + * @api stable + */ +ol.control.MousePosition.prototype.setCoordinateFormat = function(format) { + this.set(ol.control.MousePosition.Property.COORDINATE_FORMAT, format); +}; + + +/** + * Set the projection that is used to report the mouse position. + * @param {ol.proj.Projection} projection The projection to report mouse + * position in. + * @observable + * @api stable + */ +ol.control.MousePosition.prototype.setProjection = function(projection) { + this.set(ol.control.MousePosition.Property.PROJECTION, projection); +}; + + +/** + * @param {?ol.Pixel} pixel Pixel. + * @private + */ +ol.control.MousePosition.prototype.updateHTML_ = function(pixel) { + var html = this.undefinedHTML_; + if (pixel && this.mapProjection_) { + if (!this.transform_) { + var projection = this.getProjection(); + if (projection) { + this.transform_ = ol.proj.getTransformFromProjections( + this.mapProjection_, projection); + } else { + this.transform_ = ol.proj.identityTransform; + } + } + var map = this.getMap(); + var coordinate = map.getCoordinateFromPixel(pixel); + if (coordinate) { + this.transform_(coordinate, coordinate); + var coordinateFormat = this.getCoordinateFormat(); + if (coordinateFormat) { + html = coordinateFormat(coordinate); + } else { + html = coordinate.toString(); + } + } + } + if (!this.renderedHTML_ || html != this.renderedHTML_) { + this.element.innerHTML = html; + this.renderedHTML_ = html; + } +}; + + +/** + * @enum {string} + */ +ol.control.MousePosition.Property = { + PROJECTION: 'projection', + COORDINATE_FORMAT: 'coordinateFormat' +}; + +goog.provide('ol.MapBrowserEvent'); + +goog.require('ol'); +goog.require('ol.MapEvent'); +goog.require('ol.events.EventType'); + + +/** + * @classdesc + * Events emitted as map browser events are instances of this type. + * See {@link ol.Map} for which events trigger a map browser event. + * + * @constructor + * @extends {ol.MapEvent} + * @implements {oli.MapBrowserEvent} + * @param {string} type Event type. + * @param {ol.Map} map Map. + * @param {Event} browserEvent Browser event. + * @param {boolean=} opt_dragging Is the map currently being dragged? + * @param {?olx.FrameState=} opt_frameState Frame state. + */ +ol.MapBrowserEvent = function(type, map, browserEvent, opt_dragging, + opt_frameState) { + + ol.MapEvent.call(this, type, map, opt_frameState); + + /** + * The original browser event. + * @const + * @type {Event} + * @api stable + */ + this.originalEvent = browserEvent; + + /** + * The map pixel relative to the viewport corresponding to the original browser event. + * @type {ol.Pixel} + * @api stable + */ + this.pixel = map.getEventPixel(browserEvent); + + /** + * The coordinate in view projection corresponding to the original browser event. + * @type {ol.Coordinate} + * @api stable + */ + this.coordinate = map.getCoordinateFromPixel(this.pixel); + + /** + * Indicates if the map is currently being dragged. Only set for + * `POINTERDRAG` and `POINTERMOVE` events. Default is `false`. + * + * @type {boolean} + * @api stable + */ + this.dragging = opt_dragging !== undefined ? opt_dragging : false; + +}; +ol.inherits(ol.MapBrowserEvent, ol.MapEvent); + + +/** + * Prevents the default browser action. + * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault + * @override + * @api stable + */ +ol.MapBrowserEvent.prototype.preventDefault = function() { + ol.MapEvent.prototype.preventDefault.call(this); + this.originalEvent.preventDefault(); +}; + + +/** + * Prevents further propagation of the current event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation + * @override + * @api stable + */ +ol.MapBrowserEvent.prototype.stopPropagation = function() { + ol.MapEvent.prototype.stopPropagation.call(this); + this.originalEvent.stopPropagation(); +}; + + +/** + * Constants for event names. + * @enum {string} + */ +ol.MapBrowserEvent.EventType = { + + /** + * A true single click with no dragging and no double click. Note that this + * event is delayed by 250 ms to ensure that it is not a double click. + * @event ol.MapBrowserEvent#singleclick + * @api stable + */ + SINGLECLICK: 'singleclick', + + /** + * A click with no dragging. A double click will fire two of this. + * @event ol.MapBrowserEvent#click + * @api stable + */ + CLICK: ol.events.EventType.CLICK, + + /** + * A true double click, with no dragging. + * @event ol.MapBrowserEvent#dblclick + * @api stable + */ + DBLCLICK: ol.events.EventType.DBLCLICK, + + /** + * Triggered when a pointer is dragged. + * @event ol.MapBrowserEvent#pointerdrag + * @api + */ + POINTERDRAG: 'pointerdrag', + + /** + * Triggered when a pointer is moved. Note that on touch devices this is + * triggered when the map is panned, so is not the same as mousemove. + * @event ol.MapBrowserEvent#pointermove + * @api stable + */ + POINTERMOVE: 'pointermove', + + POINTERDOWN: 'pointerdown', + POINTERUP: 'pointerup', + POINTEROVER: 'pointerover', + POINTEROUT: 'pointerout', + POINTERENTER: 'pointerenter', + POINTERLEAVE: 'pointerleave', + POINTERCANCEL: 'pointercancel' +}; + +goog.provide('ol.MapBrowserPointerEvent'); + +goog.require('ol'); +goog.require('ol.MapBrowserEvent'); + + +/** + * @constructor + * @extends {ol.MapBrowserEvent} + * @param {string} type Event type. + * @param {ol.Map} map Map. + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @param {boolean=} opt_dragging Is the map currently being dragged? + * @param {?olx.FrameState=} opt_frameState Frame state. + */ +ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_dragging, + opt_frameState) { + + ol.MapBrowserEvent.call(this, type, map, pointerEvent.originalEvent, opt_dragging, + opt_frameState); + + /** + * @const + * @type {ol.pointer.PointerEvent} + */ + this.pointerEvent = pointerEvent; + +}; +ol.inherits(ol.MapBrowserPointerEvent, ol.MapBrowserEvent); + +goog.provide('ol.pointer.EventType'); + + +/** + * Constants for event names. + * @enum {string} + */ +ol.pointer.EventType = { + POINTERMOVE: 'pointermove', + POINTERDOWN: 'pointerdown', + POINTERUP: 'pointerup', + POINTEROVER: 'pointerover', + POINTEROUT: 'pointerout', + POINTERENTER: 'pointerenter', + POINTERLEAVE: 'pointerleave', + POINTERCANCEL: 'pointercancel' +}; + +goog.provide('ol.webgl'); + + +/** Constants taken from goog.webgl + */ + + +/** + * @const + * @type {number} + */ +ol.webgl.ONE = 1; + + +/** + * @const + * @type {number} + */ +ol.webgl.SRC_ALPHA = 0x0302; + + +/** + * @const + * @type {number} + */ +ol.webgl.COLOR_ATTACHMENT0 = 0x8CE0; + + +/** + * @const + * @type {number} + */ +ol.webgl.COLOR_BUFFER_BIT = 0x00004000; + + +/** + * @const + * @type {number} + */ +ol.webgl.TRIANGLES = 0x0004; + + +/** + * @const + * @type {number} + */ +ol.webgl.TRIANGLE_STRIP = 0x0005; + + +/** + * @const + * @type {number} + */ +ol.webgl.ONE_MINUS_SRC_ALPHA = 0x0303; + + +/** + * @const + * @type {number} + */ +ol.webgl.ARRAY_BUFFER = 0x8892; + + +/** + * @const + * @type {number} + */ +ol.webgl.ELEMENT_ARRAY_BUFFER = 0x8893; + + +/** + * @const + * @type {number} + */ +ol.webgl.STREAM_DRAW = 0x88E0; + + +/** + * @const + * @type {number} + */ +ol.webgl.STATIC_DRAW = 0x88E4; + + +/** + * @const + * @type {number} + */ +ol.webgl.DYNAMIC_DRAW = 0x88E8; + + +/** + * @const + * @type {number} + */ +ol.webgl.CULL_FACE = 0x0B44; + + +/** + * @const + * @type {number} + */ +ol.webgl.BLEND = 0x0BE2; + + +/** + * @const + * @type {number} + */ +ol.webgl.STENCIL_TEST = 0x0B90; + + +/** + * @const + * @type {number} + */ +ol.webgl.DEPTH_TEST = 0x0B71; + + +/** + * @const + * @type {number} + */ +ol.webgl.SCISSOR_TEST = 0x0C11; + + +/** + * @const + * @type {number} + */ +ol.webgl.UNSIGNED_BYTE = 0x1401; + + +/** + * @const + * @type {number} + */ +ol.webgl.UNSIGNED_SHORT = 0x1403; + + +/** + * @const + * @type {number} + */ +ol.webgl.UNSIGNED_INT = 0x1405; + + +/** + * @const + * @type {number} + */ +ol.webgl.FLOAT = 0x1406; + + +/** + * @const + * @type {number} + */ +ol.webgl.RGBA = 0x1908; + + +/** + * @const + * @type {number} + */ +ol.webgl.FRAGMENT_SHADER = 0x8B30; + + +/** + * @const + * @type {number} + */ +ol.webgl.VERTEX_SHADER = 0x8B31; + + +/** + * @const + * @type {number} + */ +ol.webgl.LINK_STATUS = 0x8B82; + + +/** + * @const + * @type {number} + */ +ol.webgl.LINEAR = 0x2601; + + +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_MAG_FILTER = 0x2800; + + +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_MIN_FILTER = 0x2801; + + +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_WRAP_S = 0x2802; + + +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_WRAP_T = 0x2803; + + +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_2D = 0x0DE1; + + +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE0 = 0x84C0; + + +/** + * @const + * @type {number} + */ +ol.webgl.CLAMP_TO_EDGE = 0x812F; + + +/** + * @const + * @type {number} + */ +ol.webgl.COMPILE_STATUS = 0x8B81; + + +/** + * @const + * @type {number} + */ +ol.webgl.FRAMEBUFFER = 0x8D40; + + +/** end of goog.webgl constants + */ + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.webgl.CONTEXT_IDS_ = [ + 'experimental-webgl', + 'webgl', + 'webkit-3d', + 'moz-webgl' +]; + + +/** + * @param {HTMLCanvasElement} canvas Canvas. + * @param {Object=} opt_attributes Attributes. + * @return {WebGLRenderingContext} WebGL rendering context. + */ +ol.webgl.getContext = function(canvas, opt_attributes) { + var context, i, ii = ol.webgl.CONTEXT_IDS_.length; + for (i = 0; i < ii; ++i) { + try { + context = canvas.getContext(ol.webgl.CONTEXT_IDS_[i], opt_attributes); + if (context) { + return /** @type {!WebGLRenderingContext} */ (context); + } + } catch (e) { + // pass + } + } + return null; +}; + +goog.provide('ol.has'); + +goog.require('ol'); +goog.require('ol.webgl'); + +var ua = typeof navigator !== 'undefined' ? + navigator.userAgent.toLowerCase() : ''; + +/** + * User agent string says we are dealing with Firefox as browser. + * @type {boolean} + */ +ol.has.FIREFOX = ua.indexOf('firefox') !== -1; + +/** + * User agent string says we are dealing with Safari as browser. + * @type {boolean} + */ +ol.has.SAFARI = ua.indexOf('safari') !== -1 && ua.indexOf('chrom') == -1; + +/** + * User agent string says we are dealing with a WebKit engine. + * @type {boolean} + */ +ol.has.WEBKIT = ua.indexOf('webkit') !== -1 && ua.indexOf('edge') == -1; + +/** + * User agent string says we are dealing with a Mac as platform. + * @type {boolean} + */ +ol.has.MAC = ua.indexOf('macintosh') !== -1; + + +/** + * The ratio between physical pixels and device-independent pixels + * (dips) on the device (`window.devicePixelRatio`). + * @const + * @type {number} + * @api stable + */ +ol.has.DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1; + + +/** + * True if the browser's Canvas implementation implements {get,set}LineDash. + * @type {boolean} + */ +ol.has.CANVAS_LINE_DASH = false; + + +/** + * True if both the library and browser support Canvas. Always `false` + * if `ol.ENABLE_CANVAS` is set to `false` at compile time. + * @const + * @type {boolean} + * @api stable + */ +ol.has.CANVAS = ol.ENABLE_CANVAS && ( + /** + * @return {boolean} Canvas supported. + */ + function() { + if (!('HTMLCanvasElement' in window)) { + return false; + } + try { + var context = document.createElement('CANVAS').getContext('2d'); + if (!context) { + return false; + } else { + if (context.setLineDash !== undefined) { + ol.has.CANVAS_LINE_DASH = true; + } + return true; + } + } catch (e) { + return false; + } + })(); + + +/** + * Indicates if DeviceOrientation is supported in the user's browser. + * @const + * @type {boolean} + * @api stable + */ +ol.has.DEVICE_ORIENTATION = 'DeviceOrientationEvent' in window; + + +/** + * Is HTML5 geolocation supported in the current browser? + * @const + * @type {boolean} + * @api stable + */ +ol.has.GEOLOCATION = 'geolocation' in navigator; + + +/** + * True if browser supports touch events. + * @const + * @type {boolean} + * @api stable + */ +ol.has.TOUCH = ol.ASSUME_TOUCH || 'ontouchstart' in window; + + +/** + * True if browser supports pointer events. + * @const + * @type {boolean} + */ +ol.has.POINTER = 'PointerEvent' in window; + + +/** + * True if browser supports ms pointer events (IE 10). + * @const + * @type {boolean} + */ +ol.has.MSPOINTER = !!(navigator.msPointerEnabled); + + +/** + * True if both OpenLayers and browser support WebGL. Always `false` + * if `ol.ENABLE_WEBGL` is set to `false` at compile time. + * @const + * @type {boolean} + * @api stable + */ +ol.has.WEBGL; + + +(function() { + if (ol.ENABLE_WEBGL) { + var hasWebGL = false; + var textureSize; + var /** @type {Array.<string>} */ extensions = []; + + if ('WebGLRenderingContext' in window) { + try { + var canvas = /** @type {HTMLCanvasElement} */ + (document.createElement('CANVAS')); + var gl = ol.webgl.getContext(canvas, { + failIfMajorPerformanceCaveat: true + }); + if (gl) { + hasWebGL = true; + textureSize = /** @type {number} */ + (gl.getParameter(gl.MAX_TEXTURE_SIZE)); + extensions = gl.getSupportedExtensions(); + } + } catch (e) { + // pass + } + } + ol.has.WEBGL = hasWebGL; + ol.WEBGL_EXTENSIONS = extensions; + ol.WEBGL_MAX_TEXTURE_SIZE = textureSize; + } +})(); + +goog.provide('ol.pointer.EventSource'); + + +/** + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @param {!Object.<string, function(Event)>} mapping Event + * mapping. + * @constructor + */ +ol.pointer.EventSource = function(dispatcher, mapping) { + /** + * @type {ol.pointer.PointerEventHandler} + */ + this.dispatcher = dispatcher; + + /** + * @private + * @const + * @type {!Object.<string, function(Event)>} + */ + this.mapping_ = mapping; +}; + + +/** + * List of events supported by this source. + * @return {Array.<string>} Event names + */ +ol.pointer.EventSource.prototype.getEvents = function() { + return Object.keys(this.mapping_); +}; + + +/** + * Returns a mapping between the supported event types and + * the handlers that should handle an event. + * @return {Object.<string, function(Event)>} + * Event/Handler mapping + */ +ol.pointer.EventSource.prototype.getMapping = function() { + return this.mapping_; +}; + + +/** + * Returns the handler that should handle a given event type. + * @param {string} eventType The event type. + * @return {function(Event)} Handler + */ +ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) { + return this.mapping_[eventType]; +}; + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.MouseSource'); + +goog.require('ol'); +goog.require('ol.pointer.EventSource'); + + +/** + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @constructor + * @extends {ol.pointer.EventSource} + */ +ol.pointer.MouseSource = function(dispatcher) { + var mapping = { + 'mousedown': this.mousedown, + 'mousemove': this.mousemove, + 'mouseup': this.mouseup, + 'mouseover': this.mouseover, + 'mouseout': this.mouseout + }; + ol.pointer.EventSource.call(this, dispatcher, mapping); + + /** + * @const + * @type {!Object.<string, Event|Object>} + */ + this.pointerMap = dispatcher.pointerMap; + + /** + * @const + * @type {Array.<ol.Pixel>} + */ + this.lastTouches = []; +}; +ol.inherits(ol.pointer.MouseSource, ol.pointer.EventSource); + + +/** + * @const + * @type {number} + */ +ol.pointer.MouseSource.POINTER_ID = 1; + + +/** + * @const + * @type {string} + */ +ol.pointer.MouseSource.POINTER_TYPE = 'mouse'; + + +/** + * Radius around touchend that swallows mouse events. + * + * @const + * @type {number} + */ +ol.pointer.MouseSource.DEDUP_DIST = 25; + + +/** + * Detect if a mouse event was simulated from a touch by + * checking if previously there was a touch event at the + * same position. + * + * FIXME - Known problem with the native Android browser on + * Samsung GT-I9100 (Android 4.1.2): + * In case the page is scrolled, this function does not work + * correctly when a canvas is used (WebGL or canvas renderer). + * Mouse listeners on canvas elements (for this browser), create + * two mouse events: One 'good' and one 'bad' one (on other browsers or + * when a div is used, there is only one event). For the 'bad' one, + * clientX/clientY and also pageX/pageY are wrong when the page + * is scrolled. Because of that, this function can not detect if + * the events were simulated from a touch event. As result, a + * pointer event at a wrong position is dispatched, which confuses + * the map interactions. + * It is unclear, how one can get the correct position for the event + * or detect that the positions are invalid. + * + * @private + * @param {Event} inEvent The in event. + * @return {boolean} True, if the event was generated by a touch. + */ +ol.pointer.MouseSource.prototype.isEventSimulatedFromTouch_ = function(inEvent) { + var lts = this.lastTouches; + var x = inEvent.clientX, y = inEvent.clientY; + for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { + // simulated mouse events will be swallowed near a primary touchend + var dx = Math.abs(x - t[0]), dy = Math.abs(y - t[1]); + if (dx <= ol.pointer.MouseSource.DEDUP_DIST && + dy <= ol.pointer.MouseSource.DEDUP_DIST) { + return true; + } + } + return false; +}; + + +/** + * Creates a copy of the original event that will be used + * for the fake pointer event. + * + * @param {Event} inEvent The in event. + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @return {Object} The copied event. + */ +ol.pointer.MouseSource.prepareEvent = function(inEvent, dispatcher) { + var e = dispatcher.cloneEvent(inEvent, inEvent); + + // forward mouse preventDefault + var pd = e.preventDefault; + e.preventDefault = function() { + inEvent.preventDefault(); + pd(); + }; + + e.pointerId = ol.pointer.MouseSource.POINTER_ID; + e.isPrimary = true; + e.pointerType = ol.pointer.MouseSource.POINTER_TYPE; + + return e; +}; + + +/** + * Handler for `mousedown`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MouseSource.prototype.mousedown = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + // TODO(dfreedman) workaround for some elements not sending mouseup + // http://crbug/149091 + if (ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap) { + this.cancel(inEvent); + } + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()] = inEvent; + this.dispatcher.down(e, inEvent); + } +}; + + +/** + * Handler for `mousemove`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MouseSource.prototype.mousemove = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.move(e, inEvent); + } +}; + + +/** + * Handler for `mouseup`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MouseSource.prototype.mouseup = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + var p = this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()]; + + if (p && p.button === inEvent.button) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.up(e, inEvent); + this.cleanupMouse(); + } + } +}; + + +/** + * Handler for `mouseover`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MouseSource.prototype.mouseover = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.enterOver(e, inEvent); + } +}; + + +/** + * Handler for `mouseout`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MouseSource.prototype.mouseout = function(inEvent) { + if (!this.isEventSimulatedFromTouch_(inEvent)) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.leaveOut(e, inEvent); + } +}; + + +/** + * Dispatches a `pointercancel` event. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MouseSource.prototype.cancel = function(inEvent) { + var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); + this.dispatcher.cancel(e, inEvent); + this.cleanupMouse(); +}; + + +/** + * Remove the mouse from the list of active pointers. + */ +ol.pointer.MouseSource.prototype.cleanupMouse = function() { + delete this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()]; +}; + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.MsSource'); + +goog.require('ol'); +goog.require('ol.pointer.EventSource'); + + +/** + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @constructor + * @extends {ol.pointer.EventSource} + */ +ol.pointer.MsSource = function(dispatcher) { + var mapping = { + 'MSPointerDown': this.msPointerDown, + 'MSPointerMove': this.msPointerMove, + 'MSPointerUp': this.msPointerUp, + 'MSPointerOut': this.msPointerOut, + 'MSPointerOver': this.msPointerOver, + 'MSPointerCancel': this.msPointerCancel, + 'MSGotPointerCapture': this.msGotPointerCapture, + 'MSLostPointerCapture': this.msLostPointerCapture + }; + ol.pointer.EventSource.call(this, dispatcher, mapping); + + /** + * @const + * @type {!Object.<string, Event|Object>} + */ + this.pointerMap = dispatcher.pointerMap; + + /** + * @const + * @type {Array.<string>} + */ + this.POINTER_TYPES = [ + '', + 'unavailable', + 'touch', + 'pen', + 'mouse' + ]; +}; +ol.inherits(ol.pointer.MsSource, ol.pointer.EventSource); + + +/** + * Creates a copy of the original event that will be used + * for the fake pointer event. + * + * @private + * @param {Event} inEvent The in event. + * @return {Object} The copied event. + */ +ol.pointer.MsSource.prototype.prepareEvent_ = function(inEvent) { + var e = inEvent; + if (typeof inEvent.pointerType === 'number') { + e = this.dispatcher.cloneEvent(inEvent, inEvent); + e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; + } + + return e; +}; + + +/** + * Remove this pointer from the list of active pointers. + * @param {number} pointerId Pointer identifier. + */ +ol.pointer.MsSource.prototype.cleanup = function(pointerId) { + delete this.pointerMap[pointerId.toString()]; +}; + + +/** + * Handler for `msPointerDown`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) { + this.pointerMap[inEvent.pointerId.toString()] = inEvent; + var e = this.prepareEvent_(inEvent); + this.dispatcher.down(e, inEvent); +}; + + +/** + * Handler for `msPointerMove`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msPointerMove = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.move(e, inEvent); +}; + + +/** + * Handler for `msPointerUp`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msPointerUp = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.up(e, inEvent); + this.cleanup(inEvent.pointerId); +}; + + +/** + * Handler for `msPointerOut`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msPointerOut = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.leaveOut(e, inEvent); +}; + + +/** + * Handler for `msPointerOver`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msPointerOver = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.enterOver(e, inEvent); +}; + + +/** + * Handler for `msPointerCancel`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msPointerCancel = function(inEvent) { + var e = this.prepareEvent_(inEvent); + this.dispatcher.cancel(e, inEvent); + this.cleanup(inEvent.pointerId); +}; + + +/** + * Handler for `msLostPointerCapture`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msLostPointerCapture = function(inEvent) { + var e = this.dispatcher.makeEvent('lostpointercapture', + inEvent, inEvent); + this.dispatcher.dispatchEvent(e); +}; + + +/** + * Handler for `msGotPointerCapture`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.MsSource.prototype.msGotPointerCapture = function(inEvent) { + var e = this.dispatcher.makeEvent('gotpointercapture', + inEvent, inEvent); + this.dispatcher.dispatchEvent(e); +}; + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.NativeSource'); + +goog.require('ol'); +goog.require('ol.pointer.EventSource'); + + +/** + * @param {ol.pointer.PointerEventHandler} dispatcher Event handler. + * @constructor + * @extends {ol.pointer.EventSource} + */ +ol.pointer.NativeSource = function(dispatcher) { + var mapping = { + 'pointerdown': this.pointerDown, + 'pointermove': this.pointerMove, + 'pointerup': this.pointerUp, + 'pointerout': this.pointerOut, + 'pointerover': this.pointerOver, + 'pointercancel': this.pointerCancel, + 'gotpointercapture': this.gotPointerCapture, + 'lostpointercapture': this.lostPointerCapture + }; + ol.pointer.EventSource.call(this, dispatcher, mapping); +}; +ol.inherits(ol.pointer.NativeSource, ol.pointer.EventSource); + + +/** + * Handler for `pointerdown`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.pointerDown = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `pointermove`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.pointerMove = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `pointerup`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.pointerUp = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `pointerout`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.pointerOut = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `pointerover`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.pointerOver = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `pointercancel`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.pointerCancel = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `lostpointercapture`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.lostPointerCapture = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + + +/** + * Handler for `gotpointercapture`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.NativeSource.prototype.gotPointerCapture = function(inEvent) { + this.dispatcher.fireNativeEvent(inEvent); +}; + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.PointerEvent'); + + +goog.require('ol'); +goog.require('ol.events.Event'); + + +/** + * A class for pointer events. + * + * This class is used as an abstraction for mouse events, + * touch events and even native pointer events. + * + * @constructor + * @extends {ol.events.Event} + * @param {string} type The type of the event to create. + * @param {Event} originalEvent The event. + * @param {Object.<string, ?>=} opt_eventDict An optional dictionary of + * initial event properties. + */ +ol.pointer.PointerEvent = function(type, originalEvent, opt_eventDict) { + ol.events.Event.call(this, type); + + /** + * @const + * @type {Event} + */ + this.originalEvent = originalEvent; + + var eventDict = opt_eventDict ? opt_eventDict : {}; + + /** + * @type {number} + */ + this.buttons = this.getButtons_(eventDict); + + /** + * @type {number} + */ + this.pressure = this.getPressure_(eventDict, this.buttons); + + // MouseEvent related properties + + /** + * @type {boolean} + */ + this.bubbles = 'bubbles' in eventDict ? eventDict['bubbles'] : false; + + /** + * @type {boolean} + */ + this.cancelable = 'cancelable' in eventDict ? eventDict['cancelable'] : false; + + /** + * @type {Object} + */ + this.view = 'view' in eventDict ? eventDict['view'] : null; + + /** + * @type {number} + */ + this.detail = 'detail' in eventDict ? eventDict['detail'] : null; + + /** + * @type {number} + */ + this.screenX = 'screenX' in eventDict ? eventDict['screenX'] : 0; + + /** + * @type {number} + */ + this.screenY = 'screenY' in eventDict ? eventDict['screenY'] : 0; + + /** + * @type {number} + */ + this.clientX = 'clientX' in eventDict ? eventDict['clientX'] : 0; + + /** + * @type {number} + */ + this.clientY = 'clientY' in eventDict ? eventDict['clientY'] : 0; + + /** + * @type {boolean} + */ + this.ctrlKey = 'ctrlKey' in eventDict ? eventDict['ctrlKey'] : false; + + /** + * @type {boolean} + */ + this.altKey = 'altKey' in eventDict ? eventDict['altKey'] : false; + + /** + * @type {boolean} + */ + this.shiftKey = 'shiftKey' in eventDict ? eventDict['shiftKey'] : false; + + /** + * @type {boolean} + */ + this.metaKey = 'metaKey' in eventDict ? eventDict['metaKey'] : false; + + /** + * @type {number} + */ + this.button = 'button' in eventDict ? eventDict['button'] : 0; + + /** + * @type {Node} + */ + this.relatedTarget = 'relatedTarget' in eventDict ? + eventDict['relatedTarget'] : null; + + // PointerEvent related properties + + /** + * @const + * @type {number} + */ + this.pointerId = 'pointerId' in eventDict ? eventDict['pointerId'] : 0; + + /** + * @type {number} + */ + this.width = 'width' in eventDict ? eventDict['width'] : 0; + + /** + * @type {number} + */ + this.height = 'height' in eventDict ? eventDict['height'] : 0; + + /** + * @type {number} + */ + this.tiltX = 'tiltX' in eventDict ? eventDict['tiltX'] : 0; + + /** + * @type {number} + */ + this.tiltY = 'tiltY' in eventDict ? eventDict['tiltY'] : 0; + + /** + * @type {string} + */ + this.pointerType = 'pointerType' in eventDict ? eventDict['pointerType'] : ''; + + /** + * @type {number} + */ + this.hwTimestamp = 'hwTimestamp' in eventDict ? eventDict['hwTimestamp'] : 0; + + /** + * @type {boolean} + */ + this.isPrimary = 'isPrimary' in eventDict ? eventDict['isPrimary'] : false; + + // keep the semantics of preventDefault + if (originalEvent.preventDefault) { + this.preventDefault = function() { + originalEvent.preventDefault(); + }; + } +}; +ol.inherits(ol.pointer.PointerEvent, ol.events.Event); + + +/** + * @private + * @param {Object.<string, ?>} eventDict The event dictionary. + * @return {number} Button indicator. + */ +ol.pointer.PointerEvent.prototype.getButtons_ = function(eventDict) { + // According to the w3c spec, + // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-button + // MouseEvent.button == 0 can mean either no mouse button depressed, or the + // left mouse button depressed. + // + // As of now, the only way to distinguish between the two states of + // MouseEvent.button is by using the deprecated MouseEvent.which property, as + // this maps mouse buttons to positive integers > 0, and uses 0 to mean that + // no mouse button is held. + // + // MouseEvent.which is derived from MouseEvent.button at MouseEvent creation, + // but initMouseEvent does not expose an argument with which to set + // MouseEvent.which. Calling initMouseEvent with a buttonArg of 0 will set + // MouseEvent.button == 0 and MouseEvent.which == 1, breaking the expectations + // of app developers. + // + // The only way to propagate the correct state of MouseEvent.which and + // MouseEvent.button to a new MouseEvent.button == 0 and MouseEvent.which == 0 + // is to call initMouseEvent with a buttonArg value of -1. + // + // This is fixed with DOM Level 4's use of buttons + var buttons; + if (eventDict.buttons || ol.pointer.PointerEvent.HAS_BUTTONS) { + buttons = eventDict.buttons; + } else { + switch (eventDict.which) { + case 1: buttons = 1; break; + case 2: buttons = 4; break; + case 3: buttons = 2; break; + default: buttons = 0; + } + } + return buttons; +}; + + +/** + * @private + * @param {Object.<string, ?>} eventDict The event dictionary. + * @param {number} buttons Button indicator. + * @return {number} The pressure. + */ +ol.pointer.PointerEvent.prototype.getPressure_ = function(eventDict, buttons) { + // Spec requires that pointers without pressure specified use 0.5 for down + // state and 0 for up state. + var pressure = 0; + if (eventDict.pressure) { + pressure = eventDict.pressure; + } else { + pressure = buttons ? 0.5 : 0; + } + return pressure; +}; + + +/** + * Is the `buttons` property supported? + * @type {boolean} + */ +ol.pointer.PointerEvent.HAS_BUTTONS = false; + + +/** + * Checks if the `buttons` property is supported. + */ +(function() { + try { + var ev = new MouseEvent('click', {buttons: 1}); + ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1; + } catch (e) { + // pass + } +})(); + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.TouchSource'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.pointer.EventSource'); +goog.require('ol.pointer.MouseSource'); + + +/** + * @constructor + * @param {ol.pointer.PointerEventHandler} dispatcher The event handler. + * @param {ol.pointer.MouseSource} mouseSource Mouse source. + * @extends {ol.pointer.EventSource} + */ +ol.pointer.TouchSource = function(dispatcher, mouseSource) { + var mapping = { + 'touchstart': this.touchstart, + 'touchmove': this.touchmove, + 'touchend': this.touchend, + 'touchcancel': this.touchcancel + }; + ol.pointer.EventSource.call(this, dispatcher, mapping); + + /** + * @const + * @type {!Object.<string, Event|Object>} + */ + this.pointerMap = dispatcher.pointerMap; + + /** + * @const + * @type {ol.pointer.MouseSource} + */ + this.mouseSource = mouseSource; + + /** + * @private + * @type {number|undefined} + */ + this.firstTouchId_ = undefined; + + /** + * @private + * @type {number} + */ + this.clickCount_ = 0; + + /** + * @private + * @type {number|undefined} + */ + this.resetId_ = undefined; +}; +ol.inherits(ol.pointer.TouchSource, ol.pointer.EventSource); + + +/** + * Mouse event timeout: This should be long enough to + * ignore compat mouse events made by touch. + * @const + * @type {number} + */ +ol.pointer.TouchSource.DEDUP_TIMEOUT = 2500; + + +/** + * @const + * @type {number} + */ +ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT = 200; + + +/** + * @const + * @type {string} + */ +ol.pointer.TouchSource.POINTER_TYPE = 'touch'; + + +/** + * @private + * @param {Touch} inTouch The in touch. + * @return {boolean} True, if this is the primary touch. + */ +ol.pointer.TouchSource.prototype.isPrimaryTouch_ = function(inTouch) { + return this.firstTouchId_ === inTouch.identifier; +}; + + +/** + * Set primary touch if there are no pointers, or the only pointer is the mouse. + * @param {Touch} inTouch The in touch. + * @private + */ +ol.pointer.TouchSource.prototype.setPrimaryTouch_ = function(inTouch) { + var count = Object.keys(this.pointerMap).length; + if (count === 0 || (count === 1 && + ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap)) { + this.firstTouchId_ = inTouch.identifier; + this.cancelResetClickCount_(); + } +}; + + +/** + * @private + * @param {Object} inPointer The in pointer object. + */ +ol.pointer.TouchSource.prototype.removePrimaryPointer_ = function(inPointer) { + if (inPointer.isPrimary) { + this.firstTouchId_ = undefined; + this.resetClickCount_(); + } +}; + + +/** + * @private + */ +ol.pointer.TouchSource.prototype.resetClickCount_ = function() { + this.resetId_ = setTimeout( + this.resetClickCountHandler_.bind(this), + ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT); +}; + + +/** + * @private + */ +ol.pointer.TouchSource.prototype.resetClickCountHandler_ = function() { + this.clickCount_ = 0; + this.resetId_ = undefined; +}; + + +/** + * @private + */ +ol.pointer.TouchSource.prototype.cancelResetClickCount_ = function() { + if (this.resetId_ !== undefined) { + clearTimeout(this.resetId_); + } +}; + + +/** + * @private + * @param {Event} browserEvent Browser event + * @param {Touch} inTouch Touch event + * @return {Object} A pointer object. + */ +ol.pointer.TouchSource.prototype.touchToPointer_ = function(browserEvent, inTouch) { + var e = this.dispatcher.cloneEvent(browserEvent, inTouch); + // Spec specifies that pointerId 1 is reserved for Mouse. + // Touch identifiers can start at 0. + // Add 2 to the touch identifier for compatibility. + e.pointerId = inTouch.identifier + 2; + // TODO: check if this is necessary? + //e.target = findTarget(e); + e.bubbles = true; + e.cancelable = true; + e.detail = this.clickCount_; + e.button = 0; + e.buttons = 1; + e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; + e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; + e.pressure = inTouch.webkitForce || inTouch.force || 0.5; + e.isPrimary = this.isPrimaryTouch_(inTouch); + e.pointerType = ol.pointer.TouchSource.POINTER_TYPE; + + // make sure that the properties that are different for + // each `Touch` object are not copied from the BrowserEvent object + e.clientX = inTouch.clientX; + e.clientY = inTouch.clientY; + e.screenX = inTouch.screenX; + e.screenY = inTouch.screenY; + + return e; +}; + + +/** + * @private + * @param {Event} inEvent Touch event + * @param {function(Event, Object)} inFunction In function. + */ +ol.pointer.TouchSource.prototype.processTouches_ = function(inEvent, inFunction) { + var touches = Array.prototype.slice.call( + inEvent.changedTouches); + var count = touches.length; + function preventDefault() { + inEvent.preventDefault(); + } + var i, pointer; + for (i = 0; i < count; ++i) { + pointer = this.touchToPointer_(inEvent, touches[i]); + // forward touch preventDefaults + pointer.preventDefault = preventDefault; + inFunction.call(this, inEvent, pointer); + } +}; + + +/** + * @private + * @param {TouchList} touchList The touch list. + * @param {number} searchId Search identifier. + * @return {boolean} True, if the `Touch` with the given id is in the list. + */ +ol.pointer.TouchSource.prototype.findTouch_ = function(touchList, searchId) { + var l = touchList.length; + var touch; + for (var i = 0; i < l; i++) { + touch = touchList[i]; + if (touch.identifier === searchId) { + return true; + } + } + return false; +}; + + +/** + * In some instances, a touchstart can happen without a touchend. This + * leaves the pointermap in a broken state. + * Therefore, on every touchstart, we remove the touches that did not fire a + * touchend event. + * To keep state globally consistent, we fire a pointercancel for + * this "abandoned" touch + * + * @private + * @param {Event} inEvent The in event. + */ +ol.pointer.TouchSource.prototype.vacuumTouches_ = function(inEvent) { + var touchList = inEvent.touches; + // pointerMap.getCount() should be < touchList.length here, + // as the touchstart has not been processed yet. + var keys = Object.keys(this.pointerMap); + var count = keys.length; + if (count >= touchList.length) { + var d = []; + var i, key, value; + for (i = 0; i < count; ++i) { + key = keys[i]; + value = this.pointerMap[key]; + // Never remove pointerId == 1, which is mouse. + // Touch identifiers are 2 smaller than their pointerId, which is the + // index in pointermap. + if (key != ol.pointer.MouseSource.POINTER_ID && + !this.findTouch_(touchList, key - 2)) { + d.push(value.out); + } + } + for (i = 0; i < d.length; ++i) { + this.cancelOut_(inEvent, d[i]); + } + } +}; + + +/** + * Handler for `touchstart`, triggers `pointerover`, + * `pointerenter` and `pointerdown` events. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.TouchSource.prototype.touchstart = function(inEvent) { + this.vacuumTouches_(inEvent); + this.setPrimaryTouch_(inEvent.changedTouches[0]); + this.dedupSynthMouse_(inEvent); + this.clickCount_++; + this.processTouches_(inEvent, this.overDown_); +}; + + +/** + * @private + * @param {Event} browserEvent The event. + * @param {Object} inPointer The in pointer object. + */ +ol.pointer.TouchSource.prototype.overDown_ = function(browserEvent, inPointer) { + this.pointerMap[inPointer.pointerId] = { + target: inPointer.target, + out: inPointer, + outTarget: inPointer.target + }; + this.dispatcher.over(inPointer, browserEvent); + this.dispatcher.enter(inPointer, browserEvent); + this.dispatcher.down(inPointer, browserEvent); +}; + + +/** + * Handler for `touchmove`. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.TouchSource.prototype.touchmove = function(inEvent) { + inEvent.preventDefault(); + this.processTouches_(inEvent, this.moveOverOut_); +}; + + +/** + * @private + * @param {Event} browserEvent The event. + * @param {Object} inPointer The in pointer. + */ +ol.pointer.TouchSource.prototype.moveOverOut_ = function(browserEvent, inPointer) { + var event = inPointer; + var pointer = this.pointerMap[event.pointerId]; + // a finger drifted off the screen, ignore it + if (!pointer) { + return; + } + var outEvent = pointer.out; + var outTarget = pointer.outTarget; + this.dispatcher.move(event, browserEvent); + if (outEvent && outTarget !== event.target) { + outEvent.relatedTarget = event.target; + event.relatedTarget = outTarget; + // recover from retargeting by shadow + outEvent.target = outTarget; + if (event.target) { + this.dispatcher.leaveOut(outEvent, browserEvent); + this.dispatcher.enterOver(event, browserEvent); + } else { + // clean up case when finger leaves the screen + event.target = outTarget; + event.relatedTarget = null; + this.cancelOut_(browserEvent, event); + } + } + pointer.out = event; + pointer.outTarget = event.target; +}; + + +/** + * Handler for `touchend`, triggers `pointerup`, + * `pointerout` and `pointerleave` events. + * + * @param {Event} inEvent The event. + */ +ol.pointer.TouchSource.prototype.touchend = function(inEvent) { + this.dedupSynthMouse_(inEvent); + this.processTouches_(inEvent, this.upOut_); +}; + + +/** + * @private + * @param {Event} browserEvent An event. + * @param {Object} inPointer The inPointer object. + */ +ol.pointer.TouchSource.prototype.upOut_ = function(browserEvent, inPointer) { + this.dispatcher.up(inPointer, browserEvent); + this.dispatcher.out(inPointer, browserEvent); + this.dispatcher.leave(inPointer, browserEvent); + this.cleanUpPointer_(inPointer); +}; + + +/** + * Handler for `touchcancel`, triggers `pointercancel`, + * `pointerout` and `pointerleave` events. + * + * @param {Event} inEvent The in event. + */ +ol.pointer.TouchSource.prototype.touchcancel = function(inEvent) { + this.processTouches_(inEvent, this.cancelOut_); +}; + + +/** + * @private + * @param {Event} browserEvent The event. + * @param {Object} inPointer The in pointer. + */ +ol.pointer.TouchSource.prototype.cancelOut_ = function(browserEvent, inPointer) { + this.dispatcher.cancel(inPointer, browserEvent); + this.dispatcher.out(inPointer, browserEvent); + this.dispatcher.leave(inPointer, browserEvent); + this.cleanUpPointer_(inPointer); +}; + + +/** + * @private + * @param {Object} inPointer The inPointer object. + */ +ol.pointer.TouchSource.prototype.cleanUpPointer_ = function(inPointer) { + delete this.pointerMap[inPointer.pointerId]; + this.removePrimaryPointer_(inPointer); +}; + + +/** + * Prevent synth mouse events from creating pointer events. + * + * @private + * @param {Event} inEvent The in event. + */ +ol.pointer.TouchSource.prototype.dedupSynthMouse_ = function(inEvent) { + var lts = this.mouseSource.lastTouches; + var t = inEvent.changedTouches[0]; + // only the primary finger will synth mouse events + if (this.isPrimaryTouch_(t)) { + // remember x/y of last touch + var lt = [t.clientX, t.clientY]; + lts.push(lt); + + setTimeout(function() { + // remove touch after timeout + ol.array.remove(lts, lt); + }, ol.pointer.TouchSource.DEDUP_TIMEOUT); + } +}; + +// Based on https://github.com/Polymer/PointerEvents + +// Copyright (c) 2013 The Polymer Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +goog.provide('ol.pointer.PointerEventHandler'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); + +goog.require('ol.has'); +goog.require('ol.pointer.EventType'); +goog.require('ol.pointer.MouseSource'); +goog.require('ol.pointer.MsSource'); +goog.require('ol.pointer.NativeSource'); +goog.require('ol.pointer.PointerEvent'); +goog.require('ol.pointer.TouchSource'); + + +/** + * @constructor + * @extends {ol.events.EventTarget} + * @param {Element|HTMLDocument} element Viewport element. + */ +ol.pointer.PointerEventHandler = function(element) { + ol.events.EventTarget.call(this); + + /** + * @const + * @private + * @type {Element|HTMLDocument} + */ + this.element_ = element; + + /** + * @const + * @type {!Object.<string, Event|Object>} + */ + this.pointerMap = {}; + + /** + * @type {Object.<string, function(Event)>} + * @private + */ + this.eventMap_ = {}; + + /** + * @type {Array.<ol.pointer.EventSource>} + * @private + */ + this.eventSourceList_ = []; + + this.registerSources(); +}; +ol.inherits(ol.pointer.PointerEventHandler, ol.events.EventTarget); + + +/** + * Set up the event sources (mouse, touch and native pointers) + * that generate pointer events. + */ +ol.pointer.PointerEventHandler.prototype.registerSources = function() { + if (ol.has.POINTER) { + this.registerSource('native', new ol.pointer.NativeSource(this)); + } else if (ol.has.MSPOINTER) { + this.registerSource('ms', new ol.pointer.MsSource(this)); + } else { + var mouseSource = new ol.pointer.MouseSource(this); + this.registerSource('mouse', mouseSource); + + if (ol.has.TOUCH) { + this.registerSource('touch', + new ol.pointer.TouchSource(this, mouseSource)); + } + } + + // register events on the viewport element + this.register_(); +}; + + +/** + * Add a new event source that will generate pointer events. + * + * @param {string} name A name for the event source + * @param {ol.pointer.EventSource} source The source event. + */ +ol.pointer.PointerEventHandler.prototype.registerSource = function(name, source) { + var s = source; + var newEvents = s.getEvents(); + + if (newEvents) { + newEvents.forEach(function(e) { + var handler = s.getHandlerForEvent(e); + + if (handler) { + this.eventMap_[e] = handler.bind(s); + } + }, this); + this.eventSourceList_.push(s); + } +}; + + +/** + * Set up the events for all registered event sources. + * @private + */ +ol.pointer.PointerEventHandler.prototype.register_ = function() { + var l = this.eventSourceList_.length; + var eventSource; + for (var i = 0; i < l; i++) { + eventSource = this.eventSourceList_[i]; + this.addEvents_(eventSource.getEvents()); + } +}; + + +/** + * Remove all registered events. + * @private + */ +ol.pointer.PointerEventHandler.prototype.unregister_ = function() { + var l = this.eventSourceList_.length; + var eventSource; + for (var i = 0; i < l; i++) { + eventSource = this.eventSourceList_[i]; + this.removeEvents_(eventSource.getEvents()); + } +}; + + +/** + * Calls the right handler for a new event. + * @private + * @param {Event} inEvent Browser event. + */ +ol.pointer.PointerEventHandler.prototype.eventHandler_ = function(inEvent) { + var type = inEvent.type; + var handler = this.eventMap_[type]; + if (handler) { + handler(inEvent); + } +}; + + +/** + * Setup listeners for the given events. + * @private + * @param {Array.<string>} events List of events. + */ +ol.pointer.PointerEventHandler.prototype.addEvents_ = function(events) { + events.forEach(function(eventName) { + ol.events.listen(this.element_, eventName, this.eventHandler_, this); + }, this); +}; + + +/** + * Unregister listeners for the given events. + * @private + * @param {Array.<string>} events List of events. + */ +ol.pointer.PointerEventHandler.prototype.removeEvents_ = function(events) { + events.forEach(function(e) { + ol.events.unlisten(this.element_, e, this.eventHandler_, this); + }, this); +}; + + +/** + * Returns a snapshot of inEvent, with writable properties. + * + * @param {Event} event Browser event. + * @param {Event|Touch} inEvent An event that contains + * properties to copy. + * @return {Object} An object containing shallow copies of + * `inEvent`'s properties. + */ +ol.pointer.PointerEventHandler.prototype.cloneEvent = function(event, inEvent) { + var eventCopy = {}, p; + for (var i = 0, ii = ol.pointer.PointerEventHandler.CLONE_PROPS.length; i < ii; i++) { + p = ol.pointer.PointerEventHandler.CLONE_PROPS[i][0]; + eventCopy[p] = event[p] || inEvent[p] || ol.pointer.PointerEventHandler.CLONE_PROPS[i][1]; + } + + return eventCopy; +}; + + +// EVENTS + + +/** + * Triggers a 'pointerdown' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.down = function(data, event) { + this.fireEvent(ol.pointer.EventType.POINTERDOWN, data, event); +}; + + +/** + * Triggers a 'pointermove' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.move = function(data, event) { + this.fireEvent(ol.pointer.EventType.POINTERMOVE, data, event); +}; + + +/** + * Triggers a 'pointerup' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.up = function(data, event) { + this.fireEvent(ol.pointer.EventType.POINTERUP, data, event); +}; + + +/** + * Triggers a 'pointerenter' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.enter = function(data, event) { + data.bubbles = false; + this.fireEvent(ol.pointer.EventType.POINTERENTER, data, event); +}; + + +/** + * Triggers a 'pointerleave' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.leave = function(data, event) { + data.bubbles = false; + this.fireEvent(ol.pointer.EventType.POINTERLEAVE, data, event); +}; + + +/** + * Triggers a 'pointerover' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.over = function(data, event) { + data.bubbles = true; + this.fireEvent(ol.pointer.EventType.POINTEROVER, data, event); +}; + + +/** + * Triggers a 'pointerout' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.out = function(data, event) { + data.bubbles = true; + this.fireEvent(ol.pointer.EventType.POINTEROUT, data, event); +}; + + +/** + * Triggers a 'pointercancel' event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.cancel = function(data, event) { + this.fireEvent(ol.pointer.EventType.POINTERCANCEL, data, event); +}; + + +/** + * Triggers a combination of 'pointerout' and 'pointerleave' events. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.leaveOut = function(data, event) { + this.out(data, event); + if (!this.contains_(data.target, data.relatedTarget)) { + this.leave(data, event); + } +}; + + +/** + * Triggers a combination of 'pointerover' and 'pointerevents' events. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.enterOver = function(data, event) { + this.over(data, event); + if (!this.contains_(data.target, data.relatedTarget)) { + this.enter(data, event); + } +}; + + +/** + * @private + * @param {Element} container The container element. + * @param {Element} contained The contained element. + * @return {boolean} Returns true if the container element + * contains the other element. + */ +ol.pointer.PointerEventHandler.prototype.contains_ = function(container, contained) { + if (!container || !contained) { + return false; + } + return container.contains(contained); +}; + + +// EVENT CREATION AND TRACKING +/** + * Creates a new Event of type `inType`, based on the information in + * `data`. + * + * @param {string} inType A string representing the type of event to create. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + * @return {ol.pointer.PointerEvent} A PointerEvent of type `inType`. + */ +ol.pointer.PointerEventHandler.prototype.makeEvent = function(inType, data, event) { + return new ol.pointer.PointerEvent(inType, event, data); +}; + + +/** + * Make and dispatch an event in one call. + * @param {string} inType A string representing the type of event. + * @param {Object} data Pointer event data. + * @param {Event} event The event. + */ +ol.pointer.PointerEventHandler.prototype.fireEvent = function(inType, data, event) { + var e = this.makeEvent(inType, data, event); + this.dispatchEvent(e); +}; + + +/** + * Creates a pointer event from a native pointer event + * and dispatches this event. + * @param {Event} event A platform event with a target. + */ +ol.pointer.PointerEventHandler.prototype.fireNativeEvent = function(event) { + var e = this.makeEvent(event.type, event, event); + this.dispatchEvent(e); +}; + + +/** + * Wrap a native mouse event into a pointer event. + * This proxy method is required for the legacy IE support. + * @param {string} eventType The pointer event type. + * @param {Event} event The event. + * @return {ol.pointer.PointerEvent} The wrapped event. + */ +ol.pointer.PointerEventHandler.prototype.wrapMouseEvent = function(eventType, event) { + var pointerEvent = this.makeEvent( + eventType, ol.pointer.MouseSource.prepareEvent(event, this), event); + return pointerEvent; +}; + + +/** + * @inheritDoc + */ +ol.pointer.PointerEventHandler.prototype.disposeInternal = function() { + this.unregister_(); + ol.events.EventTarget.prototype.disposeInternal.call(this); +}; + + +/** + * Properties to copy when cloning an event, with default values. + * @type {Array.<Array>} + */ +ol.pointer.PointerEventHandler.CLONE_PROPS = [ + // MouseEvent + ['bubbles', false], + ['cancelable', false], + ['view', null], + ['detail', null], + ['screenX', 0], + ['screenY', 0], + ['clientX', 0], + ['clientY', 0], + ['ctrlKey', false], + ['altKey', false], + ['shiftKey', false], + ['metaKey', false], + ['button', 0], + ['relatedTarget', null], + // DOM Level 3 + ['buttons', 0], + // PointerEvent + ['pointerId', 0], + ['width', 0], + ['height', 0], + ['pressure', 0], + ['tiltX', 0], + ['tiltY', 0], + ['pointerType', ''], + ['hwTimestamp', 0], + ['isPrimary', false], + // event instance + ['type', ''], + ['target', null], + ['currentTarget', null], + ['which', 0] +]; + +goog.provide('ol.MapBrowserEventHandler'); + +goog.require('ol'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserPointerEvent'); +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); +goog.require('ol.pointer.EventType'); +goog.require('ol.pointer.PointerEventHandler'); + + +/** + * @param {ol.Map} map The map with the viewport to listen to events on. + * @constructor + * @extends {ol.events.EventTarget} + */ +ol.MapBrowserEventHandler = function(map) { + + ol.events.EventTarget.call(this); + + /** + * This is the element that we will listen to the real events on. + * @type {ol.Map} + * @private + */ + this.map_ = map; + + /** + * @type {number} + * @private + */ + this.clickTimeoutId_ = 0; + + /** + * @type {boolean} + * @private + */ + this.dragging_ = false; + + /** + * @type {!Array.<ol.EventsKey>} + * @private + */ + this.dragListenerKeys_ = []; + + /** + * The most recent "down" type event (or null if none have occurred). + * Set on pointerdown. + * @type {ol.pointer.PointerEvent} + * @private + */ + this.down_ = null; + + var element = this.map_.getViewport(); + + /** + * @type {number} + * @private + */ + this.activePointers_ = 0; + + /** + * @type {!Object.<number, boolean>} + * @private + */ + this.trackedTouches_ = {}; + + /** + * Event handler which generates pointer events for + * the viewport element. + * + * @type {ol.pointer.PointerEventHandler} + * @private + */ + this.pointerEventHandler_ = new ol.pointer.PointerEventHandler(element); + + /** + * Event handler which generates pointer events for + * the document (used when dragging). + * + * @type {ol.pointer.PointerEventHandler} + * @private + */ + this.documentPointerEventHandler_ = null; + + /** + * @type {?ol.EventsKey} + * @private + */ + this.pointerdownListenerKey_ = ol.events.listen(this.pointerEventHandler_, + ol.pointer.EventType.POINTERDOWN, + this.handlePointerDown_, this); + + /** + * @type {?ol.EventsKey} + * @private + */ + this.relayedListenerKey_ = ol.events.listen(this.pointerEventHandler_, + ol.pointer.EventType.POINTERMOVE, + this.relayEvent_, this); + +}; +ol.inherits(ol.MapBrowserEventHandler, ol.events.EventTarget); + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private + */ +ol.MapBrowserEventHandler.prototype.emulateClick_ = function(pointerEvent) { + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.CLICK, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + if (this.clickTimeoutId_ !== 0) { + // double-click + clearTimeout(this.clickTimeoutId_); + this.clickTimeoutId_ = 0; + newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.DBLCLICK, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + } else { + // click + this.clickTimeoutId_ = setTimeout(function() { + this.clickTimeoutId_ = 0; + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.SINGLECLICK, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + }.bind(this), 250); + } +}; + + +/** + * Keeps track on how many pointers are currently active. + * + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private + */ +ol.MapBrowserEventHandler.prototype.updateActivePointers_ = function(pointerEvent) { + var event = pointerEvent; + + if (event.type == ol.MapBrowserEvent.EventType.POINTERUP || + event.type == ol.MapBrowserEvent.EventType.POINTERCANCEL) { + delete this.trackedTouches_[event.pointerId]; + } else if (event.type == ol.MapBrowserEvent.EventType.POINTERDOWN) { + this.trackedTouches_[event.pointerId] = true; + } + this.activePointers_ = Object.keys(this.trackedTouches_).length; +}; + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private + */ +ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) { + this.updateActivePointers_(pointerEvent); + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.POINTERUP, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + + // We emulate click events on left mouse button click, touch contact, and pen + // contact. isMouseActionButton returns true in these cases (evt.button is set + // to 0). + // See http://www.w3.org/TR/pointerevents/#button-states + if (!this.dragging_ && this.isMouseActionButton_(pointerEvent)) { + ol.DEBUG && console.assert(this.down_, 'this.down_ must be truthy'); + this.emulateClick_(this.down_); + } + + ol.DEBUG && console.assert(this.activePointers_ >= 0, + 'this.activePointers_ should be equal to or larger than 0'); + if (this.activePointers_ === 0) { + this.dragListenerKeys_.forEach(ol.events.unlistenByKey); + this.dragListenerKeys_.length = 0; + this.dragging_ = false; + this.down_ = null; + this.documentPointerEventHandler_.dispose(); + this.documentPointerEventHandler_ = null; + } +}; + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @return {boolean} If the left mouse button was pressed. + * @private + */ +ol.MapBrowserEventHandler.prototype.isMouseActionButton_ = function(pointerEvent) { + return pointerEvent.button === 0; +}; + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private + */ +ol.MapBrowserEventHandler.prototype.handlePointerDown_ = function(pointerEvent) { + this.updateActivePointers_(pointerEvent); + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.POINTERDOWN, this.map_, pointerEvent); + this.dispatchEvent(newEvent); + + this.down_ = pointerEvent; + + if (this.dragListenerKeys_.length === 0) { + /* Set up a pointer event handler on the `document`, + * which is required when the pointer is moved outside + * the viewport when dragging. + */ + this.documentPointerEventHandler_ = + new ol.pointer.PointerEventHandler(document); + + this.dragListenerKeys_.push( + ol.events.listen(this.documentPointerEventHandler_, + ol.MapBrowserEvent.EventType.POINTERMOVE, + this.handlePointerMove_, this), + ol.events.listen(this.documentPointerEventHandler_, + ol.MapBrowserEvent.EventType.POINTERUP, + this.handlePointerUp_, this), + /* Note that the listener for `pointercancel is set up on + * `pointerEventHandler_` and not `documentPointerEventHandler_` like + * the `pointerup` and `pointermove` listeners. + * + * The reason for this is the following: `TouchSource.vacuumTouches_()` + * issues `pointercancel` events, when there was no `touchend` for a + * `touchstart`. Now, let's say a first `touchstart` is registered on + * `pointerEventHandler_`. The `documentPointerEventHandler_` is set up. + * But `documentPointerEventHandler_` doesn't know about the first + * `touchstart`. If there is no `touchend` for the `touchstart`, we can + * only receive a `touchcancel` from `pointerEventHandler_`, because it is + * only registered there. + */ + ol.events.listen(this.pointerEventHandler_, + ol.MapBrowserEvent.EventType.POINTERCANCEL, + this.handlePointerUp_, this) + ); + } +}; + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private + */ +ol.MapBrowserEventHandler.prototype.handlePointerMove_ = function(pointerEvent) { + // Fix IE10 on windows Surface : When you tap the tablet, it triggers + // multiple pointermove events between pointerdown and pointerup with + // the exact same coordinates of the pointerdown event. To avoid a + // 'false' touchmove event to be dispatched , we test if the pointer + // effectively moved. + if (this.isMoving_(pointerEvent)) { + this.dragging_ = true; + var newEvent = new ol.MapBrowserPointerEvent( + ol.MapBrowserEvent.EventType.POINTERDRAG, this.map_, pointerEvent, + this.dragging_); + this.dispatchEvent(newEvent); + } + + // Some native android browser triggers mousemove events during small period + // of time. See: https://code.google.com/p/android/issues/detail?id=5491 or + // https://code.google.com/p/android/issues/detail?id=19827 + // ex: Galaxy Tab P3110 + Android 4.1.1 + pointerEvent.preventDefault(); +}; + + +/** + * Wrap and relay a pointer event. Note that this requires that the type + * string for the MapBrowserPointerEvent matches the PointerEvent type. + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @private + */ +ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) { + var dragging = !!(this.down_ && this.isMoving_(pointerEvent)); + this.dispatchEvent(new ol.MapBrowserPointerEvent( + pointerEvent.type, this.map_, pointerEvent, dragging)); +}; + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @return {boolean} Is moving. + * @private + */ +ol.MapBrowserEventHandler.prototype.isMoving_ = function(pointerEvent) { + return pointerEvent.clientX != this.down_.clientX || + pointerEvent.clientY != this.down_.clientY; +}; + + +/** + * @inheritDoc + */ +ol.MapBrowserEventHandler.prototype.disposeInternal = function() { + if (this.relayedListenerKey_) { + ol.events.unlistenByKey(this.relayedListenerKey_); + this.relayedListenerKey_ = null; + } + if (this.pointerdownListenerKey_) { + ol.events.unlistenByKey(this.pointerdownListenerKey_); + this.pointerdownListenerKey_ = null; + } + + this.dragListenerKeys_.forEach(ol.events.unlistenByKey); + this.dragListenerKeys_.length = 0; + + if (this.documentPointerEventHandler_) { + this.documentPointerEventHandler_.dispose(); + this.documentPointerEventHandler_ = null; + } + if (this.pointerEventHandler_) { + this.pointerEventHandler_.dispose(); + this.pointerEventHandler_ = null; + } + ol.events.EventTarget.prototype.disposeInternal.call(this); +}; + +goog.provide('ol.Tile'); + +goog.require('ol'); +goog.require('ol.events.EventTarget'); +goog.require('ol.events.EventType'); + + +/** + * @classdesc + * Base class for tiles. + * + * @constructor + * @extends {ol.events.EventTarget} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Tile.State} state State. + */ +ol.Tile = function(tileCoord, state) { + + ol.events.EventTarget.call(this); + + /** + * @type {ol.TileCoord} + */ + this.tileCoord = tileCoord; + + /** + * @protected + * @type {ol.Tile.State} + */ + this.state = state; + + /** + * An "interim" tile for this tile. The interim tile may be used while this + * one is loading, for "smooth" transitions when changing params/dimensions + * on the source. + * @type {ol.Tile} + */ + this.interimTile = null; + + /** + * A key assigned to the tile. This is used by the tile source to determine + * if this tile can effectively be used, or if a new tile should be created + * and this one be used as an interim tile for this new tile. + * @type {string} + */ + this.key = ''; + +}; +ol.inherits(ol.Tile, ol.events.EventTarget); + + +/** + * @protected + */ +ol.Tile.prototype.changed = function() { + this.dispatchEvent(ol.events.EventType.CHANGE); +}; + + +/** + * Get the HTML image element for this tile (may be a Canvas, Image, or Video). + * @abstract + * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image. + */ +ol.Tile.prototype.getImage = function() {}; + + +/** + * @return {string} Key. + */ +ol.Tile.prototype.getKey = function() { + return this.key + '/' + this.tileCoord; +}; + +/** + * Get the interim tile most suitable for rendering using the chain of interim + * tiles. This corresponds to the most recent tile that has been loaded, if no + * such tile exists, the original tile is returned. + * @return {!ol.Tile} Best tile for rendering. + */ +ol.Tile.prototype.getInterimTile = function() { + if (!this.interimTile) { + //empty chain + return this; + } + var tile = this.interimTile; + + // find the first loaded tile and return it. Since the chain is sorted in + // decreasing order of creation time, there is no need to search the remainder + // of the list (all those tiles correspond to older requests and will be + // cleaned up by refreshInterimChain) + do { + if (tile.getState() == ol.Tile.State.LOADED) { + return tile; + } + tile = tile.interimTile; + } while (tile); + + // we can not find a better tile + return this; +}; + +/** + * Goes through the chain of interim tiles and discards sections of the chain + * that are no longer relevant. + */ +ol.Tile.prototype.refreshInterimChain = function() { + if (!this.interimTile) { + return; + } + + var tile = this.interimTile; + var prev = this; + + do { + if (tile.getState() == ol.Tile.State.LOADED) { + //we have a loaded tile, we can discard the rest of the list + //we would could abort any LOADING tile request + //older than this tile (i.e. any LOADING tile following this entry in the chain) + tile.interimTile = null; + break; + } else if (tile.getState() == ol.Tile.State.LOADING) { + //keep this LOADING tile any loaded tiles later in the chain are + //older than this tile, so we're still interested in the request + prev = tile; + } else if (tile.getState() == ol.Tile.State.IDLE) { + //the head of the list is the most current tile, we don't need + //to start any other requests for this chain + prev.interimTile = tile.interimTile; + } else { + prev = tile; + } + tile = prev.interimTile; + } while (tile); +}; + +/** + * Get the tile coordinate for this tile. + * @return {ol.TileCoord} The tile coordinate. + * @api + */ +ol.Tile.prototype.getTileCoord = function() { + return this.tileCoord; +}; + + +/** + * @return {ol.Tile.State} State. + */ +ol.Tile.prototype.getState = function() { + return this.state; +}; + + +/** + * Load the image or retry if loading previously failed. + * Loading is taken care of by the tile queue, and calling this method is + * only needed for preloading or for reloading in case of an error. + * @abstract + * @api + */ +ol.Tile.prototype.load = function() {}; + + +/** + * @enum {number} + */ +ol.Tile.State = { + IDLE: 0, + LOADING: 1, + LOADED: 2, + ERROR: 3, + EMPTY: 4, + ABORT: 5 +}; + +goog.provide('ol.structs.PriorityQueue'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.obj'); + + +/** + * Priority queue. + * + * The implementation is inspired from the Closure Library's Heap class and + * Python's heapq module. + * + * @see http://closure-library.googlecode.com/svn/docs/closure_goog_structs_heap.js.source.html + * @see http://hg.python.org/cpython/file/2.7/Lib/heapq.py + * + * @constructor + * @param {function(T): number} priorityFunction Priority function. + * @param {function(T): string} keyFunction Key function. + * @struct + * @template T + */ +ol.structs.PriorityQueue = function(priorityFunction, keyFunction) { + + /** + * @type {function(T): number} + * @private + */ + this.priorityFunction_ = priorityFunction; + + /** + * @type {function(T): string} + * @private + */ + this.keyFunction_ = keyFunction; + + /** + * @type {Array.<T>} + * @private + */ + this.elements_ = []; + + /** + * @type {Array.<number>} + * @private + */ + this.priorities_ = []; + + /** + * @type {Object.<string, boolean>} + * @private + */ + this.queuedElements_ = {}; + +}; + + +/** + * @const + * @type {number} + */ +ol.structs.PriorityQueue.DROP = Infinity; + + +if (ol.DEBUG) { + /** + * FIXME empty description for jsdoc + */ + ol.structs.PriorityQueue.prototype.assertValid = function() { + var elements = this.elements_; + var priorities = this.priorities_; + var n = elements.length; + console.assert(priorities.length == n); + var i, priority; + for (i = 0; i < (n >> 1) - 1; ++i) { + priority = priorities[i]; + console.assert(priority <= priorities[this.getLeftChildIndex_(i)], + 'priority smaller than or equal to priority of left child (%s <= %s)', + priority, priorities[this.getLeftChildIndex_(i)]); + console.assert(priority <= priorities[this.getRightChildIndex_(i)], + 'priority smaller than or equal to priority of right child (%s <= %s)', + priority, priorities[this.getRightChildIndex_(i)]); + } + }; +} + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.PriorityQueue.prototype.clear = function() { + this.elements_.length = 0; + this.priorities_.length = 0; + ol.obj.clear(this.queuedElements_); +}; + + +/** + * Remove and return the highest-priority element. O(log N). + * @return {T} Element. + */ +ol.structs.PriorityQueue.prototype.dequeue = function() { + var elements = this.elements_; + ol.DEBUG && console.assert(elements.length > 0, + 'must have elements in order to be able to dequeue'); + var priorities = this.priorities_; + var element = elements[0]; + if (elements.length == 1) { + elements.length = 0; + priorities.length = 0; + } else { + elements[0] = elements.pop(); + priorities[0] = priorities.pop(); + this.siftUp_(0); + } + var elementKey = this.keyFunction_(element); + ol.DEBUG && console.assert(elementKey in this.queuedElements_, + 'key %s is not listed as queued', elementKey); + delete this.queuedElements_[elementKey]; + return element; +}; + + +/** + * Enqueue an element. O(log N). + * @param {T} element Element. + * @return {boolean} The element was added to the queue. + */ +ol.structs.PriorityQueue.prototype.enqueue = function(element) { + ol.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_), + 31); // Tried to enqueue an `element` that was already added to the queue + var priority = this.priorityFunction_(element); + if (priority != ol.structs.PriorityQueue.DROP) { + this.elements_.push(element); + this.priorities_.push(priority); + this.queuedElements_[this.keyFunction_(element)] = true; + this.siftDown_(0, this.elements_.length - 1); + return true; + } + return false; +}; + + +/** + * @return {number} Count. + */ +ol.structs.PriorityQueue.prototype.getCount = function() { + return this.elements_.length; +}; + + +/** + * Gets the index of the left child of the node at the given index. + * @param {number} index The index of the node to get the left child for. + * @return {number} The index of the left child. + * @private + */ +ol.structs.PriorityQueue.prototype.getLeftChildIndex_ = function(index) { + return index * 2 + 1; +}; + + +/** + * Gets the index of the right child of the node at the given index. + * @param {number} index The index of the node to get the right child for. + * @return {number} The index of the right child. + * @private + */ +ol.structs.PriorityQueue.prototype.getRightChildIndex_ = function(index) { + return index * 2 + 2; +}; + + +/** + * Gets the index of the parent of the node at the given index. + * @param {number} index The index of the node to get the parent for. + * @return {number} The index of the parent. + * @private + */ +ol.structs.PriorityQueue.prototype.getParentIndex_ = function(index) { + return (index - 1) >> 1; +}; + + +/** + * Make this a heap. O(N). + * @private + */ +ol.structs.PriorityQueue.prototype.heapify_ = function() { + var i; + for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) { + this.siftUp_(i); + } +}; + + +/** + * @return {boolean} Is empty. + */ +ol.structs.PriorityQueue.prototype.isEmpty = function() { + return this.elements_.length === 0; +}; + + +/** + * @param {string} key Key. + * @return {boolean} Is key queued. + */ +ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) { + return key in this.queuedElements_; +}; + + +/** + * @param {T} element Element. + * @return {boolean} Is queued. + */ +ol.structs.PriorityQueue.prototype.isQueued = function(element) { + return this.isKeyQueued(this.keyFunction_(element)); +}; + + +/** + * @param {number} index The index of the node to move down. + * @private + */ +ol.structs.PriorityQueue.prototype.siftUp_ = function(index) { + var elements = this.elements_; + var priorities = this.priorities_; + var count = elements.length; + var element = elements[index]; + var priority = priorities[index]; + var startIndex = index; + + while (index < (count >> 1)) { + var lIndex = this.getLeftChildIndex_(index); + var rIndex = this.getRightChildIndex_(index); + + var smallerChildIndex = rIndex < count && + priorities[rIndex] < priorities[lIndex] ? + rIndex : lIndex; + + elements[index] = elements[smallerChildIndex]; + priorities[index] = priorities[smallerChildIndex]; + index = smallerChildIndex; + } + + elements[index] = element; + priorities[index] = priority; + this.siftDown_(startIndex, index); +}; + + +/** + * @param {number} startIndex The index of the root. + * @param {number} index The index of the node to move up. + * @private + */ +ol.structs.PriorityQueue.prototype.siftDown_ = function(startIndex, index) { + var elements = this.elements_; + var priorities = this.priorities_; + var element = elements[index]; + var priority = priorities[index]; + + while (index > startIndex) { + var parentIndex = this.getParentIndex_(index); + if (priorities[parentIndex] > priority) { + elements[index] = elements[parentIndex]; + priorities[index] = priorities[parentIndex]; + index = parentIndex; + } else { + break; + } + } + elements[index] = element; + priorities[index] = priority; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.PriorityQueue.prototype.reprioritize = function() { + var priorityFunction = this.priorityFunction_; + var elements = this.elements_; + var priorities = this.priorities_; + var index = 0; + var n = elements.length; + var element, i, priority; + for (i = 0; i < n; ++i) { + element = elements[i]; + priority = priorityFunction(element); + if (priority == ol.structs.PriorityQueue.DROP) { + delete this.queuedElements_[this.keyFunction_(element)]; + } else { + priorities[index] = priority; + elements[index++] = element; + } + } + elements.length = index; + priorities.length = index; + this.heapify_(); +}; + +goog.provide('ol.TileQueue'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.structs.PriorityQueue'); + + +/** + * @constructor + * @extends {ol.structs.PriorityQueue.<Array>} + * @param {ol.TilePriorityFunction} tilePriorityFunction + * Tile priority function. + * @param {function(): ?} tileChangeCallback + * Function called on each tile change event. + * @struct + */ +ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) { + + ol.structs.PriorityQueue.call( + this, + /** + * @param {Array} element Element. + * @return {number} Priority. + */ + function(element) { + return tilePriorityFunction.apply(null, element); + }, + /** + * @param {Array} element Element. + * @return {string} Key. + */ + function(element) { + return /** @type {ol.Tile} */ (element[0]).getKey(); + }); + + /** + * @private + * @type {function(): ?} + */ + this.tileChangeCallback_ = tileChangeCallback; + + /** + * @private + * @type {number} + */ + this.tilesLoading_ = 0; + + /** + * @private + * @type {!Object.<string,boolean>} + */ + this.tilesLoadingKeys_ = {}; + +}; +ol.inherits(ol.TileQueue, ol.structs.PriorityQueue); + + +/** + * @inheritDoc + */ +ol.TileQueue.prototype.enqueue = function(element) { + var added = ol.structs.PriorityQueue.prototype.enqueue.call(this, element); + if (added) { + var tile = element[0]; + ol.events.listen(tile, ol.events.EventType.CHANGE, + this.handleTileChange, this); + } + return added; +}; + + +/** + * @return {number} Number of tiles loading. + */ +ol.TileQueue.prototype.getTilesLoading = function() { + return this.tilesLoading_; +}; + + +/** + * @param {ol.events.Event} event Event. + * @protected + */ +ol.TileQueue.prototype.handleTileChange = function(event) { + var tile = /** @type {ol.Tile} */ (event.target); + var state = tile.getState(); + if (state === ol.Tile.State.LOADED || state === ol.Tile.State.ERROR || + state === ol.Tile.State.EMPTY || state === ol.Tile.State.ABORT) { + ol.events.unlisten(tile, ol.events.EventType.CHANGE, + this.handleTileChange, this); + var tileKey = tile.getKey(); + if (tileKey in this.tilesLoadingKeys_) { + delete this.tilesLoadingKeys_[tileKey]; + --this.tilesLoading_; + } + this.tileChangeCallback_(); + } + ol.DEBUG && console.assert(Object.keys(this.tilesLoadingKeys_).length === this.tilesLoading_); +}; + + +/** + * @param {number} maxTotalLoading Maximum number tiles to load simultaneously. + * @param {number} maxNewLoads Maximum number of new tiles to load. + */ +ol.TileQueue.prototype.loadMoreTiles = function(maxTotalLoading, maxNewLoads) { + var newLoads = 0; + var tile, tileKey; + while (this.tilesLoading_ < maxTotalLoading && newLoads < maxNewLoads && + this.getCount() > 0) { + tile = /** @type {ol.Tile} */ (this.dequeue()[0]); + tileKey = tile.getKey(); + if (tile.getState() === ol.Tile.State.IDLE && !(tileKey in this.tilesLoadingKeys_)) { + this.tilesLoadingKeys_[tileKey] = true; + ++this.tilesLoading_; + ++newLoads; + tile.load(); + } + ol.DEBUG && console.assert(Object.keys(this.tilesLoadingKeys_).length === this.tilesLoading_); + } +}; + +goog.provide('ol.Kinetic'); + + +/** + * @classdesc + * Implementation of inertial deceleration for map movement. + * + * @constructor + * @param {number} decay Rate of decay (must be negative). + * @param {number} minVelocity Minimum velocity (pixels/millisecond). + * @param {number} delay Delay to consider to calculate the kinetic + * initial values (milliseconds). + * @struct + * @api + */ +ol.Kinetic = function(decay, minVelocity, delay) { + + /** + * @private + * @type {number} + */ + this.decay_ = decay; + + /** + * @private + * @type {number} + */ + this.minVelocity_ = minVelocity; + + /** + * @private + * @type {number} + */ + this.delay_ = delay; + + /** + * @private + * @type {Array.<number>} + */ + this.points_ = []; + + /** + * @private + * @type {number} + */ + this.angle_ = 0; + + /** + * @private + * @type {number} + */ + this.initialVelocity_ = 0; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.Kinetic.prototype.begin = function() { + this.points_.length = 0; + this.angle_ = 0; + this.initialVelocity_ = 0; +}; + + +/** + * @param {number} x X. + * @param {number} y Y. + */ +ol.Kinetic.prototype.update = function(x, y) { + this.points_.push(x, y, Date.now()); +}; + + +/** + * @return {boolean} Whether we should do kinetic animation. + */ +ol.Kinetic.prototype.end = function() { + if (this.points_.length < 6) { + // at least 2 points are required (i.e. there must be at least 6 elements + // in the array) + return false; + } + var delay = Date.now() - this.delay_; + var lastIndex = this.points_.length - 3; + if (this.points_[lastIndex + 2] < delay) { + // the last tracked point is too old, which means that the user stopped + // panning before releasing the map + return false; + } + + // get the first point which still falls into the delay time + var firstIndex = lastIndex - 3; + while (firstIndex > 0 && this.points_[firstIndex + 2] > delay) { + firstIndex -= 3; + } + var duration = this.points_[lastIndex + 2] - this.points_[firstIndex + 2]; + var dx = this.points_[lastIndex] - this.points_[firstIndex]; + var dy = this.points_[lastIndex + 1] - this.points_[firstIndex + 1]; + this.angle_ = Math.atan2(dy, dx); + this.initialVelocity_ = Math.sqrt(dx * dx + dy * dy) / duration; + return this.initialVelocity_ > this.minVelocity_; +}; + + +/** + * @private + * @return {number} Duration of animation (milliseconds). + */ +ol.Kinetic.prototype.getDuration_ = function() { + return Math.log(this.minVelocity_ / this.initialVelocity_) / this.decay_; +}; + + +/** + * @return {number} Total distance travelled (pixels). + */ +ol.Kinetic.prototype.getDistance = function() { + return (this.minVelocity_ - this.initialVelocity_) / this.decay_; +}; + + +/** + * @return {number} Angle of the kinetic panning animation (radians). + */ +ol.Kinetic.prototype.getAngle = function() { + return this.angle_; +}; + +// FIXME factor out key precondition (shift et. al) + +goog.provide('ol.interaction.Interaction'); + +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.easing'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * User actions that change the state of the map. Some are similar to controls, + * but are not associated with a DOM element. + * For example, {@link ol.interaction.KeyboardZoom} is functionally the same as + * {@link ol.control.Zoom}, but triggered by a keyboard event not a button + * element event. + * Although interactions do not have a DOM element, some of them do render + * vectors and so are visible on the screen. + * + * @constructor + * @param {olx.interaction.InteractionOptions} options Options. + * @extends {ol.Object} + * @api + */ +ol.interaction.Interaction = function(options) { + + ol.Object.call(this); + + /** + * @private + * @type {ol.Map} + */ + this.map_ = null; + + this.setActive(true); + + /** + * @type {function(ol.MapBrowserEvent):boolean} + */ + this.handleEvent = options.handleEvent; + +}; +ol.inherits(ol.interaction.Interaction, ol.Object); + + +/** + * Return whether the interaction is currently active. + * @return {boolean} `true` if the interaction is active, `false` otherwise. + * @observable + * @api + */ +ol.interaction.Interaction.prototype.getActive = function() { + return /** @type {boolean} */ ( + this.get(ol.interaction.Interaction.Property.ACTIVE)); +}; + + +/** + * Get the map associated with this interaction. + * @return {ol.Map} Map. + * @api + */ +ol.interaction.Interaction.prototype.getMap = function() { + return this.map_; +}; + + +/** + * Activate or deactivate the interaction. + * @param {boolean} active Active. + * @observable + * @api + */ +ol.interaction.Interaction.prototype.setActive = function(active) { + this.set(ol.interaction.Interaction.Property.ACTIVE, active); +}; + + +/** + * Remove the interaction from its current map and attach it to the new map. + * Subclasses may set up event handlers to get notified about changes to + * the map here. + * @param {ol.Map} map Map. + */ +ol.interaction.Interaction.prototype.setMap = function(map) { + this.map_ = map; +}; + + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {ol.Coordinate} delta Delta. + * @param {number=} opt_duration Duration. + */ +ol.interaction.Interaction.pan = function(map, view, delta, opt_duration) { + var currentCenter = view.getCenter(); + if (currentCenter) { + var center = view.constrainCenter( + [currentCenter[0] + delta[0], currentCenter[1] + delta[1]]); + if (opt_duration) { + view.animate({ + duration: opt_duration, + easing: ol.easing.linear, + center: center + }); + } else { + view.setCenter(center); + } + } +}; + + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} rotation Rotation. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + */ +ol.interaction.Interaction.rotate = function(map, view, rotation, opt_anchor, opt_duration) { + rotation = view.constrainRotation(rotation, 0); + ol.interaction.Interaction.rotateWithoutConstraints( + map, view, rotation, opt_anchor, opt_duration); +}; + + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} rotation Rotation. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + */ +ol.interaction.Interaction.rotateWithoutConstraints = function(map, view, rotation, opt_anchor, opt_duration) { + if (rotation !== undefined) { + var currentRotation = view.getRotation(); + var currentCenter = view.getCenter(); + if (currentRotation !== undefined && currentCenter && opt_duration > 0) { + view.animate({ + rotation: rotation, + anchor: opt_anchor, + duration: opt_duration, + easing: ol.easing.easeOut + }); + } else { + view.rotate(rotation, opt_anchor); + } + } +}; + + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} resolution Resolution to go to. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + * @param {number=} opt_direction Zooming direction; > 0 indicates + * zooming out, in which case the constraints system will select + * the largest nearest resolution; < 0 indicates zooming in, in + * which case the constraints system will select the smallest + * nearest resolution; == 0 indicates that the zooming direction + * is unknown/not relevant, in which case the constraints system + * will select the nearest resolution. If not defined 0 is + * assumed. + */ +ol.interaction.Interaction.zoom = function(map, view, resolution, opt_anchor, opt_duration, opt_direction) { + resolution = view.constrainResolution(resolution, 0, opt_direction); + ol.interaction.Interaction.zoomWithoutConstraints( + map, view, resolution, opt_anchor, opt_duration); +}; + + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number} delta Delta from previous zoom level. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + */ +ol.interaction.Interaction.zoomByDelta = function(map, view, delta, opt_anchor, opt_duration) { + var currentResolution = view.getResolution(); + var resolution = view.constrainResolution(currentResolution, delta, 0); + ol.interaction.Interaction.zoomWithoutConstraints( + map, view, resolution, opt_anchor, opt_duration); +}; + + +/** + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} resolution Resolution to go to. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + */ +ol.interaction.Interaction.zoomWithoutConstraints = function(map, view, resolution, opt_anchor, opt_duration) { + if (resolution) { + var currentResolution = view.getResolution(); + var currentCenter = view.getCenter(); + if (currentResolution !== undefined && currentCenter && + resolution !== currentResolution && opt_duration) { + view.animate({ + resolution: resolution, + anchor: opt_anchor, + duration: opt_duration, + easing: ol.easing.easeOut + }); + } else { + if (opt_anchor) { + var center = view.calculateCenterZoom(resolution, opt_anchor); + view.setCenter(center); + } + view.setResolution(resolution); + } + } +}; + + +/** + * @enum {string} + */ +ol.interaction.Interaction.Property = { + ACTIVE: 'active' +}; + +goog.provide('ol.interaction.DoubleClickZoom'); + +goog.require('ol'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.interaction.Interaction'); + + +/** + * @classdesc + * Allows the user to zoom by double-clicking on the map. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.DoubleClickZoomOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DoubleClickZoom = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {number} + */ + this.delta_ = options.delta ? options.delta : 1; + + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.DoubleClickZoom.handleEvent + }); + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; + +}; +ol.inherits(ol.interaction.DoubleClickZoom, ol.interaction.Interaction); + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} (if it was a + * doubleclick) and eventually zooms the map. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.DoubleClickZoom} + * @api + */ +ol.interaction.DoubleClickZoom.handleEvent = function(mapBrowserEvent) { + var stopEvent = false; + var browserEvent = mapBrowserEvent.originalEvent; + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK) { + var map = mapBrowserEvent.map; + var anchor = mapBrowserEvent.coordinate; + var delta = browserEvent.shiftKey ? -this.delta_ : this.delta_; + var view = map.getView(); + ol.interaction.Interaction.zoomByDelta( + map, view, delta, anchor, this.duration_); + mapBrowserEvent.preventDefault(); + stopEvent = true; + } + return !stopEvent; +}; + +goog.provide('ol.events.condition'); + +goog.require('ol.MapBrowserEvent'); +goog.require('ol.asserts'); +goog.require('ol.functions'); +goog.require('ol.has'); + + +/** + * Return `true` if only the alt-key is pressed, `false` otherwise (e.g. when + * additionally the shift-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the alt key is pressed. + * @api stable + */ +ol.events.condition.altKeyOnly = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + originalEvent.altKey && + !(originalEvent.metaKey || originalEvent.ctrlKey) && + !originalEvent.shiftKey); +}; + + +/** + * Return `true` if only the alt-key and shift-key is pressed, `false` otherwise + * (e.g. when additionally the platform-modifier-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the alt and shift keys are pressed. + * @api stable + */ +ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + originalEvent.altKey && + !(originalEvent.metaKey || originalEvent.ctrlKey) && + originalEvent.shiftKey); +}; + + +/** + * Return always true. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True. + * @function + * @api stable + */ +ol.events.condition.always = ol.functions.TRUE; + + +/** + * Return `true` if the event is a `click` event, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event is a map `click` event. + * @api stable + */ +ol.events.condition.click = function(mapBrowserEvent) { + return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.CLICK; +}; + + +/** + * Return `true` if the event has an "action"-producing mouse button. + * + * By definition, this includes left-click on windows/linux, and left-click + * without the ctrl key on Macs. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} The result. + */ +ol.events.condition.mouseActionButton = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return originalEvent.button == 0 && + !(ol.has.WEBKIT && ol.has.MAC && originalEvent.ctrlKey); +}; + + +/** + * Return always false. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} False. + * @function + * @api stable + */ +ol.events.condition.never = ol.functions.FALSE; + + +/** + * Return `true` if the browser event is a `pointermove` event, `false` + * otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the browser event is a `pointermove` event. + * @api + */ +ol.events.condition.pointerMove = function(mapBrowserEvent) { + return mapBrowserEvent.type == 'pointermove'; +}; + + +/** + * Return `true` if the event is a map `singleclick` event, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event is a map `singleclick` event. + * @api stable + */ +ol.events.condition.singleClick = function(mapBrowserEvent) { + return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK; +}; + + +/** + * Return `true` if the event is a map `dblclick` event, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event is a map `dblclick` event. + * @api stable + */ +ol.events.condition.doubleClick = function(mapBrowserEvent) { + return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK; +}; + + +/** + * Return `true` if no modifier key (alt-, shift- or platform-modifier-key) is + * pressed. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True only if there no modifier keys are pressed. + * @api stable + */ +ol.events.condition.noModifierKeys = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + !originalEvent.altKey && + !(originalEvent.metaKey || originalEvent.ctrlKey) && + !originalEvent.shiftKey); +}; + + +/** + * Return `true` if only the platform-modifier-key (the meta-key on Mac, + * ctrl-key otherwise) is pressed, `false` otherwise (e.g. when additionally + * the shift-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the platform modifier key is pressed. + * @api stable + */ +ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + !originalEvent.altKey && + (ol.has.MAC ? originalEvent.metaKey : originalEvent.ctrlKey) && + !originalEvent.shiftKey); +}; + + +/** + * Return `true` if only the shift-key is pressed, `false` otherwise (e.g. when + * additionally the alt-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the shift key is pressed. + * @api stable + */ +ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) { + var originalEvent = mapBrowserEvent.originalEvent; + return ( + !originalEvent.altKey && + !(originalEvent.metaKey || originalEvent.ctrlKey) && + originalEvent.shiftKey); +}; + + +/** + * Return `true` if the target element is not editable, i.e. not a `<input>`-, + * `<select>`- or `<textarea>`-element, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True only if the target element is not editable. + * @api + */ +ol.events.condition.targetNotEditable = function(mapBrowserEvent) { + var target = mapBrowserEvent.originalEvent.target; + var tagName = target.tagName; + return ( + tagName !== 'INPUT' && + tagName !== 'SELECT' && + tagName !== 'TEXTAREA'); +}; + + +/** + * Return `true` if the event originates from a mouse device. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event originates from a mouse device. + * @api stable + */ +ol.events.condition.mouseOnly = function(mapBrowserEvent) { + ol.asserts.assert(mapBrowserEvent.pointerEvent, 56); // mapBrowserEvent must originate from a pointer event + // see http://www.w3.org/TR/pointerevents/#widl-PointerEvent-pointerType + return /** @type {ol.MapBrowserEvent} */ (mapBrowserEvent).pointerEvent.pointerType == 'mouse'; +}; + + +/** + * Return `true` if the event originates from a primary pointer in + * contact with the surface or if the left mouse button is pressed. + * @see http://www.w3.org/TR/pointerevents/#button-states + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event originates from a primary pointer. + * @api + */ +ol.events.condition.primaryAction = function(mapBrowserEvent) { + var pointerEvent = mapBrowserEvent.pointerEvent; + return pointerEvent.isPrimary && pointerEvent.button === 0; +}; + +goog.provide('ol.interaction.Pointer'); + +goog.require('ol'); +goog.require('ol.functions'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserPointerEvent'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.obj'); + + +/** + * @classdesc + * Base class that calls user-defined functions on `down`, `move` and `up` + * events. This class also manages "drag sequences". + * + * When the `handleDownEvent` user function returns `true` a drag sequence is + * started. During a drag sequence the `handleDragEvent` user function is + * called on `move` events. The drag sequence ends when the `handleUpEvent` + * user function is called and returns `false`. + * + * @constructor + * @param {olx.interaction.PointerOptions=} opt_options Options. + * @extends {ol.interaction.Interaction} + * @api + */ +ol.interaction.Pointer = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var handleEvent = options.handleEvent ? + options.handleEvent : ol.interaction.Pointer.handleEvent; + + ol.interaction.Interaction.call(this, { + handleEvent: handleEvent + }); + + /** + * @type {function(ol.MapBrowserPointerEvent):boolean} + * @private + */ + this.handleDownEvent_ = options.handleDownEvent ? + options.handleDownEvent : ol.interaction.Pointer.handleDownEvent; + + /** + * @type {function(ol.MapBrowserPointerEvent)} + * @private + */ + this.handleDragEvent_ = options.handleDragEvent ? + options.handleDragEvent : ol.interaction.Pointer.handleDragEvent; + + /** + * @type {function(ol.MapBrowserPointerEvent)} + * @private + */ + this.handleMoveEvent_ = options.handleMoveEvent ? + options.handleMoveEvent : ol.interaction.Pointer.handleMoveEvent; + + /** + * @type {function(ol.MapBrowserPointerEvent):boolean} + * @private + */ + this.handleUpEvent_ = options.handleUpEvent ? + options.handleUpEvent : ol.interaction.Pointer.handleUpEvent; + + /** + * @type {boolean} + * @protected + */ + this.handlingDownUpSequence = false; + + /** + * @type {Object.<number, ol.pointer.PointerEvent>} + * @private + */ + this.trackedPointers_ = {}; + + /** + * @type {Array.<ol.pointer.PointerEvent>} + * @protected + */ + this.targetPointers = []; + +}; +ol.inherits(ol.interaction.Pointer, ol.interaction.Interaction); + + +/** + * @param {Array.<ol.pointer.PointerEvent>} pointerEvents List of events. + * @return {ol.Pixel} Centroid pixel. + */ +ol.interaction.Pointer.centroid = function(pointerEvents) { + var length = pointerEvents.length; + var clientX = 0; + var clientY = 0; + for (var i = 0; i < length; i++) { + clientX += pointerEvents[i].clientX; + clientY += pointerEvents[i].clientY; + } + return [clientX / length, clientY / length]; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Whether the event is a pointerdown, pointerdrag + * or pointerup event. + * @private + */ +ol.interaction.Pointer.prototype.isPointerDraggingEvent_ = function(mapBrowserEvent) { + var type = mapBrowserEvent.type; + return ( + type === ol.MapBrowserEvent.EventType.POINTERDOWN || + type === ol.MapBrowserEvent.EventType.POINTERDRAG || + type === ol.MapBrowserEvent.EventType.POINTERUP); +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @private + */ +ol.interaction.Pointer.prototype.updateTrackedPointers_ = function(mapBrowserEvent) { + if (this.isPointerDraggingEvent_(mapBrowserEvent)) { + var event = mapBrowserEvent.pointerEvent; + + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) { + delete this.trackedPointers_[event.pointerId]; + } else if (mapBrowserEvent.type == + ol.MapBrowserEvent.EventType.POINTERDOWN) { + this.trackedPointers_[event.pointerId] = event; + } else if (event.pointerId in this.trackedPointers_) { + // update only when there was a pointerdown event for this pointer + this.trackedPointers_[event.pointerId] = event; + } + this.targetPointers = ol.obj.getValues(this.trackedPointers_); + } +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.Pointer} + */ +ol.interaction.Pointer.handleDragEvent = ol.nullFunction; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Capture dragging. + * @this {ol.interaction.Pointer} + */ +ol.interaction.Pointer.handleUpEvent = ol.functions.FALSE; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Capture dragging. + * @this {ol.interaction.Pointer} + */ +ol.interaction.Pointer.handleDownEvent = ol.functions.FALSE; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.Pointer} + */ +ol.interaction.Pointer.handleMoveEvent = ol.nullFunction; + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} and may call into + * other functions, if event sequences like e.g. 'drag' or 'down-up' etc. are + * detected. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.Pointer} + * @api + */ +ol.interaction.Pointer.handleEvent = function(mapBrowserEvent) { + if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) { + return true; + } + + var stopEvent = false; + this.updateTrackedPointers_(mapBrowserEvent); + if (this.handlingDownUpSequence) { + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERDRAG) { + this.handleDragEvent_(mapBrowserEvent); + } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) { + this.handlingDownUpSequence = this.handleUpEvent_(mapBrowserEvent); + } + } + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERDOWN) { + var handled = this.handleDownEvent_(mapBrowserEvent); + this.handlingDownUpSequence = handled; + stopEvent = this.shouldStopEvent(handled); + } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE) { + this.handleMoveEvent_(mapBrowserEvent); + } + return !stopEvent; +}; + + +/** + * This method is used to determine if "down" events should be propagated to + * other interactions or should be stopped. + * + * The method receives the return code of the "handleDownEvent" function. + * + * By default this function is the "identity" function. It's overidden in + * child classes. + * + * @param {boolean} handled Was the event handled by the interaction? + * @return {boolean} Should the event be stopped? + * @protected + */ +ol.interaction.Pointer.prototype.shouldStopEvent = function(handled) { + return handled; +}; + +goog.provide('ol.interaction.DragPan'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.coordinate'); +goog.require('ol.easing'); +goog.require('ol.events.condition'); +goog.require('ol.functions'); +goog.require('ol.interaction.Pointer'); + + +/** + * @classdesc + * Allows the user to pan the map by dragging the map. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.DragPanOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragPan = function(opt_options) { + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.DragPan.handleDownEvent_, + handleDragEvent: ol.interaction.DragPan.handleDragEvent_, + handleUpEvent: ol.interaction.DragPan.handleUpEvent_ + }); + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {ol.Kinetic|undefined} + */ + this.kinetic_ = options.kinetic; + + /** + * @type {ol.Pixel} + */ + this.lastCentroid = null; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.noModifierKeys; + + /** + * @private + * @type {boolean} + */ + this.noKinetic_ = false; + +}; +ol.inherits(ol.interaction.DragPan, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.DragPan} + * @private + */ +ol.interaction.DragPan.handleDragEvent_ = function(mapBrowserEvent) { + ol.DEBUG && console.assert(this.targetPointers.length >= 1, + 'the length of this.targetPointers should be more than 1'); + var centroid = + ol.interaction.Pointer.centroid(this.targetPointers); + if (this.kinetic_) { + this.kinetic_.update(centroid[0], centroid[1]); + } + if (this.lastCentroid) { + var deltaX = this.lastCentroid[0] - centroid[0]; + var deltaY = centroid[1] - this.lastCentroid[1]; + var map = mapBrowserEvent.map; + var view = map.getView(); + var viewState = view.getState(); + var center = [deltaX, deltaY]; + ol.coordinate.scale(center, viewState.resolution); + ol.coordinate.rotate(center, viewState.rotation); + ol.coordinate.add(center, viewState.center); + center = view.constrainCenter(center); + view.setCenter(center); + } + this.lastCentroid = centroid; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.DragPan} + * @private + */ +ol.interaction.DragPan.handleUpEvent_ = function(mapBrowserEvent) { + var map = mapBrowserEvent.map; + var view = map.getView(); + if (this.targetPointers.length === 0) { + if (!this.noKinetic_ && this.kinetic_ && this.kinetic_.end()) { + var distance = this.kinetic_.getDistance(); + var angle = this.kinetic_.getAngle(); + var center = /** @type {!ol.Coordinate} */ (view.getCenter()); + var centerpx = map.getPixelFromCoordinate(center); + var dest = map.getCoordinateFromPixel([ + centerpx[0] - distance * Math.cos(angle), + centerpx[1] - distance * Math.sin(angle) + ]); + view.animate({ + center: view.constrainCenter(dest), + duration: 500, + easing: ol.easing.easeOut + }); + } + view.setHint(ol.View.Hint.INTERACTING, -1); + return false; + } else { + this.lastCentroid = null; + return true; + } +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.DragPan} + * @private + */ +ol.interaction.DragPan.handleDownEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length > 0 && this.condition_(mapBrowserEvent)) { + var map = mapBrowserEvent.map; + var view = map.getView(); + this.lastCentroid = null; + if (!this.handlingDownUpSequence) { + view.setHint(ol.View.Hint.INTERACTING, 1); + } + // stop any current animation + view.setCenter(mapBrowserEvent.frameState.viewState.center); + if (this.kinetic_) { + this.kinetic_.begin(); + } + // No kinetic as soon as more than one pointer on the screen is + // detected. This is to prevent nasty pans after pinch. + this.noKinetic_ = this.targetPointers.length > 1; + return true; + } else { + return false; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.DragPan.prototype.shouldStopEvent = ol.functions.FALSE; + +goog.provide('ol.interaction.DragRotate'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.events.condition'); +goog.require('ol.functions'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); + + +/** + * @classdesc + * Allows the user to rotate the map by clicking and dragging on the map, + * normally combined with an {@link ol.events.condition} that limits + * it to when the alt and shift keys are held down. + * + * This interaction is only supported for mouse devices. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.DragRotateOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragRotate = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.DragRotate.handleDownEvent_, + handleDragEvent: ol.interaction.DragRotate.handleDragEvent_, + handleUpEvent: ol.interaction.DragRotate.handleUpEvent_ + }); + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.altShiftKeysOnly; + + /** + * @private + * @type {number|undefined} + */ + this.lastAngle_ = undefined; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; +}; +ol.inherits(ol.interaction.DragRotate, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.DragRotate} + * @private + */ +ol.interaction.DragRotate.handleDragEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return; + } + + var map = mapBrowserEvent.map; + var size = map.getSize(); + var offset = mapBrowserEvent.pixel; + var theta = + Math.atan2(size[1] / 2 - offset[1], offset[0] - size[0] / 2); + if (this.lastAngle_ !== undefined) { + var delta = theta - this.lastAngle_; + var view = map.getView(); + var rotation = view.getRotation(); + ol.interaction.Interaction.rotateWithoutConstraints( + map, view, rotation - delta); + } + this.lastAngle_ = theta; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.DragRotate} + * @private + */ +ol.interaction.DragRotate.handleUpEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return true; + } + + var map = mapBrowserEvent.map; + var view = map.getView(); + view.setHint(ol.View.Hint.INTERACTING, -1); + var rotation = view.getRotation(); + ol.interaction.Interaction.rotate(map, view, rotation, + undefined, this.duration_); + return false; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.DragRotate} + * @private + */ +ol.interaction.DragRotate.handleDownEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return false; + } + + if (ol.events.condition.mouseActionButton(mapBrowserEvent) && + this.condition_(mapBrowserEvent)) { + var map = mapBrowserEvent.map; + map.getView().setHint(ol.View.Hint.INTERACTING, 1); + this.lastAngle_ = undefined; + return true; + } else { + return false; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.DragRotate.prototype.shouldStopEvent = ol.functions.FALSE; + +// FIXME add rotation + +goog.provide('ol.render.Box'); + +goog.require('ol'); +goog.require('ol.Disposable'); +goog.require('ol.geom.Polygon'); + + +/** + * @constructor + * @extends {ol.Disposable} + * @param {string} className CSS class name. + */ +ol.render.Box = function(className) { + + /** + * @type {ol.geom.Polygon} + * @private + */ + this.geometry_ = null; + + /** + * @type {HTMLDivElement} + * @private + */ + this.element_ = /** @type {HTMLDivElement} */ (document.createElement('div')); + this.element_.style.position = 'absolute'; + this.element_.className = 'ol-box ' + className; + + /** + * @private + * @type {ol.Map} + */ + this.map_ = null; + + /** + * @private + * @type {ol.Pixel} + */ + this.startPixel_ = null; + + /** + * @private + * @type {ol.Pixel} + */ + this.endPixel_ = null; + +}; +ol.inherits(ol.render.Box, ol.Disposable); + + +/** + * @inheritDoc + */ +ol.render.Box.prototype.disposeInternal = function() { + this.setMap(null); +}; + + +/** + * @private + */ +ol.render.Box.prototype.render_ = function() { + var startPixel = this.startPixel_; + var endPixel = this.endPixel_; + var px = 'px'; + var style = this.element_.style; + style.left = Math.min(startPixel[0], endPixel[0]) + px; + style.top = Math.min(startPixel[1], endPixel[1]) + px; + style.width = Math.abs(endPixel[0] - startPixel[0]) + px; + style.height = Math.abs(endPixel[1] - startPixel[1]) + px; +}; + + +/** + * @param {ol.Map} map Map. + */ +ol.render.Box.prototype.setMap = function(map) { + if (this.map_) { + this.map_.getOverlayContainer().removeChild(this.element_); + var style = this.element_.style; + style.left = style.top = style.width = style.height = 'inherit'; + } + this.map_ = map; + if (this.map_) { + this.map_.getOverlayContainer().appendChild(this.element_); + } +}; + + +/** + * @param {ol.Pixel} startPixel Start pixel. + * @param {ol.Pixel} endPixel End pixel. + */ +ol.render.Box.prototype.setPixels = function(startPixel, endPixel) { + this.startPixel_ = startPixel; + this.endPixel_ = endPixel; + this.createOrUpdateGeometry(); + this.render_(); +}; + + +/** + * Creates or updates the cached geometry. + */ +ol.render.Box.prototype.createOrUpdateGeometry = function() { + var startPixel = this.startPixel_; + var endPixel = this.endPixel_; + var pixels = [ + startPixel, + [startPixel[0], endPixel[1]], + endPixel, + [endPixel[0], startPixel[1]] + ]; + var coordinates = pixels.map(this.map_.getCoordinateFromPixel, this.map_); + // close the polygon + coordinates[4] = coordinates[0].slice(); + if (!this.geometry_) { + this.geometry_ = new ol.geom.Polygon([coordinates]); + } else { + this.geometry_.setCoordinates([coordinates]); + } +}; + + +/** + * @return {ol.geom.Polygon} Geometry. + */ +ol.render.Box.prototype.getGeometry = function() { + return this.geometry_; +}; + +// FIXME draw drag box +goog.provide('ol.interaction.DragBox'); + +goog.require('ol.events.Event'); +goog.require('ol'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.render.Box'); + + +/** + * @const + * @type {number} + */ +ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED = + ol.DRAG_BOX_HYSTERESIS_PIXELS * + ol.DRAG_BOX_HYSTERESIS_PIXELS; + + +/** + * @classdesc + * Allows the user to draw a vector box by clicking and dragging on the map, + * normally combined with an {@link ol.events.condition} that limits + * it to when the shift or other key is held down. This is used, for example, + * for zooming to a specific area of the map + * (see {@link ol.interaction.DragZoom} and + * {@link ol.interaction.DragRotateAndZoom}). + * + * This interaction is only supported for mouse devices. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @fires ol.interaction.DragBox.Event + * @param {olx.interaction.DragBoxOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragBox = function(opt_options) { + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.DragBox.handleDownEvent_, + handleDragEvent: ol.interaction.DragBox.handleDragEvent_, + handleUpEvent: ol.interaction.DragBox.handleUpEvent_ + }); + + var options = opt_options ? opt_options : {}; + + /** + * @type {ol.render.Box} + * @private + */ + this.box_ = new ol.render.Box(options.className || 'ol-dragbox'); + + /** + * @type {ol.Pixel} + * @private + */ + this.startPixel_ = null; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.always; + + /** + * @private + * @type {ol.DragBoxEndConditionType} + */ + this.boxEndCondition_ = options.boxEndCondition ? + options.boxEndCondition : ol.interaction.DragBox.defaultBoxEndCondition; +}; +ol.inherits(ol.interaction.DragBox, ol.interaction.Pointer); + + +/** + * The default condition for determining whether the boxend event + * should fire. + * @param {ol.MapBrowserEvent} mapBrowserEvent The originating MapBrowserEvent + * leading to the box end. + * @param {ol.Pixel} startPixel The starting pixel of the box. + * @param {ol.Pixel} endPixel The end pixel of the box. + * @return {boolean} Whether or not the boxend condition should be fired. + */ +ol.interaction.DragBox.defaultBoxEndCondition = function(mapBrowserEvent, + startPixel, endPixel) { + var width = endPixel[0] - startPixel[0]; + var height = endPixel[1] - startPixel[1]; + return width * width + height * height >= + ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.DragBox} + * @private + */ +ol.interaction.DragBox.handleDragEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return; + } + + this.box_.setPixels(this.startPixel_, mapBrowserEvent.pixel); + + this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType.BOXDRAG, + mapBrowserEvent.coordinate, mapBrowserEvent)); +}; + + +/** + * Returns geometry of last drawn box. + * @return {ol.geom.Polygon} Geometry. + * @api stable + */ +ol.interaction.DragBox.prototype.getGeometry = function() { + return this.box_.getGeometry(); +}; + + +/** + * To be overriden by child classes. + * FIXME: use constructor option instead of relying on overridding. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @protected + */ +ol.interaction.DragBox.prototype.onBoxEnd = ol.nullFunction; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.DragBox} + * @private + */ +ol.interaction.DragBox.handleUpEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return true; + } + + this.box_.setMap(null); + + if (this.boxEndCondition_(mapBrowserEvent, + this.startPixel_, mapBrowserEvent.pixel)) { + this.onBoxEnd(mapBrowserEvent); + this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType.BOXEND, + mapBrowserEvent.coordinate, mapBrowserEvent)); + } + return false; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.DragBox} + * @private + */ +ol.interaction.DragBox.handleDownEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return false; + } + + if (ol.events.condition.mouseActionButton(mapBrowserEvent) && + this.condition_(mapBrowserEvent)) { + this.startPixel_ = mapBrowserEvent.pixel; + this.box_.setMap(mapBrowserEvent.map); + this.box_.setPixels(this.startPixel_, this.startPixel_); + this.dispatchEvent(new ol.interaction.DragBox.Event(ol.interaction.DragBox.EventType.BOXSTART, + mapBrowserEvent.coordinate, mapBrowserEvent)); + return true; + } else { + return false; + } +}; + + +/** + * @enum {string} + */ +ol.interaction.DragBox.EventType = { + /** + * Triggered upon drag box start. + * @event ol.interaction.DragBox.Event#boxstart + * @api stable + */ + BOXSTART: 'boxstart', + + /** + * Triggered on drag when box is active. + * @event ol.interaction.DragBox.Event#boxdrag + * @api + */ + BOXDRAG: 'boxdrag', + + /** + * Triggered upon drag box end. + * @event ol.interaction.DragBox.Event#boxend + * @api stable + */ + BOXEND: 'boxend' +}; + + +/** + * @classdesc + * Events emitted by {@link ol.interaction.DragBox} instances are instances of + * this type. + * + * @param {string} type The event type. + * @param {ol.Coordinate} coordinate The event coordinate. + * @param {ol.MapBrowserEvent} mapBrowserEvent Originating event. + * @extends {ol.events.Event} + * @constructor + * @implements {oli.DragBoxEvent} + */ +ol.interaction.DragBox.Event = function(type, coordinate, mapBrowserEvent) { + ol.events.Event.call(this, type); + + /** + * The coordinate of the drag event. + * @const + * @type {ol.Coordinate} + * @api stable + */ + this.coordinate = coordinate; + + /** + * @const + * @type {ol.MapBrowserEvent} + * @api + */ + this.mapBrowserEvent = mapBrowserEvent; + +}; +ol.inherits(ol.interaction.DragBox.Event, ol.events.Event); + +goog.provide('ol.interaction.DragZoom'); + +goog.require('ol'); +goog.require('ol.easing'); +goog.require('ol.events.condition'); +goog.require('ol.extent'); +goog.require('ol.interaction.DragBox'); + + +/** + * @classdesc + * Allows the user to zoom the map by clicking and dragging on the map, + * normally combined with an {@link ol.events.condition} that limits + * it to when a key, shift by default, is held down. + * + * To change the style of the box, use CSS and the `.ol-dragzoom` selector, or + * your custom one configured with `className`. + * + * @constructor + * @extends {ol.interaction.DragBox} + * @param {olx.interaction.DragZoomOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragZoom = function(opt_options) { + var options = opt_options ? opt_options : {}; + + var condition = options.condition ? + options.condition : ol.events.condition.shiftKeyOnly; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 200; + + /** + * @private + * @type {boolean} + */ + this.out_ = options.out !== undefined ? options.out : false; + + ol.interaction.DragBox.call(this, { + condition: condition, + className: options.className || 'ol-dragzoom' + }); + +}; +ol.inherits(ol.interaction.DragZoom, ol.interaction.DragBox); + + +/** + * @inheritDoc + */ +ol.interaction.DragZoom.prototype.onBoxEnd = function() { + var map = this.getMap(); + + var view = /** @type {!ol.View} */ (map.getView()); + + var size = /** @type {!ol.Size} */ (map.getSize()); + + var extent = this.getGeometry().getExtent(); + + if (this.out_) { + var mapExtent = view.calculateExtent(size); + var boxPixelExtent = ol.extent.createOrUpdateFromCoordinates([ + map.getPixelFromCoordinate(ol.extent.getBottomLeft(extent)), + map.getPixelFromCoordinate(ol.extent.getTopRight(extent))]); + var factor = view.getResolutionForExtent(boxPixelExtent, size); + + ol.extent.scaleFromCenter(mapExtent, 1 / factor); + extent = mapExtent; + } + + var resolution = view.constrainResolution( + view.getResolutionForExtent(extent, size)); + + view.animate({ + resolution: resolution, + center: ol.extent.getCenter(extent), + duration: this.duration_, + easing: ol.easing.easeOut + }); + +}; + +goog.provide('ol.events.KeyCode'); + +/** + * @enum {number} + * @const + */ +ol.events.KeyCode = { + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40 +}; + +goog.provide('ol.interaction.KeyboardPan'); + +goog.require('ol'); +goog.require('ol.coordinate'); +goog.require('ol.events.EventType'); +goog.require('ol.events.KeyCode'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Interaction'); + + +/** + * @classdesc + * Allows the user to pan the map using keyboard arrows. + * Note that, although this interaction is by default included in maps, + * the keys can only be used when browser focus is on the element to which + * the keyboard events are attached. By default, this is the map div, + * though you can change this with the `keyboardEventTarget` in + * {@link ol.Map}. `document` never loses focus but, for any other element, + * focus will have to be on, and returned to, this element if the keys are to + * function. + * See also {@link ol.interaction.KeyboardZoom}. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.KeyboardPanOptions=} opt_options Options. + * @api stable + */ +ol.interaction.KeyboardPan = function(opt_options) { + + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.KeyboardPan.handleEvent + }); + + var options = opt_options || {}; + + /** + * @private + * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event. + * @return {boolean} Combined condition result. + */ + this.defaultCondition_ = function(mapBrowserEvent) { + return ol.events.condition.noModifierKeys(mapBrowserEvent) && + ol.events.condition.targetNotEditable(mapBrowserEvent); + }; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition !== undefined ? + options.condition : this.defaultCondition_; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 100; + + /** + * @private + * @type {number} + */ + this.pixelDelta_ = options.pixelDelta !== undefined ? + options.pixelDelta : 128; + +}; +ol.inherits(ol.interaction.KeyboardPan, ol.interaction.Interaction); + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} if it was a + * `KeyEvent`, and decides the direction to pan to (if an arrow key was + * pressed). + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.KeyboardPan} + * @api + */ +ol.interaction.KeyboardPan.handleEvent = function(mapBrowserEvent) { + var stopEvent = false; + if (mapBrowserEvent.type == ol.events.EventType.KEYDOWN) { + var keyEvent = mapBrowserEvent.originalEvent; + var keyCode = keyEvent.keyCode; + if (this.condition_(mapBrowserEvent) && + (keyCode == ol.events.KeyCode.DOWN || + keyCode == ol.events.KeyCode.LEFT || + keyCode == ol.events.KeyCode.RIGHT || + keyCode == ol.events.KeyCode.UP)) { + var map = mapBrowserEvent.map; + var view = map.getView(); + var mapUnitsDelta = view.getResolution() * this.pixelDelta_; + var deltaX = 0, deltaY = 0; + if (keyCode == ol.events.KeyCode.DOWN) { + deltaY = -mapUnitsDelta; + } else if (keyCode == ol.events.KeyCode.LEFT) { + deltaX = -mapUnitsDelta; + } else if (keyCode == ol.events.KeyCode.RIGHT) { + deltaX = mapUnitsDelta; + } else { + deltaY = mapUnitsDelta; + } + var delta = [deltaX, deltaY]; + ol.coordinate.rotate(delta, view.getRotation()); + ol.interaction.Interaction.pan(map, view, delta, this.duration_); + mapBrowserEvent.preventDefault(); + stopEvent = true; + } + } + return !stopEvent; +}; + +goog.provide('ol.interaction.KeyboardZoom'); + +goog.require('ol'); +goog.require('ol.events.EventType'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Interaction'); + + +/** + * @classdesc + * Allows the user to zoom the map using keyboard + and -. + * Note that, although this interaction is by default included in maps, + * the keys can only be used when browser focus is on the element to which + * the keyboard events are attached. By default, this is the map div, + * though you can change this with the `keyboardEventTarget` in + * {@link ol.Map}. `document` never loses focus but, for any other element, + * focus will have to be on, and returned to, this element if the keys are to + * function. + * See also {@link ol.interaction.KeyboardPan}. + * + * @constructor + * @param {olx.interaction.KeyboardZoomOptions=} opt_options Options. + * @extends {ol.interaction.Interaction} + * @api stable + */ +ol.interaction.KeyboardZoom = function(opt_options) { + + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.KeyboardZoom.handleEvent + }); + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? options.condition : + ol.events.condition.targetNotEditable; + + /** + * @private + * @type {number} + */ + this.delta_ = options.delta ? options.delta : 1; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 100; + +}; +ol.inherits(ol.interaction.KeyboardZoom, ol.interaction.Interaction); + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} if it was a + * `KeyEvent`, and decides whether to zoom in or out (depending on whether the + * key pressed was '+' or '-'). + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.KeyboardZoom} + * @api + */ +ol.interaction.KeyboardZoom.handleEvent = function(mapBrowserEvent) { + var stopEvent = false; + if (mapBrowserEvent.type == ol.events.EventType.KEYDOWN || + mapBrowserEvent.type == ol.events.EventType.KEYPRESS) { + var keyEvent = mapBrowserEvent.originalEvent; + var charCode = keyEvent.charCode; + if (this.condition_(mapBrowserEvent) && + (charCode == '+'.charCodeAt(0) || charCode == '-'.charCodeAt(0))) { + var map = mapBrowserEvent.map; + var delta = (charCode == '+'.charCodeAt(0)) ? this.delta_ : -this.delta_; + var view = map.getView(); + ol.interaction.Interaction.zoomByDelta( + map, view, delta, undefined, this.duration_); + mapBrowserEvent.preventDefault(); + stopEvent = true; + } + } + return !stopEvent; +}; + +goog.provide('ol.interaction.MouseWheelZoom'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.easing'); +goog.require('ol.events.EventType'); +goog.require('ol.has'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.math'); + + +/** + * @classdesc + * Allows the user to zoom the map by scrolling the mouse wheel. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.MouseWheelZoomOptions=} opt_options Options. + * @api stable + */ +ol.interaction.MouseWheelZoom = function(opt_options) { + + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.MouseWheelZoom.handleEvent + }); + + var options = opt_options || {}; + + /** + * @private + * @type {number} + */ + this.delta_ = 0; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; + + /** + * @private + * @type {number} + */ + this.timeout_ = options.timeout !== undefined ? options.timeout : 80; + + /** + * @private + * @type {boolean} + */ + this.useAnchor_ = options.useAnchor !== undefined ? options.useAnchor : true; + + /** + * @private + * @type {?ol.Coordinate} + */ + this.lastAnchor_ = null; + + /** + * @private + * @type {number|undefined} + */ + this.startTime_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.timeoutId_ = undefined; + + /** + * @private + * @type {ol.interaction.MouseWheelZoom.Mode|undefined} + */ + this.mode_ = undefined; + + /** + * Trackpad events separated by this delay will be considered separate + * interactions. + * @type {number} + */ + this.trackpadEventGap_ = 400; + + /** + * @type {number|undefined} + */ + this.trackpadTimeoutId_ = undefined; + + /** + * The number of delta values per zoom level + * @private + * @type {number} + */ + this.trackpadDeltaPerZoom_ = 300; + + /** + * The zoom factor by which scroll zooming is allowed to exceed the limits. + * @private + * @type {number} + */ + this.trackpadZoomBuffer_ = 1.5; + +}; +ol.inherits(ol.interaction.MouseWheelZoom, ol.interaction.Interaction); + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} (if it was a + * mousewheel-event) and eventually zooms the map. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} Allow event propagation. + * @this {ol.interaction.MouseWheelZoom} + * @api + */ +ol.interaction.MouseWheelZoom.handleEvent = function(mapBrowserEvent) { + var type = mapBrowserEvent.type; + if (type !== ol.events.EventType.WHEEL && type !== ol.events.EventType.MOUSEWHEEL) { + return true; + } + + mapBrowserEvent.preventDefault(); + + var map = mapBrowserEvent.map; + var wheelEvent = /** @type {WheelEvent} */ (mapBrowserEvent.originalEvent); + + if (this.useAnchor_) { + this.lastAnchor_ = mapBrowserEvent.coordinate; + } + + // Delta normalisation inspired by + // https://github.com/mapbox/mapbox-gl-js/blob/001c7b9/js/ui/handler/scroll_zoom.js + var delta; + if (mapBrowserEvent.type == ol.events.EventType.WHEEL) { + delta = wheelEvent.deltaY; + if (ol.has.FIREFOX && + wheelEvent.deltaMode === WheelEvent.DOM_DELTA_PIXEL) { + delta /= ol.has.DEVICE_PIXEL_RATIO; + } + if (wheelEvent.deltaMode === WheelEvent.DOM_DELTA_LINE) { + delta *= 40; + } + } else if (mapBrowserEvent.type == ol.events.EventType.MOUSEWHEEL) { + delta = -wheelEvent.wheelDeltaY; + if (ol.has.SAFARI) { + delta /= 3; + } + } + + if (delta === 0) { + return false; + } + + var now = Date.now(); + + if (this.startTime_ === undefined) { + this.startTime_ = now; + } + + if (!this.mode_ || now - this.startTime_ > this.trackpadEventGap_) { + this.mode_ = Math.abs(delta) < 4 ? + ol.interaction.MouseWheelZoom.Mode.TRACKPAD : + ol.interaction.MouseWheelZoom.Mode.WHEEL; + } + + if (this.mode_ === ol.interaction.MouseWheelZoom.Mode.TRACKPAD) { + var view = map.getView(); + if (this.trackpadTimeoutId_) { + clearTimeout(this.trackpadTimeoutId_); + } else { + view.setHint(ol.View.Hint.INTERACTING, 1); + } + this.trackpadTimeoutId_ = setTimeout(this.decrementInteractingHint_.bind(this), this.trackpadEventGap_); + var resolution = view.getResolution() * Math.pow(2, delta / this.trackpadDeltaPerZoom_); + var minResolution = view.getMinResolution(); + var maxResolution = view.getMaxResolution(); + var rebound = 0; + if (resolution < minResolution) { + resolution = Math.max(resolution, minResolution / this.trackpadZoomBuffer_); + rebound = 1; + } else if (resolution > maxResolution) { + resolution = Math.min(resolution, maxResolution * this.trackpadZoomBuffer_); + rebound = -1; + } + if (this.lastAnchor_) { + var center = view.calculateCenterZoom(resolution, this.lastAnchor_); + view.setCenter(center); + } + view.setResolution(resolution); + if (rebound > 0) { + view.animate({ + resolution: minResolution, + easing: ol.easing.easeOut, + anchor: this.lastAnchor_, + duration: 500 + }); + } else if (rebound < 0) { + view.animate({ + resolution: maxResolution, + easing: ol.easing.easeOut, + anchor: this.lastAnchor_, + duration: 500 + }); + } + this.startTime_ = now; + return false; + } + + this.delta_ += delta; + + var timeLeft = Math.max(this.timeout_ - (now - this.startTime_), 0); + + clearTimeout(this.timeoutId_); + this.timeoutId_ = setTimeout(this.handleWheelZoom_.bind(this, map), timeLeft); + + return false; +}; + + +/** + * @private + */ +ol.interaction.MouseWheelZoom.prototype.decrementInteractingHint_ = function() { + this.trackpadTimeoutId_ = undefined; + var view = this.getMap().getView(); + view.setHint(ol.View.Hint.INTERACTING, -1); +}; + + +/** + * @private + * @param {ol.Map} map Map. + */ +ol.interaction.MouseWheelZoom.prototype.handleWheelZoom_ = function(map) { + var view = map.getView(); + if (view.getAnimating()) { + view.cancelAnimations(); + } + var maxDelta = ol.MOUSEWHEELZOOM_MAXDELTA; + var delta = ol.math.clamp(this.delta_, -maxDelta, maxDelta); + ol.interaction.Interaction.zoomByDelta(map, view, -delta, this.lastAnchor_, + this.duration_); + this.mode_ = undefined; + this.delta_ = 0; + this.lastAnchor_ = null; + this.startTime_ = undefined; + this.timeoutId_ = undefined; +}; + + +/** + * Enable or disable using the mouse's location as an anchor when zooming + * @param {boolean} useAnchor true to zoom to the mouse's location, false + * to zoom to the center of the map + * @api + */ +ol.interaction.MouseWheelZoom.prototype.setMouseAnchor = function(useAnchor) { + this.useAnchor_ = useAnchor; + if (!useAnchor) { + this.lastAnchor_ = null; + } +}; + + +/** + * @enum {string} + */ +ol.interaction.MouseWheelZoom.Mode = { + TRACKPAD: 'trackpad', + WHEEL: 'wheel' +}; + +goog.provide('ol.interaction.PinchRotate'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.functions'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); + + +/** + * @classdesc + * Allows the user to rotate the map by twisting with two fingers + * on a touch screen. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.PinchRotateOptions=} opt_options Options. + * @api stable + */ +ol.interaction.PinchRotate = function(opt_options) { + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.PinchRotate.handleDownEvent_, + handleDragEvent: ol.interaction.PinchRotate.handleDragEvent_, + handleUpEvent: ol.interaction.PinchRotate.handleUpEvent_ + }); + + var options = opt_options || {}; + + /** + * @private + * @type {ol.Coordinate} + */ + this.anchor_ = null; + + /** + * @private + * @type {number|undefined} + */ + this.lastAngle_ = undefined; + + /** + * @private + * @type {boolean} + */ + this.rotating_ = false; + + /** + * @private + * @type {number} + */ + this.rotationDelta_ = 0.0; + + /** + * @private + * @type {number} + */ + this.threshold_ = options.threshold !== undefined ? options.threshold : 0.3; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 250; + +}; +ol.inherits(ol.interaction.PinchRotate, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.PinchRotate} + * @private + */ +ol.interaction.PinchRotate.handleDragEvent_ = function(mapBrowserEvent) { + ol.DEBUG && console.assert(this.targetPointers.length >= 2, + 'length of this.targetPointers should be greater than or equal to 2'); + var rotationDelta = 0.0; + + var touch0 = this.targetPointers[0]; + var touch1 = this.targetPointers[1]; + + // angle between touches + var angle = Math.atan2( + touch1.clientY - touch0.clientY, + touch1.clientX - touch0.clientX); + + if (this.lastAngle_ !== undefined) { + var delta = angle - this.lastAngle_; + this.rotationDelta_ += delta; + if (!this.rotating_ && + Math.abs(this.rotationDelta_) > this.threshold_) { + this.rotating_ = true; + } + rotationDelta = delta; + } + this.lastAngle_ = angle; + + var map = mapBrowserEvent.map; + + // rotate anchor point. + // FIXME: should be the intersection point between the lines: + // touch0,touch1 and previousTouch0,previousTouch1 + var viewportPosition = map.getViewport().getBoundingClientRect(); + var centroid = ol.interaction.Pointer.centroid(this.targetPointers); + centroid[0] -= viewportPosition.left; + centroid[1] -= viewportPosition.top; + this.anchor_ = map.getCoordinateFromPixel(centroid); + + // rotate + if (this.rotating_) { + var view = map.getView(); + var rotation = view.getRotation(); + map.render(); + ol.interaction.Interaction.rotateWithoutConstraints(map, view, + rotation + rotationDelta, this.anchor_); + } +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.PinchRotate} + * @private + */ +ol.interaction.PinchRotate.handleUpEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length < 2) { + var map = mapBrowserEvent.map; + var view = map.getView(); + view.setHint(ol.View.Hint.INTERACTING, -1); + if (this.rotating_) { + var rotation = view.getRotation(); + ol.interaction.Interaction.rotate( + map, view, rotation, this.anchor_, this.duration_); + } + return false; + } else { + return true; + } +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.PinchRotate} + * @private + */ +ol.interaction.PinchRotate.handleDownEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length >= 2) { + var map = mapBrowserEvent.map; + this.anchor_ = null; + this.lastAngle_ = undefined; + this.rotating_ = false; + this.rotationDelta_ = 0.0; + if (!this.handlingDownUpSequence) { + map.getView().setHint(ol.View.Hint.INTERACTING, 1); + } + return true; + } else { + return false; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.PinchRotate.prototype.shouldStopEvent = ol.functions.FALSE; + +goog.provide('ol.interaction.PinchZoom'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.functions'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); + + +/** + * @classdesc + * Allows the user to zoom the map by pinching with two fingers + * on a touch screen. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.PinchZoomOptions=} opt_options Options. + * @api stable + */ +ol.interaction.PinchZoom = function(opt_options) { + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.PinchZoom.handleDownEvent_, + handleDragEvent: ol.interaction.PinchZoom.handleDragEvent_, + handleUpEvent: ol.interaction.PinchZoom.handleUpEvent_ + }); + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {boolean} + */ + this.constrainResolution_ = options.constrainResolution || false; + + /** + * @private + * @type {ol.Coordinate} + */ + this.anchor_ = null; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 400; + + /** + * @private + * @type {number|undefined} + */ + this.lastDistance_ = undefined; + + /** + * @private + * @type {number} + */ + this.lastScaleDelta_ = 1; + +}; +ol.inherits(ol.interaction.PinchZoom, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.PinchZoom} + * @private + */ +ol.interaction.PinchZoom.handleDragEvent_ = function(mapBrowserEvent) { + ol.DEBUG && console.assert(this.targetPointers.length >= 2, + 'length of this.targetPointers should be 2 or more'); + var scaleDelta = 1.0; + + var touch0 = this.targetPointers[0]; + var touch1 = this.targetPointers[1]; + var dx = touch0.clientX - touch1.clientX; + var dy = touch0.clientY - touch1.clientY; + + // distance between touches + var distance = Math.sqrt(dx * dx + dy * dy); + + if (this.lastDistance_ !== undefined) { + scaleDelta = this.lastDistance_ / distance; + } + this.lastDistance_ = distance; + if (scaleDelta != 1.0) { + this.lastScaleDelta_ = scaleDelta; + } + + var map = mapBrowserEvent.map; + var view = map.getView(); + var resolution = view.getResolution(); + + // scale anchor point. + var viewportPosition = map.getViewport().getBoundingClientRect(); + var centroid = ol.interaction.Pointer.centroid(this.targetPointers); + centroid[0] -= viewportPosition.left; + centroid[1] -= viewportPosition.top; + this.anchor_ = map.getCoordinateFromPixel(centroid); + + // scale, bypass the resolution constraint + map.render(); + ol.interaction.Interaction.zoomWithoutConstraints( + map, view, resolution * scaleDelta, this.anchor_); + +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.PinchZoom} + * @private + */ +ol.interaction.PinchZoom.handleUpEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length < 2) { + var map = mapBrowserEvent.map; + var view = map.getView(); + view.setHint(ol.View.Hint.INTERACTING, -1); + if (this.constrainResolution_) { + var resolution = view.getResolution(); + // Zoom to final resolution, with an animation, and provide a + // direction not to zoom out/in if user was pinching in/out. + // Direction is > 0 if pinching out, and < 0 if pinching in. + var direction = this.lastScaleDelta_ - 1; + ol.interaction.Interaction.zoom(map, view, resolution, + this.anchor_, this.duration_, direction); + } + return false; + } else { + return true; + } +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.PinchZoom} + * @private + */ +ol.interaction.PinchZoom.handleDownEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length >= 2) { + var map = mapBrowserEvent.map; + this.anchor_ = null; + this.lastDistance_ = undefined; + this.lastScaleDelta_ = 1; + if (!this.handlingDownUpSequence) { + map.getView().setHint(ol.View.Hint.INTERACTING, 1); + } + return true; + } else { + return false; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.PinchZoom.prototype.shouldStopEvent = ol.functions.FALSE; + +goog.provide('ol.interaction'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.Kinetic'); +goog.require('ol.interaction.DoubleClickZoom'); +goog.require('ol.interaction.DragPan'); +goog.require('ol.interaction.DragRotate'); +goog.require('ol.interaction.DragZoom'); +goog.require('ol.interaction.KeyboardPan'); +goog.require('ol.interaction.KeyboardZoom'); +goog.require('ol.interaction.MouseWheelZoom'); +goog.require('ol.interaction.PinchRotate'); +goog.require('ol.interaction.PinchZoom'); + + +/** + * Set of interactions included in maps by default. Specific interactions can be + * excluded by setting the appropriate option to false in the constructor + * options, but the order of the interactions is fixed. If you want to specify + * a different order for interactions, you will need to create your own + * {@link ol.interaction.Interaction} instances and insert them into a + * {@link ol.Collection} in the order you want before creating your + * {@link ol.Map} instance. The default set of interactions, in sequence, is: + * * {@link ol.interaction.DragRotate} + * * {@link ol.interaction.DoubleClickZoom} + * * {@link ol.interaction.DragPan} + * * {@link ol.interaction.PinchRotate} + * * {@link ol.interaction.PinchZoom} + * * {@link ol.interaction.KeyboardPan} + * * {@link ol.interaction.KeyboardZoom} + * * {@link ol.interaction.MouseWheelZoom} + * * {@link ol.interaction.DragZoom} + * + * @param {olx.interaction.DefaultsOptions=} opt_options Defaults options. + * @return {ol.Collection.<ol.interaction.Interaction>} A collection of + * interactions to be used with the ol.Map constructor's interactions option. + * @api stable + */ +ol.interaction.defaults = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var interactions = new ol.Collection(); + + var kinetic = new ol.Kinetic(-0.005, 0.05, 100); + + var altShiftDragRotate = options.altShiftDragRotate !== undefined ? + options.altShiftDragRotate : true; + if (altShiftDragRotate) { + interactions.push(new ol.interaction.DragRotate()); + } + + var doubleClickZoom = options.doubleClickZoom !== undefined ? + options.doubleClickZoom : true; + if (doubleClickZoom) { + interactions.push(new ol.interaction.DoubleClickZoom({ + delta: options.zoomDelta, + duration: options.zoomDuration + })); + } + + var dragPan = options.dragPan !== undefined ? options.dragPan : true; + if (dragPan) { + interactions.push(new ol.interaction.DragPan({ + kinetic: kinetic + })); + } + + var pinchRotate = options.pinchRotate !== undefined ? options.pinchRotate : + true; + if (pinchRotate) { + interactions.push(new ol.interaction.PinchRotate()); + } + + var pinchZoom = options.pinchZoom !== undefined ? options.pinchZoom : true; + if (pinchZoom) { + interactions.push(new ol.interaction.PinchZoom({ + duration: options.zoomDuration + })); + } + + var keyboard = options.keyboard !== undefined ? options.keyboard : true; + if (keyboard) { + interactions.push(new ol.interaction.KeyboardPan()); + interactions.push(new ol.interaction.KeyboardZoom({ + delta: options.zoomDelta, + duration: options.zoomDuration + })); + } + + var mouseWheelZoom = options.mouseWheelZoom !== undefined ? + options.mouseWheelZoom : true; + if (mouseWheelZoom) { + interactions.push(new ol.interaction.MouseWheelZoom({ + duration: options.zoomDuration + })); + } + + var shiftDragZoom = options.shiftDragZoom !== undefined ? + options.shiftDragZoom : true; + if (shiftDragZoom) { + interactions.push(new ol.interaction.DragZoom({ + duration: options.zoomDuration + })); + } + + return interactions; + +}; + +goog.provide('ol.layer.Base'); + +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.math'); +goog.require('ol.obj'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Note that with `ol.layer.Base` and all its subclasses, any property set in + * the options is set as a {@link ol.Object} property on the layer object, so + * is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.Object} + * @param {olx.layer.BaseOptions} options Layer options. + * @api stable + */ +ol.layer.Base = function(options) { + + ol.Object.call(this); + + /** + * @type {Object.<string, *>} + */ + var properties = ol.obj.assign({}, options); + properties[ol.layer.Base.Property.OPACITY] = + options.opacity !== undefined ? options.opacity : 1; + properties[ol.layer.Base.Property.VISIBLE] = + options.visible !== undefined ? options.visible : true; + properties[ol.layer.Base.Property.Z_INDEX] = + options.zIndex !== undefined ? options.zIndex : 0; + properties[ol.layer.Base.Property.MAX_RESOLUTION] = + options.maxResolution !== undefined ? options.maxResolution : Infinity; + properties[ol.layer.Base.Property.MIN_RESOLUTION] = + options.minResolution !== undefined ? options.minResolution : 0; + + this.setProperties(properties); + + /** + * @type {ol.LayerState} + * @private + */ + this.state_ = /** @type {ol.LayerState} */ ({ + layer: /** @type {ol.layer.Layer} */ (this), + managed: true + }); + +}; +ol.inherits(ol.layer.Base, ol.Object); + + +/** + * @return {ol.LayerState} Layer state. + */ +ol.layer.Base.prototype.getLayerState = function() { + this.state_.opacity = ol.math.clamp(this.getOpacity(), 0, 1); + this.state_.sourceState = this.getSourceState(); + this.state_.visible = this.getVisible(); + this.state_.extent = this.getExtent(); + this.state_.zIndex = this.getZIndex(); + this.state_.maxResolution = this.getMaxResolution(); + this.state_.minResolution = Math.max(this.getMinResolution(), 0); + + return this.state_; +}; + + +/** + * @abstract + * @param {Array.<ol.layer.Layer>=} opt_array Array of layers (to be + * modified in place). + * @return {Array.<ol.layer.Layer>} Array of layers. + */ +ol.layer.Base.prototype.getLayersArray = function(opt_array) {}; + + +/** + * @abstract + * @param {Array.<ol.LayerState>=} opt_states Optional list of layer + * states (to be modified in place). + * @return {Array.<ol.LayerState>} List of layer states. + */ +ol.layer.Base.prototype.getLayerStatesArray = function(opt_states) {}; + + +/** + * Return the {@link ol.Extent extent} of the layer or `undefined` if it + * will be visible regardless of extent. + * @return {ol.Extent|undefined} The layer extent. + * @observable + * @api stable + */ +ol.layer.Base.prototype.getExtent = function() { + return /** @type {ol.Extent|undefined} */ ( + this.get(ol.layer.Base.Property.EXTENT)); +}; + + +/** + * Return the maximum resolution of the layer. + * @return {number} The maximum resolution of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.getMaxResolution = function() { + return /** @type {number} */ ( + this.get(ol.layer.Base.Property.MAX_RESOLUTION)); +}; + + +/** + * Return the minimum resolution of the layer. + * @return {number} The minimum resolution of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.getMinResolution = function() { + return /** @type {number} */ ( + this.get(ol.layer.Base.Property.MIN_RESOLUTION)); +}; + + +/** + * Return the opacity of the layer (between 0 and 1). + * @return {number} The opacity of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.getOpacity = function() { + return /** @type {number} */ (this.get(ol.layer.Base.Property.OPACITY)); +}; + + +/** + * @abstract + * @return {ol.source.State} Source state. + */ +ol.layer.Base.prototype.getSourceState = function() {}; + + +/** + * Return the visibility of the layer (`true` or `false`). + * @return {boolean} The visibility of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.getVisible = function() { + return /** @type {boolean} */ (this.get(ol.layer.Base.Property.VISIBLE)); +}; + + +/** + * Return the Z-index of the layer, which is used to order layers before + * rendering. The default Z-index is 0. + * @return {number} The Z-index of the layer. + * @observable + * @api + */ +ol.layer.Base.prototype.getZIndex = function() { + return /** @type {number} */ (this.get(ol.layer.Base.Property.Z_INDEX)); +}; + + +/** + * Set the extent at which the layer is visible. If `undefined`, the layer + * will be visible at all extents. + * @param {ol.Extent|undefined} extent The extent of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.setExtent = function(extent) { + this.set(ol.layer.Base.Property.EXTENT, extent); +}; + + +/** + * Set the maximum resolution at which the layer is visible. + * @param {number} maxResolution The maximum resolution of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.setMaxResolution = function(maxResolution) { + this.set(ol.layer.Base.Property.MAX_RESOLUTION, maxResolution); +}; + + +/** + * Set the minimum resolution at which the layer is visible. + * @param {number} minResolution The minimum resolution of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.setMinResolution = function(minResolution) { + this.set(ol.layer.Base.Property.MIN_RESOLUTION, minResolution); +}; + + +/** + * Set the opacity of the layer, allowed values range from 0 to 1. + * @param {number} opacity The opacity of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.setOpacity = function(opacity) { + this.set(ol.layer.Base.Property.OPACITY, opacity); +}; + + +/** + * Set the visibility of the layer (`true` or `false`). + * @param {boolean} visible The visibility of the layer. + * @observable + * @api stable + */ +ol.layer.Base.prototype.setVisible = function(visible) { + this.set(ol.layer.Base.Property.VISIBLE, visible); +}; + + +/** + * Set Z-index of the layer, which is used to order layers before rendering. + * The default Z-index is 0. + * @param {number} zindex The z-index of the layer. + * @observable + * @api + */ +ol.layer.Base.prototype.setZIndex = function(zindex) { + this.set(ol.layer.Base.Property.Z_INDEX, zindex); +}; + + +/** + * @enum {string} + */ +ol.layer.Base.Property = { + OPACITY: 'opacity', + VISIBLE: 'visible', + EXTENT: 'extent', + Z_INDEX: 'zIndex', + MAX_RESOLUTION: 'maxResolution', + MIN_RESOLUTION: 'minResolution', + SOURCE: 'source' +}; + +goog.provide('ol.source.State'); + + +/** + * State of the source, one of 'undefined', 'loading', 'ready' or 'error'. + * @enum {string} + */ +ol.source.State = { + UNDEFINED: 'undefined', + LOADING: 'loading', + READY: 'ready', + ERROR: 'error' +}; + + +goog.provide('ol.layer.Group'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.Collection'); +goog.require('ol.Object'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.layer.Base'); +goog.require('ol.obj'); +goog.require('ol.source.State'); + + +/** + * @classdesc + * A {@link ol.Collection} of layers that are handled together. + * + * A generic `change` event is triggered when the group/Collection changes. + * + * @constructor + * @extends {ol.layer.Base} + * @param {olx.layer.GroupOptions=} opt_options Layer options. + * @api stable + */ +ol.layer.Group = function(opt_options) { + + var options = opt_options || {}; + var baseOptions = /** @type {olx.layer.GroupOptions} */ + (ol.obj.assign({}, options)); + delete baseOptions.layers; + + var layers = options.layers; + + ol.layer.Base.call(this, baseOptions); + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.layersListenerKeys_ = []; + + /** + * @private + * @type {Object.<string, Array.<ol.EventsKey>>} + */ + this.listenerKeys_ = {}; + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.Group.Property.LAYERS), + this.handleLayersChanged_, this); + + if (layers) { + if (Array.isArray(layers)) { + layers = new ol.Collection(layers.slice()); + } else { + ol.asserts.assert(layers instanceof ol.Collection, + 43); // Expected `layers` to be an array or an `ol.Collection` + layers = layers; + } + } else { + layers = new ol.Collection(); + } + + this.setLayers(layers); + +}; +ol.inherits(ol.layer.Group, ol.layer.Base); + + +/** + * @private + */ +ol.layer.Group.prototype.handleLayerChange_ = function() { + if (this.getVisible()) { + this.changed(); + } +}; + + +/** + * @param {ol.events.Event} event Event. + * @private + */ +ol.layer.Group.prototype.handleLayersChanged_ = function(event) { + this.layersListenerKeys_.forEach(ol.events.unlistenByKey); + this.layersListenerKeys_.length = 0; + + var layers = this.getLayers(); + this.layersListenerKeys_.push( + ol.events.listen(layers, ol.Collection.EventType.ADD, + this.handleLayersAdd_, this), + ol.events.listen(layers, ol.Collection.EventType.REMOVE, + this.handleLayersRemove_, this)); + + for (var id in this.listenerKeys_) { + this.listenerKeys_[id].forEach(ol.events.unlistenByKey); + } + ol.obj.clear(this.listenerKeys_); + + var layersArray = layers.getArray(); + var i, ii, layer; + for (i = 0, ii = layersArray.length; i < ii; i++) { + layer = layersArray[i]; + this.listenerKeys_[ol.getUid(layer).toString()] = [ + ol.events.listen(layer, ol.Object.EventType.PROPERTYCHANGE, + this.handleLayerChange_, this), + ol.events.listen(layer, ol.events.EventType.CHANGE, + this.handleLayerChange_, this) + ]; + } + + this.changed(); +}; + + +/** + * @param {ol.Collection.Event} collectionEvent Collection event. + * @private + */ +ol.layer.Group.prototype.handleLayersAdd_ = function(collectionEvent) { + var layer = /** @type {ol.layer.Base} */ (collectionEvent.element); + var key = ol.getUid(layer).toString(); + ol.DEBUG && console.assert(!(key in this.listenerKeys_), + 'listeners already registered'); + this.listenerKeys_[key] = [ + ol.events.listen(layer, ol.Object.EventType.PROPERTYCHANGE, + this.handleLayerChange_, this), + ol.events.listen(layer, ol.events.EventType.CHANGE, + this.handleLayerChange_, this) + ]; + this.changed(); +}; + + +/** + * @param {ol.Collection.Event} collectionEvent Collection event. + * @private + */ +ol.layer.Group.prototype.handleLayersRemove_ = function(collectionEvent) { + var layer = /** @type {ol.layer.Base} */ (collectionEvent.element); + var key = ol.getUid(layer).toString(); + ol.DEBUG && console.assert(key in this.listenerKeys_, 'no listeners to unregister'); + this.listenerKeys_[key].forEach(ol.events.unlistenByKey); + delete this.listenerKeys_[key]; + this.changed(); +}; + + +/** + * Returns the {@link ol.Collection collection} of {@link ol.layer.Layer layers} + * in this group. + * @return {!ol.Collection.<ol.layer.Base>} Collection of + * {@link ol.layer.Base layers} that are part of this group. + * @observable + * @api stable + */ +ol.layer.Group.prototype.getLayers = function() { + return /** @type {!ol.Collection.<ol.layer.Base>} */ (this.get( + ol.layer.Group.Property.LAYERS)); +}; + + +/** + * Set the {@link ol.Collection collection} of {@link ol.layer.Layer layers} + * in this group. + * @param {!ol.Collection.<ol.layer.Base>} layers Collection of + * {@link ol.layer.Base layers} that are part of this group. + * @observable + * @api stable + */ +ol.layer.Group.prototype.setLayers = function(layers) { + this.set(ol.layer.Group.Property.LAYERS, layers); +}; + + +/** + * @inheritDoc + */ +ol.layer.Group.prototype.getLayersArray = function(opt_array) { + var array = opt_array !== undefined ? opt_array : []; + this.getLayers().forEach(function(layer) { + layer.getLayersArray(array); + }); + return array; +}; + + +/** + * @inheritDoc + */ +ol.layer.Group.prototype.getLayerStatesArray = function(opt_states) { + var states = opt_states !== undefined ? opt_states : []; + + var pos = states.length; + + this.getLayers().forEach(function(layer) { + layer.getLayerStatesArray(states); + }); + + var ownLayerState = this.getLayerState(); + var i, ii, layerState; + for (i = pos, ii = states.length; i < ii; i++) { + layerState = states[i]; + layerState.opacity *= ownLayerState.opacity; + layerState.visible = layerState.visible && ownLayerState.visible; + layerState.maxResolution = Math.min( + layerState.maxResolution, ownLayerState.maxResolution); + layerState.minResolution = Math.max( + layerState.minResolution, ownLayerState.minResolution); + if (ownLayerState.extent !== undefined) { + if (layerState.extent !== undefined) { + layerState.extent = ol.extent.getIntersection( + layerState.extent, ownLayerState.extent); + } else { + layerState.extent = ownLayerState.extent; + } + } + } + + return states; +}; + + +/** + * @inheritDoc + */ +ol.layer.Group.prototype.getSourceState = function() { + return ol.source.State.READY; +}; + +/** + * @enum {string} + */ +ol.layer.Group.Property = { + LAYERS: 'layers' +}; + +goog.provide('ol.proj.EPSG3857'); + +goog.require('ol'); +goog.require('ol.math'); +goog.require('ol.proj'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); + + +/** + * @classdesc + * Projection object for web/spherical Mercator (EPSG:3857). + * + * @constructor + * @extends {ol.proj.Projection} + * @param {string} code Code. + * @private + */ +ol.proj.EPSG3857_ = function(code) { + ol.proj.Projection.call(this, { + code: code, + units: ol.proj.Units.METERS, + extent: ol.proj.EPSG3857.EXTENT, + global: true, + worldExtent: ol.proj.EPSG3857.WORLD_EXTENT, + getPointResolution: function(resolution, point) { + return resolution / ol.math.cosh(point[1] / ol.proj.EPSG3857.RADIUS); + } + }); +}; +ol.inherits(ol.proj.EPSG3857_, ol.proj.Projection); + + +/** + * @const + * @type {number} + */ +ol.proj.EPSG3857.RADIUS = 6378137; + + +/** + * @const + * @type {number} + */ +ol.proj.EPSG3857.HALF_SIZE = Math.PI * ol.proj.EPSG3857.RADIUS; + + +/** + * @const + * @type {ol.Extent} + */ +ol.proj.EPSG3857.EXTENT = [ + -ol.proj.EPSG3857.HALF_SIZE, -ol.proj.EPSG3857.HALF_SIZE, + ol.proj.EPSG3857.HALF_SIZE, ol.proj.EPSG3857.HALF_SIZE +]; + + +/** + * @const + * @type {ol.Extent} + */ +ol.proj.EPSG3857.WORLD_EXTENT = [-180, -85, 180, 85]; + + +/** + * Lists several projection codes with the same meaning as EPSG:3857. + * + * @type {Array.<string>} + */ +ol.proj.EPSG3857.CODES = [ + 'EPSG:3857', + 'EPSG:102100', + 'EPSG:102113', + 'EPSG:900913', + 'urn:ogc:def:crs:EPSG:6.18:3:3857', + 'urn:ogc:def:crs:EPSG::3857', + 'http://www.opengis.net/gml/srs/epsg.xml#3857' +]; + + +/** + * Projections equal to EPSG:3857. + * + * @const + * @type {Array.<ol.proj.Projection>} + */ +ol.proj.EPSG3857.PROJECTIONS = ol.proj.EPSG3857.CODES.map(function(code) { + return new ol.proj.EPSG3857_(code); +}); + + +/** + * Transformation from EPSG:4326 to EPSG:3857. + * + * @param {Array.<number>} input Input array of coordinate values. + * @param {Array.<number>=} opt_output Output array of coordinate values. + * @param {number=} opt_dimension Dimension (default is `2`). + * @return {Array.<number>} Output array of coordinate values. + */ +ol.proj.EPSG3857.fromEPSG4326 = function(input, opt_output, opt_dimension) { + var length = input.length, + dimension = opt_dimension > 1 ? opt_dimension : 2, + output = opt_output; + if (output === undefined) { + if (dimension > 2) { + // preserve values beyond second dimension + output = input.slice(); + } else { + output = new Array(length); + } + } + ol.DEBUG && console.assert(output.length % dimension === 0, + 'modulus of output.length with dimension should be 0'); + var halfSize = ol.proj.EPSG3857.HALF_SIZE; + for (var i = 0; i < length; i += dimension) { + output[i] = halfSize * input[i] / 180; + var y = ol.proj.EPSG3857.RADIUS * + Math.log(Math.tan(Math.PI * (input[i + 1] + 90) / 360)); + if (y > halfSize) { + y = halfSize; + } else if (y < -halfSize) { + y = -halfSize; + } + output[i + 1] = y; + } + return output; +}; + + +/** + * Transformation from EPSG:3857 to EPSG:4326. + * + * @param {Array.<number>} input Input array of coordinate values. + * @param {Array.<number>=} opt_output Output array of coordinate values. + * @param {number=} opt_dimension Dimension (default is `2`). + * @return {Array.<number>} Output array of coordinate values. + */ +ol.proj.EPSG3857.toEPSG4326 = function(input, opt_output, opt_dimension) { + var length = input.length, + dimension = opt_dimension > 1 ? opt_dimension : 2, + output = opt_output; + if (output === undefined) { + if (dimension > 2) { + // preserve values beyond second dimension + output = input.slice(); + } else { + output = new Array(length); + } + } + ol.DEBUG && console.assert(output.length % dimension === 0, + 'modulus of output.length with dimension should be 0'); + for (var i = 0; i < length; i += dimension) { + output[i] = 180 * input[i] / ol.proj.EPSG3857.HALF_SIZE; + output[i + 1] = 360 * Math.atan( + Math.exp(input[i + 1] / ol.proj.EPSG3857.RADIUS)) / Math.PI - 90; + } + return output; +}; + +goog.provide('ol.sphere.WGS84'); + +goog.require('ol.Sphere'); + + +/** + * A sphere with radius equal to the semi-major axis of the WGS84 ellipsoid. + * @const + * @type {ol.Sphere} + */ +ol.sphere.WGS84 = new ol.Sphere(6378137); + +goog.provide('ol.proj.EPSG4326'); + +goog.require('ol'); +goog.require('ol.proj'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); +goog.require('ol.sphere.WGS84'); + + +/** + * @classdesc + * Projection object for WGS84 geographic coordinates (EPSG:4326). + * + * Note that OpenLayers does not strictly comply with the EPSG definition. + * The EPSG registry defines 4326 as a CRS for Latitude,Longitude (y,x). + * OpenLayers treats EPSG:4326 as a pseudo-projection, with x,y coordinates. + * + * @constructor + * @extends {ol.proj.Projection} + * @param {string} code Code. + * @param {string=} opt_axisOrientation Axis orientation. + * @private + */ +ol.proj.EPSG4326_ = function(code, opt_axisOrientation) { + ol.proj.Projection.call(this, { + code: code, + units: ol.proj.Units.DEGREES, + extent: ol.proj.EPSG4326.EXTENT, + axisOrientation: opt_axisOrientation, + global: true, + metersPerUnit: ol.proj.EPSG4326.METERS_PER_UNIT, + worldExtent: ol.proj.EPSG4326.EXTENT + }); +}; +ol.inherits(ol.proj.EPSG4326_, ol.proj.Projection); + + +/** + * Extent of the EPSG:4326 projection which is the whole world. + * + * @const + * @type {ol.Extent} + */ +ol.proj.EPSG4326.EXTENT = [-180, -90, 180, 90]; + + +/** + * @const + * @type {number} + */ +ol.proj.EPSG4326.METERS_PER_UNIT = Math.PI * ol.sphere.WGS84.radius / 180; + + +/** + * Projections equal to EPSG:4326. + * + * @const + * @type {Array.<ol.proj.Projection>} + */ +ol.proj.EPSG4326.PROJECTIONS = [ + new ol.proj.EPSG4326_('CRS:84'), + new ol.proj.EPSG4326_('EPSG:4326', 'neu'), + new ol.proj.EPSG4326_('urn:ogc:def:crs:EPSG::4326', 'neu'), + new ol.proj.EPSG4326_('urn:ogc:def:crs:EPSG:6.6:4326', 'neu'), + new ol.proj.EPSG4326_('urn:ogc:def:crs:OGC:1.3:CRS84'), + new ol.proj.EPSG4326_('urn:ogc:def:crs:OGC:2:84'), + new ol.proj.EPSG4326_('http://www.opengis.net/gml/srs/epsg.xml#4326', 'neu'), + new ol.proj.EPSG4326_('urn:x-ogc:def:crs:EPSG:4326', 'neu') +]; + +goog.provide('ol.proj.common'); + +goog.require('ol.proj'); +goog.require('ol.proj.EPSG3857'); +goog.require('ol.proj.EPSG4326'); + + +/** + * FIXME empty description for jsdoc + * @api + */ +ol.proj.common.add = function() { + // Add transformations that don't alter coordinates to convert within set of + // projections with equal meaning. + ol.proj.addEquivalentProjections(ol.proj.EPSG3857.PROJECTIONS); + ol.proj.addEquivalentProjections(ol.proj.EPSG4326.PROJECTIONS); + // Add transformations to convert EPSG:4326 like coordinates to EPSG:3857 like + // coordinates and back. + ol.proj.addEquivalentTransforms( + ol.proj.EPSG4326.PROJECTIONS, + ol.proj.EPSG3857.PROJECTIONS, + ol.proj.EPSG3857.fromEPSG4326, + ol.proj.EPSG3857.toEPSG4326); +}; + +goog.provide('ol.renderer.Type'); + + +/** + * Available renderers: `'canvas'` or `'webgl'`. + * @enum {string} + */ +ol.renderer.Type = { + CANVAS: 'canvas', + WEBGL: 'webgl' +}; + +goog.provide('ol.render.Event'); + +goog.require('ol'); +goog.require('ol.events.Event'); + + +/** + * @constructor + * @extends {ol.events.Event} + * @implements {oli.render.Event} + * @param {ol.render.Event.Type} type Type. + * @param {ol.render.VectorContext=} opt_vectorContext Vector context. + * @param {olx.FrameState=} opt_frameState Frame state. + * @param {?CanvasRenderingContext2D=} opt_context Context. + * @param {?ol.webgl.Context=} opt_glContext WebGL Context. + */ +ol.render.Event = function( + type, opt_vectorContext, opt_frameState, opt_context, + opt_glContext) { + + ol.events.Event.call(this, type); + + /** + * For canvas, this is an instance of {@link ol.render.canvas.Immediate}. + * @type {ol.render.VectorContext|undefined} + * @api + */ + this.vectorContext = opt_vectorContext; + + /** + * An object representing the current render frame state. + * @type {olx.FrameState|undefined} + * @api + */ + this.frameState = opt_frameState; + + /** + * Canvas context. Only available when a Canvas renderer is used, null + * otherwise. + * @type {CanvasRenderingContext2D|null|undefined} + * @api + */ + this.context = opt_context; + + /** + * WebGL context. Only available when a WebGL renderer is used, null + * otherwise. + * @type {ol.webgl.Context|null|undefined} + * @api + */ + this.glContext = opt_glContext; + +}; +ol.inherits(ol.render.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.render.Event.Type = { + /** + * @event ol.render.Event#postcompose + * @api + */ + POSTCOMPOSE: 'postcompose', + /** + * @event ol.render.Event#precompose + * @api + */ + PRECOMPOSE: 'precompose', + /** + * @event ol.render.Event#render + * @api + */ + RENDER: 'render' +}; + +goog.provide('ol.layer.Layer'); + +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.layer.Base'); +goog.require('ol.obj'); +goog.require('ol.render.Event'); +goog.require('ol.source.State'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * A visual representation of raster or vector map data. + * Layers group together those properties that pertain to how the data is to be + * displayed, irrespective of the source of that data. + * + * Layers are usually added to a map with {@link ol.Map#addLayer}. Components + * like {@link ol.interaction.Select} use unmanaged layers internally. These + * unmanaged layers are associated with the map using + * {@link ol.layer.Layer#setMap} instead. + * + * A generic `change` event is fired when the state of the source changes. + * + * @constructor + * @extends {ol.layer.Base} + * @fires ol.render.Event + * @param {olx.layer.LayerOptions} options Layer options. + * @api stable + */ +ol.layer.Layer = function(options) { + + var baseOptions = ol.obj.assign({}, options); + delete baseOptions.source; + + ol.layer.Base.call(this, /** @type {olx.layer.BaseOptions} */ (baseOptions)); + + /** + * @private + * @type {?ol.EventsKey} + */ + this.mapPrecomposeKey_ = null; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.mapRenderKey_ = null; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.sourceChangeKey_ = null; + + if (options.map) { + this.setMap(options.map); + } + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.Base.Property.SOURCE), + this.handleSourcePropertyChange_, this); + + var source = options.source ? options.source : null; + this.setSource(source); +}; +ol.inherits(ol.layer.Layer, ol.layer.Base); + + +/** + * Return `true` if the layer is visible, and if the passed resolution is + * between the layer's minResolution and maxResolution. The comparison is + * inclusive for `minResolution` and exclusive for `maxResolution`. + * @param {ol.LayerState} layerState Layer state. + * @param {number} resolution Resolution. + * @return {boolean} The layer is visible at the given resolution. + */ +ol.layer.Layer.visibleAtResolution = function(layerState, resolution) { + return layerState.visible && resolution >= layerState.minResolution && + resolution < layerState.maxResolution; +}; + + +/** + * @inheritDoc + */ +ol.layer.Layer.prototype.getLayersArray = function(opt_array) { + var array = opt_array ? opt_array : []; + array.push(this); + return array; +}; + + +/** + * @inheritDoc + */ +ol.layer.Layer.prototype.getLayerStatesArray = function(opt_states) { + var states = opt_states ? opt_states : []; + states.push(this.getLayerState()); + return states; +}; + + +/** + * Get the layer source. + * @return {ol.source.Source} The layer source (or `null` if not yet set). + * @observable + * @api stable + */ +ol.layer.Layer.prototype.getSource = function() { + var source = this.get(ol.layer.Base.Property.SOURCE); + return /** @type {ol.source.Source} */ (source) || null; +}; + + +/** + * @inheritDoc + */ +ol.layer.Layer.prototype.getSourceState = function() { + var source = this.getSource(); + return !source ? ol.source.State.UNDEFINED : source.getState(); +}; + + +/** + * @private + */ +ol.layer.Layer.prototype.handleSourceChange_ = function() { + this.changed(); +}; + + +/** + * @private + */ +ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() { + if (this.sourceChangeKey_) { + ol.events.unlistenByKey(this.sourceChangeKey_); + this.sourceChangeKey_ = null; + } + var source = this.getSource(); + if (source) { + this.sourceChangeKey_ = ol.events.listen(source, + ol.events.EventType.CHANGE, this.handleSourceChange_, this); + } + this.changed(); +}; + + +/** + * Sets the layer to be rendered on top of other layers on a map. The map will + * not manage this layer in its layers collection, and the callback in + * {@link ol.Map#forEachLayerAtPixel} will receive `null` as layer. This + * is useful for temporary layers. To remove an unmanaged layer from the map, + * use `#setMap(null)`. + * + * To add the layer to a map and have it managed by the map, use + * {@link ol.Map#addLayer} instead. + * @param {ol.Map} map Map. + * @api + */ +ol.layer.Layer.prototype.setMap = function(map) { + if (this.mapPrecomposeKey_) { + ol.events.unlistenByKey(this.mapPrecomposeKey_); + this.mapPrecomposeKey_ = null; + } + if (!map) { + this.changed(); + } + if (this.mapRenderKey_) { + ol.events.unlistenByKey(this.mapRenderKey_); + this.mapRenderKey_ = null; + } + if (map) { + this.mapPrecomposeKey_ = ol.events.listen( + map, ol.render.Event.Type.PRECOMPOSE, function(evt) { + var layerState = this.getLayerState(); + layerState.managed = false; + layerState.zIndex = Infinity; + evt.frameState.layerStatesArray.push(layerState); + evt.frameState.layerStates[ol.getUid(this)] = layerState; + }, this); + this.mapRenderKey_ = ol.events.listen( + this, ol.events.EventType.CHANGE, map.render, map); + this.changed(); + } +}; + + +/** + * Set the layer source. + * @param {ol.source.Source} source The layer source. + * @observable + * @api stable + */ +ol.layer.Layer.prototype.setSource = function(source) { + this.set(ol.layer.Base.Property.SOURCE, source); +}; + +goog.provide('ol.style.IconImageCache'); + +goog.require('ol'); +goog.require('ol.color'); + + +/** + * @constructor + */ +ol.style.IconImageCache = function() { + + /** + * @type {Object.<string, ol.style.IconImage>} + * @private + */ + this.cache_ = {}; + + /** + * @type {number} + * @private + */ + this.cacheSize_ = 0; + + /** + * @const + * @type {number} + * @private + */ + this.maxCacheSize_ = 32; +}; + + +/** + * @param {string} src Src. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Color} color Color. + * @return {string} Cache key. + */ +ol.style.IconImageCache.getKey = function(src, crossOrigin, color) { + ol.DEBUG && console.assert(crossOrigin !== undefined, + 'argument crossOrigin must be defined'); + var colorString = color ? ol.color.asString(color) : 'null'; + return crossOrigin + ':' + src + ':' + colorString; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.style.IconImageCache.prototype.clear = function() { + this.cache_ = {}; + this.cacheSize_ = 0; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.style.IconImageCache.prototype.expire = function() { + if (this.cacheSize_ > this.maxCacheSize_) { + var i = 0; + var key, iconImage; + for (key in this.cache_) { + iconImage = this.cache_[key]; + if ((i++ & 3) === 0 && !iconImage.hasListener()) { + delete this.cache_[key]; + --this.cacheSize_; + } + } + } +}; + + +/** + * @param {string} src Src. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Color} color Color. + * @return {ol.style.IconImage} Icon image. + */ +ol.style.IconImageCache.prototype.get = function(src, crossOrigin, color) { + var key = ol.style.IconImageCache.getKey(src, crossOrigin, color); + return key in this.cache_ ? this.cache_[key] : null; +}; + + +/** + * @param {string} src Src. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Color} color Color. + * @param {ol.style.IconImage} iconImage Icon image. + */ +ol.style.IconImageCache.prototype.set = function(src, crossOrigin, color, + iconImage) { + var key = ol.style.IconImageCache.getKey(src, crossOrigin, color); + this.cache_[key] = iconImage; + ++this.cacheSize_; +}; + +goog.provide('ol.style'); + +goog.require('ol.style.IconImageCache'); + +ol.style.iconImageCache = new ol.style.IconImageCache(); + +goog.provide('ol.transform'); + +goog.require('ol.asserts'); + + +/** + * Collection of affine 2d transformation functions. The functions work on an + * array of 6 elements. The element order is compatible with the [SVGMatrix + * interface](https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix) and is + * a subset (elements a to f) of a 3x3 martrix: + * ``` + * [ a c e ] + * [ b d f ] + * [ 0 0 1 ] + * ``` + */ + + +/** + * @private + * @type {ol.Transform} + */ +ol.transform.tmp_ = new Array(6); + + +/** + * Create an identity transform. + * @return {!ol.Transform} Identity transform. + */ +ol.transform.create = function() { + return [1, 0, 0, 1, 0, 0]; +}; + + +/** + * Resets the given transform to an identity transform. + * @param {!ol.Transform} transform Transform. + * @return {!ol.Transform} Transform. + */ +ol.transform.reset = function(transform) { + return ol.transform.set(transform, 1, 0, 0, 1, 0, 0); +}; + + +/** + * Multiply the underlying matrices of two transforms and return the result in + * the first transform. + * @param {!ol.Transform} transform1 Transform parameters of matrix 1. + * @param {!ol.Transform} transform2 Transform parameters of matrix 2. + * @return {!ol.Transform} transform1 multiplied with transform2. + */ +ol.transform.multiply = function(transform1, transform2) { + var a1 = transform1[0]; + var b1 = transform1[1]; + var c1 = transform1[2]; + var d1 = transform1[3]; + var e1 = transform1[4]; + var f1 = transform1[5]; + var a2 = transform2[0]; + var b2 = transform2[1]; + var c2 = transform2[2]; + var d2 = transform2[3]; + var e2 = transform2[4]; + var f2 = transform2[5]; + + transform1[0] = a1 * a2 + c1 * b2; + transform1[1] = b1 * a2 + d1 * b2; + transform1[2] = a1 * c2 + c1 * d2; + transform1[3] = b1 * c2 + d1 * d2; + transform1[4] = a1 * e2 + c1 * f2 + e1; + transform1[5] = b1 * e2 + d1 * f2 + f1; + + return transform1; +}; + +/** + * Set the transform components a-f on a given transform. + * @param {!ol.Transform} transform Transform. + * @param {number} a The a component of the transform. + * @param {number} b The b component of the transform. + * @param {number} c The c component of the transform. + * @param {number} d The d component of the transform. + * @param {number} e The e component of the transform. + * @param {number} f The f component of the transform. + * @return {!ol.Transform} Matrix with transform applied. + */ +ol.transform.set = function(transform, a, b, c, d, e, f) { + transform[0] = a; + transform[1] = b; + transform[2] = c; + transform[3] = d; + transform[4] = e; + transform[5] = f; + return transform; +}; + + +/** + * Set transform on one matrix from another matrix. + * @param {!ol.Transform} transform1 Matrix to set transform to. + * @param {!ol.Transform} transform2 Matrix to set transform from. + * @return {!ol.Transform} transform1 with transform from transform2 applied. + */ +ol.transform.setFromArray = function(transform1, transform2) { + transform1[0] = transform2[0]; + transform1[1] = transform2[1]; + transform1[2] = transform2[2]; + transform1[3] = transform2[3]; + transform1[4] = transform2[4]; + transform1[5] = transform2[5]; + return transform1; +}; + + +/** + * Transforms the given coordinate with the given transform returning the + * resulting, transformed coordinate. The coordinate will be modified in-place. + * + * @param {ol.Transform} transform The transformation. + * @param {ol.Coordinate|ol.Pixel} coordinate The coordinate to transform. + * @return {ol.Coordinate|ol.Pixel} return coordinate so that operations can be + * chained together. + */ +ol.transform.apply = function(transform, coordinate) { + var x = coordinate[0], y = coordinate[1]; + coordinate[0] = transform[0] * x + transform[2] * y + transform[4]; + coordinate[1] = transform[1] * x + transform[3] * y + transform[5]; + return coordinate; +}; + + +/** + * Applies rotation to the given transform. + * @param {!ol.Transform} transform Transform. + * @param {number} angle Angle in radians. + * @return {!ol.Transform} The rotated transform. + */ +ol.transform.rotate = function(transform, angle) { + var cos = Math.cos(angle); + var sin = Math.sin(angle); + return ol.transform.multiply(transform, + ol.transform.set(ol.transform.tmp_, cos, sin, -sin, cos, 0, 0)); +}; + + +/** + * Applies scale to a given transform. + * @param {!ol.Transform} transform Transform. + * @param {number} x Scale factor x. + * @param {number} y Scale factor y. + * @return {!ol.Transform} The scaled transform. + */ +ol.transform.scale = function(transform, x, y) { + return ol.transform.multiply(transform, + ol.transform.set(ol.transform.tmp_, x, 0, 0, y, 0, 0)); +}; + + +/** + * Applies translation to the given transform. + * @param {!ol.Transform} transform Transform. + * @param {number} dx Translation x. + * @param {number} dy Translation y. + * @return {!ol.Transform} The translated transform. + */ +ol.transform.translate = function(transform, dx, dy) { + return ol.transform.multiply(transform, + ol.transform.set(ol.transform.tmp_, 1, 0, 0, 1, dx, dy)); +}; + + +/** + * Creates a composite transform given an initial translation, scale, rotation, and + * final translation (in that order only, not commutative). + * @param {!ol.Transform} transform The transform (will be modified in place). + * @param {number} dx1 Initial translation x. + * @param {number} dy1 Initial translation y. + * @param {number} sx Scale factor x. + * @param {number} sy Scale factor y. + * @param {number} angle Rotation (in counter-clockwise radians). + * @param {number} dx2 Final translation x. + * @param {number} dy2 Final translation y. + * @return {!ol.Transform} The composite transform. + */ +ol.transform.compose = function(transform, dx1, dy1, sx, sy, angle, dx2, dy2) { + var sin = Math.sin(angle); + var cos = Math.cos(angle); + transform[0] = sx * cos; + transform[1] = sy * sin; + transform[2] = -sx * sin; + transform[3] = sy * cos; + transform[4] = dx2 * sx * cos - dy2 * sx * sin + dx1; + transform[5] = dx2 * sy * sin + dy2 * sy * cos + dy1; + return transform; +}; + + +/** + * Invert the given transform. + * @param {!ol.Transform} transform Transform. + * @return {!ol.Transform} Inverse of the transform. + */ +ol.transform.invert = function(transform) { + var det = ol.transform.determinant(transform); + ol.asserts.assert(det !== 0, 32); // Transformation matrix cannot be inverted + + var a = transform[0]; + var b = transform[1]; + var c = transform[2]; + var d = transform[3]; + var e = transform[4]; + var f = transform[5]; + + transform[0] = d / det; + transform[1] = -b / det; + transform[2] = -c / det; + transform[3] = a / det; + transform[4] = (c * f - d * e) / det; + transform[5] = -(a * f - b * e) / det; + + return transform; +}; + + +/** + * Returns the determinant of the given matrix. + * @param {!ol.Transform} mat Matrix. + * @return {number} Determinant. + */ +ol.transform.determinant = function(mat) { + return mat[0] * mat[3] - mat[1] * mat[2]; +}; + +goog.provide('ol.renderer.Map'); + +goog.require('ol'); +goog.require('ol.Disposable'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.functions'); +goog.require('ol.layer.Layer'); +goog.require('ol.style'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.Disposable} + * @param {Element} container Container. + * @param {ol.Map} map Map. + * @struct + */ +ol.renderer.Map = function(container, map) { + + ol.Disposable.call(this); + + + /** + * @private + * @type {ol.Map} + */ + this.map_ = map; + + /** + * @private + * @type {Object.<string, ol.renderer.Layer>} + */ + this.layerRenderers_ = {}; + + /** + * @private + * @type {Object.<string, ol.EventsKey>} + */ + this.layerRendererListeners_ = {}; + +}; +ol.inherits(ol.renderer.Map, ol.Disposable); + + +/** + * @param {olx.FrameState} frameState FrameState. + * @protected + */ +ol.renderer.Map.prototype.calculateMatrices2D = function(frameState) { + var viewState = frameState.viewState; + var coordinateToPixelTransform = frameState.coordinateToPixelTransform; + var pixelToCoordinateTransform = frameState.pixelToCoordinateTransform; + ol.DEBUG && console.assert(coordinateToPixelTransform, + 'frameState has a coordinateToPixelTransform'); + + ol.transform.compose(coordinateToPixelTransform, + frameState.size[0] / 2, frameState.size[1] / 2, + 1 / viewState.resolution, -1 / viewState.resolution, + -viewState.rotation, + -viewState.center[0], -viewState.center[1]); + + ol.transform.invert( + ol.transform.setFromArray(pixelToCoordinateTransform, coordinateToPixelTransform)); +}; + + +/** + * @abstract + * @param {ol.layer.Layer} layer Layer. + * @protected + * @return {ol.renderer.Layer} layerRenderer Layer renderer. + */ +ol.renderer.Map.prototype.createLayerRenderer = function(layer) {}; + + +/** + * @inheritDoc + */ +ol.renderer.Map.prototype.disposeInternal = function() { + for (var id in this.layerRenderers_) { + this.layerRenderers_[id].dispose(); + } +}; + + +/** + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.Map.expireIconCache_ = function(map, frameState) { + var cache = ol.style.iconImageCache; + cache.expire(); +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState FrameState. + * @param {number} hitTolerance Hit tolerance in pixels. + * @param {function(this: S, (ol.Feature|ol.render.Feature), + * ol.layer.Layer): T} callback Feature callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result. + * @template S,T,U + */ +ol.renderer.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg, + layerFilter, thisArg2) { + var result; + var viewState = frameState.viewState; + var viewResolution = viewState.resolution; + + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.layer.Layer} layer Layer. + * @return {?} Callback result. + */ + function forEachFeatureAtCoordinate(feature, layer) { + var key = ol.getUid(feature).toString(); + var managed = frameState.layerStates[ol.getUid(layer)].managed; + if (!(key in frameState.skippedFeatureUids && !managed)) { + return callback.call(thisArg, feature, managed ? layer : null); + } + } + + var projection = viewState.projection; + + var translatedCoordinate = coordinate; + if (projection.canWrapX()) { + var projectionExtent = projection.getExtent(); + var worldWidth = ol.extent.getWidth(projectionExtent); + var x = coordinate[0]; + if (x < projectionExtent[0] || x > projectionExtent[2]) { + var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth); + translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]]; + } + } + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + if (layer.getSource()) { + result = layerRenderer.forEachFeatureAtCoordinate( + layer.getSource().getWrapX() ? translatedCoordinate : coordinate, + frameState, hitTolerance, forEachFeatureAtCoordinate, thisArg); + } + if (result) { + return result; + } + } + } + return undefined; +}; + + +/** + * @abstract + * @param {ol.Pixel} pixel Pixel. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer + * callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result. + * @template S,T,U + */ +ol.renderer.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg, + layerFilter, thisArg2) {}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState FrameState. + * @param {number} hitTolerance Hit tolerance in pixels. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg Value to use as `this` when executing `layerFilter`. + * @return {boolean} Is there a feature at the given coordinate? + * @template U + */ +ol.renderer.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) { + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, hitTolerance, ol.functions.TRUE, this, layerFilter, thisArg); + + return hasFeature !== undefined; +}; + + +/** + * @param {ol.layer.Layer} layer Layer. + * @protected + * @return {ol.renderer.Layer} Layer renderer. + */ +ol.renderer.Map.prototype.getLayerRenderer = function(layer) { + var layerKey = ol.getUid(layer).toString(); + if (layerKey in this.layerRenderers_) { + return this.layerRenderers_[layerKey]; + } else { + var layerRenderer = this.createLayerRenderer(layer); + this.layerRenderers_[layerKey] = layerRenderer; + this.layerRendererListeners_[layerKey] = ol.events.listen(layerRenderer, + ol.events.EventType.CHANGE, this.handleLayerRendererChange_, this); + + return layerRenderer; + } +}; + + +/** + * @param {string} layerKey Layer key. + * @protected + * @return {ol.renderer.Layer} Layer renderer. + */ +ol.renderer.Map.prototype.getLayerRendererByKey = function(layerKey) { + ol.DEBUG && console.assert(layerKey in this.layerRenderers_, + 'given layerKey (%s) exists in layerRenderers', layerKey); + return this.layerRenderers_[layerKey]; +}; + + +/** + * @protected + * @return {Object.<string, ol.renderer.Layer>} Layer renderers. + */ +ol.renderer.Map.prototype.getLayerRenderers = function() { + return this.layerRenderers_; +}; + + +/** + * @return {ol.Map} Map. + */ +ol.renderer.Map.prototype.getMap = function() { + return this.map_; +}; + + +/** + * @abstract + * @return {string} Type + */ +ol.renderer.Map.prototype.getType = function() {}; + + +/** + * Handle changes in a layer renderer. + * @private + */ +ol.renderer.Map.prototype.handleLayerRendererChange_ = function() { + this.map_.render(); +}; + + +/** + * @param {string} layerKey Layer key. + * @return {ol.renderer.Layer} Layer renderer. + * @private + */ +ol.renderer.Map.prototype.removeLayerRendererByKey_ = function(layerKey) { + ol.DEBUG && console.assert(layerKey in this.layerRenderers_, + 'given layerKey (%s) exists in layerRenderers', layerKey); + var layerRenderer = this.layerRenderers_[layerKey]; + delete this.layerRenderers_[layerKey]; + + ol.DEBUG && console.assert(layerKey in this.layerRendererListeners_, + 'given layerKey (%s) exists in layerRendererListeners', layerKey); + ol.events.unlistenByKey(this.layerRendererListeners_[layerKey]); + delete this.layerRendererListeners_[layerKey]; + + return layerRenderer; +}; + + +/** + * Render. + * @param {?olx.FrameState} frameState Frame state. + */ +ol.renderer.Map.prototype.renderFrame = ol.nullFunction; + + +/** + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.Map.prototype.removeUnusedLayerRenderers_ = function(map, frameState) { + var layerKey; + for (layerKey in this.layerRenderers_) { + if (!frameState || !(layerKey in frameState.layerStates)) { + this.removeLayerRendererByKey_(layerKey).dispose(); + } + } +}; + + +/** + * @param {olx.FrameState} frameState Frame state. + * @protected + */ +ol.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) { + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (ol.renderer.Map.expireIconCache_) + ); +}; + + +/** + * @param {!olx.FrameState} frameState Frame state. + * @protected + */ +ol.renderer.Map.prototype.scheduleRemoveUnusedLayerRenderers = function(frameState) { + var layerKey; + for (layerKey in this.layerRenderers_) { + if (!(layerKey in frameState.layerStates)) { + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (this.removeUnusedLayerRenderers_.bind(this)) + ); + return; + } + } +}; + + +/** + * @param {ol.LayerState} state1 First layer state. + * @param {ol.LayerState} state2 Second layer state. + * @return {number} The zIndex difference. + */ +ol.renderer.Map.sortByZIndex = function(state1, state2) { + return state1.zIndex - state2.zIndex; +}; + +goog.provide('ol.layer.Image'); + +goog.require('ol'); +goog.require('ol.layer.Layer'); + + +/** + * @classdesc + * Server-rendered images that are available for arbitrary extents and + * resolutions. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Layer} + * @fires ol.render.Event + * @param {olx.layer.ImageOptions=} opt_options Layer options. + * @api stable + */ +ol.layer.Image = function(opt_options) { + var options = opt_options ? opt_options : {}; + ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (options)); +}; +ol.inherits(ol.layer.Image, ol.layer.Layer); + + +/** + * Return the associated {@link ol.source.Image source} of the image layer. + * @function + * @return {ol.source.Image} Source. + * @api stable + */ +ol.layer.Image.prototype.getSource; + +goog.provide('ol.layer.Tile'); + +goog.require('ol'); +goog.require('ol.layer.Layer'); +goog.require('ol.obj'); + + +/** + * @classdesc + * For layer sources that provide pre-rendered, tiled images in grids that are + * organized by zoom levels for specific resolutions. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Layer} + * @fires ol.render.Event + * @param {olx.layer.TileOptions=} opt_options Tile layer options. + * @api stable + */ +ol.layer.Tile = function(opt_options) { + var options = opt_options ? opt_options : {}; + + var baseOptions = ol.obj.assign({}, options); + + delete baseOptions.preload; + delete baseOptions.useInterimTilesOnError; + ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (baseOptions)); + + this.setPreload(options.preload !== undefined ? options.preload : 0); + this.setUseInterimTilesOnError(options.useInterimTilesOnError !== undefined ? + options.useInterimTilesOnError : true); +}; +ol.inherits(ol.layer.Tile, ol.layer.Layer); + + +/** + * Return the level as number to which we will preload tiles up to. + * @return {number} The level to preload tiles up to. + * @observable + * @api + */ +ol.layer.Tile.prototype.getPreload = function() { + return /** @type {number} */ (this.get(ol.layer.Tile.Property.PRELOAD)); +}; + + +/** + * Return the associated {@link ol.source.Tile tilesource} of the layer. + * @function + * @return {ol.source.Tile} Source. + * @api stable + */ +ol.layer.Tile.prototype.getSource; + + +/** + * Set the level as number to which we will preload tiles up to. + * @param {number} preload The level to preload tiles up to. + * @observable + * @api + */ +ol.layer.Tile.prototype.setPreload = function(preload) { + this.set(ol.layer.Tile.Property.PRELOAD, preload); +}; + + +/** + * Whether we use interim tiles on error. + * @return {boolean} Use interim tiles on error. + * @observable + * @api + */ +ol.layer.Tile.prototype.getUseInterimTilesOnError = function() { + return /** @type {boolean} */ ( + this.get(ol.layer.Tile.Property.USE_INTERIM_TILES_ON_ERROR)); +}; + + +/** + * Set whether we use interim tiles on error. + * @param {boolean} useInterimTilesOnError Use interim tiles on error. + * @observable + * @api + */ +ol.layer.Tile.prototype.setUseInterimTilesOnError = function(useInterimTilesOnError) { + this.set( + ol.layer.Tile.Property.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError); +}; + + +/** + * @enum {string} + */ +ol.layer.Tile.Property = { + PRELOAD: 'preload', + USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError' +}; + +goog.provide('ol.ImageBase'); + +goog.require('ol'); +goog.require('ol.events.EventTarget'); +goog.require('ol.events.EventType'); + + +/** + * @constructor + * @extends {ol.events.EventTarget} + * @param {ol.Extent} extent Extent. + * @param {number|undefined} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Image.State} state State. + * @param {Array.<ol.Attribution>} attributions Attributions. + */ +ol.ImageBase = function(extent, resolution, pixelRatio, state, attributions) { + + ol.events.EventTarget.call(this); + + /** + * @private + * @type {Array.<ol.Attribution>} + */ + this.attributions_ = attributions; + + /** + * @protected + * @type {ol.Extent} + */ + this.extent = extent; + + /** + * @private + * @type {number} + */ + this.pixelRatio_ = pixelRatio; + + /** + * @protected + * @type {number|undefined} + */ + this.resolution = resolution; + + /** + * @protected + * @type {ol.Image.State} + */ + this.state = state; + +}; +ol.inherits(ol.ImageBase, ol.events.EventTarget); + + +/** + * @protected + */ +ol.ImageBase.prototype.changed = function() { + this.dispatchEvent(ol.events.EventType.CHANGE); +}; + + +/** + * @return {Array.<ol.Attribution>} Attributions. + */ +ol.ImageBase.prototype.getAttributions = function() { + return this.attributions_; +}; + + +/** + * @return {ol.Extent} Extent. + */ +ol.ImageBase.prototype.getExtent = function() { + return this.extent; +}; + + +/** + * @abstract + * @param {Object=} opt_context Object. + * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image. + */ +ol.ImageBase.prototype.getImage = function(opt_context) {}; + + +/** + * @return {number} PixelRatio. + */ +ol.ImageBase.prototype.getPixelRatio = function() { + return this.pixelRatio_; +}; + + +/** + * @return {number} Resolution. + */ +ol.ImageBase.prototype.getResolution = function() { + ol.DEBUG && console.assert(this.resolution !== undefined, 'resolution not yet set'); + return /** @type {number} */ (this.resolution); +}; + + +/** + * @return {ol.Image.State} State. + */ +ol.ImageBase.prototype.getState = function() { + return this.state; +}; + + +/** + * Load not yet loaded URI. + * @abstract + */ +ol.ImageBase.prototype.load = function() {}; + +goog.provide('ol.Image'); + +goog.require('ol'); +goog.require('ol.ImageBase'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.obj'); + + +/** + * @constructor + * @extends {ol.ImageBase} + * @param {ol.Extent} extent Extent. + * @param {number|undefined} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {Array.<ol.Attribution>} attributions Attributions. + * @param {string} src Image source URI. + * @param {?string} crossOrigin Cross origin. + * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function. + */ +ol.Image = function(extent, resolution, pixelRatio, attributions, src, + crossOrigin, imageLoadFunction) { + + ol.ImageBase.call(this, extent, resolution, pixelRatio, ol.Image.State.IDLE, + attributions); + + /** + * @private + * @type {string} + */ + this.src_ = src; + + /** + * @private + * @type {HTMLCanvasElement|Image|HTMLVideoElement} + */ + this.image_ = new Image(); + if (crossOrigin !== null) { + this.image_.crossOrigin = crossOrigin; + } + + /** + * @private + * @type {Object.<number, (HTMLCanvasElement|Image|HTMLVideoElement)>} + */ + this.imageByContext_ = {}; + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.imageListenerKeys_ = null; + + /** + * @protected + * @type {ol.Image.State} + */ + this.state = ol.Image.State.IDLE; + + /** + * @private + * @type {ol.ImageLoadFunctionType} + */ + this.imageLoadFunction_ = imageLoadFunction; + +}; +ol.inherits(ol.Image, ol.ImageBase); + + +/** + * Get the HTML image element (may be a Canvas, Image, or Video). + * @param {Object=} opt_context Object. + * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image. + * @api + */ +ol.Image.prototype.getImage = function(opt_context) { + if (opt_context !== undefined) { + var image; + var key = ol.getUid(opt_context); + if (key in this.imageByContext_) { + return this.imageByContext_[key]; + } else if (ol.obj.isEmpty(this.imageByContext_)) { + image = this.image_; + } else { + image = /** @type {Image} */ (this.image_.cloneNode(false)); + } + this.imageByContext_[key] = image; + return image; + } else { + return this.image_; + } +}; + + +/** + * Tracks loading or read errors. + * + * @private + */ +ol.Image.prototype.handleImageError_ = function() { + this.state = ol.Image.State.ERROR; + this.unlistenImage_(); + this.changed(); +}; + + +/** + * Tracks successful image load. + * + * @private + */ +ol.Image.prototype.handleImageLoad_ = function() { + if (this.resolution === undefined) { + this.resolution = ol.extent.getHeight(this.extent) / this.image_.height; + } + this.state = ol.Image.State.LOADED; + this.unlistenImage_(); + this.changed(); +}; + + +/** + * Load the image or retry if loading previously failed. + * Loading is taken care of by the tile queue, and calling this method is + * only needed for preloading or for reloading in case of an error. + * @api + */ +ol.Image.prototype.load = function() { + if (this.state == ol.Image.State.IDLE || this.state == ol.Image.State.ERROR) { + this.state = ol.Image.State.LOADING; + this.changed(); + ol.DEBUG && console.assert(!this.imageListenerKeys_, + 'this.imageListenerKeys_ should be null'); + this.imageListenerKeys_ = [ + ol.events.listenOnce(this.image_, ol.events.EventType.ERROR, + this.handleImageError_, this), + ol.events.listenOnce(this.image_, ol.events.EventType.LOAD, + this.handleImageLoad_, this) + ]; + this.imageLoadFunction_(this, this.src_); + } +}; + + +/** + * @param {HTMLCanvasElement|Image|HTMLVideoElement} image Image. + */ +ol.Image.prototype.setImage = function(image) { + this.image_ = image; +}; + + +/** + * Discards event handlers which listen for load completion or errors. + * + * @private + */ +ol.Image.prototype.unlistenImage_ = function() { + this.imageListenerKeys_.forEach(ol.events.unlistenByKey); + this.imageListenerKeys_ = null; +}; + + +/** + * @enum {number} + */ +ol.Image.State = { + IDLE: 0, + LOADING: 1, + LOADED: 2, + ERROR: 3 +}; + +goog.provide('ol.render.canvas'); + + +/** + * @const + * @type {string} + */ +ol.render.canvas.defaultFont = '10px sans-serif'; + + +/** + * @const + * @type {ol.Color} + */ +ol.render.canvas.defaultFillStyle = [0, 0, 0, 1]; + + +/** + * @const + * @type {string} + */ +ol.render.canvas.defaultLineCap = 'round'; + + +/** + * @const + * @type {Array.<number>} + */ +ol.render.canvas.defaultLineDash = []; + + +/** + * @const + * @type {string} + */ +ol.render.canvas.defaultLineJoin = 'round'; + + +/** + * @const + * @type {number} + */ +ol.render.canvas.defaultMiterLimit = 10; + + +/** + * @const + * @type {ol.Color} + */ +ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1]; + + +/** + * @const + * @type {string} + */ +ol.render.canvas.defaultTextAlign = 'center'; + + +/** + * @const + * @type {string} + */ +ol.render.canvas.defaultTextBaseline = 'middle'; + + +/** + * @const + * @type {number} + */ +ol.render.canvas.defaultLineWidth = 1; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {number} rotation Rotation. + * @param {number} offsetX X offset. + * @param {number} offsetY Y offset. + */ +ol.render.canvas.rotateAtOffset = function(context, rotation, offsetX, offsetY) { + if (rotation !== 0) { + context.translate(offsetX, offsetY); + context.rotate(rotation); + context.translate(-offsetX, -offsetY); + } +}; + +goog.provide('ol.style.Image'); + + +/** + * @classdesc + * A base class used for creating subclasses and not instantiated in + * apps. Base class for {@link ol.style.Icon}, {@link ol.style.Circle} and + * {@link ol.style.RegularShape}. + * + * @constructor + * @param {ol.StyleImageOptions} options Options. + * @api + */ +ol.style.Image = function(options) { + + /** + * @private + * @type {number} + */ + this.opacity_ = options.opacity; + + /** + * @private + * @type {boolean} + */ + this.rotateWithView_ = options.rotateWithView; + + /** + * @private + * @type {number} + */ + this.rotation_ = options.rotation; + + /** + * @private + * @type {number} + */ + this.scale_ = options.scale; + + /** + * @private + * @type {boolean} + */ + this.snapToPixel_ = options.snapToPixel; + +}; + + +/** + * Get the symbolizer opacity. + * @return {number} Opacity. + * @api + */ +ol.style.Image.prototype.getOpacity = function() { + return this.opacity_; +}; + + +/** + * Determine whether the symbolizer rotates with the map. + * @return {boolean} Rotate with map. + * @api + */ +ol.style.Image.prototype.getRotateWithView = function() { + return this.rotateWithView_; +}; + + +/** + * Get the symoblizer rotation. + * @return {number} Rotation. + * @api + */ +ol.style.Image.prototype.getRotation = function() { + return this.rotation_; +}; + + +/** + * Get the symbolizer scale. + * @return {number} Scale. + * @api + */ +ol.style.Image.prototype.getScale = function() { + return this.scale_; +}; + + +/** + * Determine whether the symbolizer should be snapped to a pixel. + * @return {boolean} The symbolizer should snap to a pixel. + * @api + */ +ol.style.Image.prototype.getSnapToPixel = function() { + return this.snapToPixel_; +}; + + +/** + * Get the anchor point in pixels. The anchor determines the center point for the + * symbolizer. + * @abstract + * @return {Array.<number>} Anchor. + */ +ol.style.Image.prototype.getAnchor = function() {}; + + +/** + * Get the image element for the symbolizer. + * @abstract + * @param {number} pixelRatio Pixel ratio. + * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element. + */ +ol.style.Image.prototype.getImage = function(pixelRatio) {}; + + +/** + * @abstract + * @param {number} pixelRatio Pixel ratio. + * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element. + */ +ol.style.Image.prototype.getHitDetectionImage = function(pixelRatio) {}; + + +/** + * @abstract + * @return {ol.Image.State} Image state. + */ +ol.style.Image.prototype.getImageState = function() {}; + + +/** + * @abstract + * @return {ol.Size} Image size. + */ +ol.style.Image.prototype.getImageSize = function() {}; + + +/** + * @abstract + * @return {ol.Size} Size of the hit-detection image. + */ +ol.style.Image.prototype.getHitDetectionImageSize = function() {}; + + +/** + * Get the origin of the symbolizer. + * @abstract + * @return {Array.<number>} Origin. + */ +ol.style.Image.prototype.getOrigin = function() {}; + + +/** + * Get the size of the symbolizer (in pixels). + * @abstract + * @return {ol.Size} Size. + */ +ol.style.Image.prototype.getSize = function() {}; + + +/** + * Set the opacity. + * + * @param {number} opacity Opacity. + * @api + */ +ol.style.Image.prototype.setOpacity = function(opacity) { + this.opacity_ = opacity; +}; + + +/** + * Set whether to rotate the style with the view. + * + * @param {boolean} rotateWithView Rotate with map. + */ +ol.style.Image.prototype.setRotateWithView = function(rotateWithView) { + this.rotateWithView_ = rotateWithView; +}; + + +/** + * Set the rotation. + * + * @param {number} rotation Rotation. + * @api + */ +ol.style.Image.prototype.setRotation = function(rotation) { + this.rotation_ = rotation; +}; + + +/** + * Set the scale. + * + * @param {number} scale Scale. + * @api + */ +ol.style.Image.prototype.setScale = function(scale) { + this.scale_ = scale; +}; + + +/** + * Set whether to snap the image to the closest pixel. + * + * @param {boolean} snapToPixel Snap to pixel? + */ +ol.style.Image.prototype.setSnapToPixel = function(snapToPixel) { + this.snapToPixel_ = snapToPixel; +}; + + +/** + * @abstract + * @param {function(this: T, ol.events.Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @return {ol.EventsKey|undefined} Listener key. + * @template T + */ +ol.style.Image.prototype.listenImageChange = function(listener, thisArg) {}; + + +/** + * Load not yet loaded URI. + * @abstract + */ +ol.style.Image.prototype.load = function() {}; + + +/** + * @abstract + * @param {function(this: T, ol.events.Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @template T + */ +ol.style.Image.prototype.unlistenImageChange = function(listener, thisArg) {}; + +goog.provide('ol.style.RegularShape'); + +goog.require('ol'); +goog.require('ol.colorlike'); +goog.require('ol.dom'); +goog.require('ol.has'); +goog.require('ol.Image'); +goog.require('ol.render.canvas'); +goog.require('ol.style.Image'); + + +/** + * @classdesc + * Set regular shape style for vector features. The resulting shape will be + * a regular polygon when `radius` is provided, or a star when `radius1` and + * `radius2` are provided. + * + * @constructor + * @param {olx.style.RegularShapeOptions} options Options. + * @extends {ol.style.Image} + * @api + */ +ol.style.RegularShape = function(options) { + + ol.DEBUG && console.assert( + options.radius !== undefined || options.radius1 !== undefined, + 'must provide either "radius" or "radius1"'); + + /** + * @private + * @type {Array.<string>} + */ + this.checksums_ = null; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = null; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.hitDetectionCanvas_ = null; + + /** + * @private + * @type {ol.style.Fill} + */ + this.fill_ = options.fill !== undefined ? options.fill : null; + + /** + * @private + * @type {Array.<number>} + */ + this.origin_ = [0, 0]; + + /** + * @private + * @type {number} + */ + this.points_ = options.points; + + /** + * @protected + * @type {number} + */ + this.radius_ = /** @type {number} */ (options.radius !== undefined ? + options.radius : options.radius1); + + /** + * @private + * @type {number} + */ + this.radius2_ = + options.radius2 !== undefined ? options.radius2 : this.radius_; + + /** + * @private + * @type {number} + */ + this.angle_ = options.angle !== undefined ? options.angle : 0; + + /** + * @private + * @type {ol.style.Stroke} + */ + this.stroke_ = options.stroke !== undefined ? options.stroke : null; + + /** + * @private + * @type {Array.<number>} + */ + this.anchor_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.size_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.imageSize_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.hitDetectionImageSize_ = null; + + /** + * @protected + * @type {ol.style.AtlasManager|undefined} + */ + this.atlasManager_ = options.atlasManager; + + this.render_(this.atlasManager_); + + /** + * @type {boolean} + */ + var snapToPixel = options.snapToPixel !== undefined ? + options.snapToPixel : true; + + /** + * @type {boolean} + */ + var rotateWithView = options.rotateWithView !== undefined ? + options.rotateWithView : false; + + ol.style.Image.call(this, { + opacity: 1, + rotateWithView: rotateWithView, + rotation: options.rotation !== undefined ? options.rotation : 0, + scale: 1, + snapToPixel: snapToPixel + }); + +}; +ol.inherits(ol.style.RegularShape, ol.style.Image); + + +/** + * Clones the style. If an atlasmanager was provided to the original style it will be used in the cloned style, too. + * @return {ol.style.RegularShape} The cloned style. + * @api + */ +ol.style.RegularShape.prototype.clone = function() { + var style = new ol.style.RegularShape({ + fill: this.getFill() ? this.getFill().clone() : undefined, + points: this.getRadius2() !== this.getRadius() ? this.getPoints() / 2 : this.getPoints(), + radius: this.getRadius(), + radius2: this.getRadius2(), + angle: this.getAngle(), + snapToPixel: this.getSnapToPixel(), + stroke: this.getStroke() ? this.getStroke().clone() : undefined, + rotation: this.getRotation(), + rotateWithView: this.getRotateWithView(), + atlasManager: this.atlasManager_ + }); + style.setOpacity(this.getOpacity()); + style.setScale(this.getScale()); + return style; +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.RegularShape.prototype.getAnchor = function() { + return this.anchor_; +}; + + +/** + * Get the angle used in generating the shape. + * @return {number} Shape's rotation in radians. + * @api + */ +ol.style.RegularShape.prototype.getAngle = function() { + return this.angle_; +}; + + +/** + * Get the fill style for the shape. + * @return {ol.style.Fill} Fill style. + * @api + */ +ol.style.RegularShape.prototype.getFill = function() { + return this.fill_; +}; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.getHitDetectionImage = function(pixelRatio) { + return this.hitDetectionCanvas_; +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.RegularShape.prototype.getImage = function(pixelRatio) { + return this.canvas_; +}; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.getImageSize = function() { + return this.imageSize_; +}; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.getHitDetectionImageSize = function() { + return this.hitDetectionImageSize_; +}; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.getImageState = function() { + return ol.Image.State.LOADED; +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.RegularShape.prototype.getOrigin = function() { + return this.origin_; +}; + + +/** + * Get the number of points for generating the shape. + * @return {number} Number of points for stars and regular polygons. + * @api + */ +ol.style.RegularShape.prototype.getPoints = function() { + return this.points_; +}; + + +/** + * Get the (primary) radius for the shape. + * @return {number} Radius. + * @api + */ +ol.style.RegularShape.prototype.getRadius = function() { + return this.radius_; +}; + + +/** + * Get the secondary radius for the shape. + * @return {number} Radius2. + * @api + */ +ol.style.RegularShape.prototype.getRadius2 = function() { + return this.radius2_; +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.RegularShape.prototype.getSize = function() { + return this.size_; +}; + + +/** + * Get the stroke style for the shape. + * @return {ol.style.Stroke} Stroke style. + * @api + */ +ol.style.RegularShape.prototype.getStroke = function() { + return this.stroke_; +}; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.listenImageChange = ol.nullFunction; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.load = ol.nullFunction; + + +/** + * @inheritDoc + */ +ol.style.RegularShape.prototype.unlistenImageChange = ol.nullFunction; + + +/** + * @protected + * @param {ol.style.AtlasManager|undefined} atlasManager An atlas manager. + */ +ol.style.RegularShape.prototype.render_ = function(atlasManager) { + var imageSize; + var lineCap = ''; + var lineJoin = ''; + var miterLimit = 0; + var lineDash = null; + var strokeStyle; + var strokeWidth = 0; + + if (this.stroke_) { + strokeStyle = ol.colorlike.asColorLike(this.stroke_.getColor()); + strokeWidth = this.stroke_.getWidth(); + if (strokeWidth === undefined) { + strokeWidth = ol.render.canvas.defaultLineWidth; + } + lineDash = this.stroke_.getLineDash(); + if (!ol.has.CANVAS_LINE_DASH) { + lineDash = null; + } + lineJoin = this.stroke_.getLineJoin(); + if (lineJoin === undefined) { + lineJoin = ol.render.canvas.defaultLineJoin; + } + lineCap = this.stroke_.getLineCap(); + if (lineCap === undefined) { + lineCap = ol.render.canvas.defaultLineCap; + } + miterLimit = this.stroke_.getMiterLimit(); + if (miterLimit === undefined) { + miterLimit = ol.render.canvas.defaultMiterLimit; + } + } + + var size = 2 * (this.radius_ + strokeWidth) + 1; + + /** @type {ol.RegularShapeRenderOptions} */ + var renderOptions = { + strokeStyle: strokeStyle, + strokeWidth: strokeWidth, + size: size, + lineCap: lineCap, + lineDash: lineDash, + lineJoin: lineJoin, + miterLimit: miterLimit + }; + + if (atlasManager === undefined) { + // no atlas manager is used, create a new canvas + var context = ol.dom.createCanvasContext2D(size, size); + this.canvas_ = context.canvas; + + // canvas.width and height are rounded to the closest integer + size = this.canvas_.width; + imageSize = size; + + this.draw_(renderOptions, context, 0, 0); + + this.createHitDetectionCanvas_(renderOptions); + } else { + // an atlas manager is used, add the symbol to an atlas + size = Math.round(size); + + var hasCustomHitDetectionImage = !this.fill_; + var renderHitDetectionCallback; + if (hasCustomHitDetectionImage) { + // render the hit-detection image into a separate atlas image + renderHitDetectionCallback = + this.drawHitDetectionCanvas_.bind(this, renderOptions); + } + + var id = this.getChecksum(); + var info = atlasManager.add( + id, size, size, this.draw_.bind(this, renderOptions), + renderHitDetectionCallback); + ol.DEBUG && console.assert(info, 'shape size is too large'); + + this.canvas_ = info.image; + this.origin_ = [info.offsetX, info.offsetY]; + imageSize = info.image.width; + + if (hasCustomHitDetectionImage) { + this.hitDetectionCanvas_ = info.hitImage; + this.hitDetectionImageSize_ = + [info.hitImage.width, info.hitImage.height]; + } else { + this.hitDetectionCanvas_ = this.canvas_; + this.hitDetectionImageSize_ = [imageSize, imageSize]; + } + } + + this.anchor_ = [size / 2, size / 2]; + this.size_ = [size, size]; + this.imageSize_ = [imageSize, imageSize]; +}; + + +/** + * @private + * @param {ol.RegularShapeRenderOptions} renderOptions Render options. + * @param {CanvasRenderingContext2D} context The rendering context. + * @param {number} x The origin for the symbol (x). + * @param {number} y The origin for the symbol (y). + */ +ol.style.RegularShape.prototype.draw_ = function(renderOptions, context, x, y) { + var i, angle0, radiusC; + // reset transform + context.setTransform(1, 0, 0, 1, 0, 0); + + // then move to (x, y) + context.translate(x, y); + + context.beginPath(); + + if (this.points_ === Infinity) { + context.arc( + renderOptions.size / 2, renderOptions.size / 2, + this.radius_, 0, 2 * Math.PI, true); + } else { + if (this.radius2_ !== this.radius_) { + this.points_ = 2 * this.points_; + } + for (i = 0; i <= this.points_; i++) { + angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_; + radiusC = i % 2 === 0 ? this.radius_ : this.radius2_; + context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0), + renderOptions.size / 2 + radiusC * Math.sin(angle0)); + } + } + + + if (this.fill_) { + context.fillStyle = ol.colorlike.asColorLike(this.fill_.getColor()); + context.fill(); + } + if (this.stroke_) { + context.strokeStyle = renderOptions.strokeStyle; + context.lineWidth = renderOptions.strokeWidth; + if (renderOptions.lineDash) { + context.setLineDash(renderOptions.lineDash); + } + context.lineCap = renderOptions.lineCap; + context.lineJoin = renderOptions.lineJoin; + context.miterLimit = renderOptions.miterLimit; + context.stroke(); + } + context.closePath(); +}; + + +/** + * @private + * @param {ol.RegularShapeRenderOptions} renderOptions Render options. + */ +ol.style.RegularShape.prototype.createHitDetectionCanvas_ = function(renderOptions) { + this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size]; + if (this.fill_) { + this.hitDetectionCanvas_ = this.canvas_; + return; + } + + // if no fill style is set, create an extra hit-detection image with a + // default fill style + var context = ol.dom.createCanvasContext2D(renderOptions.size, renderOptions.size); + this.hitDetectionCanvas_ = context.canvas; + + this.drawHitDetectionCanvas_(renderOptions, context, 0, 0); +}; + + +/** + * @private + * @param {ol.RegularShapeRenderOptions} renderOptions Render options. + * @param {CanvasRenderingContext2D} context The context. + * @param {number} x The origin for the symbol (x). + * @param {number} y The origin for the symbol (y). + */ +ol.style.RegularShape.prototype.drawHitDetectionCanvas_ = function(renderOptions, context, x, y) { + // reset transform + context.setTransform(1, 0, 0, 1, 0, 0); + + // then move to (x, y) + context.translate(x, y); + + context.beginPath(); + + if (this.points_ === Infinity) { + context.arc( + renderOptions.size / 2, renderOptions.size / 2, + this.radius_, 0, 2 * Math.PI, true); + } else { + if (this.radius2_ !== this.radius_) { + this.points_ = 2 * this.points_; + } + var i, radiusC, angle0; + for (i = 0; i <= this.points_; i++) { + angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_; + radiusC = i % 2 === 0 ? this.radius_ : this.radius2_; + context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0), + renderOptions.size / 2 + radiusC * Math.sin(angle0)); + } + } + + context.fillStyle = ol.render.canvas.defaultFillStyle; + context.fill(); + if (this.stroke_) { + context.strokeStyle = renderOptions.strokeStyle; + context.lineWidth = renderOptions.strokeWidth; + if (renderOptions.lineDash) { + context.setLineDash(renderOptions.lineDash); + } + context.stroke(); + } + context.closePath(); +}; + + +/** + * @return {string} The checksum. + */ +ol.style.RegularShape.prototype.getChecksum = function() { + var strokeChecksum = this.stroke_ ? + this.stroke_.getChecksum() : '-'; + var fillChecksum = this.fill_ ? + this.fill_.getChecksum() : '-'; + + var recalculate = !this.checksums_ || + (strokeChecksum != this.checksums_[1] || + fillChecksum != this.checksums_[2] || + this.radius_ != this.checksums_[3] || + this.radius2_ != this.checksums_[4] || + this.angle_ != this.checksums_[5] || + this.points_ != this.checksums_[6]); + + if (recalculate) { + var checksum = 'r' + strokeChecksum + fillChecksum + + (this.radius_ !== undefined ? this.radius_.toString() : '-') + + (this.radius2_ !== undefined ? this.radius2_.toString() : '-') + + (this.angle_ !== undefined ? this.angle_.toString() : '-') + + (this.points_ !== undefined ? this.points_.toString() : '-'); + this.checksums_ = [checksum, strokeChecksum, fillChecksum, + this.radius_, this.radius2_, this.angle_, this.points_]; + } + + return this.checksums_[0]; +}; + +goog.provide('ol.style.Circle'); + +goog.require('ol'); +goog.require('ol.style.RegularShape'); + + +/** + * @classdesc + * Set circle style for vector features. + * + * @constructor + * @param {olx.style.CircleOptions=} opt_options Options. + * @extends {ol.style.RegularShape} + * @api + */ +ol.style.Circle = function(opt_options) { + + var options = opt_options || {}; + + ol.style.RegularShape.call(this, { + points: Infinity, + fill: options.fill, + radius: options.radius, + snapToPixel: options.snapToPixel, + stroke: options.stroke, + atlasManager: options.atlasManager + }); + +}; +ol.inherits(ol.style.Circle, ol.style.RegularShape); + + +/** + * Clones the style. If an atlasmanager was provided to the original style it will be used in the cloned style, too. + * @return {ol.style.Circle} The cloned style. + * @api + */ +ol.style.Circle.prototype.clone = function() { + var style = new ol.style.Circle({ + fill: this.getFill() ? this.getFill().clone() : undefined, + stroke: this.getStroke() ? this.getStroke().clone() : undefined, + radius: this.getRadius(), + snapToPixel: this.getSnapToPixel(), + atlasManager: this.atlasManager_ + }); + style.setOpacity(this.getOpacity()); + style.setScale(this.getScale()); + return style; +}; + + +/** + * Set the circle radius. + * + * @param {number} radius Circle radius. + * @api + */ +ol.style.Circle.prototype.setRadius = function(radius) { + this.radius_ = radius; + this.render_(this.atlasManager_); +}; + +goog.provide('ol.style.Fill'); + +goog.require('ol'); +goog.require('ol.color'); + + +/** + * @classdesc + * Set fill style for vector features. + * + * @constructor + * @param {olx.style.FillOptions=} opt_options Options. + * @api + */ +ol.style.Fill = function(opt_options) { + + var options = opt_options || {}; + + /** + * @private + * @type {ol.Color|ol.ColorLike} + */ + this.color_ = options.color !== undefined ? options.color : null; + + /** + * @private + * @type {string|undefined} + */ + this.checksum_ = undefined; +}; + + +/** + * Clones the style. The color is not cloned if it is an {@link ol.ColorLike}. + * @return {ol.style.Fill} The cloned style. + * @api + */ +ol.style.Fill.prototype.clone = function() { + var color = this.getColor(); + return new ol.style.Fill({ + color: (color && color.slice) ? color.slice() : color || undefined + }); +}; + + +/** + * Get the fill color. + * @return {ol.Color|ol.ColorLike} Color. + * @api + */ +ol.style.Fill.prototype.getColor = function() { + return this.color_; +}; + + +/** + * Set the color. + * + * @param {ol.Color|ol.ColorLike} color Color. + * @api + */ +ol.style.Fill.prototype.setColor = function(color) { + this.color_ = color; + this.checksum_ = undefined; +}; + + +/** + * @return {string} The checksum. + */ +ol.style.Fill.prototype.getChecksum = function() { + if (this.checksum_ === undefined) { + if ( + this.color_ instanceof CanvasPattern || + this.color_ instanceof CanvasGradient + ) { + this.checksum_ = ol.getUid(this.color_).toString(); + } else { + this.checksum_ = 'f' + (this.color_ ? + ol.color.asString(this.color_) : '-'); + } + } + + return this.checksum_; +}; + +goog.provide('ol.style.Stroke'); + +goog.require('ol'); + + +/** + * @classdesc + * Set stroke style for vector features. + * Note that the defaults given are the Canvas defaults, which will be used if + * option is not defined. The `get` functions return whatever was entered in + * the options; they will not return the default. + * + * @constructor + * @param {olx.style.StrokeOptions=} opt_options Options. + * @api + */ +ol.style.Stroke = function(opt_options) { + + var options = opt_options || {}; + + /** + * @private + * @type {ol.Color|ol.ColorLike} + */ + this.color_ = options.color !== undefined ? options.color : null; + + /** + * @private + * @type {string|undefined} + */ + this.lineCap_ = options.lineCap; + + /** + * @private + * @type {Array.<number>} + */ + this.lineDash_ = options.lineDash !== undefined ? options.lineDash : null; + + /** + * @private + * @type {string|undefined} + */ + this.lineJoin_ = options.lineJoin; + + /** + * @private + * @type {number|undefined} + */ + this.miterLimit_ = options.miterLimit; + + /** + * @private + * @type {number|undefined} + */ + this.width_ = options.width; + + /** + * @private + * @type {string|undefined} + */ + this.checksum_ = undefined; +}; + + +/** + * Clones the style. + * @return {ol.style.Stroke} The cloned style. + * @api + */ +ol.style.Stroke.prototype.clone = function() { + var color = this.getColor(); + return new ol.style.Stroke({ + color: (color && color.slice) ? color.slice() : color || undefined, + lineCap: this.getLineCap(), + lineDash: this.getLineDash() ? this.getLineDash().slice() : undefined, + lineJoin: this.getLineJoin(), + miterLimit: this.getMiterLimit(), + width: this.getWidth() + }); +}; + + +/** + * Get the stroke color. + * @return {ol.Color|ol.ColorLike} Color. + * @api + */ +ol.style.Stroke.prototype.getColor = function() { + return this.color_; +}; + + +/** + * Get the line cap type for the stroke. + * @return {string|undefined} Line cap. + * @api + */ +ol.style.Stroke.prototype.getLineCap = function() { + return this.lineCap_; +}; + + +/** + * Get the line dash style for the stroke. + * @return {Array.<number>} Line dash. + * @api + */ +ol.style.Stroke.prototype.getLineDash = function() { + return this.lineDash_; +}; + + +/** + * Get the line join type for the stroke. + * @return {string|undefined} Line join. + * @api + */ +ol.style.Stroke.prototype.getLineJoin = function() { + return this.lineJoin_; +}; + + +/** + * Get the miter limit for the stroke. + * @return {number|undefined} Miter limit. + * @api + */ +ol.style.Stroke.prototype.getMiterLimit = function() { + return this.miterLimit_; +}; + + +/** + * Get the stroke width. + * @return {number|undefined} Width. + * @api + */ +ol.style.Stroke.prototype.getWidth = function() { + return this.width_; +}; + + +/** + * Set the color. + * + * @param {ol.Color|ol.ColorLike} color Color. + * @api + */ +ol.style.Stroke.prototype.setColor = function(color) { + this.color_ = color; + this.checksum_ = undefined; +}; + + +/** + * Set the line cap. + * + * @param {string|undefined} lineCap Line cap. + * @api + */ +ol.style.Stroke.prototype.setLineCap = function(lineCap) { + this.lineCap_ = lineCap; + this.checksum_ = undefined; +}; + + +/** + * Set the line dash. + * + * Please note that Internet Explorer 10 and lower [do not support][mdn] the + * `setLineDash` method on the `CanvasRenderingContext2D` and therefore this + * property will have no visual effect in these browsers. + * + * [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility + * + * @param {Array.<number>} lineDash Line dash. + * @api + */ +ol.style.Stroke.prototype.setLineDash = function(lineDash) { + this.lineDash_ = lineDash; + this.checksum_ = undefined; +}; + + +/** + * Set the line join. + * + * @param {string|undefined} lineJoin Line join. + * @api + */ +ol.style.Stroke.prototype.setLineJoin = function(lineJoin) { + this.lineJoin_ = lineJoin; + this.checksum_ = undefined; +}; + + +/** + * Set the miter limit. + * + * @param {number|undefined} miterLimit Miter limit. + * @api + */ +ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) { + this.miterLimit_ = miterLimit; + this.checksum_ = undefined; +}; + + +/** + * Set the width. + * + * @param {number|undefined} width Width. + * @api + */ +ol.style.Stroke.prototype.setWidth = function(width) { + this.width_ = width; + this.checksum_ = undefined; +}; + + +/** + * @return {string} The checksum. + */ +ol.style.Stroke.prototype.getChecksum = function() { + if (this.checksum_ === undefined) { + this.checksum_ = 's'; + if (this.color_) { + if (typeof this.color_ === 'string') { + this.checksum_ += this.color_; + } else { + this.checksum_ += ol.getUid(this.color_).toString(); + } + } else { + this.checksum_ += '-'; + } + this.checksum_ += ',' + + (this.lineCap_ !== undefined ? + this.lineCap_.toString() : '-') + ',' + + (this.lineDash_ ? + this.lineDash_.toString() : '-') + ',' + + (this.lineJoin_ !== undefined ? + this.lineJoin_ : '-') + ',' + + (this.miterLimit_ !== undefined ? + this.miterLimit_.toString() : '-') + ',' + + (this.width_ !== undefined ? + this.width_.toString() : '-'); + } + + return this.checksum_; +}; + +goog.provide('ol.style.Style'); + +goog.require('ol.asserts'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.style.Circle'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Stroke'); + + +/** + * @classdesc + * Container for vector feature rendering styles. Any changes made to the style + * or its children through `set*()` methods will not take effect until the + * feature or layer that uses the style is re-rendered. + * + * @constructor + * @struct + * @param {olx.style.StyleOptions=} opt_options Style options. + * @api + */ +ol.style.Style = function(opt_options) { + + var options = opt_options || {}; + + /** + * @private + * @type {string|ol.geom.Geometry|ol.StyleGeometryFunction} + */ + this.geometry_ = null; + + /** + * @private + * @type {!ol.StyleGeometryFunction} + */ + this.geometryFunction_ = ol.style.Style.defaultGeometryFunction; + + if (options.geometry !== undefined) { + this.setGeometry(options.geometry); + } + + /** + * @private + * @type {ol.style.Fill} + */ + this.fill_ = options.fill !== undefined ? options.fill : null; + + /** + * @private + * @type {ol.style.Image} + */ + this.image_ = options.image !== undefined ? options.image : null; + + /** + * @private + * @type {ol.style.Stroke} + */ + this.stroke_ = options.stroke !== undefined ? options.stroke : null; + + /** + * @private + * @type {ol.style.Text} + */ + this.text_ = options.text !== undefined ? options.text : null; + + /** + * @private + * @type {number|undefined} + */ + this.zIndex_ = options.zIndex; + +}; + + +/** + * Clones the style. + * @return {ol.style.Style} The cloned style. + * @api + */ +ol.style.Style.prototype.clone = function() { + var geometry = this.getGeometry(); + if (geometry && geometry.clone) { + geometry = geometry.clone(); + } + return new ol.style.Style({ + geometry: geometry, + fill: this.getFill() ? this.getFill().clone() : undefined, + image: this.getImage() ? this.getImage().clone() : undefined, + stroke: this.getStroke() ? this.getStroke().clone() : undefined, + text: this.getText() ? this.getText().clone() : undefined, + zIndex: this.getZIndex() + }); +}; + + +/** + * Get the geometry to be rendered. + * @return {string|ol.geom.Geometry|ol.StyleGeometryFunction} + * Feature property or geometry or function that returns the geometry that will + * be rendered with this style. + * @api + */ +ol.style.Style.prototype.getGeometry = function() { + return this.geometry_; +}; + + +/** + * Get the function used to generate a geometry for rendering. + * @return {!ol.StyleGeometryFunction} Function that is called with a feature + * and returns the geometry to render instead of the feature's geometry. + * @api + */ +ol.style.Style.prototype.getGeometryFunction = function() { + return this.geometryFunction_; +}; + + +/** + * Get the fill style. + * @return {ol.style.Fill} Fill style. + * @api + */ +ol.style.Style.prototype.getFill = function() { + return this.fill_; +}; + + +/** + * Set the fill style. + * @param {ol.style.Fill} fill Fill style. + * @api + */ +ol.style.Style.prototype.setFill = function(fill) { + this.fill_ = fill; +}; + + +/** + * Get the image style. + * @return {ol.style.Image} Image style. + * @api + */ +ol.style.Style.prototype.getImage = function() { + return this.image_; +}; + + +/** + * Set the image style. + * @param {ol.style.Image} image Image style. + * @api + */ +ol.style.Style.prototype.setImage = function(image) { + this.image_ = image; +}; + + +/** + * Get the stroke style. + * @return {ol.style.Stroke} Stroke style. + * @api + */ +ol.style.Style.prototype.getStroke = function() { + return this.stroke_; +}; + + +/** + * Set the stroke style. + * @param {ol.style.Stroke} stroke Stroke style. + * @api + */ +ol.style.Style.prototype.setStroke = function(stroke) { + this.stroke_ = stroke; +}; + + +/** + * Get the text style. + * @return {ol.style.Text} Text style. + * @api + */ +ol.style.Style.prototype.getText = function() { + return this.text_; +}; + + +/** + * Set the text style. + * @param {ol.style.Text} text Text style. + * @api + */ +ol.style.Style.prototype.setText = function(text) { + this.text_ = text; +}; + + +/** + * Get the z-index for the style. + * @return {number|undefined} ZIndex. + * @api + */ +ol.style.Style.prototype.getZIndex = function() { + return this.zIndex_; +}; + + +/** + * Set a geometry that is rendered instead of the feature's geometry. + * + * @param {string|ol.geom.Geometry|ol.StyleGeometryFunction} geometry + * Feature property or geometry or function returning a geometry to render + * for this style. + * @api + */ +ol.style.Style.prototype.setGeometry = function(geometry) { + if (typeof geometry === 'function') { + this.geometryFunction_ = geometry; + } else if (typeof geometry === 'string') { + this.geometryFunction_ = function(feature) { + return /** @type {ol.geom.Geometry} */ (feature.get(geometry)); + }; + } else if (!geometry) { + this.geometryFunction_ = ol.style.Style.defaultGeometryFunction; + } else if (geometry !== undefined) { + this.geometryFunction_ = function() { + return /** @type {ol.geom.Geometry} */ (geometry); + }; + } + this.geometry_ = geometry; +}; + + +/** + * Set the z-index. + * + * @param {number|undefined} zIndex ZIndex. + * @api + */ +ol.style.Style.prototype.setZIndex = function(zIndex) { + this.zIndex_ = zIndex; +}; + + +/** + * Convert the provided object into a style function. Functions passed through + * unchanged. Arrays of ol.style.Style or single style objects wrapped in a + * new style function. + * @param {ol.StyleFunction|Array.<ol.style.Style>|ol.style.Style} obj + * A style function, a single style, or an array of styles. + * @return {ol.StyleFunction} A style function. + */ +ol.style.Style.createFunction = function(obj) { + var styleFunction; + + if (typeof obj === 'function') { + styleFunction = obj; + } else { + /** + * @type {Array.<ol.style.Style>} + */ + var styles; + if (Array.isArray(obj)) { + styles = obj; + } else { + ol.asserts.assert(obj instanceof ol.style.Style, + 41); // Expected an `ol.style.Style` or an array of `ol.style.Style` + styles = [obj]; + } + styleFunction = function() { + return styles; + }; + } + return styleFunction; +}; + + +/** + * @type {Array.<ol.style.Style>} + * @private + */ +ol.style.Style.default_ = null; + + +/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {number} resolution Resolution. + * @return {Array.<ol.style.Style>} Style. + */ +ol.style.Style.defaultFunction = function(feature, resolution) { + // We don't use an immediately-invoked function + // and a closure so we don't get an error at script evaluation time in + // browsers that do not support Canvas. (ol.style.Circle does + // canvas.getContext('2d') at construction time, which will cause an.error + // in such browsers.) + if (!ol.style.Style.default_) { + var fill = new ol.style.Fill({ + color: 'rgba(255,255,255,0.4)' + }); + var stroke = new ol.style.Stroke({ + color: '#3399CC', + width: 1.25 + }); + ol.style.Style.default_ = [ + new ol.style.Style({ + image: new ol.style.Circle({ + fill: fill, + stroke: stroke, + radius: 5 + }), + fill: fill, + stroke: stroke + }) + ]; + } + return ol.style.Style.default_; +}; + + +/** + * Default styles for editing features. + * @return {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} Styles + */ +ol.style.Style.createDefaultEditing = function() { + /** @type {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} */ + var styles = {}; + var white = [255, 255, 255, 1]; + var blue = [0, 153, 255, 1]; + var width = 3; + styles[ol.geom.GeometryType.POLYGON] = [ + new ol.style.Style({ + fill: new ol.style.Fill({ + color: [255, 255, 255, 0.5] + }) + }) + ]; + styles[ol.geom.GeometryType.MULTI_POLYGON] = + styles[ol.geom.GeometryType.POLYGON]; + + styles[ol.geom.GeometryType.LINE_STRING] = [ + new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: white, + width: width + 2 + }) + }), + new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: blue, + width: width + }) + }) + ]; + styles[ol.geom.GeometryType.MULTI_LINE_STRING] = + styles[ol.geom.GeometryType.LINE_STRING]; + + styles[ol.geom.GeometryType.CIRCLE] = + styles[ol.geom.GeometryType.POLYGON].concat( + styles[ol.geom.GeometryType.LINE_STRING] + ); + + + styles[ol.geom.GeometryType.POINT] = [ + new ol.style.Style({ + image: new ol.style.Circle({ + radius: width * 2, + fill: new ol.style.Fill({ + color: blue + }), + stroke: new ol.style.Stroke({ + color: white, + width: width / 2 + }) + }), + zIndex: Infinity + }) + ]; + styles[ol.geom.GeometryType.MULTI_POINT] = + styles[ol.geom.GeometryType.POINT]; + + styles[ol.geom.GeometryType.GEOMETRY_COLLECTION] = + styles[ol.geom.GeometryType.POLYGON].concat( + styles[ol.geom.GeometryType.LINE_STRING], + styles[ol.geom.GeometryType.POINT] + ); + + return styles; +}; + + +/** + * Function that is called with a feature and returns its default geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature to get the geometry + * for. + * @return {ol.geom.Geometry|ol.render.Feature|undefined} Geometry to render. + */ +ol.style.Style.defaultGeometryFunction = function(feature) { + return feature.getGeometry(); +}; + +goog.provide('ol.layer.Vector'); + +goog.require('ol'); +goog.require('ol.layer.Layer'); +goog.require('ol.obj'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * Vector data that is rendered client-side. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Layer} + * @fires ol.render.Event + * @param {olx.layer.VectorOptions=} opt_options Options. + * @api stable + */ +ol.layer.Vector = function(opt_options) { + + var options = opt_options ? + opt_options : /** @type {olx.layer.VectorOptions} */ ({}); + + ol.DEBUG && console.assert( + options.renderOrder === undefined || !options.renderOrder || + typeof options.renderOrder === 'function', + 'renderOrder must be a comparator function'); + + var baseOptions = ol.obj.assign({}, options); + + delete baseOptions.style; + delete baseOptions.renderBuffer; + delete baseOptions.updateWhileAnimating; + delete baseOptions.updateWhileInteracting; + ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (baseOptions)); + + /** + * @type {number} + * @private + */ + this.renderBuffer_ = options.renderBuffer !== undefined ? + options.renderBuffer : 100; + + /** + * User provided style. + * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction} + * @private + */ + this.style_ = null; + + /** + * Style function for use within the library. + * @type {ol.StyleFunction|undefined} + * @private + */ + this.styleFunction_ = undefined; + + this.setStyle(options.style); + + /** + * @type {boolean} + * @private + */ + this.updateWhileAnimating_ = options.updateWhileAnimating !== undefined ? + options.updateWhileAnimating : false; + + /** + * @type {boolean} + * @private + */ + this.updateWhileInteracting_ = options.updateWhileInteracting !== undefined ? + options.updateWhileInteracting : false; + +}; +ol.inherits(ol.layer.Vector, ol.layer.Layer); + + +/** + * @return {number|undefined} Render buffer. + */ +ol.layer.Vector.prototype.getRenderBuffer = function() { + return this.renderBuffer_; +}; + + +/** + * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render + * order. + */ +ol.layer.Vector.prototype.getRenderOrder = function() { + return /** @type {function(ol.Feature, ol.Feature):number|null|undefined} */ ( + this.get(ol.layer.Vector.Property.RENDER_ORDER)); +}; + + +/** + * Return the associated {@link ol.source.Vector vectorsource} of the layer. + * @function + * @return {ol.source.Vector} Source. + * @api stable + */ +ol.layer.Vector.prototype.getSource; + + +/** + * Get the style for features. This returns whatever was passed to the `style` + * option at construction or to the `setStyle` method. + * @return {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction} + * Layer style. + * @api stable + */ +ol.layer.Vector.prototype.getStyle = function() { + return this.style_; +}; + + +/** + * Get the style function. + * @return {ol.StyleFunction|undefined} Layer style function. + * @api stable + */ +ol.layer.Vector.prototype.getStyleFunction = function() { + return this.styleFunction_; +}; + + +/** + * @return {boolean} Whether the rendered layer should be updated while + * animating. + */ +ol.layer.Vector.prototype.getUpdateWhileAnimating = function() { + return this.updateWhileAnimating_; +}; + + +/** + * @return {boolean} Whether the rendered layer should be updated while + * interacting. + */ +ol.layer.Vector.prototype.getUpdateWhileInteracting = function() { + return this.updateWhileInteracting_; +}; + + +/** + * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder + * Render order. + */ +ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) { + ol.DEBUG && console.assert( + renderOrder === undefined || !renderOrder || + typeof renderOrder === 'function', + 'renderOrder must be a comparator function'); + this.set(ol.layer.Vector.Property.RENDER_ORDER, renderOrder); +}; + + +/** + * Set the style for features. This can be a single style object, an array + * of styles, or a function that takes a feature and resolution and returns + * an array of styles. If it is `undefined` the default style is used. If + * it is `null` the layer has no style (a `null` style), so only features + * that have their own styles will be rendered in the layer. See + * {@link ol.style} for information on the default style. + * @param {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction|null|undefined} + * style Layer style. + * @api stable + */ +ol.layer.Vector.prototype.setStyle = function(style) { + this.style_ = style !== undefined ? style : ol.style.Style.defaultFunction; + this.styleFunction_ = style === null ? + undefined : ol.style.Style.createFunction(this.style_); + this.changed(); +}; + + +/** + * @enum {string} + */ +ol.layer.Vector.Property = { + RENDER_ORDER: 'renderOrder' +}; + +goog.provide('ol.layer.VectorTile'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); +goog.require('ol.obj'); + + +/** + * @classdesc + * Layer for vector tile data that is rendered client-side. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Vector} + * @param {olx.layer.VectorTileOptions=} opt_options Options. + * @api + */ +ol.layer.VectorTile = function(opt_options) { + var options = opt_options ? opt_options : {}; + + var baseOptions = ol.obj.assign({}, options); + + delete baseOptions.preload; + delete baseOptions.useInterimTilesOnError; + ol.layer.Vector.call(this, /** @type {olx.layer.VectorOptions} */ (baseOptions)); + + this.setPreload(options.preload ? options.preload : 0); + this.setUseInterimTilesOnError(options.useInterimTilesOnError ? + options.useInterimTilesOnError : true); + + ol.asserts.assert(options.renderMode == undefined || + options.renderMode == ol.layer.VectorTile.RenderType.IMAGE || + options.renderMode == ol.layer.VectorTile.RenderType.HYBRID || + options.renderMode == ol.layer.VectorTile.RenderType.VECTOR, + 28); // `renderMode` must be `'image'`, `'hybrid'` or `'vector'` + + /** + * @private + * @type {ol.layer.VectorTile.RenderType|string} + */ + this.renderMode_ = options.renderMode || ol.layer.VectorTile.RenderType.HYBRID; + +}; +ol.inherits(ol.layer.VectorTile, ol.layer.Vector); + + +/** + * Return the level as number to which we will preload tiles up to. + * @return {number} The level to preload tiles up to. + * @observable + * @api + */ +ol.layer.VectorTile.prototype.getPreload = function() { + return /** @type {number} */ (this.get(ol.layer.VectorTile.Property.PRELOAD)); +}; + + +/** + * @return {ol.layer.VectorTile.RenderType|string} The render mode. + */ +ol.layer.VectorTile.prototype.getRenderMode = function() { + return this.renderMode_; +}; + + +/** + * Whether we use interim tiles on error. + * @return {boolean} Use interim tiles on error. + * @observable + * @api + */ +ol.layer.VectorTile.prototype.getUseInterimTilesOnError = function() { + return /** @type {boolean} */ ( + this.get(ol.layer.VectorTile.Property.USE_INTERIM_TILES_ON_ERROR)); +}; + + +/** + * Set the level as number to which we will preload tiles up to. + * @param {number} preload The level to preload tiles up to. + * @observable + * @api + */ +ol.layer.VectorTile.prototype.setPreload = function(preload) { + this.set(ol.layer.Tile.Property.PRELOAD, preload); +}; + + +/** + * Set whether we use interim tiles on error. + * @param {boolean} useInterimTilesOnError Use interim tiles on error. + * @observable + * @api + */ +ol.layer.VectorTile.prototype.setUseInterimTilesOnError = function(useInterimTilesOnError) { + this.set( + ol.layer.Tile.Property.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError); +}; + + +/** + * @enum {string} + */ +ol.layer.VectorTile.Property = { + PRELOAD: 'preload', + USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError' +}; + + +/** + * @enum {string} + * Render mode for vector tiles: + * * `'image'`: Vector tiles are rendered as images. Great performance, but + * point symbols and texts are always rotated with the view and pixels are + * scaled during zoom animations. + * * `'hybrid'`: Polygon and line elements are rendered as images, so pixels + * are scaled during zoom animations. Point symbols and texts are accurately + * rendered as vectors and can stay upright on rotated views. + * * `'vector'`: Vector tiles are rendered as vectors. Most accurate rendering + * even during animations, but slower performance than the other options. + * @api + */ +ol.layer.VectorTile.RenderType = { + IMAGE: 'image', + HYBRID: 'hybrid', + VECTOR: 'vector' +}; + +goog.provide('ol.render.VectorContext'); + + +/** + * Context for drawing geometries. A vector context is available on render + * events and does not need to be constructed directly. + * @constructor + * @struct + * @api + */ +ol.render.VectorContext = function() { +}; + + +/** + * Render a geometry. + * + * @abstract + * @param {ol.geom.Geometry} geometry The geometry to render. + */ +ol.render.VectorContext.prototype.drawGeometry = function(geometry) {}; + + +/** + * Set the rendering style. + * + * @abstract + * @param {ol.style.Style} style The rendering style. + */ +ol.render.VectorContext.prototype.setStyle = function(style) {}; + + +/** + * @abstract + * @param {ol.geom.Circle} circleGeometry Circle geometry. + * @param {ol.Feature} feature Feature, + */ +ol.render.VectorContext.prototype.drawCircle = function(circleGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.Feature} feature Feature. + * @param {ol.style.Style} style Style. + */ +ol.render.VectorContext.prototype.drawFeature = function(feature, style) {}; + + +/** + * @abstract + * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry + * collection. + * @param {ol.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawGeometryCollection = function(geometryCollectionGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.geom.LineString|ol.render.Feature} lineStringGeometry Line + * string geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawLineString = function(lineStringGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.geom.MultiLineString|ol.render.Feature} multiLineStringGeometry + * MultiLineString geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.geom.MultiPoint|ol.render.Feature} multiPointGeometry MultiPoint + * geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawMultiPoint = function(multiPointGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.geom.Point|ol.render.Feature} pointGeometry Point geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawPoint = function(pointGeometry, feature) {}; + + +/** + * @abstract + * @param {ol.geom.Polygon|ol.render.Feature} polygonGeometry Polygon + * geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawPolygon = function(polygonGeometry, feature) {}; + + +/** + * @abstract + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) {}; + + +/** + * @abstract + * @param {ol.style.Fill} fillStyle Fill style. + * @param {ol.style.Stroke} strokeStyle Stroke style. + */ +ol.render.VectorContext.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {}; + + +/** + * @abstract + * @param {ol.style.Image} imageStyle Image style. + */ +ol.render.VectorContext.prototype.setImageStyle = function(imageStyle) {}; + + +/** + * @abstract + * @param {ol.style.Text} textStyle Text style. + */ +ol.render.VectorContext.prototype.setTextStyle = function(textStyle) {}; + +// FIXME test, especially polygons with holes and multipolygons +// FIXME need to handle large thick features (where pixel size matters) +// FIXME add offset and end to ol.geom.flat.transform.transform2D? + +goog.provide('ol.render.canvas.Immediate'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.colorlike'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.has'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.canvas'); +goog.require('ol.transform'); + + +/** + * @classdesc + * A concrete subclass of {@link ol.render.VectorContext} that implements + * direct rendering of features and geometries to an HTML5 Canvas context. + * Instances of this class are created internally by the library and + * provided to application code as vectorContext member of the + * {@link ol.render.Event} object associated with postcompose, precompose and + * render events emitted by layers and maps. + * + * @constructor + * @extends {ol.render.VectorContext} + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Extent} extent Extent. + * @param {ol.Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @struct + */ +ol.render.canvas.Immediate = function(context, pixelRatio, extent, transform, viewRotation) { + ol.render.VectorContext.call(this); + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = context; + + /** + * @private + * @type {number} + */ + this.pixelRatio_ = pixelRatio; + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = extent; + + /** + * @private + * @type {ol.Transform} + */ + this.transform_ = transform; + + /** + * @private + * @type {number} + */ + this.viewRotation_ = viewRotation; + + /** + * @private + * @type {?ol.CanvasFillState} + */ + this.contextFillState_ = null; + + /** + * @private + * @type {?ol.CanvasStrokeState} + */ + this.contextStrokeState_ = null; + + /** + * @private + * @type {?ol.CanvasTextState} + */ + this.contextTextState_ = null; + + /** + * @private + * @type {?ol.CanvasFillState} + */ + this.fillState_ = null; + + /** + * @private + * @type {?ol.CanvasStrokeState} + */ + this.strokeState_ = null; + + /** + * @private + * @type {HTMLCanvasElement|HTMLVideoElement|Image} + */ + this.image_ = null; + + /** + * @private + * @type {number} + */ + this.imageAnchorX_ = 0; + + /** + * @private + * @type {number} + */ + this.imageAnchorY_ = 0; + + /** + * @private + * @type {number} + */ + this.imageHeight_ = 0; + + /** + * @private + * @type {number} + */ + this.imageOpacity_ = 0; + + /** + * @private + * @type {number} + */ + this.imageOriginX_ = 0; + + /** + * @private + * @type {number} + */ + this.imageOriginY_ = 0; + + /** + * @private + * @type {boolean} + */ + this.imageRotateWithView_ = false; + + /** + * @private + * @type {number} + */ + this.imageRotation_ = 0; + + /** + * @private + * @type {number} + */ + this.imageScale_ = 0; + + /** + * @private + * @type {boolean} + */ + this.imageSnapToPixel_ = false; + + /** + * @private + * @type {number} + */ + this.imageWidth_ = 0; + + /** + * @private + * @type {string} + */ + this.text_ = ''; + + /** + * @private + * @type {number} + */ + this.textOffsetX_ = 0; + + /** + * @private + * @type {number} + */ + this.textOffsetY_ = 0; + + /** + * @private + * @type {boolean} + */ + this.textRotateWithView_ = false; + + /** + * @private + * @type {number} + */ + this.textRotation_ = 0; + + /** + * @private + * @type {number} + */ + this.textScale_ = 0; + + /** + * @private + * @type {?ol.CanvasFillState} + */ + this.textFillState_ = null; + + /** + * @private + * @type {?ol.CanvasStrokeState} + */ + this.textStrokeState_ = null; + + /** + * @private + * @type {?ol.CanvasTextState} + */ + this.textState_ = null; + + /** + * @private + * @type {Array.<number>} + */ + this.pixelCoordinates_ = []; + + /** + * @private + * @type {ol.Transform} + */ + this.tmpLocalTransform_ = ol.transform.create(); + +}; +ol.inherits(ol.render.canvas.Immediate, ol.render.VectorContext); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @private + */ +ol.render.canvas.Immediate.prototype.drawImages_ = function(flatCoordinates, offset, end, stride) { + if (!this.image_) { + return; + } + ol.DEBUG && console.assert(offset === 0, 'offset should be 0'); + ol.DEBUG && console.assert(end == flatCoordinates.length, + 'end should be equal to the length of flatCoordinates'); + var pixelCoordinates = ol.geom.flat.transform.transform2D( + flatCoordinates, offset, end, 2, this.transform_, + this.pixelCoordinates_); + var context = this.context_; + var localTransform = this.tmpLocalTransform_; + var alpha = context.globalAlpha; + if (this.imageOpacity_ != 1) { + context.globalAlpha = alpha * this.imageOpacity_; + } + var rotation = this.imageRotation_; + if (this.imageRotateWithView_) { + rotation += this.viewRotation_; + } + var i, ii; + for (i = 0, ii = pixelCoordinates.length; i < ii; i += 2) { + var x = pixelCoordinates[i] - this.imageAnchorX_; + var y = pixelCoordinates[i + 1] - this.imageAnchorY_; + if (this.imageSnapToPixel_) { + x = Math.round(x); + y = Math.round(y); + } + if (rotation !== 0 || this.imageScale_ != 1) { + var centerX = x + this.imageAnchorX_; + var centerY = y + this.imageAnchorY_; + ol.transform.compose(localTransform, + centerX, centerY, + this.imageScale_, this.imageScale_, + rotation, + -centerX, -centerY); + context.setTransform.apply(context, localTransform); + } + context.drawImage(this.image_, this.imageOriginX_, this.imageOriginY_, + this.imageWidth_, this.imageHeight_, x, y, + this.imageWidth_, this.imageHeight_); + } + if (rotation !== 0 || this.imageScale_ != 1) { + context.setTransform(1, 0, 0, 1, 0, 0); + } + if (this.imageOpacity_ != 1) { + context.globalAlpha = alpha; + } +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @private + */ +ol.render.canvas.Immediate.prototype.drawText_ = function(flatCoordinates, offset, end, stride) { + if (!this.textState_ || this.text_ === '') { + return; + } + if (this.textFillState_) { + this.setContextFillState_(this.textFillState_); + } + if (this.textStrokeState_) { + this.setContextStrokeState_(this.textStrokeState_); + } + this.setContextTextState_(this.textState_); + ol.DEBUG && console.assert(offset === 0, 'offset should be 0'); + ol.DEBUG && console.assert(end == flatCoordinates.length, + 'end should be equal to the length of flatCoordinates'); + var pixelCoordinates = ol.geom.flat.transform.transform2D( + flatCoordinates, offset, end, stride, this.transform_, + this.pixelCoordinates_); + var context = this.context_; + var rotation = this.textRotation_; + if (this.textRotateWithView_) { + rotation += this.viewRotation_; + } + for (; offset < end; offset += stride) { + var x = pixelCoordinates[offset] + this.textOffsetX_; + var y = pixelCoordinates[offset + 1] + this.textOffsetY_; + if (rotation !== 0 || this.textScale_ != 1) { + var localTransform = ol.transform.compose(this.tmpLocalTransform_, + x, y, + this.textScale_, this.textScale_, + rotation, + -x, -y); + context.setTransform.apply(context, localTransform); + } + if (this.textStrokeState_) { + context.strokeText(this.text_, x, y); + } + if (this.textFillState_) { + context.fillText(this.text_, x, y); + } + } + if (rotation !== 0 || this.textScale_ != 1) { + context.setTransform(1, 0, 0, 1, 0, 0); + } +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {boolean} close Close. + * @private + * @return {number} end End. + */ +ol.render.canvas.Immediate.prototype.moveToLineTo_ = function(flatCoordinates, offset, end, stride, close) { + var context = this.context_; + var pixelCoordinates = ol.geom.flat.transform.transform2D( + flatCoordinates, offset, end, stride, this.transform_, + this.pixelCoordinates_); + context.moveTo(pixelCoordinates[0], pixelCoordinates[1]); + var length = pixelCoordinates.length; + if (close) { + length -= 2; + } + for (var i = 2; i < length; i += 2) { + context.lineTo(pixelCoordinates[i], pixelCoordinates[i + 1]); + } + if (close) { + context.closePath(); + } + return end; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @private + * @return {number} End. + */ +ol.render.canvas.Immediate.prototype.drawRings_ = function(flatCoordinates, offset, ends, stride) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + offset = this.moveToLineTo_( + flatCoordinates, offset, ends[i], stride, true); + } + return offset; +}; + + +/** + * Render a circle geometry into the canvas. Rendering is immediate and uses + * the current fill and stroke styles. + * + * @param {ol.geom.Circle} geometry Circle geometry. + * @api + */ +ol.render.canvas.Immediate.prototype.drawCircle = function(geometry) { + if (!ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + if (this.fillState_ || this.strokeState_) { + if (this.fillState_) { + this.setContextFillState_(this.fillState_); + } + if (this.strokeState_) { + this.setContextStrokeState_(this.strokeState_); + } + var pixelCoordinates = ol.geom.SimpleGeometry.transform2D( + geometry, this.transform_, this.pixelCoordinates_); + var dx = pixelCoordinates[2] - pixelCoordinates[0]; + var dy = pixelCoordinates[3] - pixelCoordinates[1]; + var radius = Math.sqrt(dx * dx + dy * dy); + var context = this.context_; + context.beginPath(); + context.arc( + pixelCoordinates[0], pixelCoordinates[1], radius, 0, 2 * Math.PI); + if (this.fillState_) { + context.fill(); + } + if (this.strokeState_) { + context.stroke(); + } + } + if (this.text_ !== '') { + this.drawText_(geometry.getCenter(), 0, 2, 2); + } +}; + + +/** + * Set the rendering style. Note that since this is an immediate rendering API, + * any `zIndex` on the provided style will be ignored. + * + * @param {ol.style.Style} style The rendering style. + * @api + */ +ol.render.canvas.Immediate.prototype.setStyle = function(style) { + this.setFillStrokeStyle(style.getFill(), style.getStroke()); + this.setImageStyle(style.getImage()); + this.setTextStyle(style.getText()); +}; + + +/** + * Render a geometry into the canvas. Call + * {@link ol.render.canvas.Immediate#setStyle} first to set the rendering style. + * + * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render. + * @api + */ +ol.render.canvas.Immediate.prototype.drawGeometry = function(geometry) { + var type = geometry.getType(); + switch (type) { + case ol.geom.GeometryType.POINT: + this.drawPoint(/** @type {ol.geom.Point} */ (geometry)); + break; + case ol.geom.GeometryType.LINE_STRING: + this.drawLineString(/** @type {ol.geom.LineString} */ (geometry)); + break; + case ol.geom.GeometryType.POLYGON: + this.drawPolygon(/** @type {ol.geom.Polygon} */ (geometry)); + break; + case ol.geom.GeometryType.MULTI_POINT: + this.drawMultiPoint(/** @type {ol.geom.MultiPoint} */ (geometry)); + break; + case ol.geom.GeometryType.MULTI_LINE_STRING: + this.drawMultiLineString(/** @type {ol.geom.MultiLineString} */ (geometry)); + break; + case ol.geom.GeometryType.MULTI_POLYGON: + this.drawMultiPolygon(/** @type {ol.geom.MultiPolygon} */ (geometry)); + break; + case ol.geom.GeometryType.GEOMETRY_COLLECTION: + this.drawGeometryCollection(/** @type {ol.geom.GeometryCollection} */ (geometry)); + break; + case ol.geom.GeometryType.CIRCLE: + this.drawCircle(/** @type {ol.geom.Circle} */ (geometry)); + break; + default: + ol.DEBUG && console.assert(false, 'Unsupported geometry type: ' + type); + } +}; + + +/** + * Render a feature into the canvas. Note that any `zIndex` on the provided + * style will be ignored - features are rendered immediately in the order that + * this method is called. If you need `zIndex` support, you should be using an + * {@link ol.layer.Vector} instead. + * + * @param {ol.Feature} feature Feature. + * @param {ol.style.Style} style Style. + * @api + */ +ol.render.canvas.Immediate.prototype.drawFeature = function(feature, style) { + var geometry = style.getGeometryFunction()(feature); + if (!geometry || + !ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + this.setStyle(style); + this.drawGeometry(geometry); +}; + + +/** + * Render a GeometryCollection to the canvas. Rendering is immediate and + * uses the current styles appropriate for each geometry in the collection. + * + * @param {ol.geom.GeometryCollection} geometry Geometry collection. + */ +ol.render.canvas.Immediate.prototype.drawGeometryCollection = function(geometry) { + var geometries = geometry.getGeometriesArray(); + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + this.drawGeometry(geometries[i]); + } +}; + + +/** + * Render a Point geometry into the canvas. Rendering is immediate and uses + * the current style. + * + * @param {ol.geom.Point|ol.render.Feature} geometry Point geometry. + */ +ol.render.canvas.Immediate.prototype.drawPoint = function(geometry) { + var flatCoordinates = geometry.getFlatCoordinates(); + var stride = geometry.getStride(); + if (this.image_) { + this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride); + } + if (this.text_ !== '') { + this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride); + } +}; + + +/** + * Render a MultiPoint geometry into the canvas. Rendering is immediate and + * uses the current style. + * + * @param {ol.geom.MultiPoint|ol.render.Feature} geometry MultiPoint geometry. + */ +ol.render.canvas.Immediate.prototype.drawMultiPoint = function(geometry) { + var flatCoordinates = geometry.getFlatCoordinates(); + var stride = geometry.getStride(); + if (this.image_) { + this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride); + } + if (this.text_ !== '') { + this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride); + } +}; + + +/** + * Render a LineString into the canvas. Rendering is immediate and uses + * the current style. + * + * @param {ol.geom.LineString|ol.render.Feature} geometry LineString geometry. + */ +ol.render.canvas.Immediate.prototype.drawLineString = function(geometry) { + if (!ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + if (this.strokeState_) { + this.setContextStrokeState_(this.strokeState_); + var context = this.context_; + var flatCoordinates = geometry.getFlatCoordinates(); + context.beginPath(); + this.moveToLineTo_(flatCoordinates, 0, flatCoordinates.length, + geometry.getStride(), false); + context.stroke(); + } + if (this.text_ !== '') { + var flatMidpoint = geometry.getFlatMidpoint(); + this.drawText_(flatMidpoint, 0, 2, 2); + } +}; + + +/** + * Render a MultiLineString geometry into the canvas. Rendering is immediate + * and uses the current style. + * + * @param {ol.geom.MultiLineString|ol.render.Feature} geometry MultiLineString + * geometry. + */ +ol.render.canvas.Immediate.prototype.drawMultiLineString = function(geometry) { + var geometryExtent = geometry.getExtent(); + if (!ol.extent.intersects(this.extent_, geometryExtent)) { + return; + } + if (this.strokeState_) { + this.setContextStrokeState_(this.strokeState_); + var context = this.context_; + var flatCoordinates = geometry.getFlatCoordinates(); + var offset = 0; + var ends = geometry.getEnds(); + var stride = geometry.getStride(); + context.beginPath(); + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + offset = this.moveToLineTo_( + flatCoordinates, offset, ends[i], stride, false); + } + context.stroke(); + } + if (this.text_ !== '') { + var flatMidpoints = geometry.getFlatMidpoints(); + this.drawText_(flatMidpoints, 0, flatMidpoints.length, 2); + } +}; + + +/** + * Render a Polygon geometry into the canvas. Rendering is immediate and uses + * the current style. + * + * @param {ol.geom.Polygon|ol.render.Feature} geometry Polygon geometry. + */ +ol.render.canvas.Immediate.prototype.drawPolygon = function(geometry) { + if (!ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + if (this.strokeState_ || this.fillState_) { + if (this.fillState_) { + this.setContextFillState_(this.fillState_); + } + if (this.strokeState_) { + this.setContextStrokeState_(this.strokeState_); + } + var context = this.context_; + context.beginPath(); + this.drawRings_(geometry.getOrientedFlatCoordinates(), + 0, geometry.getEnds(), geometry.getStride()); + if (this.fillState_) { + context.fill(); + } + if (this.strokeState_) { + context.stroke(); + } + } + if (this.text_ !== '') { + var flatInteriorPoint = geometry.getFlatInteriorPoint(); + this.drawText_(flatInteriorPoint, 0, 2, 2); + } +}; + + +/** + * Render MultiPolygon geometry into the canvas. Rendering is immediate and + * uses the current style. + * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry. + */ +ol.render.canvas.Immediate.prototype.drawMultiPolygon = function(geometry) { + if (!ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + if (this.strokeState_ || this.fillState_) { + if (this.fillState_) { + this.setContextFillState_(this.fillState_); + } + if (this.strokeState_) { + this.setContextStrokeState_(this.strokeState_); + } + var context = this.context_; + var flatCoordinates = geometry.getOrientedFlatCoordinates(); + var offset = 0; + var endss = geometry.getEndss(); + var stride = geometry.getStride(); + var i, ii; + context.beginPath(); + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + offset = this.drawRings_(flatCoordinates, offset, ends, stride); + } + if (this.fillState_) { + context.fill(); + } + if (this.strokeState_) { + context.stroke(); + } + } + if (this.text_ !== '') { + var flatInteriorPoints = geometry.getFlatInteriorPoints(); + this.drawText_(flatInteriorPoints, 0, flatInteriorPoints.length, 2); + } +}; + + +/** + * @param {ol.CanvasFillState} fillState Fill state. + * @private + */ +ol.render.canvas.Immediate.prototype.setContextFillState_ = function(fillState) { + var context = this.context_; + var contextFillState = this.contextFillState_; + if (!contextFillState) { + context.fillStyle = fillState.fillStyle; + this.contextFillState_ = { + fillStyle: fillState.fillStyle + }; + } else { + if (contextFillState.fillStyle != fillState.fillStyle) { + contextFillState.fillStyle = context.fillStyle = fillState.fillStyle; + } + } +}; + + +/** + * @param {ol.CanvasStrokeState} strokeState Stroke state. + * @private + */ +ol.render.canvas.Immediate.prototype.setContextStrokeState_ = function(strokeState) { + var context = this.context_; + var contextStrokeState = this.contextStrokeState_; + if (!contextStrokeState) { + context.lineCap = strokeState.lineCap; + if (ol.has.CANVAS_LINE_DASH) { + context.setLineDash(strokeState.lineDash); + } + context.lineJoin = strokeState.lineJoin; + context.lineWidth = strokeState.lineWidth; + context.miterLimit = strokeState.miterLimit; + context.strokeStyle = strokeState.strokeStyle; + this.contextStrokeState_ = { + lineCap: strokeState.lineCap, + lineDash: strokeState.lineDash, + lineJoin: strokeState.lineJoin, + lineWidth: strokeState.lineWidth, + miterLimit: strokeState.miterLimit, + strokeStyle: strokeState.strokeStyle + }; + } else { + if (contextStrokeState.lineCap != strokeState.lineCap) { + contextStrokeState.lineCap = context.lineCap = strokeState.lineCap; + } + if (ol.has.CANVAS_LINE_DASH) { + if (!ol.array.equals( + contextStrokeState.lineDash, strokeState.lineDash)) { + context.setLineDash(contextStrokeState.lineDash = strokeState.lineDash); + } + } + if (contextStrokeState.lineJoin != strokeState.lineJoin) { + contextStrokeState.lineJoin = context.lineJoin = strokeState.lineJoin; + } + if (contextStrokeState.lineWidth != strokeState.lineWidth) { + contextStrokeState.lineWidth = context.lineWidth = strokeState.lineWidth; + } + if (contextStrokeState.miterLimit != strokeState.miterLimit) { + contextStrokeState.miterLimit = context.miterLimit = + strokeState.miterLimit; + } + if (contextStrokeState.strokeStyle != strokeState.strokeStyle) { + contextStrokeState.strokeStyle = context.strokeStyle = + strokeState.strokeStyle; + } + } +}; + + +/** + * @param {ol.CanvasTextState} textState Text state. + * @private + */ +ol.render.canvas.Immediate.prototype.setContextTextState_ = function(textState) { + var context = this.context_; + var contextTextState = this.contextTextState_; + if (!contextTextState) { + context.font = textState.font; + context.textAlign = textState.textAlign; + context.textBaseline = textState.textBaseline; + this.contextTextState_ = { + font: textState.font, + textAlign: textState.textAlign, + textBaseline: textState.textBaseline + }; + } else { + if (contextTextState.font != textState.font) { + contextTextState.font = context.font = textState.font; + } + if (contextTextState.textAlign != textState.textAlign) { + contextTextState.textAlign = context.textAlign = textState.textAlign; + } + if (contextTextState.textBaseline != textState.textBaseline) { + contextTextState.textBaseline = context.textBaseline = + textState.textBaseline; + } + } +}; + + +/** + * Set the fill and stroke style for subsequent draw operations. To clear + * either fill or stroke styles, pass null for the appropriate parameter. + * + * @param {ol.style.Fill} fillStyle Fill style. + * @param {ol.style.Stroke} strokeStyle Stroke style. + */ +ol.render.canvas.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + if (!fillStyle) { + this.fillState_ = null; + } else { + var fillStyleColor = fillStyle.getColor(); + this.fillState_ = { + fillStyle: ol.colorlike.asColorLike(fillStyleColor ? + fillStyleColor : ol.render.canvas.defaultFillStyle) + }; + } + if (!strokeStyle) { + this.strokeState_ = null; + } else { + var strokeStyleColor = strokeStyle.getColor(); + var strokeStyleLineCap = strokeStyle.getLineCap(); + var strokeStyleLineDash = strokeStyle.getLineDash(); + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + var strokeStyleWidth = strokeStyle.getWidth(); + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + this.strokeState_ = { + lineCap: strokeStyleLineCap !== undefined ? + strokeStyleLineCap : ol.render.canvas.defaultLineCap, + lineDash: strokeStyleLineDash ? + strokeStyleLineDash : ol.render.canvas.defaultLineDash, + lineJoin: strokeStyleLineJoin !== undefined ? + strokeStyleLineJoin : ol.render.canvas.defaultLineJoin, + lineWidth: this.pixelRatio_ * (strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.canvas.defaultLineWidth), + miterLimit: strokeStyleMiterLimit !== undefined ? + strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit, + strokeStyle: ol.colorlike.asColorLike(strokeStyleColor ? + strokeStyleColor : ol.render.canvas.defaultStrokeStyle) + }; + } +}; + + +/** + * Set the image style for subsequent draw operations. Pass null to remove + * the image style. + * + * @param {ol.style.Image} imageStyle Image style. + */ +ol.render.canvas.Immediate.prototype.setImageStyle = function(imageStyle) { + if (!imageStyle) { + this.image_ = null; + } else { + var imageAnchor = imageStyle.getAnchor(); + // FIXME pixel ratio + var imageImage = imageStyle.getImage(1); + var imageOrigin = imageStyle.getOrigin(); + var imageSize = imageStyle.getSize(); + ol.DEBUG && console.assert(imageImage, 'imageImage must be truthy'); + this.imageAnchorX_ = imageAnchor[0]; + this.imageAnchorY_ = imageAnchor[1]; + this.imageHeight_ = imageSize[1]; + this.image_ = imageImage; + this.imageOpacity_ = imageStyle.getOpacity(); + this.imageOriginX_ = imageOrigin[0]; + this.imageOriginY_ = imageOrigin[1]; + this.imageRotateWithView_ = imageStyle.getRotateWithView(); + this.imageRotation_ = imageStyle.getRotation(); + this.imageScale_ = imageStyle.getScale(); + this.imageSnapToPixel_ = imageStyle.getSnapToPixel(); + this.imageWidth_ = imageSize[0]; + } +}; + + +/** + * Set the text style for subsequent draw operations. Pass null to + * remove the text style. + * + * @param {ol.style.Text} textStyle Text style. + */ +ol.render.canvas.Immediate.prototype.setTextStyle = function(textStyle) { + if (!textStyle) { + this.text_ = ''; + } else { + var textFillStyle = textStyle.getFill(); + if (!textFillStyle) { + this.textFillState_ = null; + } else { + var textFillStyleColor = textFillStyle.getColor(); + this.textFillState_ = { + fillStyle: ol.colorlike.asColorLike(textFillStyleColor ? + textFillStyleColor : ol.render.canvas.defaultFillStyle) + }; + } + var textStrokeStyle = textStyle.getStroke(); + if (!textStrokeStyle) { + this.textStrokeState_ = null; + } else { + var textStrokeStyleColor = textStrokeStyle.getColor(); + var textStrokeStyleLineCap = textStrokeStyle.getLineCap(); + var textStrokeStyleLineDash = textStrokeStyle.getLineDash(); + var textStrokeStyleLineJoin = textStrokeStyle.getLineJoin(); + var textStrokeStyleWidth = textStrokeStyle.getWidth(); + var textStrokeStyleMiterLimit = textStrokeStyle.getMiterLimit(); + this.textStrokeState_ = { + lineCap: textStrokeStyleLineCap !== undefined ? + textStrokeStyleLineCap : ol.render.canvas.defaultLineCap, + lineDash: textStrokeStyleLineDash ? + textStrokeStyleLineDash : ol.render.canvas.defaultLineDash, + lineJoin: textStrokeStyleLineJoin !== undefined ? + textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin, + lineWidth: textStrokeStyleWidth !== undefined ? + textStrokeStyleWidth : ol.render.canvas.defaultLineWidth, + miterLimit: textStrokeStyleMiterLimit !== undefined ? + textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit, + strokeStyle: ol.colorlike.asColorLike(textStrokeStyleColor ? + textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle) + }; + } + var textFont = textStyle.getFont(); + var textOffsetX = textStyle.getOffsetX(); + var textOffsetY = textStyle.getOffsetY(); + var textRotateWithView = textStyle.getRotateWithView(); + var textRotation = textStyle.getRotation(); + var textScale = textStyle.getScale(); + var textText = textStyle.getText(); + var textTextAlign = textStyle.getTextAlign(); + var textTextBaseline = textStyle.getTextBaseline(); + this.textState_ = { + font: textFont !== undefined ? + textFont : ol.render.canvas.defaultFont, + textAlign: textTextAlign !== undefined ? + textTextAlign : ol.render.canvas.defaultTextAlign, + textBaseline: textTextBaseline !== undefined ? + textTextBaseline : ol.render.canvas.defaultTextBaseline + }; + this.text_ = textText !== undefined ? textText : ''; + this.textOffsetX_ = + textOffsetX !== undefined ? (this.pixelRatio_ * textOffsetX) : 0; + this.textOffsetY_ = + textOffsetY !== undefined ? (this.pixelRatio_ * textOffsetY) : 0; + this.textRotateWithView_ = textRotateWithView !== undefined ? textRotateWithView : false; + this.textRotation_ = textRotation !== undefined ? textRotation : 0; + this.textScale_ = this.pixelRatio_ * (textScale !== undefined ? + textScale : 1); + } +}; + +goog.provide('ol.renderer.Layer'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.Observable'); +goog.require('ol.Tile'); +goog.require('ol.asserts'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.functions'); +goog.require('ol.source.State'); + + +/** + * @constructor + * @extends {ol.Observable} + * @param {ol.layer.Layer} layer Layer. + * @struct + */ +ol.renderer.Layer = function(layer) { + + ol.Observable.call(this); + + /** + * @private + * @type {ol.layer.Layer} + */ + this.layer_ = layer; + + +}; +ol.inherits(ol.renderer.Layer, ol.Observable); + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState Frame state. + * @param {number} hitTolerance Hit tolerance in pixels. + * @param {function(this: S, (ol.Feature|ol.render.Feature), ol.layer.Layer): T} + * callback Feature callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @return {T|undefined} Callback result. + * @template S,T + */ +ol.renderer.Layer.prototype.forEachFeatureAtCoordinate = ol.nullFunction; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState Frame state. + * @return {boolean} Is there a feature at the given coordinate? + */ +ol.renderer.Layer.prototype.hasFeatureAtCoordinate = ol.functions.FALSE; + + +/** + * Create a function that adds loaded tiles to the tile lookup. + * @param {ol.source.Tile} source Tile source. + * @param {ol.proj.Projection} projection Projection of the tiles. + * @param {Object.<number, Object.<string, ol.Tile>>} tiles Lookup of loaded + * tiles by zoom level. + * @return {function(number, ol.TileRange):boolean} A function that can be + * called with a zoom level and a tile range to add loaded tiles to the + * lookup. + * @protected + */ +ol.renderer.Layer.prototype.createLoadedTileFinder = function(source, projection, tiles) { + return ( + /** + * @param {number} zoom Zoom level. + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} The tile range is fully loaded. + */ + function(zoom, tileRange) { + function callback(tile) { + if (!tiles[zoom]) { + tiles[zoom] = {}; + } + tiles[zoom][tile.tileCoord.toString()] = tile; + } + return source.forEachLoadedTile(projection, zoom, tileRange, callback); + }); +}; + + +/** + * @return {ol.layer.Layer} Layer. + */ +ol.renderer.Layer.prototype.getLayer = function() { + return this.layer_; +}; + + +/** + * Handle changes in image state. + * @param {ol.events.Event} event Image change event. + * @private + */ +ol.renderer.Layer.prototype.handleImageChange_ = function(event) { + var image = /** @type {ol.Image} */ (event.target); + if (image.getState() === ol.Image.State.LOADED) { + this.renderIfReadyAndVisible(); + } +}; + + +/** + * Load the image if not already loaded, and register the image change + * listener if needed. + * @param {ol.ImageBase} image Image. + * @return {boolean} `true` if the image is already loaded, `false` + * otherwise. + * @protected + */ +ol.renderer.Layer.prototype.loadImage = function(image) { + var imageState = image.getState(); + if (imageState != ol.Image.State.LOADED && + imageState != ol.Image.State.ERROR) { + // the image is either "idle" or "loading", register the change + // listener (a noop if the listener was already registered) + ol.DEBUG && console.assert(imageState == ol.Image.State.IDLE || + imageState == ol.Image.State.LOADING, + 'imageState is "idle" or "loading"'); + ol.events.listen(image, ol.events.EventType.CHANGE, + this.handleImageChange_, this); + } + if (imageState == ol.Image.State.IDLE) { + image.load(); + imageState = image.getState(); + ol.DEBUG && console.assert(imageState == ol.Image.State.LOADING || + imageState == ol.Image.State.LOADED, + 'imageState is "loading" or "loaded"'); + } + return imageState == ol.Image.State.LOADED; +}; + + +/** + * @protected + */ +ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() { + var layer = this.getLayer(); + if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) { + this.changed(); + } +}; + + +/** + * @param {olx.FrameState} frameState Frame state. + * @param {ol.source.Tile} tileSource Tile source. + * @protected + */ +ol.renderer.Layer.prototype.scheduleExpireCache = function(frameState, tileSource) { + if (tileSource.canExpireCache()) { + /** + * @param {ol.source.Tile} tileSource Tile source. + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + */ + var postRenderFunction = function(tileSource, map, frameState) { + var tileSourceKey = ol.getUid(tileSource).toString(); + tileSource.expireCache(frameState.viewState.projection, + frameState.usedTiles[tileSourceKey]); + }.bind(null, tileSource); + + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (postRenderFunction) + ); + } +}; + + +/** + * @param {Object.<string, ol.Attribution>} attributionsSet Attributions + * set (target). + * @param {Array.<ol.Attribution>} attributions Attributions (source). + * @protected + */ +ol.renderer.Layer.prototype.updateAttributions = function(attributionsSet, attributions) { + if (attributions) { + var attribution, i, ii; + for (i = 0, ii = attributions.length; i < ii; ++i) { + attribution = attributions[i]; + attributionsSet[ol.getUid(attribution).toString()] = attribution; + } + } +}; + + +/** + * @param {olx.FrameState} frameState Frame state. + * @param {ol.source.Source} source Source. + * @protected + */ +ol.renderer.Layer.prototype.updateLogos = function(frameState, source) { + var logo = source.getLogo(); + if (logo !== undefined) { + if (typeof logo === 'string') { + frameState.logos[logo] = ''; + } else if (logo) { + ol.asserts.assert(typeof logo.href == 'string', 44); // `logo.href` should be a string. + ol.asserts.assert(typeof logo.src == 'string', 45); // `logo.src` should be a string. + frameState.logos[logo.src] = logo.href; + } + } +}; + + +/** + * @param {Object.<string, Object.<string, ol.TileRange>>} usedTiles Used tiles. + * @param {ol.source.Tile} tileSource Tile source. + * @param {number} z Z. + * @param {ol.TileRange} tileRange Tile range. + * @protected + */ +ol.renderer.Layer.prototype.updateUsedTiles = function(usedTiles, tileSource, z, tileRange) { + // FIXME should we use tilesToDrawByZ instead? + var tileSourceKey = ol.getUid(tileSource).toString(); + var zKey = z.toString(); + if (tileSourceKey in usedTiles) { + if (zKey in usedTiles[tileSourceKey]) { + usedTiles[tileSourceKey][zKey].extend(tileRange); + } else { + usedTiles[tileSourceKey][zKey] = tileRange; + } + } else { + usedTiles[tileSourceKey] = {}; + usedTiles[tileSourceKey][zKey] = tileRange; + } +}; + + +/** + * Manage tile pyramid. + * This function performs a number of functions related to the tiles at the + * current zoom and lower zoom levels: + * - registers idle tiles in frameState.wantedTiles so that they are not + * discarded by the tile queue + * - enqueues missing tiles + * @param {olx.FrameState} frameState Frame state. + * @param {ol.source.Tile} tileSource Tile source. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {ol.Extent} extent Extent. + * @param {number} currentZ Current Z. + * @param {number} preload Load low resolution tiles up to 'preload' levels. + * @param {function(this: T, ol.Tile)=} opt_tileCallback Tile callback. + * @param {T=} opt_this Object to use as `this` in `opt_tileCallback`. + * @protected + * @template T + */ +ol.renderer.Layer.prototype.manageTilePyramid = function( + frameState, tileSource, tileGrid, pixelRatio, projection, extent, + currentZ, preload, opt_tileCallback, opt_this) { + var tileSourceKey = ol.getUid(tileSource).toString(); + if (!(tileSourceKey in frameState.wantedTiles)) { + frameState.wantedTiles[tileSourceKey] = {}; + } + var wantedTiles = frameState.wantedTiles[tileSourceKey]; + var tileQueue = frameState.tileQueue; + var minZoom = tileGrid.getMinZoom(); + var tile, tileRange, tileResolution, x, y, z; + for (z = currentZ; z >= minZoom; --z) { + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange); + tileResolution = tileGrid.getResolution(z); + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + if (currentZ - z <= preload) { + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + if (tile.getState() == ol.Tile.State.IDLE) { + wantedTiles[tile.getKey()] = true; + if (!tileQueue.isKeyQueued(tile.getKey())) { + tileQueue.enqueue([tile, tileSourceKey, + tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]); + } + } + if (opt_tileCallback !== undefined) { + opt_tileCallback.call(opt_this, tile); + } + } else { + tileSource.useTile(z, x, y, projection); + } + } + } + } +}; + +goog.provide('ol.renderer.canvas.Layer'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.functions'); +goog.require('ol.render.Event'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.Immediate'); +goog.require('ol.renderer.Layer'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.renderer.Layer} + * @param {ol.layer.Layer} layer Layer. + */ +ol.renderer.canvas.Layer = function(layer) { + + ol.renderer.Layer.call(this, layer); + + /** + * @protected + * @type {number} + */ + this.renderedResolution; + + /** + * @private + * @type {ol.Transform} + */ + this.transform_ = ol.transform.create(); + +}; +ol.inherits(ol.renderer.canvas.Layer, ol.renderer.Layer); + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.Extent} extent Clip extent. + * @protected + */ +ol.renderer.canvas.Layer.prototype.clip = function(context, frameState, extent) { + var pixelRatio = frameState.pixelRatio; + var width = frameState.size[0] * pixelRatio; + var height = frameState.size[1] * pixelRatio; + var rotation = frameState.viewState.rotation; + var topLeft = ol.extent.getTopLeft(/** @type {ol.Extent} */ (extent)); + var topRight = ol.extent.getTopRight(/** @type {ol.Extent} */ (extent)); + var bottomRight = ol.extent.getBottomRight(/** @type {ol.Extent} */ (extent)); + var bottomLeft = ol.extent.getBottomLeft(/** @type {ol.Extent} */ (extent)); + + ol.transform.apply(frameState.coordinateToPixelTransform, topLeft); + ol.transform.apply(frameState.coordinateToPixelTransform, topRight); + ol.transform.apply(frameState.coordinateToPixelTransform, bottomRight); + ol.transform.apply(frameState.coordinateToPixelTransform, bottomLeft); + + context.save(); + ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2); + context.beginPath(); + context.moveTo(topLeft[0] * pixelRatio, topLeft[1] * pixelRatio); + context.lineTo(topRight[0] * pixelRatio, topRight[1] * pixelRatio); + context.lineTo(bottomRight[0] * pixelRatio, bottomRight[1] * pixelRatio); + context.lineTo(bottomLeft[0] * pixelRatio, bottomLeft[1] * pixelRatio); + context.clip(); + ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2); +}; + + +/** + * @param {ol.render.Event.Type} type Event type. + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.Transform=} opt_transform Transform. + * @private + */ +ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState, opt_transform) { + var layer = this.getLayer(); + if (layer.hasListener(type)) { + var width = frameState.size[0] * frameState.pixelRatio; + var height = frameState.size[1] * frameState.pixelRatio; + var rotation = frameState.viewState.rotation; + ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2); + var transform = opt_transform !== undefined ? + opt_transform : this.getTransform(frameState, 0); + var render = new ol.render.canvas.Immediate( + context, frameState.pixelRatio, frameState.extent, transform, + frameState.viewState.rotation); + var composeEvent = new ol.render.Event(type, render, frameState, + context, null); + layer.dispatchEvent(composeEvent); + ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2); + } +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer + * callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @return {T|undefined} Callback result. + * @template S,T,U + */ +ol.renderer.canvas.Layer.prototype.forEachLayerAtCoordinate = function(coordinate, frameState, callback, thisArg) { + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, 0, ol.functions.TRUE, this); + + if (hasFeature) { + return callback.call(thisArg, this.getLayer(), null); + } else { + return undefined; + } +}; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @param {ol.Transform=} opt_transform Transform. + * @protected + */ +ol.renderer.canvas.Layer.prototype.postCompose = function(context, frameState, layerState, opt_transform) { + this.dispatchComposeEvent_(ol.render.Event.Type.POSTCOMPOSE, context, + frameState, opt_transform); +}; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.Transform=} opt_transform Transform. + * @protected + */ +ol.renderer.canvas.Layer.prototype.preCompose = function(context, frameState, opt_transform) { + this.dispatchComposeEvent_(ol.render.Event.Type.PRECOMPOSE, context, + frameState, opt_transform); +}; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.Transform=} opt_transform Transform. + * @protected + */ +ol.renderer.canvas.Layer.prototype.dispatchRenderEvent = function(context, frameState, opt_transform) { + this.dispatchComposeEvent_(ol.render.Event.Type.RENDER, context, + frameState, opt_transform); +}; + + +/** + * @param {olx.FrameState} frameState Frame state. + * @param {number} offsetX Offset on the x-axis in view coordinates. + * @protected + * @return {!ol.Transform} Transform. + */ +ol.renderer.canvas.Layer.prototype.getTransform = function(frameState, offsetX) { + var viewState = frameState.viewState; + var pixelRatio = frameState.pixelRatio; + var dx1 = pixelRatio * frameState.size[0] / 2; + var dy1 = pixelRatio * frameState.size[1] / 2; + var sx = pixelRatio / viewState.resolution; + var sy = -sx; + var angle = -viewState.rotation; + var dx2 = -viewState.center[0] + offsetX; + var dy2 = -viewState.center[1]; + return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2); +}; + + +/** + * @abstract + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @param {CanvasRenderingContext2D} context Context. + */ +ol.renderer.canvas.Layer.prototype.composeFrame = function(frameState, layerState, context) {}; + +/** + * @abstract + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @return {boolean} whether composeFrame should be called. + */ +ol.renderer.canvas.Layer.prototype.prepareFrame = function(frameState, layerState) {}; + +goog.provide('ol.renderer.canvas.IntermediateCanvas'); + +goog.require('ol'); +goog.require('ol.coordinate'); +goog.require('ol.dom'); +goog.require('ol.renderer.canvas.Layer'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.renderer.canvas.Layer} + * @param {ol.layer.Layer} layer Layer. + */ +ol.renderer.canvas.IntermediateCanvas = function(layer) { + + ol.renderer.canvas.Layer.call(this, layer); + + /** + * @protected + * @type {ol.Transform} + */ + this.coordinateToCanvasPixelTransform = ol.transform.create(); + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitCanvasContext_ = null; + + /** + * @protected + * @type {number} + */ + this.renderedResolution; + +}; +ol.inherits(ol.renderer.canvas.IntermediateCanvas, ol.renderer.canvas.Layer); + + +/** + * @inheritDoc + */ +ol.renderer.canvas.IntermediateCanvas.prototype.composeFrame = function(frameState, layerState, context) { + + this.preCompose(context, frameState); + + var image = this.getImage(); + if (image) { + + // clipped rendering if layer extent is set + var extent = layerState.extent; + var clipped = extent !== undefined; + if (clipped) { + this.clip(context, frameState, /** @type {ol.Extent} */ (extent)); + } + + var imageTransform = this.getImageTransform(); + // for performance reasons, context.save / context.restore is not used + // to save and restore the transformation matrix and the opacity. + // see http://jsperf.com/context-save-restore-versus-variable + var alpha = context.globalAlpha; + context.globalAlpha = layerState.opacity; + + // for performance reasons, context.setTransform is only used + // when the view is rotated. see http://jsperf.com/canvas-transform + var dx = imageTransform[4]; + var dy = imageTransform[5]; + var dw = image.width * imageTransform[0]; + var dh = image.height * imageTransform[3]; + context.drawImage(image, 0, 0, +image.width, +image.height, + Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh)); + context.globalAlpha = alpha; + + if (clipped) { + context.restore(); + } + } + + this.postCompose(context, frameState, layerState); +}; + + +/** + * @abstract + * @return {HTMLCanvasElement|HTMLVideoElement|Image} Canvas. + */ +ol.renderer.canvas.IntermediateCanvas.prototype.getImage = function() {}; + + +/** + * @abstract + * @return {!ol.Transform} Image transform. + */ +ol.renderer.canvas.IntermediateCanvas.prototype.getImageTransform = function() {}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.IntermediateCanvas.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { + var layer = this.getLayer(); + var source = layer.getSource(); + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var skippedFeatureUids = frameState.skippedFeatureUids; + return source.forEachFeatureAtCoordinate( + coordinate, resolution, rotation, hitTolerance, skippedFeatureUids, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + return callback.call(thisArg, feature, layer); + }); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.IntermediateCanvas.prototype.forEachLayerAtCoordinate = function(coordinate, frameState, callback, thisArg) { + if (!this.getImage()) { + return undefined; + } + + if (this.getLayer().getSource().forEachFeatureAtCoordinate !== ol.nullFunction) { + // for ImageVector sources use the original hit-detection logic, + // so that for example also transparent polygons are detected + return ol.renderer.canvas.Layer.prototype.forEachLayerAtCoordinate.apply(this, arguments); + } else { + var pixel = ol.transform.apply(this.coordinateToCanvasPixelTransform, coordinate.slice()); + ol.coordinate.scale(pixel, frameState.viewState.resolution / this.renderedResolution); + + if (!this.hitCanvasContext_) { + this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1); + } + + this.hitCanvasContext_.clearRect(0, 0, 1, 1); + this.hitCanvasContext_.drawImage(this.getImage(), pixel[0], pixel[1], 1, 1, 0, 0, 1, 1); + + var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data; + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer(), imageData); + } else { + return undefined; + } + } +}; + +goog.provide('ol.renderer.canvas.ImageLayer'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.extent'); +goog.require('ol.proj'); +goog.require('ol.renderer.canvas.IntermediateCanvas'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.renderer.canvas.IntermediateCanvas} + * @param {ol.layer.Image} imageLayer Single image layer. + */ +ol.renderer.canvas.ImageLayer = function(imageLayer) { + + ol.renderer.canvas.IntermediateCanvas.call(this, imageLayer); + + /** + * @private + * @type {?ol.ImageBase} + */ + this.image_ = null; + + /** + * @private + * @type {ol.Transform} + */ + this.imageTransform_ = ol.transform.create(); + +}; +ol.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.IntermediateCanvas); + + +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.getImage = function() { + return !this.image_ ? null : this.image_.getImage(); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.getImageTransform = function() { + return this.imageTransform_; +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.prepareFrame = function(frameState, layerState) { + + var pixelRatio = frameState.pixelRatio; + var size = frameState.size; + var viewState = frameState.viewState; + var viewCenter = viewState.center; + var viewResolution = viewState.resolution; + + var image; + var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer()); + var imageSource = imageLayer.getSource(); + + var hints = frameState.viewHints; + + var renderedExtent = frameState.extent; + if (layerState.extent !== undefined) { + renderedExtent = ol.extent.getIntersection( + renderedExtent, layerState.extent); + } + + if (!hints[ol.View.Hint.ANIMATING] && !hints[ol.View.Hint.INTERACTING] && + !ol.extent.isEmpty(renderedExtent)) { + var projection = viewState.projection; + if (!ol.ENABLE_RASTER_REPROJECTION) { + var sourceProjection = imageSource.getProjection(); + if (sourceProjection) { + ol.DEBUG && console.assert(ol.proj.equivalent(projection, sourceProjection), + 'projection and sourceProjection are equivalent'); + projection = sourceProjection; + } + } + image = imageSource.getImage( + renderedExtent, viewResolution, pixelRatio, projection); + if (image) { + var loaded = this.loadImage(image); + if (loaded) { + this.image_ = image; + this.renderedResolution = viewResolution; + } + } + } + + if (this.image_) { + image = this.image_; + var imageExtent = image.getExtent(); + var imageResolution = image.getResolution(); + var imagePixelRatio = image.getPixelRatio(); + var scale = pixelRatio * imageResolution / + (viewResolution * imagePixelRatio); + var transform = ol.transform.compose(this.imageTransform_, + pixelRatio * size[0] / 2, pixelRatio * size[1] / 2, + scale, scale, + 0, + imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution, + imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution); + ol.transform.compose(this.coordinateToCanvasPixelTransform, + pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], + pixelRatio / viewResolution, -pixelRatio / viewResolution, + 0, + -viewCenter[0], -viewCenter[1]); + + this.updateAttributions(frameState.attributions, image.getAttributions()); + this.updateLogos(frameState, imageSource); + } + + return !!this.image_; +}; + +// FIXME find correct globalCompositeOperation + +goog.provide('ol.renderer.canvas.TileLayer'); + +goog.require('ol'); +goog.require('ol.transform'); +goog.require('ol.TileRange'); +goog.require('ol.Tile'); +goog.require('ol.array'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.renderer.canvas.IntermediateCanvas'); + + +/** + * @constructor + * @extends {ol.renderer.canvas.IntermediateCanvas} + * @param {ol.layer.Tile|ol.layer.VectorTile} tileLayer Tile layer. + */ +ol.renderer.canvas.TileLayer = function(tileLayer) { + + ol.renderer.canvas.IntermediateCanvas.call(this, tileLayer); + + /** + * @protected + * @type {CanvasRenderingContext2D} + */ + this.context = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {ol.Extent} + */ + this.renderedExtent_ = null; + + /** + * @private + * @type {number} + */ + this.renderedRevision_; + + /** + * @protected + * @type {!Array.<ol.Tile>} + */ + this.renderedTiles = []; + + /** + * @protected + * @type {ol.Extent} + */ + this.tmpExtent = ol.extent.createEmpty(); + + /** + * @private + * @type {ol.TileCoord} + */ + this.tmpTileCoord_ = [0, 0, 0]; + + /** + * @private + * @type {ol.TileRange} + */ + this.tmpTileRange_ = new ol.TileRange(0, 0, 0, 0); + + /** + * @private + * @type {ol.Transform} + */ + this.imageTransform_ = ol.transform.create(); + + /** + * @protected + * @type {number} + */ + this.zDirection = 0; + +}; +ol.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.IntermediateCanvas); + + +/** + * @inheritDoc + */ +ol.renderer.canvas.TileLayer.prototype.prepareFrame = function(frameState, layerState) { + + var pixelRatio = frameState.pixelRatio; + var size = frameState.size; + var viewState = frameState.viewState; + var projection = viewState.projection; + var viewResolution = viewState.resolution; + var viewCenter = viewState.center; + + var tileLayer = this.getLayer(); + var tileSource = /** @type {ol.source.Tile} */ (tileLayer.getSource()); + var sourceRevision = tileSource.getRevision(); + var tileGrid = tileSource.getTileGridForProjection(projection); + var z = tileGrid.getZForResolution(viewResolution, this.zDirection); + var tileResolution = tileGrid.getResolution(z); + var extent = frameState.extent; + + if (layerState.extent !== undefined) { + extent = ol.extent.getIntersection(extent, layerState.extent); + } + if (ol.extent.isEmpty(extent)) { + // Return false to prevent the rendering of the layer. + return false; + } + + var tileRange = tileGrid.getTileRangeForExtentAndResolution( + extent, tileResolution); + var imageExtent = tileGrid.getTileRangeExtent(z, tileRange); + + var tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio); + + /** + * @type {Object.<number, Object.<string, ol.Tile>>} + */ + var tilesToDrawByZ = {}; + tilesToDrawByZ[z] = {}; + + var findLoadedTiles = this.createLoadedTileFinder( + tileSource, projection, tilesToDrawByZ); + + var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError(); + var tmpExtent = this.tmpExtent; + var tmpTileRange = this.tmpTileRange_; + var newTiles = false; + var tile, x, y; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + var tileState = tile.getState(); + var drawable = tileState == ol.Tile.State.LOADED || + tileState == ol.Tile.State.EMPTY || + tileState == ol.Tile.State.ERROR && !useInterimTilesOnError; + if (!drawable) { + tile = tile.getInterimTile(); + } else { + if (tileState == ol.Tile.State.LOADED) { + tilesToDrawByZ[z][tile.tileCoord.toString()] = tile; + if (!newTiles && this.renderedTiles.indexOf(tile) == -1) { + newTiles = true; + } + } + continue; + } + + var fullyLoaded = tileGrid.forEachTileCoordParentTileRange( + tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); + if (!fullyLoaded) { + var childTileRange = tileGrid.getTileCoordChildTileRange( + tile.tileCoord, tmpTileRange, tmpExtent); + if (childTileRange) { + findLoadedTiles(z + 1, childTileRange); + } + } + + } + } + + var hints = frameState.viewHints; + if (!(this.renderedResolution && Date.now() - frameState.time > 16 && + (hints[ol.View.Hint.ANIMATING] || hints[ol.View.Hint.INTERACTING])) && + (newTiles || !(this.renderedExtent_ && + ol.extent.equals(this.renderedExtent_, imageExtent)) || + this.renderedRevision_ != sourceRevision)) { + + var tilePixelSize = tileSource.getTilePixelSize(z, pixelRatio, projection); + var width = tileRange.getWidth() * tilePixelSize[0]; + var height = tileRange.getHeight() * tilePixelSize[0]; + var context = this.context; + var canvas = context.canvas; + var opaque = tileSource.getOpaque(projection); + if (canvas.width != width || canvas.height != height) { + canvas.width = width; + canvas.height = height; + } else { + context.clearRect(0, 0, width, height); + } + + this.renderedTiles.length = 0; + /** @type {Array.<number>} */ + var zs = Object.keys(tilesToDrawByZ).map(Number); + zs.sort(ol.array.numberSafeCompareFunction); + var currentResolution, currentScale, currentTilePixelSize, currentZ, i, ii; + var tileExtent, tileGutter, tilesToDraw, w, h; + for (i = 0, ii = zs.length; i < ii; ++i) { + currentZ = zs[i]; + currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection); + currentResolution = tileGrid.getResolution(currentZ); + currentScale = currentResolution / tileResolution; + tileGutter = tilePixelRatio * tileSource.getGutter(projection); + tilesToDraw = tilesToDrawByZ[currentZ]; + for (var tileCoordKey in tilesToDraw) { + tile = tilesToDraw[tileCoordKey]; + tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent); + x = (tileExtent[0] - imageExtent[0]) / tileResolution * tilePixelRatio; + y = (imageExtent[3] - tileExtent[3]) / tileResolution * tilePixelRatio; + w = currentTilePixelSize[0] * currentScale; + h = currentTilePixelSize[1] * currentScale; + if (!opaque) { + context.clearRect(x, y, w, h); + } + this.drawTileImage(tile, frameState, layerState, x, y, w, h, tileGutter); + this.renderedTiles.push(tile); + } + } + + this.renderedRevision_ = sourceRevision; + this.renderedResolution = tileResolution; + this.renderedExtent_ = imageExtent; + } + + var scale = pixelRatio / tilePixelRatio * this.renderedResolution / viewResolution; + var transform = ol.transform.compose(this.imageTransform_, + pixelRatio * size[0] / 2, pixelRatio * size[1] / 2, + scale, scale, + 0, + tilePixelRatio * (this.renderedExtent_[0] - viewCenter[0]) / this.renderedResolution, + tilePixelRatio * (viewCenter[1] - this.renderedExtent_[3]) / this.renderedResolution); + ol.transform.compose(this.coordinateToCanvasPixelTransform, + pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], + pixelRatio / viewResolution, -pixelRatio / viewResolution, + 0, + -viewCenter[0], -viewCenter[1]); + + + this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); + this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio, + projection, extent, z, tileLayer.getPreload()); + this.scheduleExpireCache(frameState, tileSource); + this.updateLogos(frameState, tileSource); + + return this.renderedTiles.length > 0; +}; + + +/** + * @param {ol.Tile} tile Tile. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @param {number} x Left of the tile. + * @param {number} y Top of the tile. + * @param {number} w Width of the tile. + * @param {number} h Height of the tile. + * @param {number} gutter Tile gutter. + */ +ol.renderer.canvas.TileLayer.prototype.drawTileImage = function(tile, frameState, layerState, x, y, w, h, gutter) { + var image = tile.getImage(); + if (image) { + this.context.drawImage(image, gutter, gutter, + image.width - 2 * gutter, image.height - 2 * gutter, x, y, w, h); + } +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.TileLayer.prototype.getImage = function() { + return this.context.canvas; +}; + + +/** + * @function + * @return {ol.layer.Tile|ol.layer.VectorTile} + */ +ol.renderer.canvas.TileLayer.prototype.getLayer; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.TileLayer.prototype.getImageTransform = function() { + return this.imageTransform_; +}; + +goog.provide('ol.render.ReplayGroup'); + + +/** + * Base class for replay groups. + * @constructor + */ +ol.render.ReplayGroup = function() {}; + + +/** + * @abstract + * @param {number|undefined} zIndex Z index. + * @param {ol.render.ReplayType} replayType Replay type. + * @return {ol.render.VectorContext} Replay. + */ +ol.render.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {}; + + +/** + * @abstract + * @return {boolean} Is empty. + */ +ol.render.ReplayGroup.prototype.isEmpty = function() {}; + +goog.provide('ol.render.canvas.Instruction'); + +/** + * @enum {number} + */ +ol.render.canvas.Instruction = { + BEGIN_GEOMETRY: 0, + BEGIN_PATH: 1, + CIRCLE: 2, + CLOSE_PATH: 3, + DRAW_IMAGE: 4, + DRAW_TEXT: 5, + END_GEOMETRY: 6, + FILL: 7, + MOVE_TO_LINE_TO: 8, + SET_FILL_STYLE: 9, + SET_STROKE_STYLE: 10, + SET_TEXT_STYLE: 11, + STROKE: 12 +}; + +goog.provide('ol.render.canvas.Replay'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.colorlike'); +goog.require('ol.extent'); +goog.require('ol.extent.Relationship'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.has'); +goog.require('ol.obj'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.Instruction'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.render.VectorContext} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. + * @struct + */ +ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, overlaps) { + ol.render.VectorContext.call(this); + + /** + * @protected + * @type {number} + */ + this.tolerance = tolerance; + + /** + * @protected + * @const + * @type {ol.Extent} + */ + this.maxExtent = maxExtent; + + /** + * @protected + * @type {boolean} + */ + this.overlaps = overlaps; + + /** + * @protected + * @type {number} + */ + this.maxLineWidth = 0; + + /** + * @protected + * @const + * @type {number} + */ + this.resolution = resolution; + + /** + * @private + * @type {ol.Coordinate} + */ + this.fillOrigin_; + + /** + * @private + * @type {Array.<*>} + */ + this.beginGeometryInstruction1_ = null; + + /** + * @private + * @type {Array.<*>} + */ + this.beginGeometryInstruction2_ = null; + + /** + * @protected + * @type {Array.<*>} + */ + this.instructions = []; + + /** + * @protected + * @type {Array.<number>} + */ + this.coordinates = []; + + /** + * @private + * @type {ol.Transform} + */ + this.renderedTransform_ = ol.transform.create(); + + /** + * @protected + * @type {Array.<*>} + */ + this.hitDetectionInstructions = []; + + /** + * @private + * @type {Array.<number>} + */ + this.pixelCoordinates_ = []; + + /** + * @private + * @type {ol.Transform} + */ + this.tmpLocalTransform_ = ol.transform.create(); + + /** + * @private + * @type {ol.Transform} + */ + this.resetTransform_ = ol.transform.create(); +}; +ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {boolean} closed Last input coordinate equals first. + * @param {boolean} skipFirst Skip first coordinate. + * @protected + * @return {number} My end. + */ +ol.render.canvas.Replay.prototype.appendFlatCoordinates = function(flatCoordinates, offset, end, stride, closed, skipFirst) { + + var myEnd = this.coordinates.length; + var extent = this.getBufferedMaxExtent(); + if (skipFirst) { + offset += stride; + } + var lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]]; + var nextCoord = [NaN, NaN]; + var skipped = true; + + var i, lastRel, nextRel; + for (i = offset + stride; i < end; i += stride) { + nextCoord[0] = flatCoordinates[i]; + nextCoord[1] = flatCoordinates[i + 1]; + nextRel = ol.extent.coordinateRelationship(extent, nextCoord); + if (nextRel !== lastRel) { + if (skipped) { + this.coordinates[myEnd++] = lastCoord[0]; + this.coordinates[myEnd++] = lastCoord[1]; + } + this.coordinates[myEnd++] = nextCoord[0]; + this.coordinates[myEnd++] = nextCoord[1]; + skipped = false; + } else if (nextRel === ol.extent.Relationship.INTERSECTING) { + this.coordinates[myEnd++] = nextCoord[0]; + this.coordinates[myEnd++] = nextCoord[1]; + skipped = false; + } else { + skipped = true; + } + lastCoord[0] = nextCoord[0]; + lastCoord[1] = nextCoord[1]; + lastRel = nextRel; + } + + // Last coordinate equals first or only one point to append: + if ((closed && skipped) || i === offset + stride) { + this.coordinates[myEnd++] = lastCoord[0]; + this.coordinates[myEnd++] = lastCoord[1]; + } + return myEnd; +}; + + +/** + * @protected + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) { + this.beginGeometryInstruction1_ = + [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0]; + this.instructions.push(this.beginGeometryInstruction1_); + this.beginGeometryInstruction2_ = + [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0]; + this.hitDetectionInstructions.push(this.beginGeometryInstruction2_); +}; + + +/** + * @private + * @param {CanvasRenderingContext2D} context Context. + * @param {number} rotation Rotation. + */ +ol.render.canvas.Replay.prototype.fill_ = function(context, rotation) { + if (this.fillOrigin_) { + var origin = ol.transform.apply(this.renderedTransform_, this.fillOrigin_.slice()); + context.translate(origin[0], origin[1]); + context.rotate(rotation); + } + context.fill(); + if (this.fillOrigin_) { + context.setTransform.apply(context, this.resetTransform_); + } +}; + + +/** + * @private + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {Array.<*>} instructions Instructions array. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} + * featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Only check features that intersect this + * extent. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.canvas.Replay.prototype.replay_ = function( + context, pixelRatio, transform, viewRotation, skippedFeaturesHash, + instructions, featureCallback, opt_hitExtent) { + /** @type {Array.<number>} */ + var pixelCoordinates; + if (ol.array.equals(transform, this.renderedTransform_)) { + pixelCoordinates = this.pixelCoordinates_; + } else { + pixelCoordinates = ol.geom.flat.transform.transform2D( + this.coordinates, 0, this.coordinates.length, 2, + transform, this.pixelCoordinates_); + ol.transform.setFromArray(this.renderedTransform_, transform); + ol.DEBUG && console.assert(pixelCoordinates === this.pixelCoordinates_, + 'pixelCoordinates should be the same as this.pixelCoordinates_'); + } + var skipFeatures = !ol.obj.isEmpty(skippedFeaturesHash); + var i = 0; // instruction index + var ii = instructions.length; // end of instructions + var d = 0; // data index + var dd; // end of per-instruction data + var localTransform = this.tmpLocalTransform_; + var resetTransform = this.resetTransform_; + var prevX, prevY, roundX, roundY; + var pendingFill = 0; + var pendingStroke = 0; + // When the batch size gets too big, performance decreases. 200 is a good + // balance between batch size and number of fill/stroke instructions. + var batchSize = + this.instructions != instructions || this.overlaps ? 0 : 200; + while (i < ii) { + var instruction = instructions[i]; + var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]); + var feature, fill, stroke, text, x, y; + switch (type) { + case ol.render.canvas.Instruction.BEGIN_GEOMETRY: + feature = /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]); + if ((skipFeatures && + skippedFeaturesHash[ol.getUid(feature).toString()]) || + !feature.getGeometry()) { + i = /** @type {number} */ (instruction[2]); + } else if (opt_hitExtent !== undefined && !ol.extent.intersects( + opt_hitExtent, feature.getGeometry().getExtent())) { + i = /** @type {number} */ (instruction[2]) + 1; + } else { + ++i; + } + break; + case ol.render.canvas.Instruction.BEGIN_PATH: + if (pendingFill > batchSize) { + this.fill_(context, viewRotation); + pendingFill = 0; + } + if (pendingStroke > batchSize) { + context.stroke(); + pendingStroke = 0; + } + if (!pendingFill && !pendingStroke) { + context.beginPath(); + } + ++i; + break; + case ol.render.canvas.Instruction.CIRCLE: + ol.DEBUG && console.assert(typeof instruction[1] === 'number', + 'second instruction should be a number'); + d = /** @type {number} */ (instruction[1]); + var x1 = pixelCoordinates[d]; + var y1 = pixelCoordinates[d + 1]; + var x2 = pixelCoordinates[d + 2]; + var y2 = pixelCoordinates[d + 3]; + var dx = x2 - x1; + var dy = y2 - y1; + var r = Math.sqrt(dx * dx + dy * dy); + context.moveTo(x1 + r, y1); + context.arc(x1, y1, r, 0, 2 * Math.PI, true); + ++i; + break; + case ol.render.canvas.Instruction.CLOSE_PATH: + context.closePath(); + ++i; + break; + case ol.render.canvas.Instruction.DRAW_IMAGE: + ol.DEBUG && console.assert(typeof instruction[1] === 'number', + 'second instruction should be a number'); + d = /** @type {number} */ (instruction[1]); + ol.DEBUG && console.assert(typeof instruction[2] === 'number', + 'third instruction should be a number'); + dd = /** @type {number} */ (instruction[2]); + var image = /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */ + (instruction[3]); + // Remaining arguments in DRAW_IMAGE are in alphabetical order + var anchorX = /** @type {number} */ (instruction[4]) * pixelRatio; + var anchorY = /** @type {number} */ (instruction[5]) * pixelRatio; + var height = /** @type {number} */ (instruction[6]); + var opacity = /** @type {number} */ (instruction[7]); + var originX = /** @type {number} */ (instruction[8]); + var originY = /** @type {number} */ (instruction[9]); + var rotateWithView = /** @type {boolean} */ (instruction[10]); + var rotation = /** @type {number} */ (instruction[11]); + var scale = /** @type {number} */ (instruction[12]); + var snapToPixel = /** @type {boolean} */ (instruction[13]); + var width = /** @type {number} */ (instruction[14]); + if (rotateWithView) { + rotation += viewRotation; + } + for (; d < dd; d += 2) { + x = pixelCoordinates[d] - anchorX; + y = pixelCoordinates[d + 1] - anchorY; + if (snapToPixel) { + x = Math.round(x); + y = Math.round(y); + } + if (scale != 1 || rotation !== 0) { + var centerX = x + anchorX; + var centerY = y + anchorY; + ol.transform.compose(localTransform, + centerX, centerY, scale, scale, rotation, -centerX, -centerY); + context.setTransform.apply(context, localTransform); + } + var alpha = context.globalAlpha; + if (opacity != 1) { + context.globalAlpha = alpha * opacity; + } + + var w = (width + originX > image.width) ? image.width - originX : width; + var h = (height + originY > image.height) ? image.height - originY : height; + + context.drawImage(image, originX, originY, w, h, + x, y, w * pixelRatio, h * pixelRatio); + + if (opacity != 1) { + context.globalAlpha = alpha; + } + if (scale != 1 || rotation !== 0) { + context.setTransform.apply(context, resetTransform); + } + } + ++i; + break; + case ol.render.canvas.Instruction.DRAW_TEXT: + ol.DEBUG && console.assert(typeof instruction[1] === 'number', + '2nd instruction should be a number'); + d = /** @type {number} */ (instruction[1]); + ol.DEBUG && console.assert(typeof instruction[2] === 'number', + '3rd instruction should be a number'); + dd = /** @type {number} */ (instruction[2]); + ol.DEBUG && console.assert(typeof instruction[3] === 'string', + '4th instruction should be a string'); + text = /** @type {string} */ (instruction[3]); + ol.DEBUG && console.assert(typeof instruction[4] === 'number', + '5th instruction should be a number'); + var offsetX = /** @type {number} */ (instruction[4]) * pixelRatio; + ol.DEBUG && console.assert(typeof instruction[5] === 'number', + '6th instruction should be a number'); + var offsetY = /** @type {number} */ (instruction[5]) * pixelRatio; + ol.DEBUG && console.assert(typeof instruction[6] === 'number', + '7th instruction should be a number'); + rotation = /** @type {number} */ (instruction[6]); + ol.DEBUG && console.assert(typeof instruction[7] === 'number', + '8th instruction should be a number'); + scale = /** @type {number} */ (instruction[7]) * pixelRatio; + ol.DEBUG && console.assert(typeof instruction[8] === 'boolean', + '9th instruction should be a boolean'); + fill = /** @type {boolean} */ (instruction[8]); + ol.DEBUG && console.assert(typeof instruction[9] === 'boolean', + '10th instruction should be a boolean'); + stroke = /** @type {boolean} */ (instruction[9]); + rotateWithView = /** @type {boolean} */ (instruction[10]); + if (rotateWithView) { + rotation += viewRotation; + } + for (; d < dd; d += 2) { + x = pixelCoordinates[d] + offsetX; + y = pixelCoordinates[d + 1] + offsetY; + if (scale != 1 || rotation !== 0) { + ol.transform.compose(localTransform, x, y, scale, scale, rotation, -x, -y); + context.setTransform.apply(context, localTransform); + } + + // Support multiple lines separated by \n + var lines = text.split('\n'); + var numLines = lines.length; + var fontSize, lineY; + if (numLines > 1) { + // Estimate line height using width of capital M, and add padding + fontSize = Math.round(context.measureText('M').width * 1.5); + lineY = y - (((numLines - 1) / 2) * fontSize); + } else { + // No need to calculate line height/offset for a single line + fontSize = 0; + lineY = y; + } + + for (var lineIndex = 0; lineIndex < numLines; lineIndex++) { + var line = lines[lineIndex]; + if (stroke) { + context.strokeText(line, x, lineY); + } + if (fill) { + context.fillText(line, x, lineY); + } + + // Move next line down by fontSize px + lineY = lineY + fontSize; + } + + if (scale != 1 || rotation !== 0) { + context.setTransform.apply(context, resetTransform); + } + } + ++i; + break; + case ol.render.canvas.Instruction.END_GEOMETRY: + if (featureCallback !== undefined) { + feature = + /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]); + var result = featureCallback(feature); + if (result) { + return result; + } + } + ++i; + break; + case ol.render.canvas.Instruction.FILL: + if (batchSize) { + pendingFill++; + } else { + this.fill_(context, viewRotation); + } + ++i; + break; + case ol.render.canvas.Instruction.MOVE_TO_LINE_TO: + ol.DEBUG && console.assert(typeof instruction[1] === 'number', + '2nd instruction should be a number'); + d = /** @type {number} */ (instruction[1]); + ol.DEBUG && console.assert(typeof instruction[2] === 'number', + '3rd instruction should be a number'); + dd = /** @type {number} */ (instruction[2]); + x = pixelCoordinates[d]; + y = pixelCoordinates[d + 1]; + roundX = (x + 0.5) | 0; + roundY = (y + 0.5) | 0; + if (roundX !== prevX || roundY !== prevY) { + context.moveTo(x, y); + prevX = roundX; + prevY = roundY; + } + for (d += 2; d < dd; d += 2) { + x = pixelCoordinates[d]; + y = pixelCoordinates[d + 1]; + roundX = (x + 0.5) | 0; + roundY = (y + 0.5) | 0; + if (d == dd - 2 || roundX !== prevX || roundY !== prevY) { + context.lineTo(x, y); + prevX = roundX; + prevY = roundY; + } + } + ++i; + break; + case ol.render.canvas.Instruction.SET_FILL_STYLE: + ol.DEBUG && console.assert( + ol.colorlike.isColorLike(instruction[1]), + '2nd instruction should be a string, ' + + 'CanvasPattern, or CanvasGradient'); + this.fillOrigin_ = instruction[2]; + + if (pendingFill) { + this.fill_(context, viewRotation); + pendingFill = 0; + } + + context.fillStyle = /** @type {ol.ColorLike} */ (instruction[1]); + ++i; + break; + case ol.render.canvas.Instruction.SET_STROKE_STYLE: + ol.DEBUG && console.assert(ol.colorlike.isColorLike(instruction[1]), + '2nd instruction should be a string, CanvasPattern, or CanvasGradient'); + ol.DEBUG && console.assert(typeof instruction[2] === 'number', + '3rd instruction should be a number'); + ol.DEBUG && console.assert(typeof instruction[3] === 'string', + '4rd instruction should be a string'); + ol.DEBUG && console.assert(typeof instruction[4] === 'string', + '5th instruction should be a string'); + ol.DEBUG && console.assert(typeof instruction[5] === 'number', + '6th instruction should be a number'); + ol.DEBUG && console.assert(instruction[6], + '7th instruction should not be null'); + ol.DEBUG && console.assert(typeof instruction[8] === 'number', + '9th instruction should be a number'); + var usePixelRatio = instruction[7] !== undefined ? + instruction[7] : true; + var renderedPixelRatio = instruction[8]; + + var lineWidth = /** @type {number} */ (instruction[2]); + if (pendingStroke) { + context.stroke(); + pendingStroke = 0; + } + context.strokeStyle = /** @type {ol.ColorLike} */ (instruction[1]); + context.lineWidth = usePixelRatio ? lineWidth * pixelRatio : lineWidth; + context.lineCap = /** @type {string} */ (instruction[3]); + context.lineJoin = /** @type {string} */ (instruction[4]); + context.miterLimit = /** @type {number} */ (instruction[5]); + if (ol.has.CANVAS_LINE_DASH) { + var lineDash = /** @type {Array.<number>} */ (instruction[6]); + if (usePixelRatio && pixelRatio !== renderedPixelRatio) { + lineDash = lineDash.map(function(dash) { + return dash * pixelRatio / renderedPixelRatio; + }); + instruction[6] = lineDash; + instruction[8] = pixelRatio; + } + context.setLineDash(lineDash); + } + prevX = NaN; + prevY = NaN; + ++i; + break; + case ol.render.canvas.Instruction.SET_TEXT_STYLE: + ol.DEBUG && console.assert(typeof instruction[1] === 'string', + '2nd instruction should be a string'); + ol.DEBUG && console.assert(typeof instruction[2] === 'string', + '3rd instruction should be a string'); + ol.DEBUG && console.assert(typeof instruction[3] === 'string', + '4th instruction should be a string'); + context.font = /** @type {string} */ (instruction[1]); + context.textAlign = /** @type {string} */ (instruction[2]); + context.textBaseline = /** @type {string} */ (instruction[3]); + ++i; + break; + case ol.render.canvas.Instruction.STROKE: + if (batchSize) { + pendingStroke++; + } else { + context.stroke(); + } + ++i; + break; + default: + ol.DEBUG && console.assert(false, 'Unknown canvas render instruction'); + ++i; // consume the instruction anyway, to avoid an infinite loop + break; + } + } + if (pendingFill) { + this.fill_(context, viewRotation); + } + if (pendingStroke) { + context.stroke(); + } + // assert that all instructions were consumed + ol.DEBUG && console.assert(i == instructions.length, + 'all instructions should be consumed'); + return undefined; +}; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + */ +ol.render.canvas.Replay.prototype.replay = function( + context, pixelRatio, transform, viewRotation, skippedFeaturesHash) { + var instructions = this.instructions; + this.replay_(context, pixelRatio, transform, viewRotation, + skippedFeaturesHash, instructions, undefined, undefined); +}; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {ol.Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T=} opt_featureCallback + * Feature callback. + * @param {ol.Extent=} opt_hitExtent Only check features that intersect this + * extent. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.canvas.Replay.prototype.replayHitDetection = function( + context, transform, viewRotation, skippedFeaturesHash, + opt_featureCallback, opt_hitExtent) { + var instructions = this.hitDetectionInstructions; + return this.replay_(context, 1, transform, viewRotation, + skippedFeaturesHash, instructions, opt_featureCallback, opt_hitExtent); +}; + + +/** + * Reverse the hit detection instructions. + */ +ol.render.canvas.Replay.prototype.reverseHitDetectionInstructions = function() { + var hitDetectionInstructions = this.hitDetectionInstructions; + // step 1 - reverse array + hitDetectionInstructions.reverse(); + // step 2 - reverse instructions within geometry blocks + var i; + var n = hitDetectionInstructions.length; + var instruction; + var type; + var begin = -1; + for (i = 0; i < n; ++i) { + instruction = hitDetectionInstructions[i]; + type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]); + if (type == ol.render.canvas.Instruction.END_GEOMETRY) { + ol.DEBUG && console.assert(begin == -1, 'begin should be -1'); + begin = i; + } else if (type == ol.render.canvas.Instruction.BEGIN_GEOMETRY) { + instruction[2] = i; + ol.DEBUG && console.assert(begin >= 0, + 'begin should be larger than or equal to 0'); + ol.array.reverseSubArray(this.hitDetectionInstructions, begin, i); + begin = -1; + } + } +}; + + +/** + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.canvas.Replay.prototype.endGeometry = function(geometry, feature) { + ol.DEBUG && console.assert(this.beginGeometryInstruction1_, + 'this.beginGeometryInstruction1_ should not be null'); + this.beginGeometryInstruction1_[2] = this.instructions.length; + this.beginGeometryInstruction1_ = null; + ol.DEBUG && console.assert(this.beginGeometryInstruction2_, + 'this.beginGeometryInstruction2_ should not be null'); + this.beginGeometryInstruction2_[2] = this.hitDetectionInstructions.length; + this.beginGeometryInstruction2_ = null; + var endGeometryInstruction = + [ol.render.canvas.Instruction.END_GEOMETRY, feature]; + this.instructions.push(endGeometryInstruction); + this.hitDetectionInstructions.push(endGeometryInstruction); +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.render.canvas.Replay.prototype.finish = ol.nullFunction; + + +/** + * Get the buffered rendering extent. Rendering will be clipped to the extent + * provided to the constructor. To account for symbolizers that may intersect + * this extent, we calculate a buffered extent (e.g. based on stroke width). + * @return {ol.Extent} The buffered rendering extent. + * @protected + */ +ol.render.canvas.Replay.prototype.getBufferedMaxExtent = function() { + return this.maxExtent; +}; + +goog.provide('ol.render.canvas.ImageReplay'); + +goog.require('ol'); +goog.require('ol.render.canvas.Instruction'); +goog.require('ol.render.canvas.Replay'); + + +/** + * @constructor + * @extends {ol.render.canvas.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. + * @struct + */ +ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution, overlaps) { + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); + + /** + * @private + * @type {HTMLCanvasElement|HTMLVideoElement|Image} + */ + this.hitDetectionImage_ = null; + + /** + * @private + * @type {HTMLCanvasElement|HTMLVideoElement|Image} + */ + this.image_ = null; + + /** + * @private + * @type {number|undefined} + */ + this.anchorX_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.anchorY_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.height_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.opacity_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.originX_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.originY_ = undefined; + + /** + * @private + * @type {boolean|undefined} + */ + this.rotateWithView_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.rotation_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.scale_ = undefined; + + /** + * @private + * @type {boolean|undefined} + */ + this.snapToPixel_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.width_ = undefined; + +}; +ol.inherits(ol.render.canvas.ImageReplay, ol.render.canvas.Replay); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @private + * @return {number} My end. + */ +ol.render.canvas.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { + return this.appendFlatCoordinates( + flatCoordinates, offset, end, stride, false, false); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) { + if (!this.image_) { + return; + } + ol.DEBUG && console.assert(this.anchorX_ !== undefined, + 'this.anchorX_ should be defined'); + ol.DEBUG && console.assert(this.anchorY_ !== undefined, + 'this.anchorY_ should be defined'); + ol.DEBUG && console.assert(this.height_ !== undefined, + 'this.height_ should be defined'); + ol.DEBUG && console.assert(this.opacity_ !== undefined, + 'this.opacity_ should be defined'); + ol.DEBUG && console.assert(this.originX_ !== undefined, + 'this.originX_ should be defined'); + ol.DEBUG && console.assert(this.originY_ !== undefined, + 'this.originY_ should be defined'); + ol.DEBUG && console.assert(this.rotateWithView_ !== undefined, + 'this.rotateWithView_ should be defined'); + ol.DEBUG && console.assert(this.rotation_ !== undefined, + 'this.rotation_ should be defined'); + ol.DEBUG && console.assert(this.scale_ !== undefined, + 'this.scale_ should be defined'); + ol.DEBUG && console.assert(this.width_ !== undefined, + 'this.width_ should be defined'); + this.beginGeometry(pointGeometry, feature); + var flatCoordinates = pointGeometry.getFlatCoordinates(); + var stride = pointGeometry.getStride(); + var myBegin = this.coordinates.length; + var myEnd = this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + this.instructions.push([ + ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_, + // Remaining arguments to DRAW_IMAGE are in alphabetical order + this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.originX_, this.originY_, this.rotateWithView_, this.rotation_, + this.scale_, this.snapToPixel_, this.width_ + ]); + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, + this.hitDetectionImage_, + // Remaining arguments to DRAW_IMAGE are in alphabetical order + this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.originX_, this.originY_, this.rotateWithView_, this.rotation_, + this.scale_, this.snapToPixel_, this.width_ + ]); + this.endGeometry(pointGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) { + if (!this.image_) { + return; + } + ol.DEBUG && console.assert(this.anchorX_ !== undefined, + 'this.anchorX_ should be defined'); + ol.DEBUG && console.assert(this.anchorY_ !== undefined, + 'this.anchorY_ should be defined'); + ol.DEBUG && console.assert(this.height_ !== undefined, + 'this.height_ should be defined'); + ol.DEBUG && console.assert(this.opacity_ !== undefined, + 'this.opacity_ should be defined'); + ol.DEBUG && console.assert(this.originX_ !== undefined, + 'this.originX_ should be defined'); + ol.DEBUG && console.assert(this.originY_ !== undefined, + 'this.originY_ should be defined'); + ol.DEBUG && console.assert(this.rotateWithView_ !== undefined, + 'this.rotateWithView_ should be defined'); + ol.DEBUG && console.assert(this.rotation_ !== undefined, + 'this.rotation_ should be defined'); + ol.DEBUG && console.assert(this.scale_ !== undefined, + 'this.scale_ should be defined'); + ol.DEBUG && console.assert(this.width_ !== undefined, + 'this.width_ should be defined'); + this.beginGeometry(multiPointGeometry, feature); + var flatCoordinates = multiPointGeometry.getFlatCoordinates(); + var stride = multiPointGeometry.getStride(); + var myBegin = this.coordinates.length; + var myEnd = this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + this.instructions.push([ + ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_, + // Remaining arguments to DRAW_IMAGE are in alphabetical order + this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.originX_, this.originY_, this.rotateWithView_, this.rotation_, + this.scale_, this.snapToPixel_, this.width_ + ]); + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, + this.hitDetectionImage_, + // Remaining arguments to DRAW_IMAGE are in alphabetical order + this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.originX_, this.originY_, this.rotateWithView_, this.rotation_, + this.scale_, this.snapToPixel_, this.width_ + ]); + this.endGeometry(multiPointGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.ImageReplay.prototype.finish = function() { + this.reverseHitDetectionInstructions(); + // FIXME this doesn't really protect us against further calls to draw*Geometry + this.anchorX_ = undefined; + this.anchorY_ = undefined; + this.hitDetectionImage_ = null; + this.image_ = null; + this.height_ = undefined; + this.scale_ = undefined; + this.opacity_ = undefined; + this.originX_ = undefined; + this.originY_ = undefined; + this.rotateWithView_ = undefined; + this.rotation_ = undefined; + this.snapToPixel_ = undefined; + this.width_ = undefined; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) { + ol.DEBUG && console.assert(imageStyle, 'imageStyle should not be null'); + var anchor = imageStyle.getAnchor(); + ol.DEBUG && console.assert(anchor, 'anchor should not be null'); + var size = imageStyle.getSize(); + ol.DEBUG && console.assert(size, 'size should not be null'); + var hitDetectionImage = imageStyle.getHitDetectionImage(1); + ol.DEBUG && console.assert(hitDetectionImage, + 'hitDetectionImage should not be null'); + var image = imageStyle.getImage(1); + ol.DEBUG && console.assert(image, 'image should not be null'); + var origin = imageStyle.getOrigin(); + ol.DEBUG && console.assert(origin, 'origin should not be null'); + this.anchorX_ = anchor[0]; + this.anchorY_ = anchor[1]; + this.hitDetectionImage_ = hitDetectionImage; + this.image_ = image; + this.height_ = size[1]; + this.opacity_ = imageStyle.getOpacity(); + this.originX_ = origin[0]; + this.originY_ = origin[1]; + this.rotateWithView_ = imageStyle.getRotateWithView(); + this.rotation_ = imageStyle.getRotation(); + this.scale_ = imageStyle.getScale(); + this.snapToPixel_ = imageStyle.getSnapToPixel(); + this.width_ = size[0]; +}; + +goog.provide('ol.render.canvas.LineStringReplay'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.colorlike'); +goog.require('ol.extent'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.Instruction'); +goog.require('ol.render.canvas.Replay'); + + +/** + * @constructor + * @extends {ol.render.canvas.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. + * @struct + */ +ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution, overlaps) { + + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); + + /** + * @private + * @type {ol.Extent} + */ + this.bufferedMaxExtent_ = null; + + /** + * @private + * @type {{currentStrokeStyle: (ol.ColorLike|undefined), + * currentLineCap: (string|undefined), + * currentLineDash: Array.<number>, + * currentLineJoin: (string|undefined), + * currentLineWidth: (number|undefined), + * currentMiterLimit: (number|undefined), + * lastStroke: number, + * strokeStyle: (ol.ColorLike|undefined), + * lineCap: (string|undefined), + * lineDash: Array.<number>, + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined)}|null} + */ + this.state_ = { + currentStrokeStyle: undefined, + currentLineCap: undefined, + currentLineDash: null, + currentLineJoin: undefined, + currentLineWidth: undefined, + currentMiterLimit: undefined, + lastStroke: 0, + strokeStyle: undefined, + lineCap: undefined, + lineDash: null, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined + }; + +}; +ol.inherits(ol.render.canvas.LineStringReplay, ol.render.canvas.Replay); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @private + * @return {number} end. + */ +ol.render.canvas.LineStringReplay.prototype.drawFlatCoordinates_ = function(flatCoordinates, offset, end, stride) { + var myBegin = this.coordinates.length; + var myEnd = this.appendFlatCoordinates( + flatCoordinates, offset, end, stride, false, false); + var moveToLineToInstruction = + [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd]; + this.instructions.push(moveToLineToInstruction); + this.hitDetectionInstructions.push(moveToLineToInstruction); + return end; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.LineStringReplay.prototype.getBufferedMaxExtent = function() { + if (!this.bufferedMaxExtent_) { + this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent); + if (this.maxLineWidth > 0) { + var width = this.resolution * (this.maxLineWidth + 1) / 2; + ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_); + } + } + return this.bufferedMaxExtent_; +}; + + +/** + * @private + */ +ol.render.canvas.LineStringReplay.prototype.setStrokeStyle_ = function() { + var state = this.state_; + var strokeStyle = state.strokeStyle; + var lineCap = state.lineCap; + var lineDash = state.lineDash; + var lineJoin = state.lineJoin; + var lineWidth = state.lineWidth; + var miterLimit = state.miterLimit; + ol.DEBUG && console.assert(strokeStyle !== undefined, + 'strokeStyle should be defined'); + ol.DEBUG && console.assert(lineCap !== undefined, 'lineCap should be defined'); + ol.DEBUG && console.assert(lineDash, 'lineDash should not be null'); + ol.DEBUG && console.assert(lineJoin !== undefined, 'lineJoin should be defined'); + ol.DEBUG && console.assert(lineWidth !== undefined, 'lineWidth should be defined'); + ol.DEBUG && console.assert(miterLimit !== undefined, 'miterLimit should be defined'); + if (state.currentStrokeStyle != strokeStyle || + state.currentLineCap != lineCap || + !ol.array.equals(state.currentLineDash, lineDash) || + state.currentLineJoin != lineJoin || + state.currentLineWidth != lineWidth || + state.currentMiterLimit != miterLimit) { + if (state.lastStroke != this.coordinates.length) { + this.instructions.push([ol.render.canvas.Instruction.STROKE]); + state.lastStroke = this.coordinates.length; + } + this.instructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash, true, 1 + ], [ + ol.render.canvas.Instruction.BEGIN_PATH + ]); + state.currentStrokeStyle = strokeStyle; + state.currentLineCap = lineCap; + state.currentLineDash = lineDash; + state.currentLineJoin = lineJoin; + state.currentLineWidth = lineWidth; + state.currentMiterLimit = miterLimit; + } +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) { + var state = this.state_; + ol.DEBUG && console.assert(state, 'state should not be null'); + var strokeStyle = state.strokeStyle; + var lineWidth = state.lineWidth; + if (strokeStyle === undefined || lineWidth === undefined) { + return; + } + this.setStrokeStyle_(); + this.beginGeometry(lineStringGeometry, feature); + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash, true, 1 + ], [ + ol.render.canvas.Instruction.BEGIN_PATH + ]); + var flatCoordinates = lineStringGeometry.getFlatCoordinates(); + var stride = lineStringGeometry.getStride(); + this.drawFlatCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); + this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]); + this.endGeometry(lineStringGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) { + var state = this.state_; + ol.DEBUG && console.assert(state, 'state should not be null'); + var strokeStyle = state.strokeStyle; + var lineWidth = state.lineWidth; + if (strokeStyle === undefined || lineWidth === undefined) { + return; + } + this.setStrokeStyle_(); + this.beginGeometry(multiLineStringGeometry, feature); + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash, true, 1 + ], [ + ol.render.canvas.Instruction.BEGIN_PATH + ]); + var ends = multiLineStringGeometry.getEnds(); + var flatCoordinates = multiLineStringGeometry.getFlatCoordinates(); + var stride = multiLineStringGeometry.getStride(); + var offset = 0; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + offset = this.drawFlatCoordinates_( + flatCoordinates, offset, ends[i], stride); + } + this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]); + this.endGeometry(multiLineStringGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.LineStringReplay.prototype.finish = function() { + var state = this.state_; + ol.DEBUG && console.assert(state, 'state should not be null'); + if (state.lastStroke != this.coordinates.length) { + this.instructions.push([ol.render.canvas.Instruction.STROKE]); + } + this.reverseHitDetectionInstructions(); + this.state_ = null; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + ol.DEBUG && console.assert(!fillStyle, 'fillStyle should be null'); + ol.DEBUG && console.assert(strokeStyle, 'strokeStyle should not be null'); + var strokeStyleColor = strokeStyle.getColor(); + this.state_.strokeStyle = ol.colorlike.asColorLike(strokeStyleColor ? + strokeStyleColor : ol.render.canvas.defaultStrokeStyle); + var strokeStyleLineCap = strokeStyle.getLineCap(); + this.state_.lineCap = strokeStyleLineCap !== undefined ? + strokeStyleLineCap : ol.render.canvas.defaultLineCap; + var strokeStyleLineDash = strokeStyle.getLineDash(); + this.state_.lineDash = strokeStyleLineDash ? + strokeStyleLineDash : ol.render.canvas.defaultLineDash; + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + this.state_.lineJoin = strokeStyleLineJoin !== undefined ? + strokeStyleLineJoin : ol.render.canvas.defaultLineJoin; + var strokeStyleWidth = strokeStyle.getWidth(); + this.state_.lineWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.canvas.defaultLineWidth; + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + this.state_.miterLimit = strokeStyleMiterLimit !== undefined ? + strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; + + if (this.state_.lineWidth > this.maxLineWidth) { + this.maxLineWidth = this.state_.lineWidth; + // invalidate the buffered max extent cache + this.bufferedMaxExtent_ = null; + } +}; + +goog.provide('ol.render.canvas.PolygonReplay'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.color'); +goog.require('ol.colorlike'); +goog.require('ol.extent'); +goog.require('ol.geom.flat.simplify'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.Instruction'); +goog.require('ol.render.canvas.Replay'); + + +/** + * @constructor + * @extends {ol.render.canvas.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. + * @struct + */ +ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution, overlaps) { + + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); + + /** + * @private + * @type {ol.Extent} + */ + this.bufferedMaxExtent_ = null; + + /** + * @private + * @type {{currentFillStyle: (ol.ColorLike|undefined), + * currentStrokeStyle: (ol.ColorLike|undefined), + * currentLineCap: (string|undefined), + * currentLineDash: Array.<number>, + * currentLineJoin: (string|undefined), + * currentLineWidth: (number|undefined), + * currentMiterLimit: (number|undefined), + * fillStyle: (ol.ColorLike|undefined), + * strokeStyle: (ol.ColorLike|undefined), + * lineCap: (string|undefined), + * lineDash: Array.<number>, + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined)}|null} + */ + this.state_ = { + currentFillStyle: undefined, + currentStrokeStyle: undefined, + currentLineCap: undefined, + currentLineDash: null, + currentLineJoin: undefined, + currentLineWidth: undefined, + currentMiterLimit: undefined, + fillStyle: undefined, + strokeStyle: undefined, + lineCap: undefined, + lineDash: null, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined + }; + +}; +ol.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @private + * @return {number} End. + */ +ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ = function(flatCoordinates, offset, ends, stride) { + var state = this.state_; + var fill = state.fillStyle !== undefined; + var stroke = state.strokeStyle != undefined; + var numEnds = ends.length; + var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH]; + this.instructions.push(beginPathInstruction); + this.hitDetectionInstructions.push(beginPathInstruction); + for (var i = 0; i < numEnds; ++i) { + var end = ends[i]; + var myBegin = this.coordinates.length; + var myEnd = this.appendFlatCoordinates( + flatCoordinates, offset, end, stride, true, !stroke); + var moveToLineToInstruction = + [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd]; + this.instructions.push(moveToLineToInstruction); + this.hitDetectionInstructions.push(moveToLineToInstruction); + if (stroke) { + // Performance optimization: only call closePath() when we have a stroke. + // Otherwise the ring is closed already (see appendFlatCoordinates above). + var closePathInstruction = [ol.render.canvas.Instruction.CLOSE_PATH]; + this.instructions.push(closePathInstruction); + this.hitDetectionInstructions.push(closePathInstruction); + } + offset = end; + } + var fillInstruction = [ol.render.canvas.Instruction.FILL]; + this.hitDetectionInstructions.push(fillInstruction); + if (fill) { + this.instructions.push(fillInstruction); + } + if (stroke) { + ol.DEBUG && console.assert(state.lineWidth !== undefined, + 'state.lineWidth should be defined'); + var strokeInstruction = [ol.render.canvas.Instruction.STROKE]; + this.instructions.push(strokeInstruction); + this.hitDetectionInstructions.push(strokeInstruction); + } + return offset; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.drawCircle = function(circleGeometry, feature) { + var state = this.state_; + ol.DEBUG && console.assert(state, 'state should not be null'); + var fillStyle = state.fillStyle; + var strokeStyle = state.strokeStyle; + if (fillStyle === undefined && strokeStyle === undefined) { + return; + } + if (strokeStyle !== undefined) { + ol.DEBUG && console.assert(state.lineWidth !== undefined, + 'state.lineWidth should be defined'); + } + this.setFillStrokeStyles_(circleGeometry); + this.beginGeometry(circleGeometry, feature); + // always fill the circle for hit detection + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_FILL_STYLE, + ol.color.asString(ol.render.canvas.defaultFillStyle) + ]); + if (state.strokeStyle !== undefined) { + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash, true, 1 + ]); + } + var flatCoordinates = circleGeometry.getFlatCoordinates(); + var stride = circleGeometry.getStride(); + var myBegin = this.coordinates.length; + this.appendFlatCoordinates( + flatCoordinates, 0, flatCoordinates.length, stride, false, false); + var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH]; + var circleInstruction = [ol.render.canvas.Instruction.CIRCLE, myBegin]; + this.instructions.push(beginPathInstruction, circleInstruction); + this.hitDetectionInstructions.push(beginPathInstruction, circleInstruction); + var fillInstruction = [ol.render.canvas.Instruction.FILL]; + this.hitDetectionInstructions.push(fillInstruction); + if (state.fillStyle !== undefined) { + this.instructions.push(fillInstruction); + } + if (state.strokeStyle !== undefined) { + ol.DEBUG && console.assert(state.lineWidth !== undefined, + 'state.lineWidth should be defined'); + var strokeInstruction = [ol.render.canvas.Instruction.STROKE]; + this.instructions.push(strokeInstruction); + this.hitDetectionInstructions.push(strokeInstruction); + } + this.endGeometry(circleGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { + var state = this.state_; + ol.DEBUG && console.assert(state, 'state should not be null'); + var strokeStyle = state.strokeStyle; + ol.DEBUG && console.assert(state.fillStyle !== undefined || strokeStyle !== undefined, + 'fillStyle or strokeStyle should be defined'); + if (strokeStyle !== undefined) { + ol.DEBUG && console.assert(state.lineWidth !== undefined, + 'state.lineWidth should be defined'); + } + this.setFillStrokeStyles_(polygonGeometry); + this.beginGeometry(polygonGeometry, feature); + // always fill the polygon for hit detection + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_FILL_STYLE, + ol.color.asString(ol.render.canvas.defaultFillStyle)] + ); + if (state.strokeStyle !== undefined) { + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash, true, 1 + ]); + } + var ends = polygonGeometry.getEnds(); + var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates(); + var stride = polygonGeometry.getStride(); + this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride); + this.endGeometry(polygonGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) { + var state = this.state_; + ol.DEBUG && console.assert(state, 'state should not be null'); + var fillStyle = state.fillStyle; + var strokeStyle = state.strokeStyle; + if (fillStyle === undefined && strokeStyle === undefined) { + return; + } + if (strokeStyle !== undefined) { + ol.DEBUG && console.assert(state.lineWidth !== undefined, + 'state.lineWidth should be defined'); + } + this.setFillStrokeStyles_(multiPolygonGeometry); + this.beginGeometry(multiPolygonGeometry, feature); + // always fill the multi-polygon for hit detection + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_FILL_STYLE, + ol.color.asString(ol.render.canvas.defaultFillStyle) + ]); + if (state.strokeStyle !== undefined) { + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash, true, 1 + ]); + } + var endss = multiPolygonGeometry.getEndss(); + var flatCoordinates = multiPolygonGeometry.getOrientedFlatCoordinates(); + var stride = multiPolygonGeometry.getStride(); + var offset = 0; + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + offset = this.drawFlatCoordinatess_( + flatCoordinates, offset, endss[i], stride); + } + this.endGeometry(multiPolygonGeometry, feature); +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.finish = function() { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + this.reverseHitDetectionInstructions(); + this.state_ = null; + // We want to preserve topology when drawing polygons. Polygons are + // simplified using quantization and point elimination. However, we might + // have received a mix of quantized and non-quantized geometries, so ensure + // that all are quantized by quantizing all coordinates in the batch. + var tolerance = this.tolerance; + if (tolerance !== 0) { + var coordinates = this.coordinates; + var i, ii; + for (i = 0, ii = coordinates.length; i < ii; ++i) { + coordinates[i] = ol.geom.flat.simplify.snap(coordinates[i], tolerance); + } + } +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.getBufferedMaxExtent = function() { + if (!this.bufferedMaxExtent_) { + this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent); + if (this.maxLineWidth > 0) { + var width = this.resolution * (this.maxLineWidth + 1) / 2; + ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_); + } + } + return this.bufferedMaxExtent_; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + ol.DEBUG && console.assert(fillStyle || strokeStyle, + 'fillStyle or strokeStyle should not be null'); + var state = this.state_; + if (fillStyle) { + var fillStyleColor = fillStyle.getColor(); + state.fillStyle = ol.colorlike.asColorLike(fillStyleColor ? + fillStyleColor : ol.render.canvas.defaultFillStyle); + } else { + state.fillStyle = undefined; + } + if (strokeStyle) { + var strokeStyleColor = strokeStyle.getColor(); + state.strokeStyle = ol.colorlike.asColorLike(strokeStyleColor ? + strokeStyleColor : ol.render.canvas.defaultStrokeStyle); + var strokeStyleLineCap = strokeStyle.getLineCap(); + state.lineCap = strokeStyleLineCap !== undefined ? + strokeStyleLineCap : ol.render.canvas.defaultLineCap; + var strokeStyleLineDash = strokeStyle.getLineDash(); + state.lineDash = strokeStyleLineDash ? + strokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash; + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + state.lineJoin = strokeStyleLineJoin !== undefined ? + strokeStyleLineJoin : ol.render.canvas.defaultLineJoin; + var strokeStyleWidth = strokeStyle.getWidth(); + state.lineWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.canvas.defaultLineWidth; + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + state.miterLimit = strokeStyleMiterLimit !== undefined ? + strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; + + if (state.lineWidth > this.maxLineWidth) { + this.maxLineWidth = state.lineWidth; + // invalidate the buffered max extent cache + this.bufferedMaxExtent_ = null; + } + } else { + state.strokeStyle = undefined; + state.lineCap = undefined; + state.lineDash = null; + state.lineJoin = undefined; + state.lineWidth = undefined; + state.miterLimit = undefined; + } +}; + + +/** + * @private + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + */ +ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function(geometry) { + var state = this.state_; + var fillStyle = state.fillStyle; + var strokeStyle = state.strokeStyle; + var lineCap = state.lineCap; + var lineDash = state.lineDash; + var lineJoin = state.lineJoin; + var lineWidth = state.lineWidth; + var miterLimit = state.miterLimit; + if (fillStyle !== undefined && (typeof fillStyle !== 'string' || state.currentFillStyle != fillStyle)) { + var fillInstruction = [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle]; + if (typeof fillStyle !== 'string') { + var fillExtent = geometry.getExtent(); + fillInstruction.push([fillExtent[0], fillExtent[3]]); + } + this.instructions.push(fillInstruction); + state.currentFillStyle = state.fillStyle; + } + if (strokeStyle !== undefined) { + ol.DEBUG && console.assert(lineCap !== undefined, 'lineCap should be defined'); + ol.DEBUG && console.assert(lineDash, 'lineDash should not be null'); + ol.DEBUG && console.assert(lineJoin !== undefined, 'lineJoin should be defined'); + ol.DEBUG && console.assert(lineWidth !== undefined, 'lineWidth should be defined'); + ol.DEBUG && console.assert(miterLimit !== undefined, + 'miterLimit should be defined'); + if (state.currentStrokeStyle != strokeStyle || + state.currentLineCap != lineCap || + !ol.array.equals(state.currentLineDash, lineDash) || + state.currentLineJoin != lineJoin || + state.currentLineWidth != lineWidth || + state.currentMiterLimit != miterLimit) { + this.instructions.push([ + ol.render.canvas.Instruction.SET_STROKE_STYLE, + strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash, true, 1 + ]); + state.currentStrokeStyle = strokeStyle; + state.currentLineCap = lineCap; + state.currentLineDash = lineDash; + state.currentLineJoin = lineJoin; + state.currentLineWidth = lineWidth; + state.currentMiterLimit = miterLimit; + } + } +}; + +goog.provide('ol.render.canvas.TextReplay'); + +goog.require('ol'); +goog.require('ol.colorlike'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.Instruction'); +goog.require('ol.render.canvas.Replay'); + + +/** + * @constructor + * @extends {ol.render.canvas.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. + * @struct + */ +ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution, overlaps) { + + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); + + /** + * @private + * @type {?ol.CanvasFillState} + */ + this.replayFillState_ = null; + + /** + * @private + * @type {?ol.CanvasStrokeState} + */ + this.replayStrokeState_ = null; + + /** + * @private + * @type {?ol.CanvasTextState} + */ + this.replayTextState_ = null; + + /** + * @private + * @type {string} + */ + this.text_ = ''; + + /** + * @private + * @type {number} + */ + this.textOffsetX_ = 0; + + /** + * @private + * @type {number} + */ + this.textOffsetY_ = 0; + + /** + * @private + * @type {boolean|undefined} + */ + this.textRotateWithView_ = undefined; + + /** + * @private + * @type {number} + */ + this.textRotation_ = 0; + + /** + * @private + * @type {number} + */ + this.textScale_ = 0; + + /** + * @private + * @type {?ol.CanvasFillState} + */ + this.textFillState_ = null; + + /** + * @private + * @type {?ol.CanvasStrokeState} + */ + this.textStrokeState_ = null; + + /** + * @private + * @type {?ol.CanvasTextState} + */ + this.textState_ = null; + +}; +ol.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay); + + +/** + * @inheritDoc + */ +ol.render.canvas.TextReplay.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) { + if (this.text_ === '' || !this.textState_ || + (!this.textFillState_ && !this.textStrokeState_)) { + return; + } + if (this.textFillState_) { + this.setReplayFillState_(this.textFillState_); + } + if (this.textStrokeState_) { + this.setReplayStrokeState_(this.textStrokeState_); + } + this.setReplayTextState_(this.textState_); + this.beginGeometry(geometry, feature); + var myBegin = this.coordinates.length; + var myEnd = + this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false, false); + var fill = !!this.textFillState_; + var stroke = !!this.textStrokeState_; + var drawTextInstruction = [ + ol.render.canvas.Instruction.DRAW_TEXT, myBegin, myEnd, this.text_, + this.textOffsetX_, this.textOffsetY_, this.textRotation_, this.textScale_, + fill, stroke, this.textRotateWithView_]; + this.instructions.push(drawTextInstruction); + this.hitDetectionInstructions.push(drawTextInstruction); + this.endGeometry(geometry, feature); +}; + + +/** + * @param {ol.CanvasFillState} fillState Fill state. + * @private + */ +ol.render.canvas.TextReplay.prototype.setReplayFillState_ = function(fillState) { + var replayFillState = this.replayFillState_; + if (replayFillState && + replayFillState.fillStyle == fillState.fillStyle) { + return; + } + var setFillStyleInstruction = + [ol.render.canvas.Instruction.SET_FILL_STYLE, fillState.fillStyle]; + this.instructions.push(setFillStyleInstruction); + this.hitDetectionInstructions.push(setFillStyleInstruction); + if (!replayFillState) { + this.replayFillState_ = { + fillStyle: fillState.fillStyle + }; + } else { + replayFillState.fillStyle = fillState.fillStyle; + } +}; + + +/** + * @param {ol.CanvasStrokeState} strokeState Stroke state. + * @private + */ +ol.render.canvas.TextReplay.prototype.setReplayStrokeState_ = function(strokeState) { + var replayStrokeState = this.replayStrokeState_; + if (replayStrokeState && + replayStrokeState.lineCap == strokeState.lineCap && + replayStrokeState.lineDash == strokeState.lineDash && + replayStrokeState.lineJoin == strokeState.lineJoin && + replayStrokeState.lineWidth == strokeState.lineWidth && + replayStrokeState.miterLimit == strokeState.miterLimit && + replayStrokeState.strokeStyle == strokeState.strokeStyle) { + return; + } + var setStrokeStyleInstruction = [ + ol.render.canvas.Instruction.SET_STROKE_STYLE, strokeState.strokeStyle, + strokeState.lineWidth, strokeState.lineCap, strokeState.lineJoin, + strokeState.miterLimit, strokeState.lineDash, false, 1 + ]; + this.instructions.push(setStrokeStyleInstruction); + this.hitDetectionInstructions.push(setStrokeStyleInstruction); + if (!replayStrokeState) { + this.replayStrokeState_ = { + lineCap: strokeState.lineCap, + lineDash: strokeState.lineDash, + lineJoin: strokeState.lineJoin, + lineWidth: strokeState.lineWidth, + miterLimit: strokeState.miterLimit, + strokeStyle: strokeState.strokeStyle + }; + } else { + replayStrokeState.lineCap = strokeState.lineCap; + replayStrokeState.lineDash = strokeState.lineDash; + replayStrokeState.lineJoin = strokeState.lineJoin; + replayStrokeState.lineWidth = strokeState.lineWidth; + replayStrokeState.miterLimit = strokeState.miterLimit; + replayStrokeState.strokeStyle = strokeState.strokeStyle; + } +}; + + +/** + * @param {ol.CanvasTextState} textState Text state. + * @private + */ +ol.render.canvas.TextReplay.prototype.setReplayTextState_ = function(textState) { + var replayTextState = this.replayTextState_; + if (replayTextState && + replayTextState.font == textState.font && + replayTextState.textAlign == textState.textAlign && + replayTextState.textBaseline == textState.textBaseline) { + return; + } + var setTextStyleInstruction = [ol.render.canvas.Instruction.SET_TEXT_STYLE, + textState.font, textState.textAlign, textState.textBaseline]; + this.instructions.push(setTextStyleInstruction); + this.hitDetectionInstructions.push(setTextStyleInstruction); + if (!replayTextState) { + this.replayTextState_ = { + font: textState.font, + textAlign: textState.textAlign, + textBaseline: textState.textBaseline + }; + } else { + replayTextState.font = textState.font; + replayTextState.textAlign = textState.textAlign; + replayTextState.textBaseline = textState.textBaseline; + } +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) { + if (!textStyle) { + this.text_ = ''; + } else { + var textFillStyle = textStyle.getFill(); + if (!textFillStyle) { + this.textFillState_ = null; + } else { + var textFillStyleColor = textFillStyle.getColor(); + var fillStyle = ol.colorlike.asColorLike(textFillStyleColor ? + textFillStyleColor : ol.render.canvas.defaultFillStyle); + if (!this.textFillState_) { + this.textFillState_ = { + fillStyle: fillStyle + }; + } else { + var textFillState = this.textFillState_; + textFillState.fillStyle = fillStyle; + } + } + var textStrokeStyle = textStyle.getStroke(); + if (!textStrokeStyle) { + this.textStrokeState_ = null; + } else { + var textStrokeStyleColor = textStrokeStyle.getColor(); + var textStrokeStyleLineCap = textStrokeStyle.getLineCap(); + var textStrokeStyleLineDash = textStrokeStyle.getLineDash(); + var textStrokeStyleLineJoin = textStrokeStyle.getLineJoin(); + var textStrokeStyleWidth = textStrokeStyle.getWidth(); + var textStrokeStyleMiterLimit = textStrokeStyle.getMiterLimit(); + var lineCap = textStrokeStyleLineCap !== undefined ? + textStrokeStyleLineCap : ol.render.canvas.defaultLineCap; + var lineDash = textStrokeStyleLineDash ? + textStrokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash; + var lineJoin = textStrokeStyleLineJoin !== undefined ? + textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin; + var lineWidth = textStrokeStyleWidth !== undefined ? + textStrokeStyleWidth : ol.render.canvas.defaultLineWidth; + var miterLimit = textStrokeStyleMiterLimit !== undefined ? + textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; + var strokeStyle = ol.colorlike.asColorLike(textStrokeStyleColor ? + textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle); + if (!this.textStrokeState_) { + this.textStrokeState_ = { + lineCap: lineCap, + lineDash: lineDash, + lineJoin: lineJoin, + lineWidth: lineWidth, + miterLimit: miterLimit, + strokeStyle: strokeStyle + }; + } else { + var textStrokeState = this.textStrokeState_; + textStrokeState.lineCap = lineCap; + textStrokeState.lineDash = lineDash; + textStrokeState.lineJoin = lineJoin; + textStrokeState.lineWidth = lineWidth; + textStrokeState.miterLimit = miterLimit; + textStrokeState.strokeStyle = strokeStyle; + } + } + var textFont = textStyle.getFont(); + var textOffsetX = textStyle.getOffsetX(); + var textOffsetY = textStyle.getOffsetY(); + var textRotateWithView = textStyle.getRotateWithView(); + var textRotation = textStyle.getRotation(); + var textScale = textStyle.getScale(); + var textText = textStyle.getText(); + var textTextAlign = textStyle.getTextAlign(); + var textTextBaseline = textStyle.getTextBaseline(); + var font = textFont !== undefined ? + textFont : ol.render.canvas.defaultFont; + var textAlign = textTextAlign !== undefined ? + textTextAlign : ol.render.canvas.defaultTextAlign; + var textBaseline = textTextBaseline !== undefined ? + textTextBaseline : ol.render.canvas.defaultTextBaseline; + if (!this.textState_) { + this.textState_ = { + font: font, + textAlign: textAlign, + textBaseline: textBaseline + }; + } else { + var textState = this.textState_; + textState.font = font; + textState.textAlign = textAlign; + textState.textBaseline = textBaseline; + } + this.text_ = textText !== undefined ? textText : ''; + this.textOffsetX_ = textOffsetX !== undefined ? textOffsetX : 0; + this.textOffsetY_ = textOffsetY !== undefined ? textOffsetY : 0; + this.textRotateWithView_ = textRotateWithView !== undefined ? textRotateWithView : false; + this.textRotation_ = textRotation !== undefined ? textRotation : 0; + this.textScale_ = textScale !== undefined ? textScale : 1; + } +}; + +goog.provide('ol.render.ReplayType'); + + +/** + * @enum {string} + */ +ol.render.ReplayType = { + CIRCLE: 'Circle', + IMAGE: 'Image', + LINE_STRING: 'LineString', + POLYGON: 'Polygon', + TEXT: 'Text' +}; + +goog.provide('ol.render.replay'); + +goog.require('ol.render.ReplayType'); + + +/** + * @const + * @type {Array.<ol.render.ReplayType>} + */ +ol.render.replay.ORDER = [ + ol.render.ReplayType.POLYGON, + ol.render.ReplayType.CIRCLE, + ol.render.ReplayType.LINE_STRING, + ol.render.ReplayType.IMAGE, + ol.render.ReplayType.TEXT +]; + +goog.provide('ol.render.canvas.ReplayGroup'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.obj'); +goog.require('ol.render.ReplayGroup'); +goog.require('ol.render.canvas.ImageReplay'); +goog.require('ol.render.canvas.LineStringReplay'); +goog.require('ol.render.canvas.PolygonReplay'); +goog.require('ol.render.canvas.TextReplay'); +goog.require('ol.render.replay'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.render.ReplayGroup} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay group can have overlapping geometries. + * @param {number=} opt_renderBuffer Optional rendering buffer. + * @struct + */ +ol.render.canvas.ReplayGroup = function( + tolerance, maxExtent, resolution, overlaps, opt_renderBuffer) { + ol.render.ReplayGroup.call(this); + + /** + * @private + * @type {number} + */ + this.tolerance_ = tolerance; + + /** + * @private + * @type {ol.Extent} + */ + this.maxExtent_ = maxExtent; + + /** + * @private + * @type {boolean} + */ + this.overlaps_ = overlaps; + + /** + * @private + * @type {number} + */ + this.resolution_ = resolution; + + /** + * @private + * @type {number|undefined} + */ + this.renderBuffer_ = opt_renderBuffer; + + /** + * @private + * @type {!Object.<string, + * Object.<ol.render.ReplayType, ol.render.canvas.Replay>>} + */ + this.replaysByZIndex_ = {}; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitDetectionContext_ = ol.dom.createCanvasContext2D(1, 1); + + /** + * @private + * @type {ol.Transform} + */ + this.hitDetectionTransform_ = ol.transform.create(); +}; +ol.inherits(ol.render.canvas.ReplayGroup, ol.render.ReplayGroup); + + +/** + * This cache is used for storing calculated pixel circles for increasing performance. + * It is a static property to allow each Replaygroup to access it. + * @type {Object.<number, Array.<Array.<(boolean|undefined)>>>} + * @private + */ +ol.render.canvas.ReplayGroup.circleArrayCache_ = { + 0: [[true]] +}; + + +/** + * This method fills a row in the array from the given coordinate to the + * middle with `true`. + * @param {Array.<Array.<(boolean|undefined)>>} array The array that will be altered. + * @param {number} x X coordinate. + * @param {number} y Y coordinate. + * @private + */ +ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_ = function(array, x, y) { + var i; + var radius = Math.floor(array.length / 2); + if (x >= radius) { + for (i = radius; i < x; i++) { + array[i][y] = true; + } + } else if (x < radius) { + for (i = x + 1; i < radius; i++) { + array[i][y] = true; + } + } +}; + + +/** + * This methods creates a circle inside a fitting array. Points inside the + * circle are marked by true, points on the outside are undefined. + * It uses the midpoint circle algorithm. + * A cache is used to increase performance. + * @param {number} radius Radius. + * @returns {Array.<Array.<(boolean|undefined)>>} An array with marked circle points. + * @private + */ +ol.render.canvas.ReplayGroup.getCircleArray_ = function(radius) { + if (ol.render.canvas.ReplayGroup.circleArrayCache_[radius] !== undefined) { + return ol.render.canvas.ReplayGroup.circleArrayCache_[radius]; + } + + var arraySize = radius * 2 + 1; + var arr = new Array(arraySize); + for (var i = 0; i < arraySize; i++) { + arr[i] = new Array(arraySize); + } + + var x = radius; + var y = 0; + var error = 0; + + while (x >= y) { + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + x, radius + y); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + y, radius + x); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - y, radius + x); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - x, radius + y); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - x, radius - y); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - y, radius - x); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + y, radius - x); + ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + x, radius - y); + + y++; + error += 1 + 2 * y; + if (2 * (error - x) + 1 > 0) { + x -= 1; + error += 1 - 2 * x; + } + } + + ol.render.canvas.ReplayGroup.circleArrayCache_[radius] = arr; + return arr; +}; + +/** + * FIXME empty description for jsdoc + */ +ol.render.canvas.ReplayGroup.prototype.finish = function() { + var zKey; + for (zKey in this.replaysByZIndex_) { + var replays = this.replaysByZIndex_[zKey]; + var replayKey; + for (replayKey in replays) { + replays[replayKey].finish(); + } + } +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {number} hitTolerance Hit tolerance in pixels. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature + * callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( + coordinate, resolution, rotation, hitTolerance, skippedFeaturesHash, callback) { + + hitTolerance = Math.round(hitTolerance); + var contextSize = hitTolerance * 2 + 1; + var transform = ol.transform.compose(this.hitDetectionTransform_, + hitTolerance + 0.5, hitTolerance + 0.5, + 1 / resolution, -1 / resolution, + -rotation, + -coordinate[0], -coordinate[1]); + var context = this.hitDetectionContext_; + + if (context.canvas.width !== contextSize || context.canvas.height !== contextSize) { + context.canvas.width = contextSize; + context.canvas.height = contextSize; + } else { + context.clearRect(0, 0, contextSize, contextSize); + } + + /** + * @type {ol.Extent} + */ + var hitExtent; + if (this.renderBuffer_ !== undefined) { + hitExtent = ol.extent.createEmpty(); + ol.extent.extendCoordinate(hitExtent, coordinate); + ol.extent.buffer(hitExtent, resolution * (this.renderBuffer_ + hitTolerance), hitExtent); + } + + var mask = ol.render.canvas.ReplayGroup.getCircleArray_(hitTolerance); + + return this.replayHitDetection_(context, transform, rotation, + skippedFeaturesHash, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var imageData = context.getImageData(0, 0, contextSize, contextSize).data; + for (var i = 0; i < contextSize; i++) { + for (var j = 0; j < contextSize; j++) { + if (mask[i][j]) { + if (imageData[(j * contextSize + i) * 4 + 3] > 0) { + var result = callback(feature); + if (result) { + return result; + } else { + context.clearRect(0, 0, contextSize, contextSize); + return undefined; + } + } + } + } + } + }, hitExtent); +}; + + +/** + * @param {ol.Transform} transform Transform. + * @return {Array.<number>} Clip coordinates. + */ +ol.render.canvas.ReplayGroup.prototype.getClipCoords = function(transform) { + var maxExtent = this.maxExtent_; + var minX = maxExtent[0]; + var minY = maxExtent[1]; + var maxX = maxExtent[2]; + var maxY = maxExtent[3]; + var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY]; + ol.geom.flat.transform.transform2D( + flatClipCoords, 0, 8, 2, transform, flatClipCoords); + return flatClipCoords; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { + var zIndexKey = zIndex !== undefined ? zIndex.toString() : '0'; + var replays = this.replaysByZIndex_[zIndexKey]; + if (replays === undefined) { + replays = {}; + this.replaysByZIndex_[zIndexKey] = replays; + } + var replay = replays[replayType]; + if (replay === undefined) { + var Constructor = ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_[replayType]; + ol.DEBUG && console.assert(Constructor !== undefined, + replayType + + ' constructor missing from ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_'); + replay = new Constructor(this.tolerance_, this.maxExtent_, + this.resolution_, this.overlaps_); + replays[replayType] = replay; + } + return replay; +}; + + +/** + * @inheritDoc + */ +ol.render.canvas.ReplayGroup.prototype.isEmpty = function() { + return ol.obj.isEmpty(this.replaysByZIndex_); +}; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {Array.<ol.render.ReplayType>=} opt_replayTypes Ordered replay types + * to replay. Default is {@link ol.render.replay.ORDER} + */ +ol.render.canvas.ReplayGroup.prototype.replay = function(context, pixelRatio, + transform, viewRotation, skippedFeaturesHash, opt_replayTypes) { + + /** @type {Array.<number>} */ + var zs = Object.keys(this.replaysByZIndex_).map(Number); + zs.sort(ol.array.numberSafeCompareFunction); + + // setup clipping so that the parts of over-simplified geometries are not + // visible outside the current extent when panning + var flatClipCoords = this.getClipCoords(transform); + context.save(); + context.beginPath(); + context.moveTo(flatClipCoords[0], flatClipCoords[1]); + context.lineTo(flatClipCoords[2], flatClipCoords[3]); + context.lineTo(flatClipCoords[4], flatClipCoords[5]); + context.lineTo(flatClipCoords[6], flatClipCoords[7]); + context.clip(); + + var replayTypes = opt_replayTypes ? opt_replayTypes : ol.render.replay.ORDER; + var i, ii, j, jj, replays, replay; + for (i = 0, ii = zs.length; i < ii; ++i) { + replays = this.replaysByZIndex_[zs[i].toString()]; + for (j = 0, jj = replayTypes.length; j < jj; ++j) { + replay = replays[replayTypes[j]]; + if (replay !== undefined) { + replay.replay(context, pixelRatio, transform, viewRotation, + skippedFeaturesHash); + } + } + } + + context.restore(); +}; + + +/** + * @private + * @param {CanvasRenderingContext2D} context Context. + * @param {ol.Transform} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T} featureCallback + * Feature callback. + * @param {ol.Extent=} opt_hitExtent Only check features that intersect this + * extent. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function( + context, transform, viewRotation, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + /** @type {Array.<number>} */ + var zs = Object.keys(this.replaysByZIndex_).map(Number); + zs.sort(function(a, b) { + return b - a; + }); + + var i, ii, j, replays, replay, result; + for (i = 0, ii = zs.length; i < ii; ++i) { + replays = this.replaysByZIndex_[zs[i].toString()]; + for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) { + replay = replays[ol.render.replay.ORDER[j]]; + if (replay !== undefined) { + result = replay.replayHitDetection(context, transform, viewRotation, + skippedFeaturesHash, featureCallback, opt_hitExtent); + if (result) { + return result; + } + } + } + } + return undefined; +}; + + +/** + * @const + * @private + * @type {Object.<ol.render.ReplayType, + * function(new: ol.render.canvas.Replay, number, ol.Extent, + * number, boolean)>} + */ +ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_ = { + 'Circle': ol.render.canvas.PolygonReplay, + 'Image': ol.render.canvas.ImageReplay, + 'LineString': ol.render.canvas.LineStringReplay, + 'Polygon': ol.render.canvas.PolygonReplay, + 'Text': ol.render.canvas.TextReplay +}; + +goog.provide('ol.renderer.vector'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.render.ReplayType'); + + +/** + * @param {ol.Feature|ol.render.Feature} feature1 Feature 1. + * @param {ol.Feature|ol.render.Feature} feature2 Feature 2. + * @return {number} Order. + */ +ol.renderer.vector.defaultOrder = function(feature1, feature2) { + return ol.getUid(feature1) - ol.getUid(feature2); +}; + + +/** + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @return {number} Squared pixel tolerance. + */ +ol.renderer.vector.getSquaredTolerance = function(resolution, pixelRatio) { + var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio); + return tolerance * tolerance; +}; + + +/** + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @return {number} Pixel tolerance. + */ +ol.renderer.vector.getTolerance = function(resolution, pixelRatio) { + return ol.SIMPLIFY_TOLERANCE * resolution / pixelRatio; +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.Circle} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderCircleGeometry_ = function(replayGroup, geometry, style, feature) { + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + if (fillStyle || strokeStyle) { + var circleReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.CIRCLE); + circleReplay.setFillStrokeStyle(fillStyle, strokeStyle); + circleReplay.drawCircle(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + textReplay.drawText(geometry.getCenter(), 0, 2, 2, geometry, feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.style.Style} style Style. + * @param {number} squaredTolerance Squared tolerance. + * @param {function(this: T, ol.events.Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @return {boolean} `true` if style is loading. + * @template T + */ +ol.renderer.vector.renderFeature = function( + replayGroup, feature, style, squaredTolerance, listener, thisArg) { + var loading = false; + var imageStyle, imageState; + imageStyle = style.getImage(); + if (imageStyle) { + imageState = imageStyle.getImageState(); + if (imageState == ol.Image.State.LOADED || + imageState == ol.Image.State.ERROR) { + imageStyle.unlistenImageChange(listener, thisArg); + } else { + if (imageState == ol.Image.State.IDLE) { + imageStyle.load(); + } + imageState = imageStyle.getImageState(); + ol.DEBUG && console.assert(imageState == ol.Image.State.LOADING, + 'imageState should be LOADING'); + imageStyle.listenImageChange(listener, thisArg); + loading = true; + } + } + ol.renderer.vector.renderFeature_(replayGroup, feature, style, + squaredTolerance); + return loading; +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.style.Style} style Style. + * @param {number} squaredTolerance Squared tolerance. + * @private + */ +ol.renderer.vector.renderFeature_ = function( + replayGroup, feature, style, squaredTolerance) { + var geometry = style.getGeometryFunction()(feature); + if (!geometry) { + return; + } + var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance); + var geometryRenderer = + ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()]; + geometryRenderer(replayGroup, simplifiedGeometry, style, feature); +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.GeometryCollection} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderGeometryCollectionGeometry_ = function(replayGroup, geometry, style, feature) { + var geometries = geometry.getGeometriesArray(); + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + var geometryRenderer = + ol.renderer.vector.GEOMETRY_RENDERERS_[geometries[i].getType()]; + geometryRenderer(replayGroup, geometries[i], style, feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.LineString|ol.render.Feature} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderLineStringGeometry_ = function(replayGroup, geometry, style, feature) { + var strokeStyle = style.getStroke(); + if (strokeStyle) { + var lineStringReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.LINE_STRING); + lineStringReplay.setFillStrokeStyle(null, strokeStyle); + lineStringReplay.drawLineString(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + textReplay.drawText(geometry.getFlatMidpoint(), 0, 2, 2, geometry, feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.MultiLineString|ol.render.Feature} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderMultiLineStringGeometry_ = function(replayGroup, geometry, style, feature) { + var strokeStyle = style.getStroke(); + if (strokeStyle) { + var lineStringReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.LINE_STRING); + lineStringReplay.setFillStrokeStyle(null, strokeStyle); + lineStringReplay.drawMultiLineString(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + var flatMidpointCoordinates = geometry.getFlatMidpoints(); + textReplay.drawText(flatMidpointCoordinates, 0, + flatMidpointCoordinates.length, 2, geometry, feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.MultiPolygon} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderMultiPolygonGeometry_ = function(replayGroup, geometry, style, feature) { + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + if (strokeStyle || fillStyle) { + var polygonReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.POLYGON); + polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); + polygonReplay.drawMultiPolygon(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + var flatInteriorPointCoordinates = geometry.getFlatInteriorPoints(); + textReplay.drawText(flatInteriorPointCoordinates, 0, + flatInteriorPointCoordinates.length, 2, geometry, feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.Point|ol.render.Feature} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderPointGeometry_ = function(replayGroup, geometry, style, feature) { + var imageStyle = style.getImage(); + if (imageStyle) { + if (imageStyle.getImageState() != ol.Image.State.LOADED) { + return; + } + var imageReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.IMAGE); + imageReplay.setImageStyle(imageStyle); + imageReplay.drawPoint(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + textReplay.drawText(geometry.getFlatCoordinates(), 0, 2, 2, geometry, + feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.MultiPoint|ol.render.Feature} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderMultiPointGeometry_ = function(replayGroup, geometry, style, feature) { + var imageStyle = style.getImage(); + if (imageStyle) { + if (imageStyle.getImageState() != ol.Image.State.LOADED) { + return; + } + var imageReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.IMAGE); + imageReplay.setImageStyle(imageStyle); + imageReplay.drawMultiPoint(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + var flatCoordinates = geometry.getFlatCoordinates(); + textReplay.drawText(flatCoordinates, 0, flatCoordinates.length, + geometry.getStride(), geometry, feature); + } +}; + + +/** + * @param {ol.render.ReplayGroup} replayGroup Replay group. + * @param {ol.geom.Polygon|ol.render.Feature} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderPolygonGeometry_ = function(replayGroup, geometry, style, feature) { + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + if (fillStyle || strokeStyle) { + var polygonReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.POLYGON); + polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); + polygonReplay.drawPolygon(geometry, feature); + } + var textStyle = style.getText(); + if (textStyle) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + textReplay.drawText( + geometry.getFlatInteriorPoint(), 0, 2, 2, geometry, feature); + } +}; + + +/** + * @const + * @private + * @type {Object.<ol.geom.GeometryType, + * function(ol.render.ReplayGroup, ol.geom.Geometry, + * ol.style.Style, Object)>} + */ +ol.renderer.vector.GEOMETRY_RENDERERS_ = { + 'Point': ol.renderer.vector.renderPointGeometry_, + 'LineString': ol.renderer.vector.renderLineStringGeometry_, + 'Polygon': ol.renderer.vector.renderPolygonGeometry_, + 'MultiPoint': ol.renderer.vector.renderMultiPointGeometry_, + 'MultiLineString': ol.renderer.vector.renderMultiLineStringGeometry_, + 'MultiPolygon': ol.renderer.vector.renderMultiPolygonGeometry_, + 'GeometryCollection': ol.renderer.vector.renderGeometryCollectionGeometry_, + 'Circle': ol.renderer.vector.renderCircleGeometry_ +}; + +goog.provide('ol.renderer.canvas.VectorLayer'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.render.Event'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.ReplayGroup'); +goog.require('ol.renderer.canvas.Layer'); +goog.require('ol.renderer.vector'); + + +/** + * @constructor + * @extends {ol.renderer.canvas.Layer} + * @param {ol.layer.Vector} vectorLayer Vector layer. + */ +ol.renderer.canvas.VectorLayer = function(vectorLayer) { + + ol.renderer.canvas.Layer.call(this, vectorLayer); + + /** + * @private + * @type {boolean} + */ + this.dirty_ = false; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = -1; + + /** + * @private + * @type {number} + */ + this.renderedResolution_ = NaN; + + /** + * @private + * @type {ol.Extent} + */ + this.renderedExtent_ = ol.extent.createEmpty(); + + /** + * @private + * @type {function(ol.Feature, ol.Feature): number|null} + */ + this.renderedRenderOrder_ = null; + + /** + * @private + * @type {ol.render.canvas.ReplayGroup} + */ + this.replayGroup_ = null; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = ol.dom.createCanvasContext2D(); + +}; +ol.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer); + + +/** + * @inheritDoc + */ +ol.renderer.canvas.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) { + + var extent = frameState.extent; + var pixelRatio = frameState.pixelRatio; + var skippedFeatureUids = layerState.managed ? + frameState.skippedFeatureUids : {}; + var viewState = frameState.viewState; + var projection = viewState.projection; + var rotation = viewState.rotation; + var projectionExtent = projection.getExtent(); + var vectorSource = /** @type {ol.source.Vector} */ (this.getLayer().getSource()); + + var transform = this.getTransform(frameState, 0); + + this.preCompose(context, frameState, transform); + + // clipped rendering if layer extent is set + var clipExtent = layerState.extent; + var clipped = clipExtent !== undefined; + if (clipped) { + this.clip(context, frameState, /** @type {ol.Extent} */ (clipExtent)); + } + var replayGroup = this.replayGroup_; + if (replayGroup && !replayGroup.isEmpty()) { + var layer = this.getLayer(); + var drawOffsetX = 0; + var drawOffsetY = 0; + var replayContext; + if (layer.hasListener(ol.render.Event.Type.RENDER)) { + var drawWidth = context.canvas.width; + var drawHeight = context.canvas.height; + if (rotation) { + var drawSize = Math.round(Math.sqrt(drawWidth * drawWidth + drawHeight * drawHeight)); + drawOffsetX = (drawSize - drawWidth) / 2; + drawOffsetY = (drawSize - drawHeight) / 2; + drawWidth = drawHeight = drawSize; + } + // resize and clear + this.context_.canvas.width = drawWidth; + this.context_.canvas.height = drawHeight; + replayContext = this.context_; + } else { + replayContext = context; + } + // for performance reasons, context.save / context.restore is not used + // to save and restore the transformation matrix and the opacity. + // see http://jsperf.com/context-save-restore-versus-variable + var alpha = replayContext.globalAlpha; + replayContext.globalAlpha = layerState.opacity; + if (replayContext != context) { + replayContext.translate(drawOffsetX, drawOffsetY); + } + + var width = frameState.size[0] * pixelRatio; + var height = frameState.size[1] * pixelRatio; + ol.render.canvas.rotateAtOffset(replayContext, -rotation, + width / 2, height / 2); + replayGroup.replay(replayContext, pixelRatio, transform, rotation, + skippedFeatureUids); + if (vectorSource.getWrapX() && projection.canWrapX() && + !ol.extent.containsExtent(projectionExtent, extent)) { + var startX = extent[0]; + var worldWidth = ol.extent.getWidth(projectionExtent); + var world = 0; + var offsetX; + while (startX < projectionExtent[0]) { + --world; + offsetX = worldWidth * world; + transform = this.getTransform(frameState, offsetX); + replayGroup.replay(replayContext, pixelRatio, transform, rotation, + skippedFeatureUids); + startX += worldWidth; + } + world = 0; + startX = extent[2]; + while (startX > projectionExtent[2]) { + ++world; + offsetX = worldWidth * world; + transform = this.getTransform(frameState, offsetX); + replayGroup.replay(replayContext, pixelRatio, transform, rotation, + skippedFeatureUids); + startX -= worldWidth; + } + // restore original transform for render and compose events + transform = this.getTransform(frameState, 0); + } + ol.render.canvas.rotateAtOffset(replayContext, rotation, + width / 2, height / 2); + + if (replayContext != context) { + this.dispatchRenderEvent(replayContext, frameState, transform); + context.drawImage(replayContext.canvas, -drawOffsetX, -drawOffsetY); + replayContext.translate(-drawOffsetX, -drawOffsetY); + } + replayContext.globalAlpha = alpha; + } + + if (clipped) { + context.restore(); + } + this.postCompose(context, frameState, layerState, transform); + +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { + if (!this.replayGroup_) { + return undefined; + } else { + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var layer = this.getLayer(); + /** @type {Object.<string, boolean>} */ + var features = {}; + return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution, + rotation, hitTolerance, {}, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var key = ol.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); + } +}; + + +/** + * Handle changes in image style state. + * @param {ol.events.Event} event Image style change event. + * @private + */ +ol.renderer.canvas.VectorLayer.prototype.handleStyleImageChange_ = function(event) { + this.renderIfReadyAndVisible(); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.VectorLayer.prototype.prepareFrame = function(frameState, layerState) { + + var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); + var vectorSource = vectorLayer.getSource(); + + this.updateAttributions( + frameState.attributions, vectorSource.getAttributions()); + this.updateLogos(frameState, vectorSource); + + var animating = frameState.viewHints[ol.View.Hint.ANIMATING]; + var interacting = frameState.viewHints[ol.View.Hint.INTERACTING]; + var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating(); + var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting(); + + if (!this.dirty_ && (!updateWhileAnimating && animating) || + (!updateWhileInteracting && interacting)) { + return true; + } + + var frameStateExtent = frameState.extent; + var viewState = frameState.viewState; + var projection = viewState.projection; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var vectorLayerRevision = vectorLayer.getRevision(); + var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer(); + var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); + + if (vectorLayerRenderOrder === undefined) { + vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; + } + + var extent = ol.extent.buffer(frameStateExtent, + vectorLayerRenderBuffer * resolution); + var projectionExtent = viewState.projection.getExtent(); + + if (vectorSource.getWrapX() && viewState.projection.canWrapX() && + !ol.extent.containsExtent(projectionExtent, frameState.extent)) { + // For the replay group, we need an extent that intersects the real world + // (-180° to +180°). To support geometries in a coordinate range from -540° + // to +540°, we add at least 1 world width on each side of the projection + // extent. If the viewport is wider than the world, we need to add half of + // the viewport width to make sure we cover the whole viewport. + var worldWidth = ol.extent.getWidth(projectionExtent); + var buffer = Math.max(ol.extent.getWidth(extent) / 2, worldWidth); + extent[0] = projectionExtent[0] - buffer; + extent[2] = projectionExtent[2] + buffer; + } + + if (!this.dirty_ && + this.renderedResolution_ == resolution && + this.renderedRevision_ == vectorLayerRevision && + this.renderedRenderOrder_ == vectorLayerRenderOrder && + ol.extent.containsExtent(this.renderedExtent_, extent)) { + return true; + } + + this.replayGroup_ = null; + + this.dirty_ = false; + + var replayGroup = + new ol.render.canvas.ReplayGroup( + ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, + resolution, vectorSource.getOverlaps(), vectorLayer.getRenderBuffer()); + vectorSource.loadFeatures(extent, resolution, projection); + /** + * @param {ol.Feature} feature Feature. + * @this {ol.renderer.canvas.VectorLayer} + */ + var renderFeature = function(feature) { + var styles; + var styleFunction = feature.getStyleFunction(); + if (styleFunction) { + styles = styleFunction.call(feature, resolution); + } else { + styleFunction = vectorLayer.getStyleFunction(); + if (styleFunction) { + styles = styleFunction(feature, resolution); + } + } + if (styles) { + var dirty = this.renderFeature( + feature, resolution, pixelRatio, styles, replayGroup); + this.dirty_ = this.dirty_ || dirty; + } + }; + if (vectorLayerRenderOrder) { + /** @type {Array.<ol.Feature>} */ + var features = []; + vectorSource.forEachFeatureInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + features.push(feature); + }, this); + features.sort(vectorLayerRenderOrder); + features.forEach(renderFeature, this); + } else { + vectorSource.forEachFeatureInExtent(extent, renderFeature, this); + } + replayGroup.finish(); + + this.renderedResolution_ = resolution; + this.renderedRevision_ = vectorLayerRevision; + this.renderedRenderOrder_ = vectorLayerRenderOrder; + this.renderedExtent_ = extent; + this.replayGroup_ = replayGroup; + + return true; +}; + + +/** + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of + * styles. + * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + */ +ol.renderer.canvas.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) { + if (!styles) { + return false; + } + var loading = false; + if (Array.isArray(styles)) { + for (var i = 0, ii = styles.length; i < ii; ++i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleStyleImageChange_, this) || loading; + } + } else { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles, + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleStyleImageChange_, this) || loading; + } + return loading; +}; + +goog.provide('ol.renderer.canvas.VectorTileLayer'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.proj'); +goog.require('ol.proj.Units'); +goog.require('ol.layer.VectorTile'); +goog.require('ol.render.ReplayType'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.ReplayGroup'); +goog.require('ol.render.replay'); +goog.require('ol.renderer.canvas.TileLayer'); +goog.require('ol.renderer.vector'); +goog.require('ol.size'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.renderer.canvas.TileLayer} + * @param {ol.layer.VectorTile} layer VectorTile layer. + */ +ol.renderer.canvas.VectorTileLayer = function(layer) { + + ol.renderer.canvas.TileLayer.call(this, layer); + + /** + * @private + * @type {boolean} + */ + this.dirty_ = false; + + /** + * @private + * @type {ol.Transform} + */ + this.tmpTransform_ = ol.transform.create(); + + // Use lower resolution for pure vector rendering. Closest resolution otherwise. + this.zDirection = + layer.getRenderMode() == ol.layer.VectorTile.RenderType.VECTOR ? 1 : 0; + +}; +ol.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer); + + +/** + * @const + * @type {!Object.<string, Array.<ol.render.ReplayType>>} + */ +ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS = { + 'image': ol.render.replay.ORDER, + 'hybrid': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.LINE_STRING] +}; + + +/** + * @const + * @type {!Object.<string, Array.<ol.render.ReplayType>>} + */ +ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS = { + 'hybrid': [ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT], + 'vector': ol.render.replay.ORDER +}; + + +/** + * @param {ol.VectorTile} tile Tile. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(tile, + frameState) { + var layer = this.getLayer(); + var pixelRatio = frameState.pixelRatio; + var projection = frameState.viewState.projection; + var revision = layer.getRevision(); + var renderOrder = layer.getRenderOrder() || null; + + var replayState = tile.getReplayState(); + if (!replayState.dirty && replayState.renderedRevision == revision && + replayState.renderedRenderOrder == renderOrder) { + return; + } + + replayState.replayGroup = null; + replayState.dirty = false; + + var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); + var tileGrid = source.getTileGrid(); + var tileCoord = tile.tileCoord; + var tileProjection = tile.getProjection(); + var resolution = tileGrid.getResolution(tileCoord[0]); + var extent, reproject, tileResolution; + if (tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS) { + var tilePixelRatio = tileResolution = source.getTilePixelRatio(); + var tileSize = ol.size.toSize(tileGrid.getTileSize(tileCoord[0])); + extent = [0, 0, tileSize[0] * tilePixelRatio, tileSize[1] * tilePixelRatio]; + } else { + tileResolution = resolution; + extent = tileGrid.getTileCoordExtent(tileCoord); + if (!ol.proj.equivalent(projection, tileProjection)) { + reproject = true; + tile.setProjection(projection); + } + } + replayState.dirty = false; + var replayGroup = new ol.render.canvas.ReplayGroup(0, extent, + tileResolution, source.getOverlaps(), layer.getRenderBuffer()); + var squaredTolerance = ol.renderer.vector.getSquaredTolerance( + tileResolution, pixelRatio); + + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @this {ol.renderer.canvas.VectorTileLayer} + */ + function renderFeature(feature) { + var styles; + var styleFunction = feature.getStyleFunction(); + if (styleFunction) { + styles = styleFunction.call(/** @type {ol.Feature} */ (feature), resolution); + } else { + styleFunction = layer.getStyleFunction(); + if (styleFunction) { + styles = styleFunction(feature, resolution); + } + } + if (styles) { + if (!Array.isArray(styles)) { + styles = [styles]; + } + var dirty = this.renderFeature(feature, squaredTolerance, styles, + replayGroup); + this.dirty_ = this.dirty_ || dirty; + replayState.dirty = replayState.dirty || dirty; + } + } + + var features = tile.getFeatures(); + if (renderOrder && renderOrder !== replayState.renderedRenderOrder) { + features.sort(renderOrder); + } + var feature; + for (var i = 0, ii = features.length; i < ii; ++i) { + feature = features[i]; + if (reproject) { + feature.getGeometry().transform(tileProjection, projection); + } + renderFeature.call(this, feature); + } + + replayGroup.finish(); + + replayState.renderedRevision = revision; + replayState.renderedRenderOrder = renderOrder; + replayState.replayGroup = replayGroup; + replayState.resolution = NaN; +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.VectorTileLayer.prototype.drawTileImage = function( + tile, frameState, layerState, x, y, w, h, gutter) { + var vectorTile = /** @type {ol.VectorTile} */ (tile); + this.createReplayGroup_(vectorTile, frameState); + var layer = this.getLayer(); + if (layer.getRenderMode() != ol.layer.VectorTile.RenderType.VECTOR) { + this.renderTileImage_(vectorTile, frameState, layerState); + } + ol.renderer.canvas.TileLayer.prototype.drawTileImage.apply(this, arguments); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + hitTolerance = hitTolerance == undefined ? 0 : hitTolerance; + var layer = this.getLayer(); + /** @type {Object.<string, boolean>} */ + var features = {}; + + /** @type {Array.<ol.VectorTile>} */ + var replayables = this.renderedTiles; + + var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); + var tileGrid = source.getTileGrid(); + var found, tileSpaceCoordinate; + var i, ii, origin, replayGroup; + var tile, tileCoord, tileExtent, tilePixelRatio, tileResolution; + for (i = 0, ii = replayables.length; i < ii; ++i) { + tile = replayables[i]; + tileCoord = tile.tileCoord; + tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord, this.tmpExtent); + if (!ol.extent.containsCoordinate(ol.extent.buffer(tileExtent, hitTolerance * resolution), coordinate)) { + continue; + } + if (tile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) { + origin = ol.extent.getTopLeft(tileExtent); + tilePixelRatio = source.getTilePixelRatio(); + tileResolution = tileGrid.getResolution(tileCoord[0]) / tilePixelRatio; + tileSpaceCoordinate = [ + (coordinate[0] - origin[0]) / tileResolution, + (origin[1] - coordinate[1]) / tileResolution + ]; + resolution = tilePixelRatio; + } else { + tileSpaceCoordinate = coordinate; + } + replayGroup = tile.getReplayState().replayGroup; + found = found || replayGroup.forEachFeatureAtCoordinate( + tileSpaceCoordinate, resolution, rotation, hitTolerance, {}, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var key = ol.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); + } + return found; +}; + + +/** + * @param {ol.Tile} tile Tile. + * @param {olx.FrameState} frameState Frame state. + * @return {ol.Transform} transform Transform. + * @private + */ +ol.renderer.canvas.VectorTileLayer.prototype.getReplayTransform_ = function(tile, frameState) { + if (tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) { + var layer = this.getLayer(); + var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); + var tileGrid = source.getTileGrid(); + var tileCoord = tile.tileCoord; + var tileResolution = + tileGrid.getResolution(tileCoord[0]) / source.getTilePixelRatio(); + var viewState = frameState.viewState; + var pixelRatio = frameState.pixelRatio; + var renderResolution = viewState.resolution / pixelRatio; + var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent); + var center = viewState.center; + var origin = ol.extent.getTopLeft(tileExtent); + var size = frameState.size; + var offsetX = Math.round(pixelRatio * size[0] / 2); + var offsetY = Math.round(pixelRatio * size[1] / 2); + return ol.transform.compose(this.tmpTransform_, + offsetX, offsetY, + tileResolution / renderResolution, tileResolution / renderResolution, + viewState.rotation, + (origin[0] - center[0]) / tileResolution, + (center[1] - origin[1]) / tileResolution); + } else { + return this.getTransform(frameState, 0); + } +}; + + +/** + * Handle changes in image style state. + * @param {ol.events.Event} event Image style change event. + * @private + */ +ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function(event) { + this.renderIfReadyAndVisible(); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, frameState, layerState) { + var renderMode = this.getLayer().getRenderMode(); + var replays = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode]; + if (replays) { + var pixelRatio = frameState.pixelRatio; + var rotation = frameState.viewState.rotation; + var size = frameState.size; + var offsetX = Math.round(pixelRatio * size[0] / 2); + var offsetY = Math.round(pixelRatio * size[1] / 2); + var tiles = this.renderedTiles; + var clips = []; + var zs = []; + for (var i = tiles.length - 1; i >= 0; --i) { + var tile = /** @type {ol.VectorTile} */ (tiles[i]); + // Create a clip mask for regions in this low resolution tile that are + // already filled by a higher resolution tile + var transform = this.getReplayTransform_(tile, frameState); + var currentClip = tile.getReplayState().replayGroup.getClipCoords(transform); + var currentZ = tile.tileCoord[0]; + context.save(); + context.globalAlpha = layerState.opacity; + ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY); + for (var j = 0, jj = clips.length; j < jj; ++j) { + var clip = clips[j]; + if (currentZ < zs[j]) { + context.beginPath(); + // counter-clockwise (outer ring) for current tile + context.moveTo(currentClip[0], currentClip[1]); + context.lineTo(currentClip[2], currentClip[3]); + context.lineTo(currentClip[4], currentClip[5]); + context.lineTo(currentClip[6], currentClip[7]); + // clockwise (inner ring) for higher resolution tile + context.moveTo(clip[6], clip[7]); + context.lineTo(clip[4], clip[5]); + context.lineTo(clip[2], clip[3]); + context.lineTo(clip[0], clip[1]); + context.clip(); + } + } + var replayGroup = tile.getReplayState().replayGroup; + replayGroup.replay(context, pixelRatio, transform, rotation, {}, replays); + context.restore(); + clips.push(currentClip); + zs.push(currentZ); + } + } + ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments); +}; + + +/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {number} squaredTolerance Squared tolerance. + * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of + * styles. + * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + */ +ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, squaredTolerance, styles, replayGroup) { + if (!styles) { + return false; + } + var loading = false; + if (Array.isArray(styles)) { + for (var i = 0, ii = styles.length; i < ii; ++i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], squaredTolerance, + this.handleStyleImageChange_, this) || loading; + } + } else { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles, squaredTolerance, + this.handleStyleImageChange_, this) || loading; + } + return loading; +}; + + +/** + * @param {ol.VectorTile} tile Tile. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @private + */ +ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function( + tile, frameState, layerState) { + var layer = this.getLayer(); + var replayState = tile.getReplayState(); + var revision = layer.getRevision(); + var replays = ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS[layer.getRenderMode()]; + if (replays && replayState.renderedTileRevision !== revision) { + replayState.renderedTileRevision = revision; + var tileCoord = tile.tileCoord; + var z = tile.tileCoord[0]; + var pixelRatio = frameState.pixelRatio; + var source = layer.getSource(); + var tileGrid = source.getTileGrid(); + var tilePixelRatio = source.getTilePixelRatio(); + var transform = ol.transform.reset(this.tmpTransform_); + if (tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) { + var renderPixelRatio = pixelRatio / tilePixelRatio; + ol.transform.scale(transform, renderPixelRatio, renderPixelRatio); + } else { + var resolution = tileGrid.getResolution(z); + var pixelScale = pixelRatio / resolution; + var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent); + ol.transform.scale(transform, pixelScale, -pixelScale); + ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]); + } + + var context = tile.getContext(); + var size = source.getTilePixelSize(z, pixelRatio, frameState.viewState.projection); + context.canvas.width = size[0]; + context.canvas.height = size[1]; + replayState.replayGroup.replay(context, pixelRatio, transform, 0, {}, replays); + } +}; + +// FIXME offset panning + +goog.provide('ol.renderer.canvas.Map'); + +goog.require('ol.transform'); +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.css'); +goog.require('ol.dom'); +goog.require('ol.layer.Image'); +goog.require('ol.layer.Layer'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); +goog.require('ol.layer.VectorTile'); +goog.require('ol.render.Event'); +goog.require('ol.render.canvas'); +goog.require('ol.render.canvas.Immediate'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.Type'); +goog.require('ol.renderer.canvas.ImageLayer'); +goog.require('ol.renderer.canvas.TileLayer'); +goog.require('ol.renderer.canvas.VectorLayer'); +goog.require('ol.renderer.canvas.VectorTileLayer'); +goog.require('ol.source.State'); + + +/** + * @constructor + * @extends {ol.renderer.Map} + * @param {Element} container Container. + * @param {ol.Map} map Map. + */ +ol.renderer.canvas.Map = function(container, map) { + + ol.renderer.Map.call(this, container, map); + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = this.context_.canvas; + + this.canvas_.style.width = '100%'; + this.canvas_.style.height = '100%'; + this.canvas_.className = ol.css.CLASS_UNSELECTABLE; + container.insertBefore(this.canvas_, container.childNodes[0] || null); + + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = true; + + /** + * @private + * @type {ol.Transform} + */ + this.transform_ = ol.transform.create(); + +}; +ol.inherits(ol.renderer.canvas.Map, ol.renderer.Map); + + +/** + * @inheritDoc + */ +ol.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) { + if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) { + return new ol.renderer.canvas.ImageLayer(layer); + } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) { + return new ol.renderer.canvas.TileLayer(layer); + } else if (ol.ENABLE_VECTOR_TILE && layer instanceof ol.layer.VectorTile) { + return new ol.renderer.canvas.VectorTileLayer(layer); + } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) { + return new ol.renderer.canvas.VectorLayer(layer); + } else { + ol.DEBUG && console.assert(false, 'unexpected layer configuration'); + return null; + } +}; + + +/** + * @param {ol.render.Event.Type} type Event type. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ = function(type, frameState) { + var map = this.getMap(); + var context = this.context_; + if (map.hasListener(type)) { + var extent = frameState.extent; + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var rotation = viewState.rotation; + + var transform = this.getTransform(frameState); + + var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio, + extent, transform, rotation); + var composeEvent = new ol.render.Event(type, vectorContext, + frameState, context, null); + map.dispatchEvent(composeEvent); + } +}; + + +/** + * @param {olx.FrameState} frameState Frame state. + * @protected + * @return {!ol.Transform} Transform. + */ +ol.renderer.canvas.Map.prototype.getTransform = function(frameState) { + var viewState = frameState.viewState; + var dx1 = this.canvas_.width / 2; + var dy1 = this.canvas_.height / 2; + var sx = frameState.pixelRatio / viewState.resolution; + var sy = -sx; + var angle = -viewState.rotation; + var dx2 = -viewState.center[0]; + var dy2 = -viewState.center[1]; + return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.Map.prototype.getType = function() { + return ol.renderer.Type.CANVAS; +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) { + + if (!frameState) { + if (this.renderedVisible_) { + this.canvas_.style.display = 'none'; + this.renderedVisible_ = false; + } + return; + } + + var context = this.context_; + var pixelRatio = frameState.pixelRatio; + var width = Math.round(frameState.size[0] * pixelRatio); + var height = Math.round(frameState.size[1] * pixelRatio); + if (this.canvas_.width != width || this.canvas_.height != height) { + this.canvas_.width = width; + this.canvas_.height = height; + } else { + context.clearRect(0, 0, width, height); + } + + var rotation = frameState.viewState.rotation; + + this.calculateMatrices2D(frameState); + + this.dispatchComposeEvent_(ol.render.Event.Type.PRECOMPOSE, frameState); + + var layerStatesArray = frameState.layerStatesArray; + ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex); + + ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2); + + var viewResolution = frameState.viewState.resolution; + var i, ii, layer, layerRenderer, layerState; + for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerState = layerStatesArray[i]; + layer = layerState.layer; + layerRenderer = /** @type {ol.renderer.canvas.Layer} */ (this.getLayerRenderer(layer)); + if (!ol.layer.Layer.visibleAtResolution(layerState, viewResolution) || + layerState.sourceState != ol.source.State.READY) { + continue; + } + if (layerRenderer.prepareFrame(frameState, layerState)) { + layerRenderer.composeFrame(frameState, layerState, context); + } + } + + ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2); + + this.dispatchComposeEvent_( + ol.render.Event.Type.POSTCOMPOSE, frameState); + + if (!this.renderedVisible_) { + this.canvas_.style.display = ''; + this.renderedVisible_ = true; + } + + this.scheduleRemoveUnusedLayerRenderers(frameState); + this.scheduleExpireIconCache(frameState); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg, + layerFilter, thisArg2) { + var result; + var viewState = frameState.viewState; + var viewResolution = viewState.resolution; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + + var coordinate = ol.transform.apply( + frameState.pixelToCoordinateTransform, pixel.slice()); + + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = /** @type {ol.renderer.canvas.Layer} */ (this.getLayerRenderer(layer)); + result = layerRenderer.forEachLayerAtCoordinate( + coordinate, frameState, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; +}; + +goog.provide('ol.render.webgl'); + +/** + * @const + * @type {ol.Color} + */ +ol.render.webgl.defaultFillStyle = [0.0, 0.0, 0.0, 1.0]; + +/** + * @const + * @type {string} + */ +ol.render.webgl.defaultLineCap = 'round'; + + +/** + * @const + * @type {Array.<number>} + */ +ol.render.webgl.defaultLineDash = []; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.defaultLineJoin = 'round'; + + +/** + * @const + * @type {number} + */ +ol.render.webgl.defaultMiterLimit = 10; + +/** + * @const + * @type {ol.Color} + */ +ol.render.webgl.defaultStrokeStyle = [0.0, 0.0, 0.0, 1.0]; + +/** + * @const + * @type {number} + */ +ol.render.webgl.defaultLineWidth = 1; + +/** + * @enum {number} + */ +ol.render.webgl.lineStringInstruction = { + ROUND: 2, + BEGIN_LINE: 3, + END_LINE: 5, + BEGIN_LINE_CAP: 7, + END_LINE_CAP : 11, + BEVEL_FIRST: 13, + BEVEL_SECOND: 17, + MITER_BOTTOM: 19, + MITER_TOP: 23 +}; + +/** + * Calculates the orientation of a triangle based on the determinant method. + * @param {number} x1 First X coordinate. + * @param {number} y1 First Y coordinate. + * @param {number} x2 Second X coordinate. + * @param {number} y2 Second Y coordinate. + * @param {number} x3 Third X coordinate. + * @param {number} y3 Third Y coordinate. + * @return {boolean|undefined} Triangle is clockwise. + */ +ol.render.webgl.triangleIsCounterClockwise = function(x1, y1, x2, y2, x3, y3) { + var area = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1); + return (area <= ol.render.webgl.EPSILON && area >= -ol.render.webgl.EPSILON) ? + undefined : area > 0; +}; + +/** + * @const + * @type {number} + */ +ol.render.webgl.EPSILON = Number.EPSILON || 2.220446049250313e-16; + +goog.provide('ol.webgl.Shader'); + +goog.require('ol.functions'); +goog.require('ol.webgl'); + + +/** + * @constructor + * @param {string} source Source. + * @struct + */ +ol.webgl.Shader = function(source) { + + /** + * @private + * @type {string} + */ + this.source_ = source; + +}; + + +/** + * @abstract + * @return {number} Type. + */ +ol.webgl.Shader.prototype.getType = function() {}; + + +/** + * @return {string} Source. + */ +ol.webgl.Shader.prototype.getSource = function() { + return this.source_; +}; + + +/** + * @return {boolean} Is animated? + */ +ol.webgl.Shader.prototype.isAnimated = ol.functions.FALSE; + +goog.provide('ol.webgl.Fragment'); + +goog.require('ol'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Shader'); + + +/** + * @constructor + * @extends {ol.webgl.Shader} + * @param {string} source Source. + * @struct + */ +ol.webgl.Fragment = function(source) { + ol.webgl.Shader.call(this, source); +}; +ol.inherits(ol.webgl.Fragment, ol.webgl.Shader); + + +/** + * @inheritDoc + */ +ol.webgl.Fragment.prototype.getType = function() { + return ol.webgl.FRAGMENT_SHADER; +}; + +goog.provide('ol.webgl.Vertex'); + +goog.require('ol'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Shader'); + + +/** + * @constructor + * @extends {ol.webgl.Shader} + * @param {string} source Source. + * @struct + */ +ol.webgl.Vertex = function(source) { + ol.webgl.Shader.call(this, source); +}; +ol.inherits(ol.webgl.Vertex, ol.webgl.Shader); + + +/** + * @inheritDoc + */ +ol.webgl.Vertex.prototype.getType = function() { + return ol.webgl.VERTEX_SHADER; +}; + +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.circlereplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.circlereplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.circlereplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.circlereplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_center;\nvarying vec2 v_offset;\nvarying float v_halfWidth;\nvarying float v_pixelRatio;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_fillColor;\nuniform vec4 u_strokeColor;\nuniform vec2 u_size;\n\nvoid main(void) {\n vec2 windowCenter = vec2((v_center.x + 1.0) / 2.0 * u_size.x * v_pixelRatio,\n (v_center.y + 1.0) / 2.0 * u_size.y * v_pixelRatio);\n vec2 windowOffset = vec2((v_offset.x + 1.0) / 2.0 * u_size.x * v_pixelRatio,\n (v_offset.y + 1.0) / 2.0 * u_size.y * v_pixelRatio);\n float radius = length(windowCenter - windowOffset);\n float dist = length(windowCenter - gl_FragCoord.xy);\n if (dist > radius + v_halfWidth) {\n if (u_strokeColor.a == 0.0) {\n gl_FragColor = u_fillColor;\n } else {\n gl_FragColor = u_strokeColor;\n }\n gl_FragColor.a = gl_FragColor.a - (dist - (radius + v_halfWidth));\n } else if (u_fillColor.a == 0.0) {\n // Hooray, no fill, just stroke. We can use real antialiasing.\n gl_FragColor = u_strokeColor;\n if (dist < radius - v_halfWidth) {\n gl_FragColor.a = gl_FragColor.a - (radius - v_halfWidth - dist);\n }\n } else {\n gl_FragColor = u_fillColor;\n float strokeDist = radius - v_halfWidth;\n float antialias = 2.0 * v_pixelRatio;\n if (dist > strokeDist) {\n gl_FragColor = u_strokeColor;\n } else if (dist >= strokeDist - antialias) {\n float step = smoothstep(strokeDist - antialias, strokeDist, dist);\n gl_FragColor = mix(u_fillColor, u_strokeColor, step);\n }\n }\n gl_FragColor.a = gl_FragColor.a * u_opacity;\n if (gl_FragColor.a <= 0.0) {\n discard;\n }\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying vec2 b;varying float c;varying float d;uniform float m;uniform vec4 n;uniform vec4 o;uniform vec2 p;void main(void){vec2 windowCenter=vec2((a.x+1.0)/2.0*p.x*d,(a.y+1.0)/2.0*p.y*d);vec2 windowOffset=vec2((b.x+1.0)/2.0*p.x*d,(b.y+1.0)/2.0*p.y*d);float radius=length(windowCenter-windowOffset);float dist=length(windowCenter-gl_FragCoord.xy);if(dist>radius+c){if(o.a==0.0){gl_FragColor=n;}else{gl_FragColor=o;}gl_FragColor.a=gl_FragColor.a-(dist-(radius+c));}else if(n.a==0.0){gl_FragColor=o;if(dist<radius-c){gl_FragColor.a=gl_FragColor.a-(radius-c-dist);}} else{gl_FragColor=n;float strokeDist=radius-c;float antialias=2.0*d;if(dist>strokeDist){gl_FragColor=o;}else if(dist>=strokeDist-antialias){float step=smoothstep(strokeDist-antialias,strokeDist,dist);gl_FragColor=mix(n,o,step);}} gl_FragColor.a=gl_FragColor.a*m;if(gl_FragColor.a<=0.0){discard;}}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + ol.render.webgl.circlereplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.circlereplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.circlereplay.defaultshader.fragment = new ol.render.webgl.circlereplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.circlereplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.circlereplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.circlereplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_center;\nvarying vec2 v_offset;\nvarying float v_halfWidth;\nvarying float v_pixelRatio;\n\n\nattribute vec2 a_position;\nattribute float a_instruction;\nattribute float a_radius;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\nuniform float u_lineWidth;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n v_center = vec4(u_projectionMatrix * vec4(a_position, 0.0, 1.0)).xy;\n v_pixelRatio = u_pixelRatio;\n float lineWidth = u_lineWidth * u_pixelRatio;\n v_halfWidth = lineWidth / 2.0;\n if (lineWidth == 0.0) {\n lineWidth = 2.0 * u_pixelRatio;\n }\n vec2 offset;\n // Radius with anitaliasing (roughly).\n float radius = a_radius + 3.0 * u_pixelRatio;\n // Until we get gl_VertexID in WebGL, we store an instruction.\n if (a_instruction == 0.0) {\n // Offsetting the edges of the triangle by lineWidth / 2 is necessary, however\n // we should also leave some space for the antialiasing, thus we offset by lineWidth.\n offset = vec2(-1.0, 1.0);\n } else if (a_instruction == 1.0) {\n offset = vec2(-1.0, -1.0);\n } else if (a_instruction == 2.0) {\n offset = vec2(1.0, -1.0);\n } else {\n offset = vec2(1.0, 1.0);\n }\n\n gl_Position = u_projectionMatrix * vec4(a_position + offset * radius, 0.0, 1.0) +\n offsetMatrix * vec4(offset * lineWidth, 0.0, 0.0);\n v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y,\n 0.0, 1.0)).xy;\n\n if (distance(v_center, v_offset) > 20000.0) {\n gl_Position = vec4(v_center, 0.0, 1.0);\n }\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying vec2 b;varying float c;varying float d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;void main(void){mat4 offsetMatrix=i*j;a=vec4(h*vec4(e,0.0,1.0)).xy;d=l;float lineWidth=k*l;c=lineWidth/2.0;if(lineWidth==0.0){lineWidth=2.0*l;}vec2 offset;float radius=g+3.0*l;if(f==0.0){offset=vec2(-1.0,1.0);}else if(f==1.0){offset=vec2(-1.0,-1.0);}else if(f==2.0){offset=vec2(1.0,-1.0);}else{offset=vec2(1.0,1.0);}gl_Position=h*vec4(e+offset*radius,0.0,1.0)+offsetMatrix*vec4(offset*lineWidth,0.0,0.0);b=vec4(h*vec4(e.x+g,e.y,0.0,1.0)).xy;if(distance(a,b)>20000.0){gl_Position=vec4(a,0.0,1.0);}}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + ol.render.webgl.circlereplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.circlereplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.circlereplay.defaultshader.vertex = new ol.render.webgl.circlereplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.circlereplay.defaultshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_fillColor = gl.getUniformLocation( + program, ol.DEBUG ? 'u_fillColor' : 'n'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_lineWidth = gl.getUniformLocation( + program, ol.DEBUG ? 'u_lineWidth' : 'k'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'j'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'i'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'm'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_pixelRatio = gl.getUniformLocation( + program, ol.DEBUG ? 'u_pixelRatio' : 'l'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'h'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_size = gl.getUniformLocation( + program, ol.DEBUG ? 'u_size' : 'p'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_strokeColor = gl.getUniformLocation( + program, ol.DEBUG ? 'u_strokeColor' : 'o'); + + /** + * @type {number} + */ + this.a_instruction = gl.getAttribLocation( + program, ol.DEBUG ? 'a_instruction' : 'f'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'e'); + + /** + * @type {number} + */ + this.a_radius = gl.getAttribLocation( + program, ol.DEBUG ? 'a_radius' : 'g'); +}; + +goog.provide('ol.vec.Mat4'); + + +/** + * @return {Array.<number>} 4x4 matrix representing a 3D identity transform. + */ +ol.vec.Mat4.create = function() { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; +}; + + +/** + * @param {Array.<number>} mat4 Flattened 4x4 matrix receiving the result. + * @param {ol.Transform} transform Transformation matrix. + * @return {Array.<number>} 2D transformation matrix as flattened 4x4 matrix. + */ +ol.vec.Mat4.fromTransform = function(mat4, transform) { + mat4[0] = transform[0]; + mat4[1] = transform[1]; + mat4[4] = transform[2]; + mat4[5] = transform[3]; + mat4[12] = transform[4]; + mat4[13] = transform[5]; + return mat4; +}; + +goog.provide('ol.render.webgl.Replay'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.render.VectorContext'); +goog.require('ol.transform'); +goog.require('ol.vec.Mat4'); +goog.require('ol.webgl'); + +/** + * @constructor + * @extends {ol.render.VectorContext} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.Replay = function(tolerance, maxExtent) { + ol.render.VectorContext.call(this); + + /** + * @protected + * @type {number} + */ + this.tolerance = tolerance; + + /** + * @protected + * @const + * @type {ol.Extent} + */ + this.maxExtent = maxExtent; + + /** + * The origin of the coordinate system for the point coordinates sent to + * the GPU. To eliminate jitter caused by precision problems in the GPU + * we use the "Rendering Relative to Eye" technique described in the "3D + * Engine Design for Virtual Globes" book. + * @protected + * @type {ol.Coordinate} + */ + this.origin = ol.extent.getCenter(maxExtent); + + /** + * @private + * @type {ol.Transform} + */ + this.projectionMatrix_ = ol.transform.create(); + + /** + * @private + * @type {ol.Transform} + */ + this.offsetRotateMatrix_ = ol.transform.create(); + + /** + * @private + * @type {ol.Transform} + */ + this.offsetScaleMatrix_ = ol.transform.create(); + + /** + * @private + * @type {Array.<number>} + */ + this.tmpMat4_ = ol.vec.Mat4.create(); + + /** + * @protected + * @type {Array.<number>} + */ + this.indices = []; + + /** + * @protected + * @type {?ol.webgl.Buffer} + */ + this.indicesBuffer = null; + + /** + * Start index per feature (the index). + * @protected + * @type {Array.<number>} + */ + this.startIndices = []; + + /** + * Start index per feature (the feature). + * @protected + * @type {Array.<ol.Feature|ol.render.Feature>} + */ + this.startIndicesFeature = []; + + /** + * @protected + * @type {Array.<number>} + */ + this.vertices = []; + + /** + * @protected + * @type {?ol.webgl.Buffer} + */ + this.verticesBuffer = null; + + /** + * Optional parameter for PolygonReplay instances. + * @protected + * @type {ol.render.webgl.LineStringReplay|undefined} + */ + this.lineStringReplay = undefined; + +}; +ol.inherits(ol.render.webgl.Replay, ol.render.VectorContext); + + +/** + * @abstract + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = function(context) {}; + + +/** + * @abstract + * @param {ol.webgl.Context} context Context. + */ +ol.render.webgl.Replay.prototype.finish = function(context) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @return {ol.render.webgl.circlereplay.defaultshader.Locations| + ol.render.webgl.imagereplay.defaultshader.Locations| + ol.render.webgl.linestringreplay.defaultshader.Locations| + ol.render.webgl.polygonreplay.defaultshader.Locations} Locations. + */ +ol.render.webgl.Replay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.render.webgl.circlereplay.defaultshader.Locations| + ol.render.webgl.imagereplay.defaultshader.Locations| + ol.render.webgl.linestringreplay.defaultshader.Locations| + ol.render.webgl.polygonreplay.defaultshader.Locations} locations Locations. + */ +ol.render.webgl.Replay.prototype.shutDownProgram = function(gl, locations) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {boolean} hitDetection Hit detection mode. + */ +ol.render.webgl.Replay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, featureCallback, opt_hitExtent) {}; + + +/** + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplay = function(gl, context, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + if (!oneByOne) { + // draw all hit-detection features in "once" (by texture group) + return this.drawHitDetectionReplayAll(gl, context, + skippedFeaturesHash, featureCallback); + } else { + // draw hit-detection features one by one + return this.drawHitDetectionReplayOneByOne(gl, context, + skippedFeaturesHash, featureCallback, opt_hitExtent); + } +}; + + +/** + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplayAll = function(gl, context, skippedFeaturesHash, + featureCallback) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawReplay(gl, context, skippedFeaturesHash, true); + + var result = featureCallback(null); + if (result) { + return result; + } else { + return undefined; + } +}; + + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + var gl = context.getGL(); + var tmpStencil, tmpStencilFunc, tmpStencilMaskVal, tmpStencilRef, tmpStencilMask, + tmpStencilOpFail, tmpStencilOpPass, tmpStencilOpZFail; + + if (this.lineStringReplay) { + tmpStencil = gl.isEnabled(gl.STENCIL_TEST); + tmpStencilFunc = gl.getParameter(gl.STENCIL_FUNC); + tmpStencilMaskVal = gl.getParameter(gl.STENCIL_VALUE_MASK); + tmpStencilRef = gl.getParameter(gl.STENCIL_REF); + tmpStencilMask = gl.getParameter(gl.STENCIL_WRITEMASK); + tmpStencilOpFail = gl.getParameter(gl.STENCIL_FAIL); + tmpStencilOpPass = gl.getParameter(gl.STENCIL_PASS_DEPTH_PASS); + tmpStencilOpZFail = gl.getParameter(gl.STENCIL_PASS_DEPTH_FAIL); + + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilMask(255); + gl.stencilFunc(gl.ALWAYS, 1, 255); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); + + this.lineStringReplay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + + gl.stencilMask(0); + gl.stencilFunc(gl.NOTEQUAL, 1, 255); + } + + // bind the vertices buffer + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer); + + // bind the indices buffer + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer); + + var locations = this.setUpProgram(gl, context, size, pixelRatio); + + // set the "uniform" values + var projectionMatrix = ol.transform.reset(this.projectionMatrix_); + ol.transform.scale(projectionMatrix, 2 / (resolution * size[0]), 2 / (resolution * size[1])); + ol.transform.rotate(projectionMatrix, -rotation); + ol.transform.translate(projectionMatrix, -(center[0] - this.origin[0]), -(center[1] - this.origin[1])); + + var offsetScaleMatrix = ol.transform.reset(this.offsetScaleMatrix_); + ol.transform.scale(offsetScaleMatrix, 2 / size[0], 2 / size[1]); + + var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_); + if (rotation !== 0) { + ol.transform.rotate(offsetRotateMatrix, -rotation); + } + + gl.uniformMatrix4fv(locations.u_projectionMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, projectionMatrix)); + gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetScaleMatrix)); + gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetRotateMatrix)); + gl.uniform1f(locations.u_opacity, opacity); + + // draw! + var result; + if (featureCallback === undefined) { + this.drawReplay(gl, context, skippedFeaturesHash, false); + } else { + // draw feature by feature for the hit-detection + result = this.drawHitDetectionReplay(gl, context, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + } + + // disable the vertex attrib arrays + this.shutDownProgram(gl, locations); + + if (this.lineStringReplay) { + if (!tmpStencil) { + gl.disable(gl.STENCIL_TEST); + } + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(/** @type {number} */ (tmpStencilFunc), + /** @type {number} */ (tmpStencilRef), /** @type {number} */ (tmpStencilMaskVal)); + gl.stencilMask(/** @type {number} */ (tmpStencilMask)); + gl.stencilOp(/** @type {number} */ (tmpStencilOpFail), + /** @type {number} */ (tmpStencilOpZFail), /** @type {number} */ (tmpStencilOpPass)); + } + + return result; +}; + +/** + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {number} start Start index. + * @param {number} end End index. + */ +ol.render.webgl.Replay.prototype.drawElements = function( + gl, context, start, end) { + var elementType = context.hasOESElementIndexUint ? + ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT; + var elementSize = context.hasOESElementIndexUint ? 4 : 2; + + var numItems = end - start; + var offsetInBytes = start * elementSize; + gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, offsetInBytes); +}; + +goog.provide('ol.webgl.Buffer'); + +goog.require('ol'); +goog.require('ol.webgl'); + + +/** + * @constructor + * @param {Array.<number>=} opt_arr Array. + * @param {number=} opt_usage Usage. + * @struct + */ +ol.webgl.Buffer = function(opt_arr, opt_usage) { + + /** + * @private + * @type {Array.<number>} + */ + this.arr_ = opt_arr !== undefined ? opt_arr : []; + + /** + * @private + * @type {number} + */ + this.usage_ = opt_usage !== undefined ? + opt_usage : ol.webgl.Buffer.Usage.STATIC_DRAW; + +}; + + +/** + * @return {Array.<number>} Array. + */ +ol.webgl.Buffer.prototype.getArray = function() { + return this.arr_; +}; + + +/** + * @return {number} Usage. + */ +ol.webgl.Buffer.prototype.getUsage = function() { + return this.usage_; +}; + + +/** + * @enum {number} + */ +ol.webgl.Buffer.Usage = { + STATIC_DRAW: ol.webgl.STATIC_DRAW, + STREAM_DRAW: ol.webgl.STREAM_DRAW, + DYNAMIC_DRAW: ol.webgl.DYNAMIC_DRAW +}; + +goog.provide('ol.render.webgl.CircleReplay'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.color'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.render.webgl.circlereplay.defaultshader'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.CircleReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + /** + * @private + * @type {ol.render.webgl.circlereplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.<Array.<Array.<number>|number>>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.<number>} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {number} + */ + this.radius_ = 0; + + /** + * @private + * @type {{fillColor: (Array.<number>|null), + * strokeColor: (Array.<number>|null), + * lineDash: Array.<number>, + * lineWidth: (number|undefined), + * changed: boolean}|null} + */ + this.state_ = { + fillColor: null, + strokeColor: null, + lineDash: null, + lineWidth: undefined, + changed: false + }; + +}; +ol.inherits(ol.render.webgl.CircleReplay, ol.render.webgl.Replay); + + +/** + * @private + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + */ +ol.render.webgl.CircleReplay.prototype.drawCoordinates_ = function( + flatCoordinates, offset, end, stride) { + var numVertices = this.vertices.length; + var numIndices = this.indices.length; + var n = numVertices / 4; + var i, ii; + for (i = offset, ii = end; i < ii; i += stride) { + this.vertices[numVertices++] = flatCoordinates[i]; + this.vertices[numVertices++] = flatCoordinates[i + 1]; + this.vertices[numVertices++] = 0; + this.vertices[numVertices++] = this.radius_; + + this.vertices[numVertices++] = flatCoordinates[i]; + this.vertices[numVertices++] = flatCoordinates[i + 1]; + this.vertices[numVertices++] = 1; + this.vertices[numVertices++] = this.radius_; + + this.vertices[numVertices++] = flatCoordinates[i]; + this.vertices[numVertices++] = flatCoordinates[i + 1]; + this.vertices[numVertices++] = 2; + this.vertices[numVertices++] = this.radius_; + + this.vertices[numVertices++] = flatCoordinates[i]; + this.vertices[numVertices++] = flatCoordinates[i + 1]; + this.vertices[numVertices++] = 3; + this.vertices[numVertices++] = this.radius_; + + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 2; + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n; + + n += 4; + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.drawCircle = function(circleGeometry, feature) { + var radius = circleGeometry.getRadius(); + var stride = circleGeometry.getStride(); + if (radius) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(this.indices.length); + this.state_.changed = false; + } + + this.radius_ = radius; + var flatCoordinates = circleGeometry.getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, 2, + stride, -this.origin[0], -this.origin[1]); + this.drawCoordinates_(flatCoordinates, 0, 2, stride); + } else { + if (this.state_.changed) { + this.styles_.pop(); + if (this.styles_.length) { + var lastState = this.styles_[this.styles_.length - 1]; + this.state_.fillColor = /** @type {Array.<number>} */ (lastState[0]); + this.state_.strokeColor = /** @type {Array.<number>} */ (lastState[1]); + this.state_.lineWidth = /** @type {number} */ (lastState[2]); + this.state_.changed = false; + } + } + } +}; + + +/** + * @inheritDoc + **/ +ol.render.webgl.CircleReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); + + this.startIndices.push(this.indices.length); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } + + this.vertices = null; + this.indices = null; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other CircleReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.circlereplay.defaultshader.fragment; + vertexShader = ol.render.webgl.circlereplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.circlereplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 16, 0); + + gl.enableVertexAttribArray(locations.a_instruction); + gl.vertexAttribPointer(locations.a_instruction, 1, ol.webgl.FLOAT, + false, 16, 8); + + gl.enableVertexAttribArray(locations.a_radius); + gl.vertexAttribPointer(locations.a_radius, 1, ol.webgl.FLOAT, + false, 16, 12); + + // Enable renderer specific uniforms. + gl.uniform2fv(locations.u_size, size); + gl.uniform1f(locations.u_pixelRatio, pixelRatio); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_instruction); + gl.disableVertexAttribArray(locations.a_radius); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); + } else { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, start, end, nextStyle; + end = this.startIndices[this.startIndices.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, /** @type {Array.<number>} */ (nextStyle[0])); + this.setStrokeStyle_(gl, /** @type {Array.<number>} */ (nextStyle[1]), + /** @type {number} */ (nextStyle[2])); + this.drawElements(gl, context, start, end); + end = start; + } + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices.length - 2; + end = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, /** @type {Array.<number>} */ (nextStyle[0])); + this.setStrokeStyle_(gl, /** @type {Array.<number>} */ (nextStyle[1]), + /** @type {number} */ (nextStyle[2])); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array<number>} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; + } + } + return undefined; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.CircleReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices.length - 2; + end = start = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, /** @type {Array.<number>} */ (nextStyle[0])); + this.setStrokeStyle_(gl, /** @type {Array.<number>} */ (nextStyle[1]), + /** @type {number} */ (nextStyle[2])); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + featureStart = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements(gl, context, start, end); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements(gl, context, start, end); + } + start = end = groupStart; + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.<number>} color Color. + */ +ol.render.webgl.CircleReplay.prototype.setFillStyle_ = function(gl, color) { + gl.uniform4fv(this.defaultLocations_.u_fillColor, color); +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.<number>} color Color. + * @param {number} lineWidth Line width. + */ +ol.render.webgl.CircleReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth) { + gl.uniform4fv(this.defaultLocations_.u_strokeColor, color); + gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.CircleReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + var strokeStyleColor, strokeStyleWidth; + if (strokeStyle) { + var strokeStyleLineDash = strokeStyle.getLineDash(); + this.state_.lineDash = strokeStyleLineDash ? + strokeStyleLineDash : ol.render.webgl.defaultLineDash; + strokeStyleColor = strokeStyle.getColor(); + if (!(strokeStyleColor instanceof CanvasGradient) && + !(strokeStyleColor instanceof CanvasPattern)) { + strokeStyleColor = ol.color.asArray(strokeStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultStrokeStyle; + } else { + strokeStyleColor = ol.render.webgl.defaultStrokeStyle; + } + strokeStyleWidth = strokeStyle.getWidth(); + strokeStyleWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.webgl.defaultLineWidth; + } else { + strokeStyleColor = [0, 0, 0, 0]; + strokeStyleWidth = 0; + } + var fillStyleColor = fillStyle ? fillStyle.getColor() : [0, 0, 0, 0]; + if (!(fillStyleColor instanceof CanvasGradient) && + !(fillStyleColor instanceof CanvasPattern)) { + fillStyleColor = ol.color.asArray(fillStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultFillStyle; + } else { + fillStyleColor = ol.render.webgl.defaultFillStyle; + } + if (!this.state_.strokeColor || !ol.array.equals(this.state_.strokeColor, strokeStyleColor) || + !this.state_.fillColor || !ol.array.equals(this.state_.fillColor, fillStyleColor) || + this.state_.lineWidth !== strokeStyleWidth) { + this.state_.changed = true; + this.state_.fillColor = fillStyleColor; + this.state_.strokeColor = strokeStyleColor; + this.state_.lineWidth = strokeStyleWidth; + this.styles_.push([fillStyleColor, strokeStyleColor, strokeStyleWidth]); + } +}; + +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.imagereplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.imagereplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.imagereplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.imagereplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + ol.render.webgl.imagereplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.imagereplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.imagereplay.defaultshader.fragment = new ol.render.webgl.imagereplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.imagereplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.imagereplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.imagereplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);\n gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.0,0.0);gl_Position=h*vec4(c,0.0,1.0)+offsets;a=d;b=f;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + ol.render.webgl.imagereplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.imagereplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.imagereplay.defaultshader.vertex = new ol.render.webgl.imagereplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.imagereplay.defaultshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_image = gl.getUniformLocation( + program, ol.DEBUG ? 'u_image' : 'l'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'j'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'i'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'k'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'h'); + + /** + * @type {number} + */ + this.a_offsets = gl.getAttribLocation( + program, ol.DEBUG ? 'a_offsets' : 'e'); + + /** + * @type {number} + */ + this.a_opacity = gl.getAttribLocation( + program, ol.DEBUG ? 'a_opacity' : 'f'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'c'); + + /** + * @type {number} + */ + this.a_rotateWithView = gl.getAttribLocation( + program, ol.DEBUG ? 'a_rotateWithView' : 'g'); + + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, ol.DEBUG ? 'a_texCoord' : 'd'); +}; + +goog.provide('ol.webgl.ContextEventType'); + + +/** + * @enum {string} + */ +ol.webgl.ContextEventType = { + LOST: 'webglcontextlost', + RESTORED: 'webglcontextrestored' +}; + +goog.provide('ol.webgl.Context'); + +goog.require('ol'); +goog.require('ol.Disposable'); +goog.require('ol.array'); +goog.require('ol.events'); +goog.require('ol.obj'); +goog.require('ol.webgl'); +goog.require('ol.webgl.ContextEventType'); + + +/** + * @classdesc + * A WebGL context for accessing low-level WebGL capabilities. + * + * @constructor + * @extends {ol.Disposable} + * @param {HTMLCanvasElement} canvas Canvas. + * @param {WebGLRenderingContext} gl GL. + */ +ol.webgl.Context = function(canvas, gl) { + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = canvas; + + /** + * @private + * @type {WebGLRenderingContext} + */ + this.gl_ = gl; + + /** + * @private + * @type {Object.<string, ol.WebglBufferCacheEntry>} + */ + this.bufferCache_ = {}; + + /** + * @private + * @type {Object.<string, WebGLShader>} + */ + this.shaderCache_ = {}; + + /** + * @private + * @type {Object.<string, WebGLProgram>} + */ + this.programCache_ = {}; + + /** + * @private + * @type {WebGLProgram} + */ + this.currentProgram_ = null; + + /** + * @private + * @type {WebGLFramebuffer} + */ + this.hitDetectionFramebuffer_ = null; + + /** + * @private + * @type {WebGLTexture} + */ + this.hitDetectionTexture_ = null; + + /** + * @private + * @type {WebGLRenderbuffer} + */ + this.hitDetectionRenderbuffer_ = null; + + /** + * @type {boolean} + */ + this.hasOESElementIndexUint = ol.array.includes( + ol.WEBGL_EXTENSIONS, 'OES_element_index_uint'); + + // use the OES_element_index_uint extension if available + if (this.hasOESElementIndexUint) { + var ext = gl.getExtension('OES_element_index_uint'); + ol.DEBUG && console.assert(ext, + 'Failed to get extension "OES_element_index_uint"'); + } + + ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST, + this.handleWebGLContextLost, this); + ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED, + this.handleWebGLContextRestored, this); + +}; +ol.inherits(ol.webgl.Context, ol.Disposable); + + +/** + * Just bind the buffer if it's in the cache. Otherwise create + * the WebGL buffer, bind it, populate it, and add an entry to + * the cache. + * @param {number} target Target. + * @param {ol.webgl.Buffer} buf Buffer. + */ +ol.webgl.Context.prototype.bindBuffer = function(target, buf) { + var gl = this.getGL(); + var arr = buf.getArray(); + var bufferKey = String(ol.getUid(buf)); + if (bufferKey in this.bufferCache_) { + var bufferCacheEntry = this.bufferCache_[bufferKey]; + gl.bindBuffer(target, bufferCacheEntry.buffer); + } else { + var buffer = gl.createBuffer(); + gl.bindBuffer(target, buffer); + ol.DEBUG && console.assert(target == ol.webgl.ARRAY_BUFFER || + target == ol.webgl.ELEMENT_ARRAY_BUFFER, + 'target is supposed to be an ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER'); + var /** @type {ArrayBufferView} */ arrayBuffer; + if (target == ol.webgl.ARRAY_BUFFER) { + arrayBuffer = new Float32Array(arr); + } else if (target == ol.webgl.ELEMENT_ARRAY_BUFFER) { + arrayBuffer = this.hasOESElementIndexUint ? + new Uint32Array(arr) : new Uint16Array(arr); + } + gl.bufferData(target, arrayBuffer, buf.getUsage()); + this.bufferCache_[bufferKey] = { + buf: buf, + buffer: buffer + }; + } +}; + + +/** + * @param {ol.webgl.Buffer} buf Buffer. + */ +ol.webgl.Context.prototype.deleteBuffer = function(buf) { + var gl = this.getGL(); + var bufferKey = String(ol.getUid(buf)); + ol.DEBUG && console.assert(bufferKey in this.bufferCache_, + 'attempted to delete uncached buffer'); + var bufferCacheEntry = this.bufferCache_[bufferKey]; + if (!gl.isContextLost()) { + gl.deleteBuffer(bufferCacheEntry.buffer); + } + delete this.bufferCache_[bufferKey]; +}; + + +/** + * @inheritDoc + */ +ol.webgl.Context.prototype.disposeInternal = function() { + ol.events.unlistenAll(this.canvas_); + var gl = this.getGL(); + if (!gl.isContextLost()) { + var key; + for (key in this.bufferCache_) { + gl.deleteBuffer(this.bufferCache_[key].buffer); + } + for (key in this.programCache_) { + gl.deleteProgram(this.programCache_[key]); + } + for (key in this.shaderCache_) { + gl.deleteShader(this.shaderCache_[key]); + } + // delete objects for hit-detection + gl.deleteFramebuffer(this.hitDetectionFramebuffer_); + gl.deleteRenderbuffer(this.hitDetectionRenderbuffer_); + gl.deleteTexture(this.hitDetectionTexture_); + } +}; + + +/** + * @return {HTMLCanvasElement} Canvas. + */ +ol.webgl.Context.prototype.getCanvas = function() { + return this.canvas_; +}; + + +/** + * Get the WebGL rendering context + * @return {WebGLRenderingContext} The rendering context. + * @api + */ +ol.webgl.Context.prototype.getGL = function() { + return this.gl_; +}; + + +/** + * Get the frame buffer for hit detection. + * @return {WebGLFramebuffer} The hit detection frame buffer. + */ +ol.webgl.Context.prototype.getHitDetectionFramebuffer = function() { + if (!this.hitDetectionFramebuffer_) { + this.initHitDetectionFramebuffer_(); + } + return this.hitDetectionFramebuffer_; +}; + + +/** + * Get shader from the cache if it's in the cache. Otherwise, create + * the WebGL shader, compile it, and add entry to cache. + * @param {ol.webgl.Shader} shaderObject Shader object. + * @return {WebGLShader} Shader. + */ +ol.webgl.Context.prototype.getShader = function(shaderObject) { + var shaderKey = String(ol.getUid(shaderObject)); + if (shaderKey in this.shaderCache_) { + return this.shaderCache_[shaderKey]; + } else { + var gl = this.getGL(); + var shader = gl.createShader(shaderObject.getType()); + gl.shaderSource(shader, shaderObject.getSource()); + gl.compileShader(shader); + ol.DEBUG && console.assert( + gl.getShaderParameter(shader, ol.webgl.COMPILE_STATUS) || + gl.isContextLost(), + gl.getShaderInfoLog(shader) || 'illegal state, shader not compiled or context lost'); + this.shaderCache_[shaderKey] = shader; + return shader; + } +}; + + +/** + * Get the program from the cache if it's in the cache. Otherwise create + * the WebGL program, attach the shaders to it, and add an entry to the + * cache. + * @param {ol.webgl.Fragment} fragmentShaderObject Fragment shader. + * @param {ol.webgl.Vertex} vertexShaderObject Vertex shader. + * @return {WebGLProgram} Program. + */ +ol.webgl.Context.prototype.getProgram = function( + fragmentShaderObject, vertexShaderObject) { + var programKey = + ol.getUid(fragmentShaderObject) + '/' + ol.getUid(vertexShaderObject); + if (programKey in this.programCache_) { + return this.programCache_[programKey]; + } else { + var gl = this.getGL(); + var program = gl.createProgram(); + gl.attachShader(program, this.getShader(fragmentShaderObject)); + gl.attachShader(program, this.getShader(vertexShaderObject)); + gl.linkProgram(program); + ol.DEBUG && console.assert( + gl.getProgramParameter(program, ol.webgl.LINK_STATUS) || + gl.isContextLost(), + gl.getProgramInfoLog(program) || 'illegal state, shader not linked or context lost'); + this.programCache_[programKey] = program; + return program; + } +}; + + +/** + * FIXME empy description for jsdoc + */ +ol.webgl.Context.prototype.handleWebGLContextLost = function() { + ol.obj.clear(this.bufferCache_); + ol.obj.clear(this.shaderCache_); + ol.obj.clear(this.programCache_); + this.currentProgram_ = null; + this.hitDetectionFramebuffer_ = null; + this.hitDetectionTexture_ = null; + this.hitDetectionRenderbuffer_ = null; +}; + + +/** + * FIXME empy description for jsdoc + */ +ol.webgl.Context.prototype.handleWebGLContextRestored = function() { +}; + + +/** + * Creates a 1x1 pixel framebuffer for the hit-detection. + * @private + */ +ol.webgl.Context.prototype.initHitDetectionFramebuffer_ = function() { + var gl = this.gl_; + var framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + + var texture = ol.webgl.Context.createEmptyTexture(gl, 1, 1); + var renderbuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1, 1); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, + gl.RENDERBUFFER, renderbuffer); + + gl.bindTexture(gl.TEXTURE_2D, null); + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + this.hitDetectionFramebuffer_ = framebuffer; + this.hitDetectionTexture_ = texture; + this.hitDetectionRenderbuffer_ = renderbuffer; +}; + + +/** + * Use a program. If the program is already in use, this will return `false`. + * @param {WebGLProgram} program Program. + * @return {boolean} Changed. + * @api + */ +ol.webgl.Context.prototype.useProgram = function(program) { + if (program == this.currentProgram_) { + return false; + } else { + var gl = this.getGL(); + gl.useProgram(program); + this.currentProgram_ = program; + return true; + } +}; + + +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} The texture. + * @private + */ +ol.webgl.Context.createTexture_ = function(gl, opt_wrapS, opt_wrapT) { + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + if (opt_wrapS !== undefined) { + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S, opt_wrapS); + } + if (opt_wrapT !== undefined) { + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T, opt_wrapT); + } + + return texture; +}; + + +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {number} width Width. + * @param {number} height Height. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} The texture. + */ +ol.webgl.Context.createEmptyTexture = function( + gl, width, height, opt_wrapS, opt_wrapT) { + var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, + null); + + return texture; +}; + + +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} The texture. + */ +ol.webgl.Context.createTexture = function(gl, image, opt_wrapS, opt_wrapT) { + var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); + + return texture; +}; + +goog.provide('ol.render.webgl.ImageReplay'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.render.webgl.imagereplay.defaultshader'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); +goog.require('ol.webgl.Context'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + /** + * @type {number|undefined} + * @private + */ + this.anchorX_ = undefined; + + /** + * @type {number|undefined} + * @private + */ + this.anchorY_ = undefined; + + /** + * @type {Array.<number>} + * @private + */ + this.groupIndices_ = []; + + /** + * @type {Array.<number>} + * @private + */ + this.hitDetectionGroupIndices_ = []; + + /** + * @type {number|undefined} + * @private + */ + this.height_ = undefined; + + /** + * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} + * @private + */ + this.images_ = []; + + /** + * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} + * @private + */ + this.hitDetectionImages_ = []; + + /** + * @type {number|undefined} + * @private + */ + this.imageHeight_ = undefined; + + /** + * @type {number|undefined} + * @private + */ + this.imageWidth_ = undefined; + + /** + * @private + * @type {ol.render.webgl.imagereplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {number|undefined} + */ + this.opacity_ = undefined; + + /** + * @type {number|undefined} + * @private + */ + this.originX_ = undefined; + + /** + * @type {number|undefined} + * @private + */ + this.originY_ = undefined; + + /** + * @private + * @type {boolean|undefined} + */ + this.rotateWithView_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.rotation_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.scale_ = undefined; + + /** + * @type {Array.<WebGLTexture>} + * @private + */ + this.textures_ = []; + + /** + * @type {Array.<WebGLTexture>} + * @private + */ + this.hitDetectionTextures_ = []; + + /** + * @type {number|undefined} + * @private + */ + this.width_ = undefined; +}; +ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.Replay); + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other ImageReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + var textures = this.textures_; + var hitDetectionTextures = this.hitDetectionTextures_; + var gl = context.getGL(); + return function() { + if (!gl.isContextLost()) { + var i, ii; + for (i = 0, ii = textures.length; i < ii; ++i) { + gl.deleteTexture(textures[i]); + } + for (i = 0, ii = hitDetectionTextures.length; i < ii; ++i) { + gl.deleteTexture(hitDetectionTextures[i]); + } + } + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} My end. + * @private + */ +ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { + ol.DEBUG && console.assert(this.anchorX_ !== undefined, 'anchorX is defined'); + ol.DEBUG && console.assert(this.anchorY_ !== undefined, 'anchorY is defined'); + ol.DEBUG && console.assert(this.height_ !== undefined, 'height is defined'); + ol.DEBUG && console.assert(this.imageHeight_ !== undefined, + 'imageHeight is defined'); + ol.DEBUG && console.assert(this.imageWidth_ !== undefined, 'imageWidth is defined'); + ol.DEBUG && console.assert(this.opacity_ !== undefined, 'opacity is defined'); + ol.DEBUG && console.assert(this.originX_ !== undefined, 'originX is defined'); + ol.DEBUG && console.assert(this.originY_ !== undefined, 'originY is defined'); + ol.DEBUG && console.assert(this.rotateWithView_ !== undefined, + 'rotateWithView is defined'); + ol.DEBUG && console.assert(this.rotation_ !== undefined, 'rotation is defined'); + ol.DEBUG && console.assert(this.scale_ !== undefined, 'scale is defined'); + ol.DEBUG && console.assert(this.width_ !== undefined, 'width is defined'); + var anchorX = /** @type {number} */ (this.anchorX_); + var anchorY = /** @type {number} */ (this.anchorY_); + var height = /** @type {number} */ (this.height_); + var imageHeight = /** @type {number} */ (this.imageHeight_); + var imageWidth = /** @type {number} */ (this.imageWidth_); + var opacity = /** @type {number} */ (this.opacity_); + var originX = /** @type {number} */ (this.originX_); + var originY = /** @type {number} */ (this.originY_); + var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0; + // this.rotation_ is anti-clockwise, but rotation is clockwise + var rotation = /** @type {number} */ (-this.rotation_); + var scale = /** @type {number} */ (this.scale_); + var width = /** @type {number} */ (this.width_); + var cos = Math.cos(rotation); + var sin = Math.sin(rotation); + var numIndices = this.indices.length; + var numVertices = this.vertices.length; + var i, n, offsetX, offsetY, x, y; + for (i = offset; i < end; i += stride) { + x = flatCoordinates[i] - this.origin[0]; + y = flatCoordinates[i + 1] - this.origin[1]; + + // There are 4 vertices per [x, y] point, one for each corner of the + // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if + // WebGL supported Geometry Shaders (which can emit new vertices), but that + // is not currently the case. + // + // And each vertex includes 8 values: the x and y coordinates, the x and + // y offsets used to calculate the position of the corner, the u and + // v texture coordinates for the corner, the opacity, and whether the + // the image should be rotated with the view (rotateWithView). + + n = numVertices / 8; + + // bottom-left corner + offsetX = -scale * anchorX; + offsetY = -scale * (height - anchorY); + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = originX / imageWidth; + this.vertices[numVertices++] = (originY + height) / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; + + // bottom-right corner + offsetX = scale * (width - anchorX); + offsetY = -scale * (height - anchorY); + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = (originX + width) / imageWidth; + this.vertices[numVertices++] = (originY + height) / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; + + // top-right corner + offsetX = scale * (width - anchorX); + offsetY = scale * anchorY; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = (originX + width) / imageWidth; + this.vertices[numVertices++] = originY / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; + + // top-left corner + offsetX = -scale * anchorX; + offsetY = scale * anchorY; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = originX / imageWidth; + this.vertices[numVertices++] = originY / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; + + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 3; + } + + return numVertices; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + var flatCoordinates = multiPointGeometry.getFlatCoordinates(); + var stride = multiPointGeometry.getStride(); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + var flatCoordinates = pointGeometry.getFlatCoordinates(); + var stride = pointGeometry.getStride(); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.finish = function(context) { + var gl = context.getGL(); + + this.groupIndices_.push(this.indices.length); + ol.DEBUG && console.assert(this.images_.length === this.groupIndices_.length, + 'number of images and groupIndices match'); + this.hitDetectionGroupIndices_.push(this.indices.length); + ol.DEBUG && console.assert(this.hitDetectionImages_.length === + this.hitDetectionGroupIndices_.length, + 'number of hitDetectionImages and hitDetectionGroupIndices match'); + + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + var indices = this.indices; + var bits = context.hasOESElementIndexUint ? 32 : 16; + ol.DEBUG && console.assert(indices[indices.length - 1] < Math.pow(2, bits), + 'Too large element index detected [%s] (OES_element_index_uint "%s")', + indices[indices.length - 1], context.hasOESElementIndexUint); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(indices); + + // create textures + /** @type {Object.<string, WebGLTexture>} */ + var texturePerImage = {}; + + this.createTextures_(this.textures_, this.images_, texturePerImage, gl); + ol.DEBUG && console.assert(this.textures_.length === this.groupIndices_.length, + 'number of textures and groupIndices match'); + + this.createTextures_(this.hitDetectionTextures_, this.hitDetectionImages_, + texturePerImage, gl); + ol.DEBUG && console.assert(this.hitDetectionTextures_.length === + this.hitDetectionGroupIndices_.length, + 'number of hitDetectionTextures and hitDetectionGroupIndices match'); + + this.anchorX_ = undefined; + this.anchorY_ = undefined; + this.height_ = undefined; + this.images_ = null; + this.hitDetectionImages_ = null; + this.imageHeight_ = undefined; + this.imageWidth_ = undefined; + this.indices = null; + this.opacity_ = undefined; + this.originX_ = undefined; + this.originY_ = undefined; + this.rotateWithView_ = undefined; + this.rotation_ = undefined; + this.scale_ = undefined; + this.vertices = null; + this.width_ = undefined; +}; + + +/** + * @private + * @param {Array.<WebGLTexture>} textures Textures. + * @param {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} images + * Images. + * @param {Object.<string, WebGLTexture>} texturePerImage Texture cache. + * @param {WebGLRenderingContext} gl Gl. + */ +ol.render.webgl.ImageReplay.prototype.createTextures_ = function(textures, images, texturePerImage, gl) { + ol.DEBUG && console.assert(textures.length === 0, + 'upon creation, textures is empty'); + + var texture, image, uid, i; + var ii = images.length; + for (i = 0; i < ii; ++i) { + image = images[i]; + + uid = ol.getUid(image).toString(); + if (uid in texturePerImage) { + texture = texturePerImage[uid]; + } else { + texture = ol.webgl.Context.createTexture( + gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE); + texturePerImage[uid] = texture; + } + textures[i] = texture; + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader = ol.render.webgl.imagereplay.defaultshader.fragment; + var vertexShader = ol.render.webgl.imagereplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.imagereplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + // use the program (FIXME: use the return value) + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 32, 0); + + gl.enableVertexAttribArray(locations.a_offsets); + gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT, + false, 32, 8); + + gl.enableVertexAttribArray(locations.a_texCoord); + gl.vertexAttribPointer(locations.a_texCoord, 2, ol.webgl.FLOAT, + false, 32, 16); + + gl.enableVertexAttribArray(locations.a_opacity); + gl.vertexAttribPointer(locations.a_opacity, 1, ol.webgl.FLOAT, + false, 32, 24); + + gl.enableVertexAttribArray(locations.a_rotateWithView); + gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT, + false, 32, 28); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_offsets); + gl.disableVertexAttribArray(locations.a_texCoord); + gl.disableVertexAttribArray(locations.a_opacity); + gl.disableVertexAttribArray(locations.a_rotateWithView); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + var textures = hitDetection ? this.hitDetectionTextures_ : this.textures_; + var groupIndices = hitDetection ? this.hitDetectionGroupIndices_ : this.groupIndices_; + ol.DEBUG && console.assert(textures.length === groupIndices.length, + 'number of textures and groupIndeces match'); + + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_( + gl, context, skippedFeaturesHash, textures, groupIndices); + } else { + var i, ii, start; + for (i = 0, ii = textures.length, start = 0; i < ii; ++i) { + gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); + var end = groupIndices[i]; + this.drawElements(gl, context, start, end); + start = end; + } + } +}; + + +/** + * Draw the replay while paying attention to skipped features. + * + * This functions creates groups of features that can be drawn to together, + * so that the number of `drawElements` calls is minimized. + * + * For example given the following texture groups: + * + * Group 1: A B C + * Group 2: D [E] F G + * + * If feature E should be skipped, the following `drawElements` calls will be + * made: + * + * drawElements with feature A, B and C + * drawElements with feature D + * drawElements with feature F and G + * + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {Array.<WebGLTexture>} textures Textures. + * @param {Array.<number>} groupIndices Texture group indices. + */ +ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash, textures, + groupIndices) { + var featureIndex = 0; + + var i, ii; + for (i = 0, ii = textures.length; i < ii; ++i) { + gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); + var groupStart = (i > 0) ? groupIndices[i - 1] : 0; + var groupEnd = groupIndices[i]; + + var start = groupStart; + var end = groupStart; + while (featureIndex < this.startIndices.length && + this.startIndices[featureIndex] <= groupEnd) { + var feature = this.startIndicesFeature[featureIndex]; + + var featureUid = ol.getUid(feature).toString(); + if (skippedFeaturesHash[featureUid] !== undefined) { + // feature should be skipped + if (start !== end) { + // draw the features so far + this.drawElements(gl, context, start, end); + } + // continue with the next feature + start = (featureIndex === this.startIndices.length - 1) ? + groupEnd : this.startIndices[featureIndex + 1]; + end = start; + } else { + // the feature is not skipped, augment the end index + end = (featureIndex === this.startIndices.length - 1) ? + groupEnd : this.startIndices[featureIndex + 1]; + } + featureIndex++; + } + + if (start !== end) { + // draw the remaining features (in case there was no skipped feature + // in this texture group, all features of a group are drawn together) + this.drawElements(gl, context, start, end); + } + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + ol.DEBUG && console.assert(this.hitDetectionTextures_.length === + this.hitDetectionGroupIndices_.length, + 'number of hitDetectionTextures and hitDetectionGroupIndices match'); + + var i, groupStart, start, end, feature, featureUid; + var featureIndex = this.startIndices.length - 1; + for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) { + gl.bindTexture(ol.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]); + groupStart = (i > 0) ? this.hitDetectionGroupIndices_[i - 1] : 0; + end = this.hitDetectionGroupIndices_[i]; + + // draw all features for this texture group + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array<number>} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + if (result) { + return result; + } + } + + end = start; + featureIndex--; + } + } + return undefined; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { + var anchor = imageStyle.getAnchor(); + var image = imageStyle.getImage(1); + var imageSize = imageStyle.getImageSize(); + var hitDetectionImage = imageStyle.getHitDetectionImage(1); + var hitDetectionImageSize = imageStyle.getHitDetectionImageSize(); + var opacity = imageStyle.getOpacity(); + var origin = imageStyle.getOrigin(); + var rotateWithView = imageStyle.getRotateWithView(); + var rotation = imageStyle.getRotation(); + var size = imageStyle.getSize(); + var scale = imageStyle.getScale(); + ol.DEBUG && console.assert(anchor, 'imageStyle anchor is not null'); + ol.DEBUG && console.assert(image, 'imageStyle image is not null'); + ol.DEBUG && console.assert(imageSize, + 'imageStyle imageSize is not null'); + ol.DEBUG && console.assert(hitDetectionImage, + 'imageStyle hitDetectionImage is not null'); + ol.DEBUG && console.assert(hitDetectionImageSize, + 'imageStyle hitDetectionImageSize is not null'); + ol.DEBUG && console.assert(opacity !== undefined, 'imageStyle opacity is defined'); + ol.DEBUG && console.assert(origin, 'imageStyle origin is not null'); + ol.DEBUG && console.assert(rotateWithView !== undefined, + 'imageStyle rotateWithView is defined'); + ol.DEBUG && console.assert(rotation !== undefined, 'imageStyle rotation is defined'); + ol.DEBUG && console.assert(size, 'imageStyle size is not null'); + ol.DEBUG && console.assert(scale !== undefined, 'imageStyle scale is defined'); + + var currentImage; + if (this.images_.length === 0) { + this.images_.push(image); + } else { + currentImage = this.images_[this.images_.length - 1]; + if (ol.getUid(currentImage) != ol.getUid(image)) { + this.groupIndices_.push(this.indices.length); + ol.DEBUG && console.assert(this.groupIndices_.length === this.images_.length, + 'number of groupIndices and images match'); + this.images_.push(image); + } + } + + if (this.hitDetectionImages_.length === 0) { + this.hitDetectionImages_.push(hitDetectionImage); + } else { + currentImage = + this.hitDetectionImages_[this.hitDetectionImages_.length - 1]; + if (ol.getUid(currentImage) != ol.getUid(hitDetectionImage)) { + this.hitDetectionGroupIndices_.push(this.indices.length); + ol.DEBUG && console.assert(this.hitDetectionGroupIndices_.length === + this.hitDetectionImages_.length, + 'number of hitDetectionGroupIndices and hitDetectionImages match'); + this.hitDetectionImages_.push(hitDetectionImage); + } + } + + this.anchorX_ = anchor[0]; + this.anchorY_ = anchor[1]; + this.height_ = size[1]; + this.imageHeight_ = imageSize[1]; + this.imageWidth_ = imageSize[0]; + this.opacity_ = opacity; + this.originX_ = origin[0]; + this.originY_ = origin[1]; + this.rotation_ = rotation; + this.rotateWithView_ = rotateWithView; + this.scale_ = scale; + this.width_ = size[0]; +}; + +goog.provide('ol.geom.flat.topology'); + +goog.require('ol.geom.flat.area'); + +/** + * Check if the linestring is a boundary. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {boolean} The linestring is a boundary. + */ +ol.geom.flat.topology.lineStringIsClosed = function(flatCoordinates, offset, end, stride) { + var lastCoord = end - stride; + if (flatCoordinates[offset] === flatCoordinates[lastCoord] && + flatCoordinates[offset + 1] === flatCoordinates[lastCoord + 1] && (end - offset) / stride > 3) { + return !!ol.geom.flat.area.linearRing(flatCoordinates, offset, end, stride); + } + return false; +}; + +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.linestringreplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.linestringreplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\nuniform vec2 u_size;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n if (v_round > 0.0) {\n vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_roundVertex.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth * u_pixelRatio) {\n discard;\n }\n }\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec2 b;varying float c;uniform float m;uniform vec4 n;uniform vec2 o;uniform float p;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*o.x*p,(b.y+1.0)/2.0*o.y*p);if(length(windowCoords-gl_FragCoord.xy)>c*p){discard;}} gl_FragColor=n;float alpha=n.a*m;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.linestringreplay.defaultshader.fragment = new ol.render.webgl.linestringreplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.linestringreplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nbool nearlyEquals(in float value, in float ref) {\n float epsilon = 0.000000000001;\n return value >= ref - epsilon && value <= ref + epsilon;\n}\n\nvoid alongNormal(out vec2 offset, in vec2 nextP, in float turnDir, in float direction) {\n vec2 dirVect = nextP - a_position;\n vec2 normal = normalize(vec2(-turnDir * dirVect.y, turnDir * dirVect.x));\n offset = u_lineWidth / 2.0 * normal * direction;\n}\n\nvoid miterUp(out vec2 offset, out float round, in bool isRound, in float direction) {\n float halfWidth = u_lineWidth / 2.0;\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n float miterLength = abs(halfWidth / dot(normal, tmpNormal));\n offset = normal * direction * miterLength;\n round = 0.0;\n if (isRound) {\n round = 1.0;\n } else if (miterLength > u_miterLimit + u_lineWidth) {\n offset = halfWidth * tmpNormal * direction;\n }\n}\n\nbool miterDown(out vec2 offset, in vec4 projPos, in mat4 offsetMatrix, in float direction) {\n bool degenerate = false;\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n vec2 dirVect = a_lastPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n float halfWidth = u_lineWidth / 2.0;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0.0, 1.0);\n } else {\n shortOffset = tmpNormal * direction * halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0.0, 1.0);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0.0, 1.0) + offsetMatrix * vec4(longOffset, 0.0, 0.0);\n vec4 p2 = projPos + offsetMatrix * vec4(longOffset, 0.0, 0.0);\n vec4 p3 = shortProjVertex + offsetMatrix * vec4(-shortOffset, 0.0, 0.0);\n vec4 p4 = shortProjVertex + offsetMatrix * vec4(shortOffset, 0.0, 0.0);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n float epsilon = 0.000000000001;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n shortProjVertex.x = p1.x + firstU * (p2.x - p1.x);\n shortProjVertex.y = p1.y + firstU * (p2.y - p1.y);\n offset = shortProjVertex.xy;\n degenerate = true;\n } else {\n float miterLength = abs(halfWidth / dot(normal, tmpNormal));\n offset = normal * direction * miterLength;\n }\n return degenerate;\n}\n\nvoid squareCap(out vec2 offset, out float round, in bool isRound, in vec2 nextP,\n in float turnDir, in float direction) {\n round = 0.0;\n vec2 dirVect = a_position - nextP;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(turnDir * firstNormal.y * direction, -turnDir * firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n vec2 normal = vec2(turnDir * hypotenuse.y * direction, -turnDir * hypotenuse.x * direction);\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (isRound) {\n round = 1.0;\n }\n}\n\nvoid main(void) {\n bool degenerate = false;\n float direction = float(sign(a_direction));\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n vec2 offset;\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0.0, 1.0);\n bool round = nearlyEquals(mod(a_direction, 2.0), 0.0);\n\n v_round = 0.0;\n v_halfWidth = u_lineWidth / 2.0;\n v_roundVertex = projPos.xy;\n\n if (nearlyEquals(mod(a_direction, 3.0), 0.0) || nearlyEquals(mod(a_direction, 17.0), 0.0)) {\n alongNormal(offset, a_nextPos, 1.0, direction);\n } else if (nearlyEquals(mod(a_direction, 5.0), 0.0) || nearlyEquals(mod(a_direction, 13.0), 0.0)) {\n alongNormal(offset, a_lastPos, -1.0, direction);\n } else if (nearlyEquals(mod(a_direction, 23.0), 0.0)) {\n miterUp(offset, v_round, round, direction);\n } else if (nearlyEquals(mod(a_direction, 19.0), 0.0)) {\n degenerate = miterDown(offset, projPos, offsetMatrix, direction);\n } else if (nearlyEquals(mod(a_direction, 7.0), 0.0)) {\n squareCap(offset, v_round, round, a_nextPos, 1.0, direction);\n } else if (nearlyEquals(mod(a_direction, 11.0), 0.0)) {\n squareCap(offset, v_round, round, a_lastPos, -1.0, direction);\n }\n if (!degenerate) {\n vec4 offsets = offsetMatrix * vec4(offset, 0.0, 0.0);\n gl_Position = projPos + offsets;\n } else {\n gl_Position = vec4(offset, 0.0, 1.0);\n }\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;bool nearlyEquals(in float value,in float ref){float epsilon=0.000000000001;return value>=ref-epsilon&&value<=ref+epsilon;}void alongNormal(out vec2 offset,in vec2 nextP,in float turnDir,in float direction){vec2 dirVect=nextP-e;vec2 normal=normalize(vec2(-turnDir*dirVect.y,turnDir*dirVect.x));offset=k/2.0*normal*direction;}void miterUp(out vec2 offset,out float round,in bool isRound,in float direction){float halfWidth=k/2.0;vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));float miterLength=abs(halfWidth/dot(normal,tmpNormal));offset=normal*direction*miterLength;round=0.0;if(isRound){round=1.0;}else if(miterLength>l+k){offset=halfWidth*tmpNormal*direction;}} bool miterDown(out vec2 offset,in vec4 projPos,in mat4 offsetMatrix,in float direction){bool degenerate=false;vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);vec2 dirVect=d-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;float halfWidth=k/2.0;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*halfWidth;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*halfWidth;longVertex=f;shortProjVertex=h*vec4(d,0.0,1.0);}else{shortOffset=tmpNormal*direction*halfWidth;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*halfWidth;longVertex=d;shortProjVertex=h*vec4(f,0.0,1.0);}vec4 p1=h*vec4(longVertex,0.0,1.0)+offsetMatrix*vec4(longOffset,0.0,0.0);vec4 p2=projPos+offsetMatrix*vec4(longOffset,0.0,0.0);vec4 p3=shortProjVertex+offsetMatrix*vec4(-shortOffset,0.0,0.0);vec4 p4=shortProjVertex+offsetMatrix*vec4(shortOffset,0.0,0.0);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;float epsilon=0.000000000001;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){shortProjVertex.x=p1.x+firstU*(p2.x-p1.x);shortProjVertex.y=p1.y+firstU*(p2.y-p1.y);offset=shortProjVertex.xy;degenerate=true;}else{float miterLength=abs(halfWidth/dot(normal,tmpNormal));offset=normal*direction*miterLength;}return degenerate;}void squareCap(out vec2 offset,out float round,in bool isRound,in vec2 nextP,in float turnDir,in float direction){round=0.0;vec2 dirVect=e-nextP;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(turnDir*firstNormal.y*direction,-turnDir*firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);vec2 normal=vec2(turnDir*hypotenuse.y*direction,-turnDir*hypotenuse.x*direction);float length=sqrt(c*c*2.0);offset=normal*length;if(isRound){round=1.0;}} void main(void){bool degenerate=false;float direction=float(sign(g));mat4 offsetMatrix=i*j;vec2 offset;vec4 projPos=h*vec4(e,0.0,1.0);bool round=nearlyEquals(mod(g,2.0),0.0);a=0.0;c=k/2.0;b=projPos.xy;if(nearlyEquals(mod(g,3.0),0.0)||nearlyEquals(mod(g,17.0),0.0)){alongNormal(offset,f,1.0,direction);}else if(nearlyEquals(mod(g,5.0),0.0)||nearlyEquals(mod(g,13.0),0.0)){alongNormal(offset,d,-1.0,direction);}else if(nearlyEquals(mod(g,23.0),0.0)){miterUp(offset,a,round,direction);}else if(nearlyEquals(mod(g,19.0),0.0)){degenerate=miterDown(offset,projPos,offsetMatrix,direction);}else if(nearlyEquals(mod(g,7.0),0.0)){squareCap(offset,a,round,f,1.0,direction);}else if(nearlyEquals(mod(g,11.0),0.0)){squareCap(offset,a,round,d,-1.0,direction);}if(!degenerate){vec4 offsets=offsetMatrix*vec4(offset,0.0,0.0);gl_Position=projPos+offsets;}else{gl_Position=vec4(offset,0.0,1.0);}}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.linestringreplay.defaultshader.vertex = new ol.render.webgl.linestringreplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.linestringreplay.defaultshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_color = gl.getUniformLocation( + program, ol.DEBUG ? 'u_color' : 'n'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_lineWidth = gl.getUniformLocation( + program, ol.DEBUG ? 'u_lineWidth' : 'k'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_miterLimit = gl.getUniformLocation( + program, ol.DEBUG ? 'u_miterLimit' : 'l'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'j'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'i'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'm'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_pixelRatio = gl.getUniformLocation( + program, ol.DEBUG ? 'u_pixelRatio' : 'p'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'h'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_size = gl.getUniformLocation( + program, ol.DEBUG ? 'u_size' : 'o'); + + /** + * @type {number} + */ + this.a_direction = gl.getAttribLocation( + program, ol.DEBUG ? 'a_direction' : 'g'); + + /** + * @type {number} + */ + this.a_lastPos = gl.getAttribLocation( + program, ol.DEBUG ? 'a_lastPos' : 'd'); + + /** + * @type {number} + */ + this.a_nextPos = gl.getAttribLocation( + program, ol.DEBUG ? 'a_nextPos' : 'f'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'e'); +}; + +goog.provide('ol.render.webgl.LineStringReplay'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.color'); +goog.require('ol.extent'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.geom.flat.topology'); +goog.require('ol.obj'); +goog.require('ol.render.webgl'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl.linestringreplay.defaultshader'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + /** + * @private + * @type {ol.render.webgl.linestringreplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.<Array.<?>>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.<number>} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {{strokeColor: (Array.<number>|null), + * lineCap: (string|undefined), + * lineDash: Array.<number>, + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined), + * changed: boolean}|null} + */ + this.state_ = { + strokeColor: null, + lineCap: undefined, + lineDash: null, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined, + changed: false + }; + +}; +ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); + + +/** + * Draw one segment. + * @private + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + */ +ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { + + var i, ii; + var numVertices = this.vertices.length; + var numIndices = this.indices.length; + //To save a vertex, the direction of a point is a product of the sign (1 or -1), a prime from + //ol.render.webgl.lineStringInstruction, and a rounding factor (1 or 2). If the product is even, + //we round it. If it is odd, we don't. + var lineJoin = this.state_.lineJoin === 'bevel' ? 0 : + this.state_.lineJoin === 'miter' ? 1 : 2; + var lineCap = this.state_.lineCap === 'butt' ? 0 : + this.state_.lineCap === 'square' ? 1 : 2; + var closed = ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, offset, end, stride); + var startCoords, sign, n; + var lastIndex = numIndices; + var lastSign = 1; + //We need the adjacent vertices to define normals in joins. p0 = last, p1 = current, p2 = next. + var p0, p1, p2; + + for (i = offset, ii = end; i < ii; i += stride) { + + n = numVertices / 7; + + p0 = p1; + p1 = p2 || [flatCoordinates[i], flatCoordinates[i + 1]]; + //First vertex. + if (i === offset) { + p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; + if (end - offset === stride * 2 && ol.array.equals(p1, p2)) { + break; + } + if (closed) { + //A closed line! Complete the circle. + p0 = [flatCoordinates[end - stride * 2], + flatCoordinates[end - stride * 2 + 1]]; + + startCoords = p2; + } else { + //Add the first two/four vertices. + + if (lineCap) { + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n + 2; + + } + + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + + lastIndex = numVertices / 7 - 1; + + continue; + } + } else if (i === end - stride) { + //Last vertex. + if (closed) { + //Same as the first vertex. + p2 = startCoords; + break; + } else { + //For the compiler not to complain. This will never be [0, 0]. + ol.DEBUG && console.assert(p0, 'p0 should be defined'); + p0 = p0 || [0, 0]; + + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); + + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastIndex - 1; + this.indices[numIndices++] = lastIndex; + + this.indices[numIndices++] = lastIndex; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n; + + if (lineCap) { + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n + 2; + + } + + break; + } + } else { + p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; + } + + // We group CW and straight lines, thus the not so inituitive CCW checking function. + sign = ol.render.webgl.triangleIsCounterClockwise(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]) + ? -1 : 1; + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.BEVEL_SECOND * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + + if (i > offset) { + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastIndex - 1; + this.indices[numIndices++] = lastIndex; + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; + } + + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 1; + + lastIndex = n + 2; + lastSign = sign; + + //Add miter + if (lineJoin) { + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.MITER_TOP * lineJoin, numVertices); + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n; + } + } + + if (closed) { + //Link the last triangle/rhombus to the first one. + //n will never be numVertices / 7 here. However, the compiler complains otherwise. + ol.DEBUG && console.assert(n, 'n should be defined'); + n = n || numVertices / 7; + sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) + ? 1 : -1; + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastIndex - 1; + this.indices[numIndices++] = lastIndex; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; + } +}; + +/** + * @param {Array.<number>} p0 Last coordinates. + * @param {Array.<number>} p1 Current coordinates. + * @param {Array.<number>} p2 Next coordinates. + * @param {number} product Sign, instruction, and rounding product. + * @param {number} numVertices Vertex counter. + * @return {number} Vertex counter. + * @private + */ +ol.render.webgl.LineStringReplay.prototype.addVertices_ = function(p0, p1, p2, product, numVertices) { + this.vertices[numVertices++] = p0[0]; + this.vertices[numVertices++] = p0[1]; + this.vertices[numVertices++] = p1[0]; + this.vertices[numVertices++] = p1[1]; + this.vertices[numVertices++] = p2[0]; + this.vertices[numVertices++] = p2[1]; + this.vertices[numVertices++] = product; + + return numVertices; +}; + +/** + * Check if the linestring can be drawn (i. e. valid). + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {boolean} The linestring can be drawn. + * @private + */ +ol.render.webgl.LineStringReplay.prototype.isValid_ = function(flatCoordinates, offset, end, stride) { + var range = end - offset; + if (range < stride * 2) { + return false; + } else if (range === stride * 2) { + var firstP = [flatCoordinates[offset], flatCoordinates[offset + 1]]; + var lastP = [flatCoordinates[offset + stride], flatCoordinates[offset + stride + 1]]; + return !ol.array.equals(firstP, lastP); + } + + return true; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) { + var flatCoordinates = lineStringGeometry.getFlatCoordinates(); + var stride = lineStringGeometry.getStride(); + if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + if (this.state_.changed) { + this.styleIndices_.push(this.indices.length); + this.state_.changed = false; + } + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) { + var indexCount = this.indices.length; + var lineStringGeometries = multiLineStringGeometry.getLineStrings(); + var i, ii; + for (i = 0, ii = lineStringGeometries.length; i < ii; ++i) { + var flatCoordinates = lineStringGeometries[i].getFlatCoordinates(); + var stride = lineStringGeometries[i].getStride(); + if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + } + } + if (this.indices.length > indexCount) { + this.startIndices.push(indexCount); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(indexCount); + this.state_.changed = false; + } + } +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<Array.<number>>} holeFlatCoordinates Hole flat coordinates. + * @param {number} stride Stride. + */ +ol.render.webgl.LineStringReplay.prototype.drawPolygonCoordinates = function( + flatCoordinates, holeFlatCoordinates, stride) { + if (!ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, 0, + flatCoordinates.length, stride)) { + flatCoordinates.push(flatCoordinates[0]); + flatCoordinates.push(flatCoordinates[1]); + } + this.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); + if (holeFlatCoordinates.length) { + var i, ii; + for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { + if (!ol.geom.flat.topology.lineStringIsClosed(holeFlatCoordinates[i], 0, + holeFlatCoordinates[i].length, stride)) { + holeFlatCoordinates[i].push(holeFlatCoordinates[i][0]); + holeFlatCoordinates[i].push(holeFlatCoordinates[i][1]); + } + this.drawCoordinates_(holeFlatCoordinates[i], 0, + holeFlatCoordinates[i].length, stride); + } + } +}; + + +/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {number=} opt_index Index count. + */ +ol.render.webgl.LineStringReplay.prototype.setPolygonStyle = function(feature, opt_index) { + var index = opt_index === undefined ? this.indices.length : opt_index; + this.startIndices.push(index); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(index); + this.state_.changed = false; + } +}; + + +/** + * @return {number} Current index. + */ +ol.render.webgl.LineStringReplay.prototype.getCurrentIndex = function() { + return this.indices.length; +}; + + +/** + * @inheritDoc + **/ +ol.render.webgl.LineStringReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); + + this.startIndices.push(this.indices.length); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } + + this.vertices = null; + this.indices = null; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other LineStringReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, 'verticesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.linestringreplay.defaultshader.fragment; + vertexShader = ol.render.webgl.linestringreplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.linestringreplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_lastPos); + gl.vertexAttribPointer(locations.a_lastPos, 2, ol.webgl.FLOAT, + false, 28, 0); + + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 28, 8); + + gl.enableVertexAttribArray(locations.a_nextPos); + gl.vertexAttribPointer(locations.a_nextPos, 2, ol.webgl.FLOAT, + false, 28, 16); + + gl.enableVertexAttribArray(locations.a_direction); + gl.vertexAttribPointer(locations.a_direction, 1, ol.webgl.FLOAT, + false, 28, 24); + + // Enable renderer specific uniforms. + gl.uniform2fv(locations.u_size, size); + gl.uniform1f(locations.u_pixelRatio, pixelRatio); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_lastPos); + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_nextPos); + gl.disableVertexAttribArray(locations.a_direction); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + //Save GL parameters. + var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); + var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); + + if (!hitDetection) { + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.depthFunc(gl.NOTEQUAL); + } + + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); + } else { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, start, end, nextStyle; + end = this.startIndices[this.startIndices.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); + end = start; + } + } + if (!hitDetection) { + gl.disable(gl.DEPTH_TEST); + gl.clear(gl.DEPTH_BUFFER_BIT); + //Restore GL parameters. + gl.depthMask(tmpDepthMask); + gl.depthFunc(tmpDepthFunc); + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.LineStringReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices.length - 2; + end = start = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + featureStart = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); + } + start = end = groupStart; + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices.length - 2; + end = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array<number>} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; + } + } + return undefined; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.<number>} color Color. + * @param {number} lineWidth Line width. + * @param {number} miterLimit Miter limit. + */ +ol.render.webgl.LineStringReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth, miterLimit) { + gl.uniform4fv(this.defaultLocations_.u_color, color); + gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth); + gl.uniform1f(this.defaultLocations_.u_miterLimit, miterLimit); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + var strokeStyleLineCap = strokeStyle.getLineCap(); + this.state_.lineCap = strokeStyleLineCap !== undefined ? + strokeStyleLineCap : ol.render.webgl.defaultLineCap; + var strokeStyleLineDash = strokeStyle.getLineDash(); + this.state_.lineDash = strokeStyleLineDash ? + strokeStyleLineDash : ol.render.webgl.defaultLineDash; + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + this.state_.lineJoin = strokeStyleLineJoin !== undefined ? + strokeStyleLineJoin : ol.render.webgl.defaultLineJoin; + var strokeStyleColor = strokeStyle.getColor(); + if (!(strokeStyleColor instanceof CanvasGradient) && + !(strokeStyleColor instanceof CanvasPattern)) { + strokeStyleColor = ol.color.asArray(strokeStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultStrokeStyle; + } else { + strokeStyleColor = ol.render.webgl.defaultStrokeStyle; + } + var strokeStyleWidth = strokeStyle.getWidth(); + strokeStyleWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.webgl.defaultLineWidth; + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + strokeStyleMiterLimit = strokeStyleMiterLimit !== undefined ? + strokeStyleMiterLimit : ol.render.webgl.defaultMiterLimit; + if (!this.state_.strokeColor || !ol.array.equals(this.state_.strokeColor, strokeStyleColor) || + this.state_.lineWidth !== strokeStyleWidth || this.state_.miterLimit !== strokeStyleMiterLimit) { + this.state_.changed = true; + this.state_.strokeColor = strokeStyleColor; + this.state_.lineWidth = strokeStyleWidth; + this.state_.miterLimit = strokeStyleMiterLimit; + this.styles_.push([strokeStyleColor, strokeStyleWidth, strokeStyleMiterLimit]); + } +}; + +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.polygonreplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nuniform vec4 u_color;\nuniform float u_opacity;\n\nvoid main(void) {\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform vec4 e;uniform float f;void main(void){gl_FragColor=e;float alpha=e.a*f;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.polygonreplay.defaultshader.fragment = new ol.render.webgl.polygonreplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0);\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){gl_Position=b*vec4(a,0.0,1.0);}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.polygonreplay.defaultshader.vertex = new ol.render.webgl.polygonreplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_color = gl.getUniformLocation( + program, ol.DEBUG ? 'u_color' : 'e'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'd'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'c'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'f'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'b'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'a'); +}; + +goog.provide('ol.structs.LinkedList'); + +/** + * Creates an empty linked list structure. + * + * @constructor + * @struct + * @param {boolean=} opt_circular The last item is connected to the first one, + * and the first item to the last one. Default is true. + */ +ol.structs.LinkedList = function(opt_circular) { + + /** + * @private + * @type {ol.LinkedListItem|undefined} + */ + this.first_ = undefined; + + /** + * @private + * @type {ol.LinkedListItem|undefined} + */ + this.last_ = undefined; + + /** + * @private + * @type {ol.LinkedListItem|undefined} + */ + this.head_ = undefined; + + /** + * @private + * @type {boolean} + */ + this.circular_ = opt_circular === undefined ? true : opt_circular; + + /** + * @private + * @type {number} + */ + this.length_ = 0; +}; + +/** + * Inserts an item into the linked list right after the current one. + * + * @param {?} data Item data. + */ +ol.structs.LinkedList.prototype.insertItem = function(data) { + + /** @type {ol.LinkedListItem} */ + var item = { + prev: undefined, + next: undefined, + data: data + }; + + var head = this.head_; + + //Initialize the list. + if (!head) { + this.first_ = item; + this.last_ = item; + if (this.circular_) { + item.next = item; + item.prev = item; + } + } else { + //Link the new item to the adjacent ones. + var next = head.next; + item.prev = head; + item.next = next; + head.next = item; + if (next) { + next.prev = item; + } + + if (head === this.last_) { + this.last_ = item; + } + } + this.head_ = item; + this.length_++; +}; + +/** + * Removes the current item from the list. Sets the cursor to the next item, + * if possible. + */ +ol.structs.LinkedList.prototype.removeItem = function() { + var head = this.head_; + if (head) { + var next = head.next; + var prev = head.prev; + if (next) { + next.prev = prev; + } + if (prev) { + prev.next = next; + } + this.head_ = next || prev; + + if (this.first_ === this.last_) { + this.head_ = undefined; + this.first_ = undefined; + this.last_ = undefined; + } else if (this.first_ === head) { + this.first_ = this.head_; + } else if (this.last_ === head) { + this.last_ = prev ? this.head_.prev : this.head_; + } + this.length_--; + } +}; + +/** + * Sets the cursor to the first item, and returns the associated data. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.firstItem = function() { + this.head_ = this.first_; + if (this.head_) { + return this.head_.data; + } + return undefined; +}; + +/** +* Sets the cursor to the last item, and returns the associated data. +* +* @return {?} Item data. +*/ +ol.structs.LinkedList.prototype.lastItem = function() { + this.head_ = this.last_; + if (this.head_) { + return this.head_.data; + } + return undefined; +}; + +/** + * Sets the cursor to the next item, and returns the associated data. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.nextItem = function() { + if (this.head_ && this.head_.next) { + this.head_ = this.head_.next; + return this.head_.data; + } + return undefined; +}; + +/** + * Returns the next item's data without moving the cursor. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.getNextItem = function() { + if (this.head_ && this.head_.next) { + return this.head_.next.data; + } + return undefined; +}; + +/** + * Sets the cursor to the previous item, and returns the associated data. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.prevItem = function() { + if (this.head_ && this.head_.prev) { + this.head_ = this.head_.prev; + return this.head_.data; + } + return undefined; +}; + +/** + * Returns the previous item's data without moving the cursor. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.getPrevItem = function() { + if (this.head_ && this.head_.prev) { + return this.head_.prev.data; + } + return undefined; +}; + +/** + * Returns the current item's data. + * + * @return {?} Item data. + */ +ol.structs.LinkedList.prototype.getCurrItem = function() { + if (this.head_) { + return this.head_.data; + } + return undefined; +}; + +/** + * Sets the first item of the list. This only works for circular lists, and sets + * the last item accordingly. + */ +ol.structs.LinkedList.prototype.setFirstItem = function() { + if (this.circular_ && this.head_) { + this.first_ = this.head_; + this.last_ = this.head_.prev; + } +}; + +/** + * Concatenates two lists. + * @param {ol.structs.LinkedList} list List to merge into the current list. + */ +ol.structs.LinkedList.prototype.concat = function(list) { + if (list.head_) { + if (this.head_) { + var end = this.head_.next; + this.head_.next = list.first_; + list.first_.prev = this.head_; + end.prev = list.last_; + list.last_.next = end; + this.length_ += list.length_; + } else { + this.head_ = list.head_; + this.first_ = list.first_; + this.last_ = list.last_; + this.length_ = list.length_; + } + list.head_ = undefined; + list.first_ = undefined; + list.last_ = undefined; + list.length_ = 0; + } +}; + +/** + * Returns the current length of the list. + * + * @return {number} Length. + */ +ol.structs.LinkedList.prototype.getLength = function() { + return this.length_; +}; + +goog.provide('ol.ext.rbush'); +/** @typedef {function(*)} */ +ol.ext.rbush; +(function() { +var exports = {}; +var module = {exports: exports}; +var define; +/** + * @fileoverview + * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility} + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.rbush = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ +'use strict'; + +module.exports = partialSort; + +// Floyd-Rivest selection algorithm: +// Rearrange items so that all items in the [left, k] range are smaller than all items in (k, right]; +// The k-th element will have the (k - left + 1)th smallest value in [left, right] + +function partialSort(arr, k, left, right, compare) { + left = left || 0; + right = right || (arr.length - 1); + compare = compare || defaultCompare; + + while (right > left) { + if (right - left > 600) { + var n = right - left + 1; + var m = k - left + 1; + var z = Math.log(n); + var s = 0.5 * Math.exp(2 * z / 3); + var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); + var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); + var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); + partialSort(arr, k, newLeft, newRight, compare); + } + + var t = arr[k]; + var i = left; + var j = right; + + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); + + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } + + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); + } + + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } +} + +function swap(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; +} + +function defaultCompare(a, b) { + return a < b ? -1 : a > b ? 1 : 0; +} + +},{}],2:[function(_dereq_,module,exports){ +'use strict'; + +module.exports = rbush; + +var quickselect = _dereq_('quickselect'); + +function rbush(maxEntries, format) { + if (!(this instanceof rbush)) return new rbush(maxEntries, format); + + // max entries in a node is 9 by default; min node fill is 40% for best performance + this._maxEntries = Math.max(4, maxEntries || 9); + this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); + + if (format) { + this._initFormat(format); + } + + this.clear(); +} + +rbush.prototype = { + + all: function () { + return this._all(this.data, []); + }, + + search: function (bbox) { + + var node = this.data, + result = [], + toBBox = this.toBBox; + + if (!intersects(bbox, node)) return result; + + var nodesToSearch = [], + i, len, child, childBBox; + + while (node) { + for (i = 0, len = node.children.length; i < len; i++) { + + child = node.children[i]; + childBBox = node.leaf ? toBBox(child) : child; + + if (intersects(bbox, childBBox)) { + if (node.leaf) result.push(child); + else if (contains(bbox, childBBox)) this._all(child, result); + else nodesToSearch.push(child); + } + } + node = nodesToSearch.pop(); + } + + return result; + }, + + collides: function (bbox) { + + var node = this.data, + toBBox = this.toBBox; + + if (!intersects(bbox, node)) return false; + + var nodesToSearch = [], + i, len, child, childBBox; + + while (node) { + for (i = 0, len = node.children.length; i < len; i++) { + + child = node.children[i]; + childBBox = node.leaf ? toBBox(child) : child; + + if (intersects(bbox, childBBox)) { + if (node.leaf || contains(bbox, childBBox)) return true; + nodesToSearch.push(child); + } + } + node = nodesToSearch.pop(); + } + + return false; + }, + + load: function (data) { + if (!(data && data.length)) return this; + + if (data.length < this._minEntries) { + for (var i = 0, len = data.length; i < len; i++) { + this.insert(data[i]); + } + return this; + } + + // recursively build the tree with the given data from stratch using OMT algorithm + var node = this._build(data.slice(), 0, data.length - 1, 0); + + if (!this.data.children.length) { + // save as is if tree is empty + this.data = node; + + } else if (this.data.height === node.height) { + // split root if trees have the same height + this._splitRoot(this.data, node); + + } else { + if (this.data.height < node.height) { + // swap trees if inserted one is bigger + var tmpNode = this.data; + this.data = node; + node = tmpNode; + } + + // insert the small tree into the large tree at appropriate level + this._insert(node, this.data.height - node.height - 1, true); + } + + return this; + }, + + insert: function (item) { + if (item) this._insert(item, this.data.height - 1); + return this; + }, + + clear: function () { + this.data = createNode([]); + return this; + }, + + remove: function (item, equalsFn) { + if (!item) return this; + + var node = this.data, + bbox = this.toBBox(item), + path = [], + indexes = [], + i, parent, index, goingUp; + + // depth-first iterative tree traversal + while (node || path.length) { + + if (!node) { // go up + node = path.pop(); + parent = path[path.length - 1]; + i = indexes.pop(); + goingUp = true; + } + + if (node.leaf) { // check current node + index = findItem(item, node.children, equalsFn); + + if (index !== -1) { + // item found, remove the item and condense tree upwards + node.children.splice(index, 1); + path.push(node); + this._condense(path); + return this; + } + } + + if (!goingUp && !node.leaf && contains(node, bbox)) { // go down + path.push(node); + indexes.push(i); + i = 0; + parent = node; + node = node.children[0]; + + } else if (parent) { // go right + i++; + node = parent.children[i]; + goingUp = false; + + } else node = null; // nothing found + } + + return this; + }, + + toBBox: function (item) { return item; }, + + compareMinX: compareNodeMinX, + compareMinY: compareNodeMinY, + + toJSON: function () { return this.data; }, + + fromJSON: function (data) { + this.data = data; + return this; + }, + + _all: function (node, result) { + var nodesToSearch = []; + while (node) { + if (node.leaf) result.push.apply(result, node.children); + else nodesToSearch.push.apply(nodesToSearch, node.children); + + node = nodesToSearch.pop(); + } + return result; + }, + + _build: function (items, left, right, height) { + + var N = right - left + 1, + M = this._maxEntries, + node; + + if (N <= M) { + // reached leaf level; return leaf + node = createNode(items.slice(left, right + 1)); + calcBBox(node, this.toBBox); + return node; + } + + if (!height) { + // target height of the bulk-loaded tree + height = Math.ceil(Math.log(N) / Math.log(M)); + + // target number of root entries to maximize storage utilization + M = Math.ceil(N / Math.pow(M, height - 1)); + } + + node = createNode([]); + node.leaf = false; + node.height = height; + + // split the items into M mostly square tiles + + var N2 = Math.ceil(N / M), + N1 = N2 * Math.ceil(Math.sqrt(M)), + i, j, right2, right3; + + multiSelect(items, left, right, N1, this.compareMinX); + + for (i = left; i <= right; i += N1) { + + right2 = Math.min(i + N1 - 1, right); + + multiSelect(items, i, right2, N2, this.compareMinY); + + for (j = i; j <= right2; j += N2) { + + right3 = Math.min(j + N2 - 1, right2); + + // pack each entry recursively + node.children.push(this._build(items, j, right3, height - 1)); + } + } + + calcBBox(node, this.toBBox); + + return node; + }, + + _chooseSubtree: function (bbox, node, level, path) { + + var i, len, child, targetNode, area, enlargement, minArea, minEnlargement; + + while (true) { + path.push(node); + + if (node.leaf || path.length - 1 === level) break; + + minArea = minEnlargement = Infinity; + + for (i = 0, len = node.children.length; i < len; i++) { + child = node.children[i]; + area = bboxArea(child); + enlargement = enlargedArea(bbox, child) - area; + + // choose entry with the least area enlargement + if (enlargement < minEnlargement) { + minEnlargement = enlargement; + minArea = area < minArea ? area : minArea; + targetNode = child; + + } else if (enlargement === minEnlargement) { + // otherwise choose one with the smallest area + if (area < minArea) { + minArea = area; + targetNode = child; + } + } + } + + node = targetNode || node.children[0]; + } + + return node; + }, + + _insert: function (item, level, isNode) { + + var toBBox = this.toBBox, + bbox = isNode ? item : toBBox(item), + insertPath = []; + + // find the best node for accommodating the item, saving all nodes along the path too + var node = this._chooseSubtree(bbox, this.data, level, insertPath); + + // put the item into the node + node.children.push(item); + extend(node, bbox); + + // split on node overflow; propagate upwards if necessary + while (level >= 0) { + if (insertPath[level].children.length > this._maxEntries) { + this._split(insertPath, level); + level--; + } else break; + } + + // adjust bboxes along the insertion path + this._adjustParentBBoxes(bbox, insertPath, level); + }, + + // split overflowed node into two + _split: function (insertPath, level) { + + var node = insertPath[level], + M = node.children.length, + m = this._minEntries; + + this._chooseSplitAxis(node, m, M); + + var splitIndex = this._chooseSplitIndex(node, m, M); + + var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); + newNode.height = node.height; + newNode.leaf = node.leaf; + + calcBBox(node, this.toBBox); + calcBBox(newNode, this.toBBox); + + if (level) insertPath[level - 1].children.push(newNode); + else this._splitRoot(node, newNode); + }, + + _splitRoot: function (node, newNode) { + // split root node + this.data = createNode([node, newNode]); + this.data.height = node.height + 1; + this.data.leaf = false; + calcBBox(this.data, this.toBBox); + }, + + _chooseSplitIndex: function (node, m, M) { + + var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index; + + minOverlap = minArea = Infinity; + + for (i = m; i <= M - m; i++) { + bbox1 = distBBox(node, 0, i, this.toBBox); + bbox2 = distBBox(node, i, M, this.toBBox); + + overlap = intersectionArea(bbox1, bbox2); + area = bboxArea(bbox1) + bboxArea(bbox2); + + // choose distribution with minimum overlap + if (overlap < minOverlap) { + minOverlap = overlap; + index = i; + + minArea = area < minArea ? area : minArea; + + } else if (overlap === minOverlap) { + // otherwise choose distribution with minimum area + if (area < minArea) { + minArea = area; + index = i; + } + } + } + + return index; + }, + + // sorts node children by the best axis for split + _chooseSplitAxis: function (node, m, M) { + + var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX, + compareMinY = node.leaf ? this.compareMinY : compareNodeMinY, + xMargin = this._allDistMargin(node, m, M, compareMinX), + yMargin = this._allDistMargin(node, m, M, compareMinY); + + // if total distributions margin value is minimal for x, sort by minX, + // otherwise it's already sorted by minY + if (xMargin < yMargin) node.children.sort(compareMinX); + }, + + // total margin of all possible split distributions where each node is at least m full + _allDistMargin: function (node, m, M, compare) { + + node.children.sort(compare); + + var toBBox = this.toBBox, + leftBBox = distBBox(node, 0, m, toBBox), + rightBBox = distBBox(node, M - m, M, toBBox), + margin = bboxMargin(leftBBox) + bboxMargin(rightBBox), + i, child; + + for (i = m; i < M - m; i++) { + child = node.children[i]; + extend(leftBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(leftBBox); + } + + for (i = M - m - 1; i >= m; i--) { + child = node.children[i]; + extend(rightBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(rightBBox); + } + + return margin; + }, + + _adjustParentBBoxes: function (bbox, path, level) { + // adjust bboxes along the given tree path + for (var i = level; i >= 0; i--) { + extend(path[i], bbox); + } + }, + + _condense: function (path) { + // go through the path, removing empty nodes and updating bboxes + for (var i = path.length - 1, siblings; i >= 0; i--) { + if (path[i].children.length === 0) { + if (i > 0) { + siblings = path[i - 1].children; + siblings.splice(siblings.indexOf(path[i]), 1); + + } else this.clear(); + + } else calcBBox(path[i], this.toBBox); + } + }, + + _initFormat: function (format) { + // data format (minX, minY, maxX, maxY accessors) + + // uses eval-type function compilation instead of just accepting a toBBox function + // because the algorithms are very sensitive to sorting functions performance, + // so they should be dead simple and without inner calls + + var compareArr = ['return a', ' - b', ';']; + + this.compareMinX = new Function('a', 'b', compareArr.join(format[0])); + this.compareMinY = new Function('a', 'b', compareArr.join(format[1])); + + this.toBBox = new Function('a', + 'return {minX: a' + format[0] + + ', minY: a' + format[1] + + ', maxX: a' + format[2] + + ', maxY: a' + format[3] + '};'); + } +}; + +function findItem(item, items, equalsFn) { + if (!equalsFn) return items.indexOf(item); + + for (var i = 0; i < items.length; i++) { + if (equalsFn(item, items[i])) return i; + } + return -1; +} + +// calculate node's bbox from bboxes of its children +function calcBBox(node, toBBox) { + distBBox(node, 0, node.children.length, toBBox, node); +} + +// min bounding rectangle of node children from k to p-1 +function distBBox(node, k, p, toBBox, destNode) { + if (!destNode) destNode = createNode(null); + destNode.minX = Infinity; + destNode.minY = Infinity; + destNode.maxX = -Infinity; + destNode.maxY = -Infinity; + + for (var i = k, child; i < p; i++) { + child = node.children[i]; + extend(destNode, node.leaf ? toBBox(child) : child); + } + + return destNode; +} + +function extend(a, b) { + a.minX = Math.min(a.minX, b.minX); + a.minY = Math.min(a.minY, b.minY); + a.maxX = Math.max(a.maxX, b.maxX); + a.maxY = Math.max(a.maxY, b.maxY); + return a; +} + +function compareNodeMinX(a, b) { return a.minX - b.minX; } +function compareNodeMinY(a, b) { return a.minY - b.minY; } + +function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); } +function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); } + +function enlargedArea(a, b) { + return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * + (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)); +} + +function intersectionArea(a, b) { + var minX = Math.max(a.minX, b.minX), + minY = Math.max(a.minY, b.minY), + maxX = Math.min(a.maxX, b.maxX), + maxY = Math.min(a.maxY, b.maxY); + + return Math.max(0, maxX - minX) * + Math.max(0, maxY - minY); +} + +function contains(a, b) { + return a.minX <= b.minX && + a.minY <= b.minY && + b.maxX <= a.maxX && + b.maxY <= a.maxY; +} + +function intersects(a, b) { + return b.minX <= a.maxX && + b.minY <= a.maxY && + b.maxX >= a.minX && + b.maxY >= a.minY; +} + +function createNode(children) { + return { + children: children, + height: 1, + leaf: true, + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity + }; +} + +// sort an array so that items come in groups of n unsorted items, with groups sorted between each other; +// combines selection algorithm with binary divide & conquer approach + +function multiSelect(arr, left, right, n, compare) { + var stack = [left, right], + mid; + + while (stack.length) { + right = stack.pop(); + left = stack.pop(); + + if (right - left <= n) continue; + + mid = left + Math.ceil((right - left) / n / 2) * n; + quickselect(arr, mid, left, right, compare); + + stack.push(left, mid, mid, right); + } +} + +},{"quickselect":1}]},{},[2])(2) +}); +ol.ext.rbush = module.exports; +})(); + +goog.provide('ol.structs.RBush'); + +goog.require('ol'); +goog.require('ol.ext.rbush'); +goog.require('ol.extent'); +goog.require('ol.obj'); + + +/** + * Wrapper around the RBush by Vladimir Agafonkin. + * + * @constructor + * @param {number=} opt_maxEntries Max entries. + * @see https://github.com/mourner/rbush + * @struct + * @template T + */ +ol.structs.RBush = function(opt_maxEntries) { + + /** + * @private + */ + this.rbush_ = ol.ext.rbush(opt_maxEntries); + + /** + * A mapping between the objects added to this rbush wrapper + * and the objects that are actually added to the internal rbush. + * @private + * @type {Object.<number, ol.RBushEntry>} + */ + this.items_ = {}; + + if (ol.DEBUG) { + /** + * @private + * @type {number} + */ + this.readers_ = 0; + } +}; + + +/** + * Insert a value into the RBush. + * @param {ol.Extent} extent Extent. + * @param {T} value Value. + */ +ol.structs.RBush.prototype.insert = function(extent, value) { + if (ol.DEBUG && this.readers_) { + throw new Error('Can not insert value while reading'); + } + /** @type {ol.RBushEntry} */ + var item = { + minX: extent[0], + minY: extent[1], + maxX: extent[2], + maxY: extent[3], + value: value + }; + + this.rbush_.insert(item); + // remember the object that was added to the internal rbush + ol.DEBUG && console.assert(!(ol.getUid(value) in this.items_), + 'uid (%s) of value (%s) already exists', ol.getUid(value), value); + this.items_[ol.getUid(value)] = item; +}; + + +/** + * Bulk-insert values into the RBush. + * @param {Array.<ol.Extent>} extents Extents. + * @param {Array.<T>} values Values. + */ +ol.structs.RBush.prototype.load = function(extents, values) { + if (ol.DEBUG && this.readers_) { + throw new Error('Can not insert values while reading'); + } + ol.DEBUG && console.assert(extents.length === values.length, + 'extens and values must have same length (%s === %s)', + extents.length, values.length); + + var items = new Array(values.length); + for (var i = 0, l = values.length; i < l; i++) { + var extent = extents[i]; + var value = values[i]; + + /** @type {ol.RBushEntry} */ + var item = { + minX: extent[0], + minY: extent[1], + maxX: extent[2], + maxY: extent[3], + value: value + }; + items[i] = item; + ol.DEBUG && console.assert(!(ol.getUid(value) in this.items_), + 'uid (%s) of value (%s) already exists', ol.getUid(value), value); + this.items_[ol.getUid(value)] = item; + } + this.rbush_.load(items); +}; + + +/** + * Remove a value from the RBush. + * @param {T} value Value. + * @return {boolean} Removed. + */ +ol.structs.RBush.prototype.remove = function(value) { + if (ol.DEBUG && this.readers_) { + throw new Error('Can not remove value while reading'); + } + var uid = ol.getUid(value); + ol.DEBUG && console.assert(uid in this.items_, + 'uid (%s) of value (%s) does not exist', uid, value); + + // get the object in which the value was wrapped when adding to the + // internal rbush. then use that object to do the removal. + var item = this.items_[uid]; + delete this.items_[uid]; + return this.rbush_.remove(item) !== null; +}; + + +/** + * Update the extent of a value in the RBush. + * @param {ol.Extent} extent Extent. + * @param {T} value Value. + */ +ol.structs.RBush.prototype.update = function(extent, value) { + ol.DEBUG && console.assert(ol.getUid(value) in this.items_, + 'uid (%s) of value (%s) does not exist', ol.getUid(value), value); + + var item = this.items_[ol.getUid(value)]; + var bbox = [item.minX, item.minY, item.maxX, item.maxY]; + if (!ol.extent.equals(bbox, extent)) { + if (ol.DEBUG && this.readers_) { + throw new Error('Can not update extent while reading'); + } + this.remove(value); + this.insert(extent, value); + } +}; + + +/** + * Return all values in the RBush. + * @return {Array.<T>} All. + */ +ol.structs.RBush.prototype.getAll = function() { + var items = this.rbush_.all(); + return items.map(function(item) { + return item.value; + }); +}; + + +/** + * Return all values in the given extent. + * @param {ol.Extent} extent Extent. + * @return {Array.<T>} All in extent. + */ +ol.structs.RBush.prototype.getInExtent = function(extent) { + /** @type {ol.RBushEntry} */ + var bbox = { + minX: extent[0], + minY: extent[1], + maxX: extent[2], + maxY: extent[3] + }; + var items = this.rbush_.search(bbox); + return items.map(function(item) { + return item.value; + }); +}; + + +/** + * Calls a callback function with each value in the tree. + * If the callback returns a truthy value, this value is returned without + * checking the rest of the tree. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_this The object to use as `this` in `callback`. + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEach = function(callback, opt_this) { + if (ol.DEBUG) { + ++this.readers_; + try { + return this.forEach_(this.getAll(), callback, opt_this); + } finally { + --this.readers_; + } + } else { + return this.forEach_(this.getAll(), callback, opt_this); + } +}; + + +/** + * Calls a callback function with each value in the provided extent. + * @param {ol.Extent} extent Extent. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_this The object to use as `this` in `callback`. + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEachInExtent = function(extent, callback, opt_this) { + if (ol.DEBUG) { + ++this.readers_; + try { + return this.forEach_(this.getInExtent(extent), callback, opt_this); + } finally { + --this.readers_; + } + } else { + return this.forEach_(this.getInExtent(extent), callback, opt_this); + } +}; + + +/** + * @param {Array.<T>} values Values. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_this The object to use as `this` in `callback`. + * @private + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEach_ = function(values, callback, opt_this) { + var result; + for (var i = 0, l = values.length; i < l; i++) { + result = callback.call(opt_this, values[i]); + if (result) { + return result; + } + } + return result; +}; + + +/** + * @return {boolean} Is empty. + */ +ol.structs.RBush.prototype.isEmpty = function() { + return ol.obj.isEmpty(this.items_); +}; + + +/** + * Remove all values from the RBush. + */ +ol.structs.RBush.prototype.clear = function() { + this.rbush_.clear(); + this.items_ = {}; +}; + + +/** + * @param {ol.Extent=} opt_extent Extent. + * @return {!ol.Extent} Extent. + */ +ol.structs.RBush.prototype.getExtent = function(opt_extent) { + // FIXME add getExtent() to rbush + var data = this.rbush_.data; + return [data.minX, data.minY, data.maxX, data.maxY]; +}; + +goog.provide('ol.render.webgl.PolygonReplay'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.color'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.geom.flat.contains'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.render.webgl.polygonreplay.defaultshader'); +goog.require('ol.render.webgl.LineStringReplay'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl'); +goog.require('ol.style.Stroke'); +goog.require('ol.structs.LinkedList'); +goog.require('ol.structs.RBush'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + this.lineStringReplay = new ol.render.webgl.LineStringReplay( + tolerance, maxExtent); + + /** + * @private + * @type {ol.render.webgl.polygonreplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.<Array.<number>>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.<number>} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {{fillColor: (Array.<number>|null), + * changed: boolean}|null} + */ + this.state_ = { + fillColor: null, + changed: false + }; + +}; +ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); + + +/** + * Draw one polygon. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<Array.<number>>} holeFlatCoordinates Hole flat coordinates. + * @param {number} stride Stride. + * @private + */ +ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function( + flatCoordinates, holeFlatCoordinates, stride) { + // Triangulate the polygon + var outerRing = new ol.structs.LinkedList(); + var rtree = new ol.structs.RBush(); + // Initialize the outer ring + var maxX = this.processFlatCoordinates_(flatCoordinates, stride, outerRing, rtree, true); + + // Eliminate holes, if there are any + if (holeFlatCoordinates.length) { + var i, ii; + var holeLists = []; + for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { + var holeList = { + list: new ol.structs.LinkedList(), + maxX: undefined + }; + holeLists.push(holeList); + holeList.maxX = this.processFlatCoordinates_(holeFlatCoordinates[i], + stride, holeList.list, rtree, false); + } + holeLists.sort(function(a, b) { + return b.maxX - a.maxX; + }); + for (i = 0; i < holeLists.length; ++i) { + this.bridgeHole_(holeLists[i].list, holeLists[i].maxX, outerRing, maxX, rtree); + } + } + this.classifyPoints_(outerRing, rtree, false); + this.triangulate_(outerRing, rtree); +}; + + +/** + * Inserts flat coordinates in a linked list and adds them to the vertex buffer. + * @private + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} stride Stride. + * @param {ol.structs.LinkedList} list Linked list. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} clockwise Coordinate order should be clockwise. + * @return {number} Maximum X value. + */ +ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( + flatCoordinates, stride, list, rtree, clockwise) { + var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, + 0, flatCoordinates.length, stride); + var i, ii, maxX; + var n = this.vertices.length / 2; + /** @type {ol.WebglPolygonVertex} */ + var start; + /** @type {ol.WebglPolygonVertex} */ + var p0; + /** @type {ol.WebglPolygonVertex} */ + var p1; + var extents = []; + var segments = []; + if (clockwise === isClockwise) { + start = this.createPoint_(flatCoordinates[0], flatCoordinates[1], n++); + p0 = start; + maxX = flatCoordinates[0]; + for (i = stride, ii = flatCoordinates.length; i < ii; i += stride) { + p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); + segments.push(this.insertItem_(p0, p1, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; + p0 = p1; + } + segments.push(this.insertItem_(p1, start, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + } else { + var end = flatCoordinates.length - stride; + start = this.createPoint_(flatCoordinates[end], flatCoordinates[end + 1], n++); + p0 = start; + maxX = flatCoordinates[end]; + for (i = end - stride, ii = 0; i >= ii; i -= stride) { + p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); + segments.push(this.insertItem_(p0, p1, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; + p0 = p1; + } + segments.push(this.insertItem_(p1, start, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + } + rtree.load(extents, segments); + + return maxX; +}; + + +/** + * Classifies the points of a polygon list as convex, reflex. Removes collinear vertices. + * @private + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} ccw The orientation of the polygon is counter-clockwise. + * @return {boolean} There were reclassified points. + */ +ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, rtree, ccw) { + var start = list.firstItem(); + var s0 = start; + var s1 = list.nextItem(); + var pointsReclassified = false; + do { + var reflex = ccw ? ol.render.webgl.triangleIsCounterClockwise(s1.p1.x, + s1.p1.y, s0.p1.x, s0.p1.y, s0.p0.x, s0.p0.y) : + ol.render.webgl.triangleIsCounterClockwise(s0.p0.x, s0.p0.y, s0.p1.x, + s0.p1.y, s1.p1.x, s1.p1.y); + if (reflex === undefined) { + this.removeItem_(s0, s1, list, rtree); + pointsReclassified = true; + if (s1 === start) { + start = list.getNextItem(); + } + s1 = s0; + list.prevItem(); + } else if (s0.p1.reflex !== reflex) { + s0.p1.reflex = reflex; + pointsReclassified = true; + } + s0 = s1; + s1 = list.nextItem(); + } while (s0 !== start); + return pointsReclassified; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} hole Linked list of the hole. + * @param {number} holeMaxX Maximum X value of the hole. + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {number} listMaxX Maximum X value of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, + list, listMaxX, rtree) { + this.classifyPoints_(hole, rtree, true); + var seg = hole.firstItem(); + while (seg.p1.x !== holeMaxX) { + seg = hole.nextItem(); + } + + var p1 = seg.p1; + /** @type {ol.WebglPolygonVertex} */ + var p2 = {x: listMaxX, y: p1.y, i: -1}; + var minDist = Infinity; + var i, ii, bestPoint; + /** @type {ol.WebglPolygonVertex} */ + var p5; + + var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, rtree, true); + for (i = 0, ii = intersectingSegments.length; i < ii; ++i) { + var currSeg = intersectingSegments[i]; + if (currSeg.p0.reflex === undefined) { + var intersection = this.calculateIntersection_(p1, p2, currSeg.p0, + currSeg.p1, true); + var dist = Math.abs(p1.x - intersection[0]); + if (dist < minDist) { + minDist = dist; + p5 = {x: intersection[0], y: intersection[1], i: -1}; + seg = currSeg; + } + } + } + if (minDist === Infinity) { + return; + } + bestPoint = seg.p1; + + if (minDist > 0) { + var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1, rtree); + if (pointsInTriangle.length) { + var theta = Infinity; + for (i = 0, ii = pointsInTriangle.length; i < ii; ++i) { + var currPoint = pointsInTriangle[i]; + var currTheta = Math.atan2(p1.y - currPoint.y, p2.x - currPoint.x); + if (currTheta < theta || (currTheta === theta && currPoint.x < bestPoint.x)) { + theta = currTheta; + bestPoint = currPoint; + } + } + } + } + + seg = list.firstItem(); + while (seg.p1 !== bestPoint) { + seg = list.nextItem(); + } + + //We clone the bridge points as they can have different convexity. + var p0Bridge = {x: p1.x, y: p1.y, i: p1.i, reflex: undefined}; + var p1Bridge = {x: seg.p1.x, y: seg.p1.y, i: seg.p1.i, reflex: undefined}; + + hole.getNextItem().p0 = p0Bridge; + this.insertItem_(p1, seg.p1, hole, rtree); + this.insertItem_(p1Bridge, p0Bridge, hole, rtree); + seg.p1 = p1Bridge; + hole.setFirstItem(); + list.concat(hole); +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) { + var ccw = false; + var simple = this.isSimple_(list, rtree); + + // Start clipping ears + while (list.getLength() > 3) { + if (simple) { + if (!this.clipEars_(list, rtree, simple, ccw)) { + if (!this.classifyPoints_(list, rtree, ccw)) { + // Due to the behavior of OL's PIP algorithm, the ear clipping cannot + // introduce touching segments. However, the original data may have some. + if (!this.resolveLocalSelfIntersections_(list, rtree, true)) { + // Something went wrong. + ol.DEBUG && console.assert(false, 'Unexpected simple polygon geometry'); + break; + } + } + } + } else { + if (!this.clipEars_(list, rtree, simple, ccw)) { + // We ran out of ears, try to reclassify. + if (!this.classifyPoints_(list, rtree, ccw)) { + // We have a bad polygon, try to resolve local self-intersections. + if (!this.resolveLocalSelfIntersections_(list, rtree)) { + simple = this.isSimple_(list, rtree); + if (!simple) { + // We have a really bad polygon, try more time consuming methods. + this.splitPolygon_(list, rtree); + break; + } else { + ccw = !this.isClockwise_(list); + this.classifyPoints_(list, rtree, ccw); + } + } + } + } + } + } + if (list.getLength() === 3) { + var numIndices = this.indices.length; + this.indices[numIndices++] = list.getPrevItem().p0.i; + this.indices[numIndices++] = list.getCurrItem().p0.i; + this.indices[numIndices++] = list.getNextItem().p0.i; + } +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} simple The polygon is simple. + * @param {boolean} ccw Orientation of the polygon is counter-clockwise. + * @return {boolean} There were processed ears. + */ +ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, rtree, simple, ccw) { + var numIndices = this.indices.length; + var start = list.firstItem(); + var s0 = list.getPrevItem(); + var s1 = start; + var s2 = list.nextItem(); + var s3 = list.getNextItem(); + var p0, p1, p2; + var processedEars = false; + do { + p0 = s1.p0; + p1 = s1.p1; + p2 = s2.p1; + if (p1.reflex === false) { + // We might have a valid ear + var diagonalIsInside = ccw ? this.diagonalIsInside_(s3.p1, p2, p1, p0, + s0.p0) : this.diagonalIsInside_(s0.p0, p0, p1, p2, s3.p1); + if ((simple || this.getIntersections_({p0: p0, p1: p2}, rtree).length === 0) && + diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0) { + //The diagonal is completely inside the polygon + if (simple || p0.reflex === false || p2.reflex === false || + ol.geom.flat.orient.linearRingIsClockwise([s0.p0.x, s0.p0.y, p0.x, + p0.y, p1.x, p1.y, p2.x, p2.y, s3.p1.x, s3.p1.y], 0, 10, 2) === !ccw) { + //The diagonal is persumably valid, we have an ear + this.indices[numIndices++] = p0.i; + this.indices[numIndices++] = p1.i; + this.indices[numIndices++] = p2.i; + this.removeItem_(s1, s2, list, rtree); + if (s2 === start) { + start = s3; + } + processedEars = true; + } + } + } + // Else we have a reflex point. + s0 = list.getPrevItem(); + s1 = list.getCurrItem(); + s2 = list.nextItem(); + s3 = list.getNextItem(); + } while (s1 !== start && list.getLength() > 3); + + return processedEars; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_touch Resolve touching segments. + * @return {boolean} There were resolved intersections. +*/ +ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = function( + list, rtree, opt_touch) { + var start = list.firstItem(); + list.nextItem(); + var s0 = start; + var s1 = list.nextItem(); + var resolvedIntersections = false; + + do { + var intersection = this.calculateIntersection_(s0.p0, s0.p1, s1.p0, s1.p1, + opt_touch); + if (intersection) { + var breakCond = false; + var numVertices = this.vertices.length; + var numIndices = this.indices.length; + var n = numVertices / 2; + var seg = list.prevItem(); + list.removeItem(); + rtree.remove(seg); + breakCond = (seg === start); + var p; + if (opt_touch) { + if (intersection[0] === s0.p0.x && intersection[1] === s0.p0.y) { + list.prevItem(); + p = s0.p0; + s1.p0 = p; + rtree.remove(s0); + breakCond = breakCond || (s0 === start); + } else { + p = s1.p1; + s0.p1 = p; + rtree.remove(s1); + breakCond = breakCond || (s1 === start); + } + list.removeItem(); + } else { + p = this.createPoint_(intersection[0], intersection[1], n); + s0.p1 = p; + s1.p0 = p; + rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), + Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); + rtree.update([Math.min(s1.p0.x, s1.p1.x), Math.min(s1.p0.y, s1.p1.y), + Math.max(s1.p0.x, s1.p1.x), Math.max(s1.p0.y, s1.p1.y)], s1); + } + + this.indices[numIndices++] = seg.p0.i; + this.indices[numIndices++] = seg.p1.i; + this.indices[numIndices++] = p.i; + + resolvedIntersections = true; + if (breakCond) { + break; + } + } + + s0 = list.getPrevItem(); + s1 = list.nextItem(); + } while (s0 !== start); + return resolvedIntersections; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @return {boolean} The polygon is simple. + */ +ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list, rtree) { + var start = list.firstItem(); + var seg = start; + do { + if (this.getIntersections_(seg, rtree).length) { + return false; + } + seg = list.nextItem(); + } while (seg !== start); + return true; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @return {boolean} Orientation is clockwise. + */ +ol.render.webgl.PolygonReplay.prototype.isClockwise_ = function(list) { + var length = list.getLength() * 2; + var flatCoordinates = new Array(length); + var start = list.firstItem(); + var seg = start; + var i = 0; + do { + flatCoordinates[i++] = seg.p0.x; + flatCoordinates[i++] = seg.p0.y; + seg = list.nextItem(); + } while (seg !== start); + return ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, 0, length, 2); +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.splitPolygon_ = function(list, rtree) { + var start = list.firstItem(); + var s0 = start; + do { + var intersections = this.getIntersections_(s0, rtree); + if (intersections.length) { + var s1 = intersections[0]; + var n = this.vertices.length / 2; + var intersection = this.calculateIntersection_(s0.p0, + s0.p1, s1.p0, s1.p1); + var p = this.createPoint_(intersection[0], intersection[1], n); + var newPolygon = new ol.structs.LinkedList(); + var newRtree = new ol.structs.RBush(); + this.insertItem_(p, s0.p1, newPolygon, newRtree); + s0.p1 = p; + rtree.update([Math.min(s0.p0.x, p.x), Math.min(s0.p0.y, p.y), + Math.max(s0.p0.x, p.x), Math.max(s0.p0.y, p.y)], s0); + var currItem = list.nextItem(); + while (currItem !== s1) { + this.insertItem_(currItem.p0, currItem.p1, newPolygon, newRtree); + rtree.remove(currItem); + list.removeItem(); + currItem = list.getCurrItem(); + } + this.insertItem_(s1.p0, p, newPolygon, newRtree); + s1.p0 = p; + rtree.update([Math.min(s1.p1.x, p.x), Math.min(s1.p1.y, p.y), + Math.max(s1.p1.x, p.x), Math.max(s1.p1.y, p.y)], s1); + this.classifyPoints_(list, rtree, false); + this.triangulate_(list, rtree); + this.classifyPoints_(newPolygon, newRtree, false); + this.triangulate_(newPolygon, newRtree); + break; + } + s0 = list.nextItem(); + } while (s0 !== start); +}; + + +/** + * @private + * @param {number} x X coordinate. + * @param {number} y Y coordinate. + * @param {number} i Index. + * @return {ol.WebglPolygonVertex} List item. + */ +ol.render.webgl.PolygonReplay.prototype.createPoint_ = function(x, y, i) { + var numVertices = this.vertices.length; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + /** @type {ol.WebglPolygonVertex} */ + var p = { + x: x, + y: y, + i: i, + reflex: undefined + }; + return p; +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 First point of segment. + * @param {ol.WebglPolygonVertex} p1 Second point of segment. + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush=} opt_rtree Insert the segment into the R-Tree. + * @return {ol.WebglPolygonSegment} segment. + */ +ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt_rtree) { + var seg = { + p0: p0, + p1: p1 + }; + list.insertItem(seg); + if (opt_rtree) { + opt_rtree.insert([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), + Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)], seg); + } + return seg; +}; + + + /** + * @private + * @param {ol.WebglPolygonSegment} s0 Segment before the remove candidate. + * @param {ol.WebglPolygonSegment} s1 Remove candidate segment. + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list, rtree) { + if (list.getCurrItem() === s1) { + list.removeItem(); + s0.p1 = s1.p1; + rtree.remove(s1); + rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), + Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); + } +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 First point. + * @param {ol.WebglPolygonVertex} p1 Second point. + * @param {ol.WebglPolygonVertex} p2 Third point. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_reflex Only include reflex points. + * @return {Array.<ol.WebglPolygonVertex>} Points in the triangle. + */ +ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1, + p2, rtree, opt_reflex) { + var i, ii, j, p; + var result = []; + var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x, p2.x), + Math.min(p0.y, p1.y, p2.y), Math.max(p0.x, p1.x, p2.x), Math.max(p0.y, + p1.y, p2.y)]); + for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { + for (j in segmentsInExtent[i]) { + p = segmentsInExtent[i][j]; + if (typeof p === 'object' && (!opt_reflex || p.reflex)) { + if ((p.x !== p0.x || p.y !== p0.y) && (p.x !== p1.x || p.y !== p1.y) && + (p.x !== p2.x || p.y !== p2.y) && result.indexOf(p) === -1 && + ol.geom.flat.contains.linearRingContainsXY([p0.x, p0.y, p1.x, p1.y, + p2.x, p2.y], 0, 6, 2, p.x, p.y)) { + result.push(p); + } + } + } + } + return result; +}; + + +/** + * @private + * @param {ol.WebglPolygonSegment} segment Segment. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_touch Touching segments should be considered an intersection. + * @return {Array.<ol.WebglPolygonSegment>} Intersecting segments. + */ +ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, rtree, opt_touch) { + var p0 = segment.p0; + var p1 = segment.p1; + var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x), + Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)]); + var result = []; + var i, ii; + for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { + var currSeg = segmentsInExtent[i]; + if (segment !== currSeg && (opt_touch || currSeg.p0 !== p1 || currSeg.p1 !== p0) && + this.calculateIntersection_(p0, p1, currSeg.p0, currSeg.p1, opt_touch)) { + result.push(currSeg); + } + } + return result; +}; + + +/** + * Line intersection algorithm by Paul Bourke. + * @see http://paulbourke.net/geometry/pointlineplane/ + * + * @private + * @param {ol.WebglPolygonVertex} p0 First point. + * @param {ol.WebglPolygonVertex} p1 Second point. + * @param {ol.WebglPolygonVertex} p2 Third point. + * @param {ol.WebglPolygonVertex} p3 Fourth point. + * @param {boolean=} opt_touch Touching segments should be considered an intersection. + * @return {Array.<number>|undefined} Intersection coordinates. + */ +ol.render.webgl.PolygonReplay.prototype.calculateIntersection_ = function(p0, + p1, p2, p3, opt_touch) { + var denom = (p3.y - p2.y) * (p1.x - p0.x) - (p3.x - p2.x) * (p1.y - p0.y); + if (denom !== 0) { + var ua = ((p3.x - p2.x) * (p0.y - p2.y) - (p3.y - p2.y) * (p0.x - p2.x)) / denom; + var ub = ((p1.x - p0.x) * (p0.y - p2.y) - (p1.y - p0.y) * (p0.x - p2.x)) / denom; + if ((!opt_touch && ua > ol.render.webgl.EPSILON && ua < 1 - ol.render.webgl.EPSILON && + ub > ol.render.webgl.EPSILON && ub < 1 - ol.render.webgl.EPSILON) || (opt_touch && + ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)) { + return [p0.x + ua * (p1.x - p0.x), p0.y + ua * (p1.y - p0.y)]; + } + } + return undefined; +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 Point before the start of the diagonal. + * @param {ol.WebglPolygonVertex} p1 Start point of the diagonal. + * @param {ol.WebglPolygonVertex} p2 Ear candidate. + * @param {ol.WebglPolygonVertex} p3 End point of the diagonal. + * @param {ol.WebglPolygonVertex} p4 Point after the end of the diagonal. + * @return {boolean} Diagonal is inside the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.diagonalIsInside_ = function(p0, p1, p2, p3, p4) { + if (p1.reflex === undefined || p3.reflex === undefined) { + return false; + } + var p1IsLeftOf = (p2.x - p3.x) * (p1.y - p3.y) > (p2.y - p3.y) * (p1.x - p3.x); + var p1IsRightOf = (p4.x - p3.x) * (p1.y - p3.y) < (p4.y - p3.y) * (p1.x - p3.x); + var p3IsLeftOf = (p0.x - p1.x) * (p3.y - p1.y) > (p0.y - p1.y) * (p3.x - p1.x); + var p3IsRightOf = (p2.x - p1.x) * (p3.y - p1.y) < (p2.y - p1.y) * (p3.x - p1.x); + var p1InCone = p3.reflex ? p1IsRightOf || p1IsLeftOf : p1IsRightOf && p1IsLeftOf; + var p3InCone = p1.reflex ? p3IsRightOf || p3IsLeftOf : p3IsRightOf && p3IsLeftOf; + return p1InCone && p3InCone; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) { + var polygons = multiPolygonGeometry.getPolygons(); + var stride = multiPolygonGeometry.getStride(); + var currIndex = this.indices.length; + var currLineIndex = this.lineStringReplay.getCurrentIndex(); + var i, ii, j, jj; + for (i = 0, ii = polygons.length; i < ii; ++i) { + var linearRings = polygons[i].getLinearRings(); + if (linearRings.length > 0) { + var flatCoordinates = linearRings[0].getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + var holes = []; + var holeFlatCoords; + for (j = 1, jj = linearRings.length; j < jj; ++j) { + holeFlatCoords = linearRings[j].getFlatCoordinates(); + holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, + stride, -this.origin[0], -this.origin[1]); + holes.push(holeFlatCoords); + } + this.lineStringReplay.drawPolygonCoordinates(flatCoordinates, holes, stride); + this.drawCoordinates_(flatCoordinates, holes, stride); + } + } + if (this.indices.length > currIndex) { + this.startIndices.push(currIndex); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(currIndex); + this.state_.changed = false; + } + } + if (this.lineStringReplay.getCurrentIndex() > currLineIndex) { + this.lineStringReplay.setPolygonStyle(feature, currLineIndex); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { + var linearRings = polygonGeometry.getLinearRings(); + var stride = polygonGeometry.getStride(); + if (linearRings.length > 0) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(this.indices.length); + this.state_.changed = false; + } + this.lineStringReplay.setPolygonStyle(feature); + + var flatCoordinates = linearRings[0].getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + var holes = []; + var i, ii, holeFlatCoords; + for (i = 1, ii = linearRings.length; i < ii; ++i) { + holeFlatCoords = linearRings[i].getFlatCoordinates(); + holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, + stride, -this.origin[0], -this.origin[1]); + holes.push(holeFlatCoords); + } + this.lineStringReplay.drawPolygonCoordinates(flatCoordinates, holes, stride); + this.drawCoordinates_(flatCoordinates, holes, stride); + } +}; + + +/** + * @inheritDoc + **/ +ol.render.webgl.PolygonReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); + + this.startIndices.push(this.indices.length); + + this.lineStringReplay.finish(context); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } + + this.vertices = null; + this.indices = null; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other PolygonReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + var lineDeleter = this.lineStringReplay.getDeleteResourcesFunction(context); + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + lineDeleter(); + }; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.polygonreplay.defaultshader.fragment; + vertexShader = ol.render.webgl.polygonreplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.polygonreplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 8, 0); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + //Save GL parameters. + var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); + var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); + + if (!hitDetection) { + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.depthFunc(gl.NOTEQUAL); + } + + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); + } else { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, start, end, nextStyle; + end = this.startIndices[this.startIndices.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + this.drawElements(gl, context, start, end); + end = start; + } + } + if (!hitDetection) { + gl.disable(gl.DEPTH_TEST); + gl.clear(gl.DEPTH_BUFFER_BIT); + //Restore GL parameters. + gl.depthMask(tmpDepthMask); + gl.depthFunc(tmpDepthFunc); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices.length - 2; + end = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array<number>} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; + } + } + return undefined; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.PolygonReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices.length - 2; + end = start = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + featureStart = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); + } + start = end = groupStart; + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.<number>} color Color. + */ +ol.render.webgl.PolygonReplay.prototype.setFillStyle_ = function(gl, color) { + gl.uniform4fv(this.defaultLocations_.u_color, color); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + var fillStyleColor = fillStyle ? fillStyle.getColor() : [0, 0, 0, 0]; + if (!(fillStyleColor instanceof CanvasGradient) && + !(fillStyleColor instanceof CanvasPattern)) { + fillStyleColor = ol.color.asArray(fillStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultFillStyle; + } else { + fillStyleColor = ol.render.webgl.defaultFillStyle; + } + if (!this.state_.fillColor || !ol.array.equals(fillStyleColor, this.state_.fillColor)) { + this.state_.fillColor = fillStyleColor; + this.state_.changed = true; + this.styles_.push(fillStyleColor); + } + //Provide a null stroke style, if no strokeStyle is provided. Required for the draw interaction to work. + if (strokeStyle) { + this.lineStringReplay.setFillStrokeStyle(null, strokeStyle); + } else { + var nullStrokeStyle = new ol.style.Stroke({ + color: [0, 0, 0, 0], + lineWidth: 0 + }); + this.lineStringReplay.setFillStrokeStyle(null, nullStrokeStyle); + } +}; + +goog.provide('ol.render.webgl.TextReplay'); + +goog.require('ol'); + +/** + * @constructor + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.TextReplay = function(tolerance, maxExtent) {}; + +/** + * @param {ol.style.Text} textStyle Text style. + */ +ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) {}; + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.TextReplay.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + return undefined; +}; + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ +ol.render.webgl.TextReplay.prototype.drawText = function(flatCoordinates, offset, + end, stride, geometry, feature) {}; + +/** + * @abstract + * @param {ol.webgl.Context} context Context. + */ +ol.render.webgl.TextReplay.prototype.finish = function(context) {}; + +/** + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.TextReplay.prototype.getDeleteResourcesFunction = function(context) { + return ol.nullFunction; +}; + +goog.provide('ol.render.webgl.ReplayGroup'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.render.replay'); +goog.require('ol.render.ReplayGroup'); +goog.require('ol.render.webgl'); +goog.require('ol.render.webgl.CircleReplay'); +goog.require('ol.render.webgl.ImageReplay'); +goog.require('ol.render.webgl.LineStringReplay'); +goog.require('ol.render.webgl.PolygonReplay'); +goog.require('ol.render.webgl.TextReplay'); + +/** + * @constructor + * @extends {ol.render.ReplayGroup} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @param {number=} opt_renderBuffer Render buffer. + * @struct + */ +ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) { + ol.render.ReplayGroup.call(this); + + /** + * @type {ol.Extent} + * @private + */ + this.maxExtent_ = maxExtent; + + /** + * @type {number} + * @private + */ + this.tolerance_ = tolerance; + + /** + * @type {number|undefined} + * @private + */ + this.renderBuffer_ = opt_renderBuffer; + + /** + * @private + * @type {!Object.<string, + * Object.<ol.render.ReplayType, ol.render.webgl.Replay>>} + */ + this.replaysByZIndex_ = {}; + +}; +ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup); + + +/** + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) { + var functions = []; + var zKey; + for (zKey in this.replaysByZIndex_) { + var replays = this.replaysByZIndex_[zKey]; + var replayKey; + for (replayKey in replays) { + functions.push( + replays[replayKey].getDeleteResourcesFunction(context)); + } + } + return function() { + var length = functions.length; + var result; + for (var i = 0; i < length; i++) { + result = functions[i].apply(this, arguments); + } + return result; + }; +}; + + +/** + * @param {ol.webgl.Context} context Context. + */ +ol.render.webgl.ReplayGroup.prototype.finish = function(context) { + var zKey; + for (zKey in this.replaysByZIndex_) { + var replays = this.replaysByZIndex_[zKey]; + var replayKey; + for (replayKey in replays) { + replays[replayKey].finish(context); + } + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { + var zIndexKey = zIndex !== undefined ? zIndex.toString() : '0'; + var replays = this.replaysByZIndex_[zIndexKey]; + if (replays === undefined) { + replays = {}; + this.replaysByZIndex_[zIndexKey] = replays; + } + var replay = replays[replayType]; + if (replay === undefined) { + var Constructor = ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_[replayType]; + ol.DEBUG && console.assert(Constructor !== undefined, + replayType + + ' constructor missing from ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_'); + replay = new Constructor(this.tolerance_, this.maxExtent_); + replays[replayType] = replay; + } + return replay; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { + return ol.obj.isEmpty(this.replaysByZIndex_); +}; + + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + */ +ol.render.webgl.ReplayGroup.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash) { + /** @type {Array.<number>} */ + var zs = Object.keys(this.replaysByZIndex_).map(Number); + zs.sort(ol.array.numberSafeCompareFunction); + + var i, ii, j, jj, replays, replay; + for (i = 0, ii = zs.length; i < ii; ++i) { + replays = this.replaysByZIndex_[zs[i].toString()]; + for (j = 0, jj = ol.render.replay.ORDER.length; j < jj; ++j) { + replay = replays[ol.render.replay.ORDER[j]]; + if (replay !== undefined) { + replay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + undefined, false); + } + } + } +}; + + +/** + * @private + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, + center, resolution, rotation, size, pixelRatio, opacity, + skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent) { + /** @type {Array.<number>} */ + var zs = Object.keys(this.replaysByZIndex_).map(Number); + zs.sort(function(a, b) { + return b - a; + }); + + var i, ii, j, replays, replay, result; + for (i = 0, ii = zs.length; i < ii; ++i) { + replays = this.replaysByZIndex_[zs[i].toString()]; + for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) { + replay = replays[ol.render.replay.ORDER[j]]; + if (replay !== undefined) { + result = replay.replay(context, + center, resolution, rotation, size, pixelRatio, opacity, + skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent); + if (result) { + return result; + } + } + } + } + return undefined; +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} callback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + callback) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + + + /** + * @type {ol.Extent} + */ + var hitExtent; + if (this.renderBuffer_ !== undefined) { + // build an extent around the coordinate, so that only features that + // intersect this extent are checked + hitExtent = ol.extent.buffer( + ol.extent.createOrUpdateFromCoordinate(coordinate), + resolution * this.renderBuffer_); + } + + return this.replayHitDetection_(context, + coordinate, resolution, rotation, ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_, + pixelRatio, opacity, skippedFeaturesHash, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var imageData = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); + + if (imageData[3] > 0) { + var result = callback(feature); + if (result) { + return result; + } + } + }, true, hitExtent); +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features + * to skip. + * @return {boolean} Is there a feature at the given coordinate? + */ +ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + + var hasFeature = this.replayHitDetection_(context, + coordinate, resolution, rotation, ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_, + pixelRatio, opacity, skippedFeaturesHash, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {boolean} Is there a feature? + */ + function(feature) { + var imageData = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); + return imageData[3] > 0; + }, false); + + return hasFeature !== undefined; +}; + +/** + * @const + * @private + * @type {Array.<number>} + */ +ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_ = [1, 1]; + +/** + * @const + * @private + * @type {Object.<ol.render.ReplayType, + * function(new: ol.render.webgl.Replay, number, + * ol.Extent)>} + */ +ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_ = { + 'Circle': ol.render.webgl.CircleReplay, + 'Image': ol.render.webgl.ImageReplay, + 'LineString': ol.render.webgl.LineStringReplay, + 'Polygon': ol.render.webgl.PolygonReplay, + 'Text': ol.render.webgl.TextReplay +}; + +goog.provide('ol.render.webgl.Immediate'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.render.ReplayType'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.webgl.ReplayGroup'); +goog.require('ol.render.webgl'); + + +/** + * @constructor + * @extends {ol.render.VectorContext} + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {ol.Extent} extent Extent. + * @param {number} pixelRatio Pixel ratio. + * @struct + */ +ol.render.webgl.Immediate = function(context, center, resolution, rotation, size, extent, pixelRatio) { + ol.render.VectorContext.call(this); + + /** + * @private + */ + this.context_ = context; + + /** + * @private + */ + this.center_ = center; + + /** + * @private + */ + this.extent_ = extent; + + /** + * @private + */ + this.pixelRatio_ = pixelRatio; + + /** + * @private + */ + this.size_ = size; + + /** + * @private + */ + this.rotation_ = rotation; + + /** + * @private + */ + this.resolution_ = resolution; + + /** + * @private + * @type {ol.style.Image} + */ + this.imageStyle_ = null; + + /** + * @private + * @type {ol.style.Fill} + */ + this.fillStyle_ = null; + + /** + * @private + * @type {ol.style.Stroke} + */ + this.strokeStyle_ = null; + +}; +ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext); + + +/** + * Set the rendering style. Note that since this is an immediate rendering API, + * any `zIndex` on the provided style will be ignored. + * + * @param {ol.style.Style} style The rendering style. + * @api + */ +ol.render.webgl.Immediate.prototype.setStyle = function(style) { + this.setFillStrokeStyle(style.getFill(), style.getStroke()); + this.setImageStyle(style.getImage()); +}; + + +/** + * Render a geometry into the canvas. Call + * {@link ol.render.webgl.Immediate#setStyle} first to set the rendering style. + * + * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render. + * @api + */ +ol.render.webgl.Immediate.prototype.drawGeometry = function(geometry) { + var type = geometry.getType(); + switch (type) { + case ol.geom.GeometryType.POINT: + this.drawPoint(/** @type {ol.geom.Point} */ (geometry), null); + break; + case ol.geom.GeometryType.LINE_STRING: + this.drawLineString(/** @type {ol.geom.LineString} */ (geometry), null); + break; + case ol.geom.GeometryType.POLYGON: + this.drawPolygon(/** @type {ol.geom.Polygon} */ (geometry), null); + break; + case ol.geom.GeometryType.MULTI_POINT: + this.drawMultiPoint(/** @type {ol.geom.MultiPoint} */ (geometry), null); + break; + case ol.geom.GeometryType.MULTI_LINE_STRING: + this.drawMultiLineString(/** @type {ol.geom.MultiLineString} */ (geometry), null); + break; + case ol.geom.GeometryType.MULTI_POLYGON: + this.drawMultiPolygon(/** @type {ol.geom.MultiPolygon} */ (geometry), null); + break; + case ol.geom.GeometryType.GEOMETRY_COLLECTION: + this.drawGeometryCollection(/** @type {ol.geom.GeometryCollection} */ (geometry), null); + break; + case ol.geom.GeometryType.CIRCLE: + this.drawCircle(/** @type {ol.geom.Circle} */ (geometry), null); + break; + default: + ol.DEBUG && console.assert(false, 'Unsupported geometry type: ' + type); + } +}; + + +/** + * @inheritDoc + * @api + */ +ol.render.webgl.Immediate.prototype.drawFeature = function(feature, style) { + var geometry = style.getGeometryFunction()(feature); + if (!geometry || + !ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + this.setStyle(style); + ol.DEBUG && console.assert(geometry, 'geometry must be truthy'); + this.drawGeometry(geometry); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawGeometryCollection = function(geometry, data) { + var geometries = geometry.getGeometriesArray(); + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + this.drawGeometry(geometries[i]); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawPoint = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.ImageReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.IMAGE)); + replay.setImageStyle(this.imageStyle_); + replay.drawPoint(geometry, data); + replay.finish(context); + // default colors + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawMultiPoint = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.ImageReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.IMAGE)); + replay.setImageStyle(this.imageStyle_); + replay.drawMultiPoint(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawLineString = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.LineStringReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.LINE_STRING)); + replay.setFillStrokeStyle(null, this.strokeStyle_); + replay.drawLineString(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawMultiLineString = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.LineStringReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.LINE_STRING)); + replay.setFillStrokeStyle(null, this.strokeStyle_); + replay.drawMultiLineString(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawPolygon = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.PolygonReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.POLYGON)); + replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); + replay.drawPolygon(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawMultiPolygon = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.PolygonReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.POLYGON)); + replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); + replay.drawMultiPolygon(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.drawCircle = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.CircleReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.CIRCLE)); + replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); + replay.drawCircle(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) { + this.imageStyle_ = imageStyle; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + this.fillStyle_ = fillStyle; + this.strokeStyle_ = strokeStyle; +}; + +// This file is automatically generated, do not edit +goog.provide('ol.renderer.webgl.defaultmapshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.renderer.webgl.defaultmapshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.renderer.webgl.defaultmapshader.Fragment.SOURCE); +}; +ol.inherits(ol.renderer.webgl.defaultmapshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_texture, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n gl_FragColor.a = texColor.a * u_opacity;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Fragment.SOURCE = ol.DEBUG ? + ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE : + ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE; + + +ol.renderer.webgl.defaultmapshader.fragment = new ol.renderer.webgl.defaultmapshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.renderer.webgl.defaultmapshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.renderer.webgl.defaultmapshader.Vertex.SOURCE); +}; +ol.inherits(ol.renderer.webgl.defaultmapshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Vertex.SOURCE = ol.DEBUG ? + ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE : + ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE; + + +ol.renderer.webgl.defaultmapshader.vertex = new ol.renderer.webgl.defaultmapshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.renderer.webgl.defaultmapshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'f'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'e'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_texCoordMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_texCoordMatrix' : 'd'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_texture = gl.getUniformLocation( + program, ol.DEBUG ? 'u_texture' : 'g'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'b'); + + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, ol.DEBUG ? 'a_texCoord' : 'c'); +}; + +goog.provide('ol.renderer.webgl.Layer'); + +goog.require('ol'); +goog.require('ol.render.Event'); +goog.require('ol.render.webgl.Immediate'); +goog.require('ol.renderer.Layer'); +goog.require('ol.renderer.webgl.defaultmapshader'); +goog.require('ol.transform'); +goog.require('ol.vec.Mat4'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); +goog.require('ol.webgl.Context'); + + +/** + * @constructor + * @extends {ol.renderer.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Layer} layer Layer. + */ +ol.renderer.webgl.Layer = function(mapRenderer, layer) { + + ol.renderer.Layer.call(this, layer); + + /** + * @protected + * @type {ol.renderer.webgl.Map} + */ + this.mapRenderer = mapRenderer; + + /** + * @private + * @type {ol.webgl.Buffer} + */ + this.arrayBuffer_ = new ol.webgl.Buffer([ + -1, -1, 0, 0, + 1, -1, 1, 0, + -1, 1, 0, 1, + 1, 1, 1, 1 + ]); + + /** + * @protected + * @type {WebGLTexture} + */ + this.texture = null; + + /** + * @protected + * @type {WebGLFramebuffer} + */ + this.framebuffer = null; + + /** + * @protected + * @type {number|undefined} + */ + this.framebufferDimension = undefined; + + /** + * @protected + * @type {ol.Transform} + */ + this.texCoordMatrix = ol.transform.create(); + + /** + * @protected + * @type {ol.Transform} + */ + this.projectionMatrix = ol.transform.create(); + + /** + * @type {Array.<number>} + * @private + */ + this.tmpMat4_ = ol.vec.Mat4.create(); + + /** + * @private + * @type {ol.renderer.webgl.defaultmapshader.Locations} + */ + this.defaultLocations_ = null; + +}; +ol.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer); + + +/** + * @param {olx.FrameState} frameState Frame state. + * @param {number} framebufferDimension Framebuffer dimension. + * @protected + */ +ol.renderer.webgl.Layer.prototype.bindFramebuffer = function(frameState, framebufferDimension) { + + var gl = this.mapRenderer.getGL(); + + if (this.framebufferDimension === undefined || + this.framebufferDimension != framebufferDimension) { + /** + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLFramebuffer} framebuffer Framebuffer. + * @param {WebGLTexture} texture Texture. + */ + var postRenderFunction = function(gl, framebuffer, texture) { + if (!gl.isContextLost()) { + gl.deleteFramebuffer(framebuffer); + gl.deleteTexture(texture); + } + }.bind(null, gl, this.framebuffer, this.texture); + + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (postRenderFunction) + ); + + var texture = ol.webgl.Context.createEmptyTexture( + gl, framebufferDimension, framebufferDimension); + + var framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D(ol.webgl.FRAMEBUFFER, + ol.webgl.COLOR_ATTACHMENT0, ol.webgl.TEXTURE_2D, texture, 0); + + this.texture = texture; + this.framebuffer = framebuffer; + this.framebufferDimension = framebufferDimension; + + } else { + gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, this.framebuffer); + } + +}; + + +/** + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @param {ol.webgl.Context} context Context. + */ +ol.renderer.webgl.Layer.prototype.composeFrame = function(frameState, layerState, context) { + + this.dispatchComposeEvent_( + ol.render.Event.Type.PRECOMPOSE, context, frameState); + + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.arrayBuffer_); + + var gl = context.getGL(); + + var fragmentShader = ol.renderer.webgl.defaultmapshader.fragment; + var vertexShader = ol.renderer.webgl.defaultmapshader.vertex; + + var program = context.getProgram(fragmentShader, vertexShader); + + var locations; + if (!this.defaultLocations_) { + locations = + new ol.renderer.webgl.defaultmapshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + if (context.useProgram(program)) { + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer( + locations.a_position, 2, ol.webgl.FLOAT, false, 16, 0); + gl.enableVertexAttribArray(locations.a_texCoord); + gl.vertexAttribPointer( + locations.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8); + gl.uniform1i(locations.u_texture, 0); + } + + gl.uniformMatrix4fv(locations.u_texCoordMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getTexCoordMatrix())); + gl.uniformMatrix4fv(locations.u_projectionMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getProjectionMatrix())); + gl.uniform1f(locations.u_opacity, layerState.opacity); + gl.bindTexture(ol.webgl.TEXTURE_2D, this.getTexture()); + gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4); + + this.dispatchComposeEvent_( + ol.render.Event.Type.POSTCOMPOSE, context, frameState); + +}; + + +/** + * @param {ol.render.Event.Type} type Event type. + * @param {ol.webgl.Context} context WebGL context. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState) { + var layer = this.getLayer(); + if (layer.hasListener(type)) { + var viewState = frameState.viewState; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var extent = frameState.extent; + var center = viewState.center; + var rotation = viewState.rotation; + var size = frameState.size; + + var render = new ol.render.webgl.Immediate( + context, center, resolution, rotation, size, extent, pixelRatio); + var composeEvent = new ol.render.Event( + type, render, frameState, null, context); + layer.dispatchEvent(composeEvent); + } +}; + + +/** + * @return {!ol.Transform} Matrix. + */ +ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() { + return this.texCoordMatrix; +}; + + +/** + * @return {WebGLTexture} Texture. + */ +ol.renderer.webgl.Layer.prototype.getTexture = function() { + return this.texture; +}; + + +/** + * @return {!ol.Transform} Matrix. + */ +ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() { + return this.projectionMatrix; +}; + + +/** + * Handle webglcontextlost. + */ +ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() { + this.texture = null; + this.framebuffer = null; + this.framebufferDimension = undefined; +}; + + +/** + * @abstract + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @param {ol.webgl.Context} context Context. + * @return {boolean} whether composeFrame should be called. + */ +ol.renderer.webgl.Layer.prototype.prepareFrame = function(frameState, layerState, context) {}; + + +/** + * @abstract + * @param {ol.Pixel} pixel Pixel. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer + * callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @return {T|undefined} Callback result. + * @template S,T,U + */ +ol.renderer.webgl.Layer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {}; + +goog.provide('ol.ImageCanvas'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.ImageBase'); + + +/** + * @constructor + * @extends {ol.ImageBase} + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {Array.<ol.Attribution>} attributions Attributions. + * @param {HTMLCanvasElement} canvas Canvas. + * @param {ol.ImageCanvasLoader=} opt_loader Optional loader function to + * support asynchronous canvas drawing. + */ +ol.ImageCanvas = function(extent, resolution, pixelRatio, attributions, + canvas, opt_loader) { + + /** + * Optional canvas loader function. + * @type {?ol.ImageCanvasLoader} + * @private + */ + this.loader_ = opt_loader !== undefined ? opt_loader : null; + + var state = opt_loader !== undefined ? + ol.Image.State.IDLE : ol.Image.State.LOADED; + + ol.ImageBase.call(this, extent, resolution, pixelRatio, state, attributions); + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = canvas; + + /** + * @private + * @type {Error} + */ + this.error_ = null; + +}; +ol.inherits(ol.ImageCanvas, ol.ImageBase); + + +/** + * Get any error associated with asynchronous rendering. + * @return {Error} Any error that occurred during rendering. + */ +ol.ImageCanvas.prototype.getError = function() { + return this.error_; +}; + + +/** + * Handle async drawing complete. + * @param {Error} err Any error during drawing. + * @private + */ +ol.ImageCanvas.prototype.handleLoad_ = function(err) { + if (err) { + this.error_ = err; + this.state = ol.Image.State.ERROR; + } else { + this.state = ol.Image.State.LOADED; + } + this.changed(); +}; + + +/** + * Trigger drawing on canvas. + */ +ol.ImageCanvas.prototype.load = function() { + if (this.state == ol.Image.State.IDLE) { + ol.DEBUG && console.assert(this.loader_, 'this.loader_ must be set'); + this.state = ol.Image.State.LOADING; + this.changed(); + this.loader_(this.handleLoad_.bind(this)); + } +}; + + +/** + * @inheritDoc + */ +ol.ImageCanvas.prototype.getImage = function(opt_context) { + return this.canvas_; +}; + +goog.provide('ol.reproj'); + +goog.require('ol'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.math'); +goog.require('ol.proj'); + + +/** + * We need to employ more sophisticated solution + * if the web browser antialiases clipping edges on canvas. + * + * Currently only Chrome does not antialias the edges, but this is probably + * going to be "fixed" in the future: http://crbug.com/424291 + * + * @type {boolean} + * @private + */ +ol.reproj.browserAntialiasesClip_ = (function() { + // Adapted from http://stackoverflow.com/questions/4565112/javascript-how-to-find-out-if-the-user-browser-is-chrome + var isOpera = navigator.userAgent.indexOf('OPR') > -1; + var isIEedge = navigator.userAgent.indexOf('Edge') > -1; + return !( + !navigator.userAgent.match('CriOS') && // Not Chrome on iOS + 'chrome' in window && // Has chrome in window + navigator.vendor === 'Google Inc.' && // Vendor is Google. + isOpera == false && // Not Opera + isIEedge == false // Not Edge + ); +})(); + + +/** + * Calculates ideal resolution to use from the source in order to achieve + * pixel mapping as close as possible to 1:1 during reprojection. + * The resolution is calculated regardless of what resolutions + * are actually available in the dataset (TileGrid, Image, ...). + * + * @param {ol.proj.Projection} sourceProj Source projection. + * @param {ol.proj.Projection} targetProj Target projection. + * @param {ol.Coordinate} targetCenter Target center. + * @param {number} targetResolution Target resolution. + * @return {number} The best resolution to use. Can be +-Infinity, NaN or 0. + */ +ol.reproj.calculateSourceResolution = function(sourceProj, targetProj, + targetCenter, targetResolution) { + + var sourceCenter = ol.proj.transform(targetCenter, targetProj, sourceProj); + + // calculate the ideal resolution of the source data + var sourceResolution = + ol.proj.getPointResolution(targetProj, targetResolution, targetCenter); + + var targetMetersPerUnit = targetProj.getMetersPerUnit(); + if (targetMetersPerUnit !== undefined) { + sourceResolution *= targetMetersPerUnit; + } + var sourceMetersPerUnit = sourceProj.getMetersPerUnit(); + if (sourceMetersPerUnit !== undefined) { + sourceResolution /= sourceMetersPerUnit; + } + + // Based on the projection properties, the point resolution at the specified + // coordinates may be slightly different. We need to reverse-compensate this + // in order to achieve optimal results. + + var compensationFactor = + ol.proj.getPointResolution(sourceProj, sourceResolution, sourceCenter) / + sourceResolution; + + if (isFinite(compensationFactor) && compensationFactor > 0) { + sourceResolution /= compensationFactor; + } + + return sourceResolution; +}; + + +/** + * Enlarge the clipping triangle point by 1 pixel to ensure the edges overlap + * in order to mask gaps caused by antialiasing. + * + * @param {number} centroidX Centroid of the triangle (x coordinate in pixels). + * @param {number} centroidY Centroid of the triangle (y coordinate in pixels). + * @param {number} x X coordinate of the point (in pixels). + * @param {number} y Y coordinate of the point (in pixels). + * @return {ol.Coordinate} New point 1 px farther from the centroid. + * @private + */ +ol.reproj.enlargeClipPoint_ = function(centroidX, centroidY, x, y) { + var dX = x - centroidX, dY = y - centroidY; + var distance = Math.sqrt(dX * dX + dY * dY); + return [Math.round(x + dX / distance), Math.round(y + dY / distance)]; +}; + + +/** + * Renders the source data into new canvas based on the triangulation. + * + * @param {number} width Width of the canvas. + * @param {number} height Height of the canvas. + * @param {number} pixelRatio Pixel ratio. + * @param {number} sourceResolution Source resolution. + * @param {ol.Extent} sourceExtent Extent of the data source. + * @param {number} targetResolution Target resolution. + * @param {ol.Extent} targetExtent Target extent. + * @param {ol.reproj.Triangulation} triangulation Calculated triangulation. + * @param {Array.<{extent: ol.Extent, + * image: (HTMLCanvasElement|Image|HTMLVideoElement)}>} sources + * Array of sources. + * @param {number} gutter Gutter of the sources. + * @param {boolean=} opt_renderEdges Render reprojection edges. + * @return {HTMLCanvasElement} Canvas with reprojected data. + */ +ol.reproj.render = function(width, height, pixelRatio, + sourceResolution, sourceExtent, targetResolution, targetExtent, + triangulation, sources, gutter, opt_renderEdges) { + + var context = ol.dom.createCanvasContext2D(Math.round(pixelRatio * width), + Math.round(pixelRatio * height)); + + if (sources.length === 0) { + return context.canvas; + } + + context.scale(pixelRatio, pixelRatio); + + var sourceDataExtent = ol.extent.createEmpty(); + sources.forEach(function(src, i, arr) { + ol.extent.extend(sourceDataExtent, src.extent); + }); + + var canvasWidthInUnits = ol.extent.getWidth(sourceDataExtent); + var canvasHeightInUnits = ol.extent.getHeight(sourceDataExtent); + var stitchContext = ol.dom.createCanvasContext2D( + Math.round(pixelRatio * canvasWidthInUnits / sourceResolution), + Math.round(pixelRatio * canvasHeightInUnits / sourceResolution)); + + var stitchScale = pixelRatio / sourceResolution; + + sources.forEach(function(src, i, arr) { + var xPos = src.extent[0] - sourceDataExtent[0]; + var yPos = -(src.extent[3] - sourceDataExtent[3]); + var srcWidth = ol.extent.getWidth(src.extent); + var srcHeight = ol.extent.getHeight(src.extent); + + stitchContext.drawImage( + src.image, + gutter, gutter, + src.image.width - 2 * gutter, src.image.height - 2 * gutter, + xPos * stitchScale, yPos * stitchScale, + srcWidth * stitchScale, srcHeight * stitchScale); + }); + + var targetTopLeft = ol.extent.getTopLeft(targetExtent); + + triangulation.getTriangles().forEach(function(triangle, i, arr) { + /* Calculate affine transform (src -> dst) + * Resulting matrix can be used to transform coordinate + * from `sourceProjection` to destination pixels. + * + * To optimize number of context calls and increase numerical stability, + * we also do the following operations: + * trans(-topLeftExtentCorner), scale(1 / targetResolution), scale(1, -1) + * here before solving the linear system so [ui, vi] are pixel coordinates. + * + * Src points: xi, yi + * Dst points: ui, vi + * Affine coefficients: aij + * + * | x0 y0 1 0 0 0 | |a00| |u0| + * | x1 y1 1 0 0 0 | |a01| |u1| + * | x2 y2 1 0 0 0 | x |a02| = |u2| + * | 0 0 0 x0 y0 1 | |a10| |v0| + * | 0 0 0 x1 y1 1 | |a11| |v1| + * | 0 0 0 x2 y2 1 | |a12| |v2| + */ + var source = triangle.source, target = triangle.target; + var x0 = source[0][0], y0 = source[0][1], + x1 = source[1][0], y1 = source[1][1], + x2 = source[2][0], y2 = source[2][1]; + var u0 = (target[0][0] - targetTopLeft[0]) / targetResolution, + v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution; + var u1 = (target[1][0] - targetTopLeft[0]) / targetResolution, + v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution; + var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution, + v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution; + + // Shift all the source points to improve numerical stability + // of all the subsequent calculations. The [x0, y0] is used here. + // This is also used to simplify the linear system. + var sourceNumericalShiftX = x0, sourceNumericalShiftY = y0; + x0 = 0; + y0 = 0; + x1 -= sourceNumericalShiftX; + y1 -= sourceNumericalShiftY; + x2 -= sourceNumericalShiftX; + y2 -= sourceNumericalShiftY; + + var augmentedMatrix = [ + [x1, y1, 0, 0, u1 - u0], + [x2, y2, 0, 0, u2 - u0], + [0, 0, x1, y1, v1 - v0], + [0, 0, x2, y2, v2 - v0] + ]; + var affineCoefs = ol.math.solveLinearSystem(augmentedMatrix); + if (!affineCoefs) { + return; + } + + context.save(); + context.beginPath(); + if (ol.reproj.browserAntialiasesClip_) { + var centroidX = (u0 + u1 + u2) / 3, centroidY = (v0 + v1 + v2) / 3; + var p0 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u0, v0); + var p1 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u1, v1); + var p2 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u2, v2); + + context.moveTo(p1[0], p1[1]); + context.lineTo(p0[0], p0[1]); + context.lineTo(p2[0], p2[1]); + } else { + context.moveTo(u1, v1); + context.lineTo(u0, v0); + context.lineTo(u2, v2); + } + context.clip(); + + context.transform( + affineCoefs[0], affineCoefs[2], affineCoefs[1], affineCoefs[3], u0, v0); + + context.translate(sourceDataExtent[0] - sourceNumericalShiftX, + sourceDataExtent[3] - sourceNumericalShiftY); + + context.scale(sourceResolution / pixelRatio, + -sourceResolution / pixelRatio); + + context.drawImage(stitchContext.canvas, 0, 0); + context.restore(); + }); + + if (opt_renderEdges) { + context.save(); + + context.strokeStyle = 'black'; + context.lineWidth = 1; + + triangulation.getTriangles().forEach(function(triangle, i, arr) { + var target = triangle.target; + var u0 = (target[0][0] - targetTopLeft[0]) / targetResolution, + v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution; + var u1 = (target[1][0] - targetTopLeft[0]) / targetResolution, + v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution; + var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution, + v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution; + + context.beginPath(); + context.moveTo(u1, v1); + context.lineTo(u0, v0); + context.lineTo(u2, v2); + context.closePath(); + context.stroke(); + }); + + context.restore(); + } + return context.canvas; +}; + +goog.provide('ol.reproj.Triangulation'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.math'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Class containing triangulation of the given target extent. + * Used for determining source data and the reprojection itself. + * + * @param {ol.proj.Projection} sourceProj Source projection. + * @param {ol.proj.Projection} targetProj Target projection. + * @param {ol.Extent} targetExtent Target extent to triangulate. + * @param {ol.Extent} maxSourceExtent Maximal source extent that can be used. + * @param {number} errorThreshold Acceptable error (in source units). + * @constructor + */ +ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent, + maxSourceExtent, errorThreshold) { + + /** + * @type {ol.proj.Projection} + * @private + */ + this.sourceProj_ = sourceProj; + + /** + * @type {ol.proj.Projection} + * @private + */ + this.targetProj_ = targetProj; + + /** @type {!Object.<string, ol.Coordinate>} */ + var transformInvCache = {}; + var transformInv = ol.proj.getTransform(this.targetProj_, this.sourceProj_); + + /** + * @param {ol.Coordinate} c A coordinate. + * @return {ol.Coordinate} Transformed coordinate. + * @private + */ + this.transformInv_ = function(c) { + var key = c[0] + '/' + c[1]; + if (!transformInvCache[key]) { + transformInvCache[key] = transformInv(c); + } + return transformInvCache[key]; + }; + + /** + * @type {ol.Extent} + * @private + */ + this.maxSourceExtent_ = maxSourceExtent; + + /** + * @type {number} + * @private + */ + this.errorThresholdSquared_ = errorThreshold * errorThreshold; + + /** + * @type {Array.<ol.ReprojTriangle>} + * @private + */ + this.triangles_ = []; + + /** + * Indicates that the triangulation crosses edge of the source projection. + * @type {boolean} + * @private + */ + this.wrapsXInSource_ = false; + + /** + * @type {boolean} + * @private + */ + this.canWrapXInSource_ = this.sourceProj_.canWrapX() && + !!maxSourceExtent && + !!this.sourceProj_.getExtent() && + (ol.extent.getWidth(maxSourceExtent) == + ol.extent.getWidth(this.sourceProj_.getExtent())); + + /** + * @type {?number} + * @private + */ + this.sourceWorldWidth_ = this.sourceProj_.getExtent() ? + ol.extent.getWidth(this.sourceProj_.getExtent()) : null; + + /** + * @type {?number} + * @private + */ + this.targetWorldWidth_ = this.targetProj_.getExtent() ? + ol.extent.getWidth(this.targetProj_.getExtent()) : null; + + var destinationTopLeft = ol.extent.getTopLeft(targetExtent); + var destinationTopRight = ol.extent.getTopRight(targetExtent); + var destinationBottomRight = ol.extent.getBottomRight(targetExtent); + var destinationBottomLeft = ol.extent.getBottomLeft(targetExtent); + var sourceTopLeft = this.transformInv_(destinationTopLeft); + var sourceTopRight = this.transformInv_(destinationTopRight); + var sourceBottomRight = this.transformInv_(destinationBottomRight); + var sourceBottomLeft = this.transformInv_(destinationBottomLeft); + + this.addQuad_( + destinationTopLeft, destinationTopRight, + destinationBottomRight, destinationBottomLeft, + sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft, + ol.RASTER_REPROJECTION_MAX_SUBDIVISION); + + if (this.wrapsXInSource_) { + // Fix coordinates (ol.proj returns wrapped coordinates, "unwrap" here). + // This significantly simplifies the rest of the reprojection process. + + ol.DEBUG && console.assert(this.sourceWorldWidth_ !== null); + var leftBound = Infinity; + this.triangles_.forEach(function(triangle, i, arr) { + leftBound = Math.min(leftBound, + triangle.source[0][0], triangle.source[1][0], triangle.source[2][0]); + }); + + // Shift triangles to be as close to `leftBound` as possible + // (if the distance is more than `worldWidth / 2` it can be closer. + this.triangles_.forEach(function(triangle) { + if (Math.max(triangle.source[0][0], triangle.source[1][0], + triangle.source[2][0]) - leftBound > this.sourceWorldWidth_ / 2) { + var newTriangle = [[triangle.source[0][0], triangle.source[0][1]], + [triangle.source[1][0], triangle.source[1][1]], + [triangle.source[2][0], triangle.source[2][1]]]; + if ((newTriangle[0][0] - leftBound) > this.sourceWorldWidth_ / 2) { + newTriangle[0][0] -= this.sourceWorldWidth_; + } + if ((newTriangle[1][0] - leftBound) > this.sourceWorldWidth_ / 2) { + newTriangle[1][0] -= this.sourceWorldWidth_; + } + if ((newTriangle[2][0] - leftBound) > this.sourceWorldWidth_ / 2) { + newTriangle[2][0] -= this.sourceWorldWidth_; + } + + // Rarely (if the extent contains both the dateline and prime meridian) + // the shift can in turn break some triangles. + // Detect this here and don't shift in such cases. + var minX = Math.min( + newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]); + var maxX = Math.max( + newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]); + if ((maxX - minX) < this.sourceWorldWidth_ / 2) { + triangle.source = newTriangle; + } + } + }, this); + } + + transformInvCache = {}; +}; + + +/** + * Adds triangle to the triangulation. + * @param {ol.Coordinate} a The target a coordinate. + * @param {ol.Coordinate} b The target b coordinate. + * @param {ol.Coordinate} c The target c coordinate. + * @param {ol.Coordinate} aSrc The source a coordinate. + * @param {ol.Coordinate} bSrc The source b coordinate. + * @param {ol.Coordinate} cSrc The source c coordinate. + * @private + */ +ol.reproj.Triangulation.prototype.addTriangle_ = function(a, b, c, + aSrc, bSrc, cSrc) { + this.triangles_.push({ + source: [aSrc, bSrc, cSrc], + target: [a, b, c] + }); +}; + + +/** + * Adds quad (points in clock-wise order) to the triangulation + * (and reprojects the vertices) if valid. + * Performs quad subdivision if needed to increase precision. + * + * @param {ol.Coordinate} a The target a coordinate. + * @param {ol.Coordinate} b The target b coordinate. + * @param {ol.Coordinate} c The target c coordinate. + * @param {ol.Coordinate} d The target d coordinate. + * @param {ol.Coordinate} aSrc The source a coordinate. + * @param {ol.Coordinate} bSrc The source b coordinate. + * @param {ol.Coordinate} cSrc The source c coordinate. + * @param {ol.Coordinate} dSrc The source d coordinate. + * @param {number} maxSubdivision Maximal allowed subdivision of the quad. + * @private + */ +ol.reproj.Triangulation.prototype.addQuad_ = function(a, b, c, d, + aSrc, bSrc, cSrc, dSrc, maxSubdivision) { + + var sourceQuadExtent = ol.extent.boundingExtent([aSrc, bSrc, cSrc, dSrc]); + var sourceCoverageX = this.sourceWorldWidth_ ? + ol.extent.getWidth(sourceQuadExtent) / this.sourceWorldWidth_ : null; + var sourceWorldWidth = /** @type {number} */ (this.sourceWorldWidth_); + + // when the quad is wrapped in the source projection + // it covers most of the projection extent, but not fully + var wrapsX = this.sourceProj_.canWrapX() && + sourceCoverageX > 0.5 && sourceCoverageX < 1; + + var needsSubdivision = false; + + if (maxSubdivision > 0) { + if (this.targetProj_.isGlobal() && this.targetWorldWidth_) { + var targetQuadExtent = ol.extent.boundingExtent([a, b, c, d]); + var targetCoverageX = + ol.extent.getWidth(targetQuadExtent) / this.targetWorldWidth_; + needsSubdivision |= + targetCoverageX > ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH; + } + if (!wrapsX && this.sourceProj_.isGlobal() && sourceCoverageX) { + needsSubdivision |= + sourceCoverageX > ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH; + } + } + + if (!needsSubdivision && this.maxSourceExtent_) { + if (!ol.extent.intersects(sourceQuadExtent, this.maxSourceExtent_)) { + // whole quad outside source projection extent -> ignore + return; + } + } + + if (!needsSubdivision) { + if (!isFinite(aSrc[0]) || !isFinite(aSrc[1]) || + !isFinite(bSrc[0]) || !isFinite(bSrc[1]) || + !isFinite(cSrc[0]) || !isFinite(cSrc[1]) || + !isFinite(dSrc[0]) || !isFinite(dSrc[1])) { + if (maxSubdivision > 0) { + needsSubdivision = true; + } else { + return; + } + } + } + + if (maxSubdivision > 0) { + if (!needsSubdivision) { + var center = [(a[0] + c[0]) / 2, (a[1] + c[1]) / 2]; + var centerSrc = this.transformInv_(center); + + var dx; + if (wrapsX) { + var centerSrcEstimX = + (ol.math.modulo(aSrc[0], sourceWorldWidth) + + ol.math.modulo(cSrc[0], sourceWorldWidth)) / 2; + dx = centerSrcEstimX - + ol.math.modulo(centerSrc[0], sourceWorldWidth); + } else { + dx = (aSrc[0] + cSrc[0]) / 2 - centerSrc[0]; + } + var dy = (aSrc[1] + cSrc[1]) / 2 - centerSrc[1]; + var centerSrcErrorSquared = dx * dx + dy * dy; + needsSubdivision = centerSrcErrorSquared > this.errorThresholdSquared_; + } + if (needsSubdivision) { + if (Math.abs(a[0] - c[0]) <= Math.abs(a[1] - c[1])) { + // split horizontally (top & bottom) + var bc = [(b[0] + c[0]) / 2, (b[1] + c[1]) / 2]; + var bcSrc = this.transformInv_(bc); + var da = [(d[0] + a[0]) / 2, (d[1] + a[1]) / 2]; + var daSrc = this.transformInv_(da); + + this.addQuad_( + a, b, bc, da, aSrc, bSrc, bcSrc, daSrc, maxSubdivision - 1); + this.addQuad_( + da, bc, c, d, daSrc, bcSrc, cSrc, dSrc, maxSubdivision - 1); + } else { + // split vertically (left & right) + var ab = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]; + var abSrc = this.transformInv_(ab); + var cd = [(c[0] + d[0]) / 2, (c[1] + d[1]) / 2]; + var cdSrc = this.transformInv_(cd); + + this.addQuad_( + a, ab, cd, d, aSrc, abSrc, cdSrc, dSrc, maxSubdivision - 1); + this.addQuad_( + ab, b, c, cd, abSrc, bSrc, cSrc, cdSrc, maxSubdivision - 1); + } + return; + } + } + + if (wrapsX) { + if (!this.canWrapXInSource_) { + return; + } + this.wrapsXInSource_ = true; + } + + this.addTriangle_(a, c, d, aSrc, cSrc, dSrc); + this.addTriangle_(a, b, c, aSrc, bSrc, cSrc); +}; + + +/** + * Calculates extent of the 'source' coordinates from all the triangles. + * + * @return {ol.Extent} Calculated extent. + */ +ol.reproj.Triangulation.prototype.calculateSourceExtent = function() { + var extent = ol.extent.createEmpty(); + + this.triangles_.forEach(function(triangle, i, arr) { + var src = triangle.source; + ol.extent.extendCoordinate(extent, src[0]); + ol.extent.extendCoordinate(extent, src[1]); + ol.extent.extendCoordinate(extent, src[2]); + }); + + return extent; +}; + + +/** + * @return {Array.<ol.ReprojTriangle>} Array of the calculated triangles. + */ +ol.reproj.Triangulation.prototype.getTriangles = function() { + return this.triangles_; +}; + +goog.provide('ol.reproj.Image'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.ImageBase'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.reproj'); +goog.require('ol.reproj.Triangulation'); + + +/** + * @classdesc + * Class encapsulating single reprojected image. + * See {@link ol.source.Image}. + * + * @constructor + * @extends {ol.ImageBase} + * @param {ol.proj.Projection} sourceProj Source projection (of the data). + * @param {ol.proj.Projection} targetProj Target projection. + * @param {ol.Extent} targetExtent Target extent. + * @param {number} targetResolution Target resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.ReprojImageFunctionType} getImageFunction + * Function returning source images (extent, resolution, pixelRatio). + */ +ol.reproj.Image = function(sourceProj, targetProj, + targetExtent, targetResolution, pixelRatio, getImageFunction) { + + /** + * @private + * @type {ol.proj.Projection} + */ + this.targetProj_ = targetProj; + + /** + * @private + * @type {ol.Extent} + */ + this.maxSourceExtent_ = sourceProj.getExtent(); + var maxTargetExtent = targetProj.getExtent(); + + var limitedTargetExtent = maxTargetExtent ? + ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent; + + var targetCenter = ol.extent.getCenter(limitedTargetExtent); + var sourceResolution = ol.reproj.calculateSourceResolution( + sourceProj, targetProj, targetCenter, targetResolution); + + var errorThresholdInPixels = ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD; + + /** + * @private + * @type {!ol.reproj.Triangulation} + */ + this.triangulation_ = new ol.reproj.Triangulation( + sourceProj, targetProj, limitedTargetExtent, this.maxSourceExtent_, + sourceResolution * errorThresholdInPixels); + + /** + * @private + * @type {number} + */ + this.targetResolution_ = targetResolution; + + /** + * @private + * @type {ol.Extent} + */ + this.targetExtent_ = targetExtent; + + var sourceExtent = this.triangulation_.calculateSourceExtent(); + + /** + * @private + * @type {ol.ImageBase} + */ + this.sourceImage_ = + getImageFunction(sourceExtent, sourceResolution, pixelRatio); + + /** + * @private + * @type {number} + */ + this.sourcePixelRatio_ = + this.sourceImage_ ? this.sourceImage_.getPixelRatio() : 1; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = null; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.sourceListenerKey_ = null; + + + var state = ol.Image.State.LOADED; + var attributions = []; + + if (this.sourceImage_) { + state = ol.Image.State.IDLE; + attributions = this.sourceImage_.getAttributions(); + } + + ol.ImageBase.call(this, targetExtent, targetResolution, this.sourcePixelRatio_, + state, attributions); +}; +ol.inherits(ol.reproj.Image, ol.ImageBase); + + +/** + * @inheritDoc + */ +ol.reproj.Image.prototype.disposeInternal = function() { + if (this.state == ol.Image.State.LOADING) { + this.unlistenSource_(); + } + ol.ImageBase.prototype.disposeInternal.call(this); +}; + + +/** + * @inheritDoc + */ +ol.reproj.Image.prototype.getImage = function(opt_context) { + return this.canvas_; +}; + + +/** + * @return {ol.proj.Projection} Projection. + */ +ol.reproj.Image.prototype.getProjection = function() { + return this.targetProj_; +}; + + +/** + * @private + */ +ol.reproj.Image.prototype.reproject_ = function() { + var sourceState = this.sourceImage_.getState(); + if (sourceState == ol.Image.State.LOADED) { + var width = ol.extent.getWidth(this.targetExtent_) / this.targetResolution_; + var height = + ol.extent.getHeight(this.targetExtent_) / this.targetResolution_; + + this.canvas_ = ol.reproj.render(width, height, this.sourcePixelRatio_, + this.sourceImage_.getResolution(), this.maxSourceExtent_, + this.targetResolution_, this.targetExtent_, this.triangulation_, [{ + extent: this.sourceImage_.getExtent(), + image: this.sourceImage_.getImage() + }], 0); + } + this.state = sourceState; + this.changed(); +}; + + +/** + * @inheritDoc + */ +ol.reproj.Image.prototype.load = function() { + if (this.state == ol.Image.State.IDLE) { + this.state = ol.Image.State.LOADING; + this.changed(); + + var sourceState = this.sourceImage_.getState(); + if (sourceState == ol.Image.State.LOADED || + sourceState == ol.Image.State.ERROR) { + this.reproject_(); + } else { + this.sourceListenerKey_ = ol.events.listen(this.sourceImage_, + ol.events.EventType.CHANGE, function(e) { + var sourceState = this.sourceImage_.getState(); + if (sourceState == ol.Image.State.LOADED || + sourceState == ol.Image.State.ERROR) { + this.unlistenSource_(); + this.reproject_(); + } + }, this); + this.sourceImage_.load(); + } + } +}; + + +/** + * @private + */ +ol.reproj.Image.prototype.unlistenSource_ = function() { + ol.DEBUG && console.assert(this.sourceListenerKey_, + 'this.sourceListenerKey_ should not be null'); + ol.events.unlistenByKey(/** @type {!ol.EventsKey} */ (this.sourceListenerKey_)); + this.sourceListenerKey_ = null; +}; + +goog.provide('ol.source.Source'); + +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.Object'); +goog.require('ol.proj'); +goog.require('ol.source.State'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for {@link ol.layer.Layer} sources. + * + * A generic `change` event is triggered when the state of the source changes. + * + * @constructor + * @extends {ol.Object} + * @param {ol.SourceSourceOptions} options Source options. + * @api stable + */ +ol.source.Source = function(options) { + + ol.Object.call(this); + + /** + * @private + * @type {ol.proj.Projection} + */ + this.projection_ = ol.proj.get(options.projection); + + /** + * @private + * @type {Array.<ol.Attribution>} + */ + this.attributions_ = ol.source.Source.toAttributionsArray_(options.attributions); + + /** + * @private + * @type {string|olx.LogoOptions|undefined} + */ + this.logo_ = options.logo; + + /** + * @private + * @type {ol.source.State} + */ + this.state_ = options.state !== undefined ? + options.state : ol.source.State.READY; + + /** + * @private + * @type {boolean} + */ + this.wrapX_ = options.wrapX !== undefined ? options.wrapX : false; + +}; +ol.inherits(ol.source.Source, ol.Object); + +/** + * Turns various ways of defining an attribution to an array of `ol.Attributions`. + * + * @param {ol.AttributionLike|undefined} + * attributionLike The attributions as string, array of strings, + * `ol.Attribution`, array of `ol.Attribution` or undefined. + * @return {Array.<ol.Attribution>} The array of `ol.Attribution` or null if + * `undefined` was given. + */ +ol.source.Source.toAttributionsArray_ = function(attributionLike) { + if (typeof attributionLike === 'string') { + return [new ol.Attribution({html: attributionLike})]; + } else if (attributionLike instanceof ol.Attribution) { + return [attributionLike]; + } else if (Array.isArray(attributionLike)) { + var len = attributionLike.length; + var attributions = new Array(len); + for (var i = 0; i < len; i++) { + var item = attributionLike[i]; + if (typeof item === 'string') { + attributions[i] = new ol.Attribution({html: item}); + } else { + attributions[i] = item; + } + } + return attributions; + } else { + return null; + } +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {number} hitTolerance Hit tolerance in pixels. + * @param {Object.<string, boolean>} skippedFeatureUids Skipped feature uids. + * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature + * callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.source.Source.prototype.forEachFeatureAtCoordinate = ol.nullFunction; + + +/** + * Get the attributions of the source. + * @return {Array.<ol.Attribution>} Attributions. + * @api stable + */ +ol.source.Source.prototype.getAttributions = function() { + return this.attributions_; +}; + + +/** + * Get the logo of the source. + * @return {string|olx.LogoOptions|undefined} Logo. + * @api stable + */ +ol.source.Source.prototype.getLogo = function() { + return this.logo_; +}; + + +/** + * Get the projection of the source. + * @return {ol.proj.Projection} Projection. + * @api + */ +ol.source.Source.prototype.getProjection = function() { + return this.projection_; +}; + + +/** + * @abstract + * @return {Array.<number>|undefined} Resolutions. + */ +ol.source.Source.prototype.getResolutions = function() {}; + + +/** + * Get the state of the source, see {@link ol.source.State} for possible states. + * @return {ol.source.State} State. + * @api + */ +ol.source.Source.prototype.getState = function() { + return this.state_; +}; + + +/** + * @return {boolean|undefined} Wrap X. + */ +ol.source.Source.prototype.getWrapX = function() { + return this.wrapX_; +}; + + +/** + * Refreshes the source and finally dispatches a 'change' event. + * @api + */ +ol.source.Source.prototype.refresh = function() { + this.changed(); +}; + + +/** + * Set the attributions of the source. + * @param {ol.AttributionLike|undefined} attributions Attributions. + * Can be passed as `string`, `Array<string>`, `{@link ol.Attribution}`, + * `Array<{@link ol.Attribution}>` or `undefined`. + * @api + */ +ol.source.Source.prototype.setAttributions = function(attributions) { + this.attributions_ = ol.source.Source.toAttributionsArray_(attributions); + this.changed(); +}; + + +/** + * Set the logo of the source. + * @param {string|olx.LogoOptions|undefined} logo Logo. + */ +ol.source.Source.prototype.setLogo = function(logo) { + this.logo_ = logo; +}; + + +/** + * Set the state of the source. + * @param {ol.source.State} state State. + * @protected + */ +ol.source.Source.prototype.setState = function(state) { + this.state_ = state; + this.changed(); +}; + +goog.provide('ol.source.Image'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.array'); +goog.require('ol.events.Event'); +goog.require('ol.extent'); +goog.require('ol.proj'); +goog.require('ol.reproj.Image'); +goog.require('ol.source.Source'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for sources providing a single image. + * + * @constructor + * @extends {ol.source.Source} + * @param {ol.SourceImageOptions} options Single image source options. + * @api + */ +ol.source.Image = function(options) { + + ol.source.Source.call(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + projection: options.projection, + state: options.state + }); + + /** + * @private + * @type {Array.<number>} + */ + this.resolutions_ = options.resolutions !== undefined ? + options.resolutions : null; + ol.DEBUG && console.assert(!this.resolutions_ || + ol.array.isSorted(this.resolutions_, + function(a, b) { + return b - a; + }, true), 'resolutions must be null or sorted in descending order'); + + + /** + * @private + * @type {ol.reproj.Image} + */ + this.reprojectedImage_ = null; + + + /** + * @private + * @type {number} + */ + this.reprojectedRevision_ = 0; + +}; +ol.inherits(ol.source.Image, ol.source.Source); + + +/** + * @return {Array.<number>} Resolutions. + */ +ol.source.Image.prototype.getResolutions = function() { + return this.resolutions_; +}; + + +/** + * @protected + * @param {number} resolution Resolution. + * @return {number} Resolution. + */ +ol.source.Image.prototype.findNearestResolution = function(resolution) { + if (this.resolutions_) { + var idx = ol.array.linearFindNearest(this.resolutions_, resolution, 0); + resolution = this.resolutions_[idx]; + } + return resolution; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.ImageBase} Single image. + */ +ol.source.Image.prototype.getImage = function(extent, resolution, pixelRatio, projection) { + var sourceProjection = this.getProjection(); + if (!ol.ENABLE_RASTER_REPROJECTION || + !sourceProjection || + !projection || + ol.proj.equivalent(sourceProjection, projection)) { + if (sourceProjection) { + projection = sourceProjection; + } + return this.getImageInternal(extent, resolution, pixelRatio, projection); + } else { + if (this.reprojectedImage_) { + if (this.reprojectedRevision_ == this.getRevision() && + ol.proj.equivalent( + this.reprojectedImage_.getProjection(), projection) && + this.reprojectedImage_.getResolution() == resolution && + this.reprojectedImage_.getPixelRatio() == pixelRatio && + ol.extent.equals(this.reprojectedImage_.getExtent(), extent)) { + return this.reprojectedImage_; + } + this.reprojectedImage_.dispose(); + this.reprojectedImage_ = null; + } + + this.reprojectedImage_ = new ol.reproj.Image( + sourceProjection, projection, extent, resolution, pixelRatio, + function(extent, resolution, pixelRatio) { + return this.getImageInternal(extent, resolution, + pixelRatio, sourceProjection); + }.bind(this)); + this.reprojectedRevision_ = this.getRevision(); + + return this.reprojectedImage_; + } +}; + + +/** + * @abstract + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.ImageBase} Single image. + * @protected + */ +ol.source.Image.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {}; + + +/** + * Handle image change events. + * @param {ol.events.Event} event Event. + * @protected + */ +ol.source.Image.prototype.handleImageChange = function(event) { + var image = /** @type {ol.Image} */ (event.target); + switch (image.getState()) { + case ol.Image.State.LOADING: + this.dispatchEvent( + new ol.source.Image.Event(ol.source.Image.EventType.IMAGELOADSTART, + image)); + break; + case ol.Image.State.LOADED: + this.dispatchEvent( + new ol.source.Image.Event(ol.source.Image.EventType.IMAGELOADEND, + image)); + break; + case ol.Image.State.ERROR: + this.dispatchEvent( + new ol.source.Image.Event(ol.source.Image.EventType.IMAGELOADERROR, + image)); + break; + default: + // pass + } +}; + + +/** + * Default image load function for image sources that use ol.Image image + * instances. + * @param {ol.Image} image Image. + * @param {string} src Source. + */ +ol.source.Image.defaultImageLoadFunction = function(image, src) { + image.getImage().src = src; +}; + + +/** + * @classdesc + * Events emitted by {@link ol.source.Image} instances are instances of this + * type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.source.ImageEvent} + * @param {string} type Type. + * @param {ol.Image} image The image. + */ +ol.source.Image.Event = function(type, image) { + + ol.events.Event.call(this, type); + + /** + * The image related to the event. + * @type {ol.Image} + * @api + */ + this.image = image; + +}; +ol.inherits(ol.source.Image.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.source.Image.EventType = { + + /** + * Triggered when an image starts loading. + * @event ol.source.Image.Event#imageloadstart + * @api + */ + IMAGELOADSTART: 'imageloadstart', + + /** + * Triggered when an image finishes loading. + * @event ol.source.Image.Event#imageloadend + * @api + */ + IMAGELOADEND: 'imageloadend', + + /** + * Triggered if image loading results in an error. + * @event ol.source.Image.Event#imageloaderror + * @api + */ + IMAGELOADERROR: 'imageloaderror' + +}; + +goog.provide('ol.source.ImageCanvas'); + +goog.require('ol'); +goog.require('ol.ImageCanvas'); +goog.require('ol.extent'); +goog.require('ol.source.Image'); + + +/** + * @classdesc + * Base class for image sources where a canvas element is the image. + * + * @constructor + * @extends {ol.source.Image} + * @param {olx.source.ImageCanvasOptions} options Constructor options. + * @api + */ +ol.source.ImageCanvas = function(options) { + + ol.source.Image.call(this, { + attributions: options.attributions, + logo: options.logo, + projection: options.projection, + resolutions: options.resolutions, + state: options.state + }); + + /** + * @private + * @type {ol.CanvasFunctionType} + */ + this.canvasFunction_ = options.canvasFunction; + + /** + * @private + * @type {ol.ImageCanvas} + */ + this.canvas_ = null; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = 0; + + /** + * @private + * @type {number} + */ + this.ratio_ = options.ratio !== undefined ? + options.ratio : 1.5; + +}; +ol.inherits(ol.source.ImageCanvas, ol.source.Image); + + +/** + * @inheritDoc + */ +ol.source.ImageCanvas.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) { + resolution = this.findNearestResolution(resolution); + + var canvas = this.canvas_; + if (canvas && + this.renderedRevision_ == this.getRevision() && + canvas.getResolution() == resolution && + canvas.getPixelRatio() == pixelRatio && + ol.extent.containsExtent(canvas.getExtent(), extent)) { + return canvas; + } + + extent = extent.slice(); + ol.extent.scaleFromCenter(extent, this.ratio_); + var width = ol.extent.getWidth(extent) / resolution; + var height = ol.extent.getHeight(extent) / resolution; + var size = [width * pixelRatio, height * pixelRatio]; + + var canvasElement = this.canvasFunction_( + extent, resolution, pixelRatio, size, projection); + if (canvasElement) { + canvas = new ol.ImageCanvas(extent, resolution, pixelRatio, + this.getAttributions(), canvasElement); + } + this.canvas_ = canvas; + this.renderedRevision_ = this.getRevision(); + + return canvas; +}; + +goog.provide('ol.source.ImageVector'); + +goog.require('ol'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.render.canvas.ReplayGroup'); +goog.require('ol.renderer.vector'); +goog.require('ol.source.ImageCanvas'); +goog.require('ol.style.Style'); +goog.require('ol.transform'); + + +/** + * @classdesc + * An image source whose images are canvas elements into which vector features + * read from a vector source (`ol.source.Vector`) are drawn. An + * `ol.source.ImageVector` object is to be used as the `source` of an image + * layer (`ol.layer.Image`). Image layers are rotated, scaled, and translated, + * as opposed to being re-rendered, during animations and interactions. So, like + * any other image layer, an image layer configured with an + * `ol.source.ImageVector` will exhibit this behaviour. This is in contrast to a + * vector layer, where vector features are re-drawn during animations and + * interactions. + * + * @constructor + * @extends {ol.source.ImageCanvas} + * @param {olx.source.ImageVectorOptions} options Options. + * @api + */ +ol.source.ImageVector = function(options) { + + /** + * @private + * @type {ol.source.Vector} + */ + this.source_ = options.source; + + /** + * @private + * @type {ol.Transform} + */ + this.transform_ = ol.transform.create(); + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.canvasContext_ = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {ol.Size} + */ + this.canvasSize_ = [0, 0]; + + /** + * @private + * @type {number} + */ + this.renderBuffer_ = options.renderBuffer == undefined ? 100 : options.renderBuffer; + + /** + * @private + * @type {ol.render.canvas.ReplayGroup} + */ + this.replayGroup_ = null; + + ol.source.ImageCanvas.call(this, { + attributions: options.attributions, + canvasFunction: this.canvasFunctionInternal_.bind(this), + logo: options.logo, + projection: options.projection, + ratio: options.ratio, + resolutions: options.resolutions, + state: this.source_.getState() + }); + + /** + * User provided style. + * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction} + * @private + */ + this.style_ = null; + + /** + * Style function for use within the library. + * @type {ol.StyleFunction|undefined} + * @private + */ + this.styleFunction_ = undefined; + + this.setStyle(options.style); + + ol.events.listen(this.source_, ol.events.EventType.CHANGE, + this.handleSourceChange_, this); + +}; +ol.inherits(ol.source.ImageVector, ol.source.ImageCanvas); + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Size} size Size. + * @param {ol.proj.Projection} projection Projection; + * @return {HTMLCanvasElement} Canvas element. + * @private + */ +ol.source.ImageVector.prototype.canvasFunctionInternal_ = function(extent, resolution, pixelRatio, size, projection) { + + var replayGroup = new ol.render.canvas.ReplayGroup( + ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, + resolution, this.source_.getOverlaps(), this.renderBuffer_); + + this.source_.loadFeatures(extent, resolution, projection); + + var loading = false; + this.source_.forEachFeatureInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + loading = loading || + this.renderFeature_(feature, resolution, pixelRatio, replayGroup); + }, this); + replayGroup.finish(); + + if (loading) { + return null; + } + + if (this.canvasSize_[0] != size[0] || this.canvasSize_[1] != size[1]) { + this.canvasContext_.canvas.width = size[0]; + this.canvasContext_.canvas.height = size[1]; + this.canvasSize_[0] = size[0]; + this.canvasSize_[1] = size[1]; + } else { + this.canvasContext_.clearRect(0, 0, size[0], size[1]); + } + + var transform = this.getTransform_(ol.extent.getCenter(extent), + resolution, pixelRatio, size); + replayGroup.replay(this.canvasContext_, pixelRatio, transform, 0, {}); + + this.replayGroup_ = replayGroup; + + return this.canvasContext_.canvas; +}; + + +/** + * @inheritDoc + */ +ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function( + coordinate, resolution, rotation, hitTolerance, skippedFeatureUids, callback) { + if (!this.replayGroup_) { + return undefined; + } else { + /** @type {Object.<string, boolean>} */ + var features = {}; + return this.replayGroup_.forEachFeatureAtCoordinate( + coordinate, resolution, 0, hitTolerance, skippedFeatureUids, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var key = ol.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback(feature); + } + }); + } +}; + + +/** + * Get a reference to the wrapped source. + * @return {ol.source.Vector} Source. + * @api + */ +ol.source.ImageVector.prototype.getSource = function() { + return this.source_; +}; + + +/** + * Get the style for features. This returns whatever was passed to the `style` + * option at construction or to the `setStyle` method. + * @return {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction} + * Layer style. + * @api stable + */ +ol.source.ImageVector.prototype.getStyle = function() { + return this.style_; +}; + + +/** + * Get the style function. + * @return {ol.StyleFunction|undefined} Layer style function. + * @api stable + */ +ol.source.ImageVector.prototype.getStyleFunction = function() { + return this.styleFunction_; +}; + + +/** + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Size} size Size. + * @return {!ol.Transform} Transform. + * @private + */ +ol.source.ImageVector.prototype.getTransform_ = function(center, resolution, pixelRatio, size) { + var dx1 = size[0] / 2; + var dy1 = size[1] / 2; + var sx = pixelRatio / resolution; + var sy = -sx; + var dx2 = -center[0]; + var dy2 = -center[1]; + + return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, 0, dx2, dy2); +}; + + +/** + * Handle changes in image style state. + * @param {ol.events.Event} event Image style change event. + * @private + */ +ol.source.ImageVector.prototype.handleImageChange_ = function(event) { + this.changed(); +}; + + +/** + * @private + */ +ol.source.ImageVector.prototype.handleSourceChange_ = function() { + // setState will trigger a CHANGE event, so we always rely + // change events by calling setState. + this.setState(this.source_.getState()); +}; + + +/** + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + * @private + */ +ol.source.ImageVector.prototype.renderFeature_ = function(feature, resolution, pixelRatio, replayGroup) { + var styles; + var styleFunction = feature.getStyleFunction(); + if (styleFunction) { + styles = styleFunction.call(feature, resolution); + } else if (this.styleFunction_) { + styles = this.styleFunction_(feature, resolution); + } + if (!styles) { + return false; + } + var i, ii, loading = false; + if (!Array.isArray(styles)) { + styles = [styles]; + } + for (i = 0, ii = styles.length; i < ii; ++i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleImageChange_, this) || loading; + } + return loading; +}; + + +/** + * Set the style for features. This can be a single style object, an array + * of styles, or a function that takes a feature and resolution and returns + * an array of styles. If it is `undefined` the default style is used. If + * it is `null` the layer has no style (a `null` style), so only features + * that have their own styles will be rendered in the layer. See + * {@link ol.style} for information on the default style. + * @param {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction|undefined} + * style Layer style. + * @api stable + */ +ol.source.ImageVector.prototype.setStyle = function(style) { + this.style_ = style !== undefined ? style : ol.style.Style.defaultFunction; + this.styleFunction_ = !style ? + undefined : ol.style.Style.createFunction(this.style_); + this.changed(); +}; + +goog.provide('ol.renderer.webgl.ImageLayer'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.functions'); +goog.require('ol.proj'); +goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.source.ImageVector'); +goog.require('ol.transform'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Context'); + + +/** + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Image} imageLayer Tile layer. + */ +ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { + + ol.renderer.webgl.Layer.call(this, mapRenderer, imageLayer); + + /** + * The last rendered image. + * @private + * @type {?ol.ImageBase} + */ + this.image_ = null; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitCanvasContext_ = null; + + /** + * @private + * @type {?ol.Transform} + */ + this.hitTransformationMatrix_ = null; + +}; +ol.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); + + +/** + * @param {ol.ImageBase} image Image. + * @private + * @return {WebGLTexture} Texture. + */ +ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) { + + // We meet the conditions to work with non-power of two textures. + // http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support + // http://learningwebgl.com/blog/?p=2101 + + var imageElement = image.getImage(); + var gl = this.mapRenderer.getGL(); + + return ol.webgl.Context.createTexture( + gl, imageElement, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { + var layer = this.getLayer(); + var source = layer.getSource(); + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var skippedFeatureUids = frameState.skippedFeatureUids; + return source.forEachFeatureAtCoordinate( + coordinate, resolution, rotation, hitTolerance, skippedFeatureUids, + + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + return callback.call(thisArg, feature, layer); + }); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.prepareFrame = function(frameState, layerState, context) { + + var gl = this.mapRenderer.getGL(); + + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var viewCenter = viewState.center; + var viewResolution = viewState.resolution; + var viewRotation = viewState.rotation; + + var image = this.image_; + var texture = this.texture; + var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer()); + var imageSource = imageLayer.getSource(); + + var hints = frameState.viewHints; + + var renderedExtent = frameState.extent; + if (layerState.extent !== undefined) { + renderedExtent = ol.extent.getIntersection( + renderedExtent, layerState.extent); + } + if (!hints[ol.View.Hint.ANIMATING] && !hints[ol.View.Hint.INTERACTING] && + !ol.extent.isEmpty(renderedExtent)) { + var projection = viewState.projection; + if (!ol.ENABLE_RASTER_REPROJECTION) { + var sourceProjection = imageSource.getProjection(); + if (sourceProjection) { + ol.DEBUG && console.assert(ol.proj.equivalent(projection, sourceProjection), + 'projection and sourceProjection are equivalent'); + projection = sourceProjection; + } + } + var image_ = imageSource.getImage(renderedExtent, viewResolution, + pixelRatio, projection); + if (image_) { + var loaded = this.loadImage(image_); + if (loaded) { + image = image_; + texture = this.createTexture_(image_); + if (this.texture) { + /** + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLTexture} texture Texture. + */ + var postRenderFunction = function(gl, texture) { + if (!gl.isContextLost()) { + gl.deleteTexture(texture); + } + }.bind(null, gl, this.texture); + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (postRenderFunction) + ); + } + } + } + } + + if (image) { + ol.DEBUG && console.assert(texture, 'texture is truthy'); + + var canvas = this.mapRenderer.getContext().getCanvas(); + + this.updateProjectionMatrix_(canvas.width, canvas.height, + pixelRatio, viewCenter, viewResolution, viewRotation, + image.getExtent()); + this.hitTransformationMatrix_ = null; + + // Translate and scale to flip the Y coord. + var texCoordMatrix = this.texCoordMatrix; + ol.transform.reset(texCoordMatrix); + ol.transform.scale(texCoordMatrix, 1, -1); + ol.transform.translate(texCoordMatrix, 0, -1); + + this.image_ = image; + this.texture = texture; + + this.updateAttributions(frameState.attributions, image.getAttributions()); + this.updateLogos(frameState, imageSource); + } + + return !!image; +}; + + +/** + * @param {number} canvasWidth Canvas width. + * @param {number} canvasHeight Canvas height. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Coordinate} viewCenter View center. + * @param {number} viewResolution View resolution. + * @param {number} viewRotation View rotation. + * @param {ol.Extent} imageExtent Image extent. + * @private + */ +ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = function(canvasWidth, canvasHeight, pixelRatio, + viewCenter, viewResolution, viewRotation, imageExtent) { + + var canvasExtentWidth = canvasWidth * viewResolution; + var canvasExtentHeight = canvasHeight * viewResolution; + + var projectionMatrix = this.projectionMatrix; + ol.transform.reset(projectionMatrix); + ol.transform.scale(projectionMatrix, + pixelRatio * 2 / canvasExtentWidth, + pixelRatio * 2 / canvasExtentHeight); + ol.transform.rotate(projectionMatrix, -viewRotation); + ol.transform.translate(projectionMatrix, + imageExtent[0] - viewCenter[0], + imageExtent[1] - viewCenter[1]); + ol.transform.scale(projectionMatrix, + (imageExtent[2] - imageExtent[0]) / 2, + (imageExtent[3] - imageExtent[1]) / 2); + ol.transform.translate(projectionMatrix, 1, 1); + +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) { + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, 0, ol.functions.TRUE, this); + return hasFeature !== undefined; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { + if (!this.image_ || !this.image_.getImage()) { + return undefined; + } + + if (this.getLayer().getSource() instanceof ol.source.ImageVector) { + // for ImageVector sources use the original hit-detection logic, + // so that for example also transparent polygons are detected + var coordinate = ol.transform.apply( + frameState.pixelToCoordinateTransform, pixel.slice()); + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, 0, ol.functions.TRUE, this); + + if (hasFeature) { + return callback.call(thisArg, this.getLayer(), null); + } else { + return undefined; + } + } else { + var imageSize = + [this.image_.getImage().width, this.image_.getImage().height]; + + if (!this.hitTransformationMatrix_) { + this.hitTransformationMatrix_ = this.getHitTransformationMatrix_( + frameState.size, imageSize); + } + + var pixelOnFrameBuffer = ol.transform.apply( + this.hitTransformationMatrix_, pixel.slice()); + + if (pixelOnFrameBuffer[0] < 0 || pixelOnFrameBuffer[0] > imageSize[0] || + pixelOnFrameBuffer[1] < 0 || pixelOnFrameBuffer[1] > imageSize[1]) { + // outside the image, no need to check + return undefined; + } + + if (!this.hitCanvasContext_) { + this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1); + } + + this.hitCanvasContext_.clearRect(0, 0, 1, 1); + this.hitCanvasContext_.drawImage(this.image_.getImage(), + pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, 0, 0, 1, 1); + + var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data; + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer(), imageData); + } else { + return undefined; + } + } +}; + + +/** + * The transformation matrix to get the pixel on the image for a + * pixel on the map. + * @param {ol.Size} mapSize The map size. + * @param {ol.Size} imageSize The image size. + * @return {ol.Transform} The transformation matrix. + * @private + */ +ol.renderer.webgl.ImageLayer.prototype.getHitTransformationMatrix_ = function(mapSize, imageSize) { + // the first matrix takes a map pixel, flips the y-axis and scales to + // a range between -1 ... 1 + var mapCoordTransform = ol.transform.create(); + ol.transform.translate(mapCoordTransform, -1, -1); + ol.transform.scale(mapCoordTransform, 2 / mapSize[0], 2 / mapSize[1]); + ol.transform.translate(mapCoordTransform, 0, mapSize[1]); + ol.transform.scale(mapCoordTransform, 1, -1); + + // the second matrix is the inverse of the projection matrix used in the + // shader for drawing + var projectionMatrixInv = ol.transform.invert(this.projectionMatrix.slice()); + + // the third matrix scales to the image dimensions and flips the y-axis again + var transform = ol.transform.create(); + ol.transform.translate(transform, 0, imageSize[1]); + ol.transform.scale(transform, 1, -1); + ol.transform.scale(transform, imageSize[0] / 2, imageSize[1] / 2); + ol.transform.translate(transform, 1, 1); + + ol.transform.multiply(transform, projectionMatrixInv); + ol.transform.multiply(transform, mapCoordTransform); + + return transform; +}; + +// This file is automatically generated, do not edit +goog.provide('ol.renderer.webgl.tilelayershader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.renderer.webgl.tilelayershader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.renderer.webgl.tilelayershader.Fragment.SOURCE); +}; +ol.inherits(ol.renderer.webgl.tilelayershader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform sampler2D u_texture;\n\nvoid main(void) {\n gl_FragColor = texture2D(u_texture, v_texCoord);\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Fragment.SOURCE = ol.DEBUG ? + ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE : + ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE; + + +ol.renderer.webgl.tilelayershader.fragment = new ol.renderer.webgl.tilelayershader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.renderer.webgl.tilelayershader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.renderer.webgl.tilelayershader.Vertex.SOURCE); +}; +ol.inherits(ol.renderer.webgl.tilelayershader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nuniform vec4 u_tileOffset;\n\nvoid main(void) {\n gl_Position = vec4(a_position * u_tileOffset.xy + u_tileOffset.zw, 0., 1.);\n v_texCoord = a_texCoord;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Vertex.SOURCE = ol.DEBUG ? + ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE : + ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE; + + +ol.renderer.webgl.tilelayershader.vertex = new ol.renderer.webgl.tilelayershader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.renderer.webgl.tilelayershader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_texture = gl.getUniformLocation( + program, ol.DEBUG ? 'u_texture' : 'e'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_tileOffset = gl.getUniformLocation( + program, ol.DEBUG ? 'u_tileOffset' : 'd'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'b'); + + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, ol.DEBUG ? 'a_texCoord' : 'c'); +}; + +// FIXME large resolutions lead to too large framebuffers :-( +// FIXME animated shaders! check in redraw + +goog.provide('ol.renderer.webgl.TileLayer'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.TileRange'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.math'); +goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.renderer.webgl.tilelayershader'); +goog.require('ol.size'); +goog.require('ol.transform'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Tile} tileLayer Tile layer. + */ +ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { + + ol.renderer.webgl.Layer.call(this, mapRenderer, tileLayer); + + /** + * @private + * @type {ol.webgl.Fragment} + */ + this.fragmentShader_ = ol.renderer.webgl.tilelayershader.fragment; + + /** + * @private + * @type {ol.webgl.Vertex} + */ + this.vertexShader_ = ol.renderer.webgl.tilelayershader.vertex; + + /** + * @private + * @type {ol.renderer.webgl.tilelayershader.Locations} + */ + this.locations_ = null; + + /** + * @private + * @type {ol.webgl.Buffer} + */ + this.renderArrayBuffer_ = new ol.webgl.Buffer([ + 0, 0, 0, 1, + 1, 0, 1, 1, + 0, 1, 0, 0, + 1, 1, 1, 0 + ]); + + /** + * @private + * @type {ol.TileRange} + */ + this.renderedTileRange_ = null; + + /** + * @private + * @type {ol.Extent} + */ + this.renderedFramebufferExtent_ = null; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = -1; + + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; + +}; +ol.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer); + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() { + var context = this.mapRenderer.getContext(); + context.deleteBuffer(this.renderArrayBuffer_); + ol.renderer.webgl.Layer.prototype.disposeInternal.call(this); +}; + + +/** + * Create a function that adds loaded tiles to the tile lookup. + * @param {ol.source.Tile} source Tile source. + * @param {ol.proj.Projection} projection Projection of the tiles. + * @param {Object.<number, Object.<string, ol.Tile>>} tiles Lookup of loaded + * tiles by zoom level. + * @return {function(number, ol.TileRange):boolean} A function that can be + * called with a zoom level and a tile range to add loaded tiles to the + * lookup. + * @protected + */ +ol.renderer.webgl.TileLayer.prototype.createLoadedTileFinder = function(source, projection, tiles) { + var mapRenderer = this.mapRenderer; + + return ( + /** + * @param {number} zoom Zoom level. + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} The tile range is fully loaded. + */ + function(zoom, tileRange) { + function callback(tile) { + var loaded = mapRenderer.isTileTextureLoaded(tile); + if (loaded) { + if (!tiles[zoom]) { + tiles[zoom] = {}; + } + tiles[zoom][tile.tileCoord.toString()] = tile; + } + return loaded; + } + return source.forEachLoadedTile(projection, zoom, tileRange, callback); + }); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() { + ol.renderer.webgl.Layer.prototype.handleWebGLContextLost.call(this); + this.locations_ = null; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.prepareFrame = function(frameState, layerState, context) { + + var mapRenderer = this.mapRenderer; + var gl = context.getGL(); + + var viewState = frameState.viewState; + var projection = viewState.projection; + + var tileLayer = /** @type {ol.layer.Tile} */ (this.getLayer()); + var tileSource = tileLayer.getSource(); + var tileGrid = tileSource.getTileGridForProjection(projection); + var z = tileGrid.getZForResolution(viewState.resolution); + var tileResolution = tileGrid.getResolution(z); + + var tilePixelSize = + tileSource.getTilePixelSize(z, frameState.pixelRatio, projection); + var pixelRatio = tilePixelSize[0] / + ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize_)[0]; + var tilePixelResolution = tileResolution / pixelRatio; + var tileGutter = tileSource.getTilePixelRatio(pixelRatio) * tileSource.getGutter(projection); + + var center = viewState.center; + var extent = frameState.extent; + var tileRange = tileGrid.getTileRangeForExtentAndResolution( + extent, tileResolution); + + var framebufferExtent; + if (this.renderedTileRange_ && + this.renderedTileRange_.equals(tileRange) && + this.renderedRevision_ == tileSource.getRevision()) { + framebufferExtent = this.renderedFramebufferExtent_; + } else { + + var tileRangeSize = tileRange.getSize(); + + var maxDimension = Math.max( + tileRangeSize[0] * tilePixelSize[0], + tileRangeSize[1] * tilePixelSize[1]); + var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension); + var framebufferExtentDimension = tilePixelResolution * framebufferDimension; + var origin = tileGrid.getOrigin(z); + var minX = origin[0] + + tileRange.minX * tilePixelSize[0] * tilePixelResolution; + var minY = origin[1] + + tileRange.minY * tilePixelSize[1] * tilePixelResolution; + framebufferExtent = [ + minX, minY, + minX + framebufferExtentDimension, minY + framebufferExtentDimension + ]; + + this.bindFramebuffer(frameState, framebufferDimension); + gl.viewport(0, 0, framebufferDimension, framebufferDimension); + + gl.clearColor(0, 0, 0, 0); + gl.clear(ol.webgl.COLOR_BUFFER_BIT); + gl.disable(ol.webgl.BLEND); + + var program = context.getProgram(this.fragmentShader_, this.vertexShader_); + context.useProgram(program); + if (!this.locations_) { + this.locations_ = + new ol.renderer.webgl.tilelayershader.Locations(gl, program); + } + + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.renderArrayBuffer_); + gl.enableVertexAttribArray(this.locations_.a_position); + gl.vertexAttribPointer( + this.locations_.a_position, 2, ol.webgl.FLOAT, false, 16, 0); + gl.enableVertexAttribArray(this.locations_.a_texCoord); + gl.vertexAttribPointer( + this.locations_.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8); + gl.uniform1i(this.locations_.u_texture, 0); + + /** + * @type {Object.<number, Object.<string, ol.Tile>>} + */ + var tilesToDrawByZ = {}; + tilesToDrawByZ[z] = {}; + + var findLoadedTiles = this.createLoadedTileFinder( + tileSource, projection, tilesToDrawByZ); + + var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError(); + var allTilesLoaded = true; + var tmpExtent = ol.extent.createEmpty(); + var tmpTileRange = new ol.TileRange(0, 0, 0, 0); + var childTileRange, drawable, fullyLoaded, tile, tileState; + var x, y, tileExtent; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + if (layerState.extent !== undefined) { + // ignore tiles outside layer extent + tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); + if (!ol.extent.intersects(tileExtent, layerState.extent)) { + continue; + } + } + tileState = tile.getState(); + drawable = tileState == ol.Tile.State.LOADED || + tileState == ol.Tile.State.EMPTY || + tileState == ol.Tile.State.ERROR && !useInterimTilesOnError; + if (!drawable) { + tile = tile.getInterimTile(); + } + tileState = tile.getState(); + if (tileState == ol.Tile.State.LOADED) { + if (mapRenderer.isTileTextureLoaded(tile)) { + tilesToDrawByZ[z][tile.tileCoord.toString()] = tile; + continue; + } + } else if (tileState == ol.Tile.State.EMPTY || + (tileState == ol.Tile.State.ERROR && + !useInterimTilesOnError)) { + continue; + } + + allTilesLoaded = false; + fullyLoaded = tileGrid.forEachTileCoordParentTileRange( + tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); + if (!fullyLoaded) { + childTileRange = tileGrid.getTileCoordChildTileRange( + tile.tileCoord, tmpTileRange, tmpExtent); + if (childTileRange) { + findLoadedTiles(z + 1, childTileRange); + } + } + + } + + } + + /** @type {Array.<number>} */ + var zs = Object.keys(tilesToDrawByZ).map(Number); + zs.sort(ol.array.numberSafeCompareFunction); + var u_tileOffset = new Float32Array(4); + var i, ii, tileKey, tilesToDraw; + for (i = 0, ii = zs.length; i < ii; ++i) { + tilesToDraw = tilesToDrawByZ[zs[i]]; + for (tileKey in tilesToDraw) { + tile = tilesToDraw[tileKey]; + tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); + u_tileOffset[0] = 2 * (tileExtent[2] - tileExtent[0]) / + framebufferExtentDimension; + u_tileOffset[1] = 2 * (tileExtent[3] - tileExtent[1]) / + framebufferExtentDimension; + u_tileOffset[2] = 2 * (tileExtent[0] - framebufferExtent[0]) / + framebufferExtentDimension - 1; + u_tileOffset[3] = 2 * (tileExtent[1] - framebufferExtent[1]) / + framebufferExtentDimension - 1; + gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset); + mapRenderer.bindTileTexture(tile, tilePixelSize, + tileGutter * pixelRatio, ol.webgl.LINEAR, ol.webgl.LINEAR); + gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4); + } + } + + if (allTilesLoaded) { + this.renderedTileRange_ = tileRange; + this.renderedFramebufferExtent_ = framebufferExtent; + this.renderedRevision_ = tileSource.getRevision(); + } else { + this.renderedTileRange_ = null; + this.renderedFramebufferExtent_ = null; + this.renderedRevision_ = -1; + frameState.animate = true; + } + + } + + this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); + var tileTextureQueue = mapRenderer.getTileTextureQueue(); + this.manageTilePyramid( + frameState, tileSource, tileGrid, pixelRatio, projection, extent, z, + tileLayer.getPreload(), + /** + * @param {ol.Tile} tile Tile. + */ + function(tile) { + if (tile.getState() == ol.Tile.State.LOADED && + !mapRenderer.isTileTextureLoaded(tile) && + !tileTextureQueue.isKeyQueued(tile.getKey())) { + tileTextureQueue.enqueue([ + tile, + tileGrid.getTileCoordCenter(tile.tileCoord), + tileGrid.getResolution(tile.tileCoord[0]), + tilePixelSize, tileGutter * pixelRatio + ]); + } + }, this); + this.scheduleExpireCache(frameState, tileSource); + this.updateLogos(frameState, tileSource); + + var texCoordMatrix = this.texCoordMatrix; + ol.transform.reset(texCoordMatrix); + ol.transform.translate(texCoordMatrix, + (Math.round(center[0] / tileResolution) * tileResolution - framebufferExtent[0]) / + (framebufferExtent[2] - framebufferExtent[0]), + (Math.round(center[1] / tileResolution) * tileResolution - framebufferExtent[1]) / + (framebufferExtent[3] - framebufferExtent[1])); + if (viewState.rotation !== 0) { + ol.transform.rotate(texCoordMatrix, viewState.rotation); + } + ol.transform.scale(texCoordMatrix, + frameState.size[0] * viewState.resolution / + (framebufferExtent[2] - framebufferExtent[0]), + frameState.size[1] * viewState.resolution / + (framebufferExtent[3] - framebufferExtent[1])); + ol.transform.translate(texCoordMatrix, -0.5, -0.5); + + return true; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { + if (!this.framebuffer) { + return undefined; + } + + var pixelOnMapScaled = [ + pixel[0] / frameState.size[0], + (frameState.size[1] - pixel[1]) / frameState.size[1]]; + + var pixelOnFrameBufferScaled = ol.transform.apply( + this.texCoordMatrix, pixelOnMapScaled.slice()); + var pixelOnFrameBuffer = [ + pixelOnFrameBufferScaled[0] * this.framebufferDimension, + pixelOnFrameBufferScaled[1] * this.framebufferDimension]; + + var gl = this.mapRenderer.getContext().getGL(); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + var imageData = new Uint8Array(4); + gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, + gl.RGBA, gl.UNSIGNED_BYTE, imageData); + + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer(), imageData); + } else { + return undefined; + } +}; + +goog.provide('ol.renderer.webgl.VectorLayer'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.extent'); +goog.require('ol.render.webgl.ReplayGroup'); +goog.require('ol.renderer.vector'); +goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.transform'); + + +/** + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Vector} vectorLayer Vector layer. + */ +ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) { + + ol.renderer.webgl.Layer.call(this, mapRenderer, vectorLayer); + + /** + * @private + * @type {boolean} + */ + this.dirty_ = false; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = -1; + + /** + * @private + * @type {number} + */ + this.renderedResolution_ = NaN; + + /** + * @private + * @type {ol.Extent} + */ + this.renderedExtent_ = ol.extent.createEmpty(); + + /** + * @private + * @type {function(ol.Feature, ol.Feature): number|null} + */ + this.renderedRenderOrder_ = null; + + /** + * @private + * @type {ol.render.webgl.ReplayGroup} + */ + this.replayGroup_ = null; + + /** + * The last layer state. + * @private + * @type {?ol.LayerState} + */ + this.layerState_ = null; + +}; +ol.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer); + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) { + this.layerState_ = layerState; + var viewState = frameState.viewState; + var replayGroup = this.replayGroup_; + var size = frameState.size; + var pixelRatio = frameState.pixelRatio; + var gl = this.mapRenderer.getGL(); + if (replayGroup && !replayGroup.isEmpty()) { + gl.enable(gl.SCISSOR_TEST); + gl.scissor(0, 0, size[0] * pixelRatio, size[1] * pixelRatio); + replayGroup.replay(context, + viewState.center, viewState.resolution, viewState.rotation, + size, pixelRatio, layerState.opacity, + layerState.managed ? frameState.skippedFeatureUids : {}); + gl.disable(gl.SCISSOR_TEST); + } + +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() { + var replayGroup = this.replayGroup_; + if (replayGroup) { + var context = this.mapRenderer.getContext(); + replayGroup.getDeleteResourcesFunction(context)(); + this.replayGroup_ = null; + } + ol.renderer.webgl.Layer.prototype.disposeInternal.call(this); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { + if (!this.replayGroup_ || !this.layerState_) { + return undefined; + } else { + var context = this.mapRenderer.getContext(); + var viewState = frameState.viewState; + var layer = this.getLayer(); + var layerState = this.layerState_; + /** @type {Object.<string, boolean>} */ + var features = {}; + return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, layerState.opacity, + {}, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var key = ol.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); + } +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) { + if (!this.replayGroup_ || !this.layerState_) { + return false; + } else { + var context = this.mapRenderer.getContext(); + var viewState = frameState.viewState; + var layerState = this.layerState_; + return this.replayGroup_.hasFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, layerState.opacity, + frameState.skippedFeatureUids); + } +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { + var coordinate = ol.transform.apply( + frameState.pixelToCoordinateTransform, pixel.slice()); + var hasFeature = this.hasFeatureAtCoordinate(coordinate, frameState); + + if (hasFeature) { + return callback.call(thisArg, this.getLayer(), null); + } else { + return undefined; + } +}; + + +/** + * Handle changes in image style state. + * @param {ol.events.Event} event Image style change event. + * @private + */ +ol.renderer.webgl.VectorLayer.prototype.handleStyleImageChange_ = function(event) { + this.renderIfReadyAndVisible(); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.prepareFrame = function(frameState, layerState, context) { + + var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); + var vectorSource = vectorLayer.getSource(); + + this.updateAttributions( + frameState.attributions, vectorSource.getAttributions()); + this.updateLogos(frameState, vectorSource); + + var animating = frameState.viewHints[ol.View.Hint.ANIMATING]; + var interacting = frameState.viewHints[ol.View.Hint.INTERACTING]; + var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating(); + var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting(); + + if (!this.dirty_ && (!updateWhileAnimating && animating) || + (!updateWhileInteracting && interacting)) { + return true; + } + + var frameStateExtent = frameState.extent; + var viewState = frameState.viewState; + var projection = viewState.projection; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var vectorLayerRevision = vectorLayer.getRevision(); + var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer(); + var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); + + if (vectorLayerRenderOrder === undefined) { + vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; + } + + var extent = ol.extent.buffer(frameStateExtent, + vectorLayerRenderBuffer * resolution); + + if (!this.dirty_ && + this.renderedResolution_ == resolution && + this.renderedRevision_ == vectorLayerRevision && + this.renderedRenderOrder_ == vectorLayerRenderOrder && + ol.extent.containsExtent(this.renderedExtent_, extent)) { + return true; + } + + if (this.replayGroup_) { + frameState.postRenderFunctions.push( + this.replayGroup_.getDeleteResourcesFunction(context)); + } + + this.dirty_ = false; + + var replayGroup = new ol.render.webgl.ReplayGroup( + ol.renderer.vector.getTolerance(resolution, pixelRatio), + extent, vectorLayer.getRenderBuffer()); + vectorSource.loadFeatures(extent, resolution, projection); + /** + * @param {ol.Feature} feature Feature. + * @this {ol.renderer.webgl.VectorLayer} + */ + var renderFeature = function(feature) { + var styles; + var styleFunction = feature.getStyleFunction(); + if (styleFunction) { + styles = styleFunction.call(feature, resolution); + } else { + styleFunction = vectorLayer.getStyleFunction(); + if (styleFunction) { + styles = styleFunction(feature, resolution); + } + } + if (styles) { + var dirty = this.renderFeature( + feature, resolution, pixelRatio, styles, replayGroup); + this.dirty_ = this.dirty_ || dirty; + } + }; + if (vectorLayerRenderOrder) { + /** @type {Array.<ol.Feature>} */ + var features = []; + vectorSource.forEachFeatureInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + features.push(feature); + }, this); + features.sort(vectorLayerRenderOrder); + features.forEach(renderFeature, this); + } else { + vectorSource.forEachFeatureInExtent(extent, renderFeature, this); + } + replayGroup.finish(context); + + this.renderedResolution_ = resolution; + this.renderedRevision_ = vectorLayerRevision; + this.renderedRenderOrder_ = vectorLayerRenderOrder; + this.renderedExtent_ = extent; + this.replayGroup_ = replayGroup; + + return true; +}; + + +/** + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of + * styles. + * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + */ +ol.renderer.webgl.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) { + if (!styles) { + return false; + } + var loading = false; + if (Array.isArray(styles)) { + for (var i = styles.length - 1, ii = 0; i >= ii; --i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleStyleImageChange_, this) || loading; + } + } else { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles, + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleStyleImageChange_, this) || loading; + } + return loading; +}; + +goog.provide('ol.structs.LRUCache'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.obj'); + + +/** + * Implements a Least-Recently-Used cache where the keys do not conflict with + * Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring + * items from the cache is the responsibility of the user. + * @constructor + * @struct + * @template T + */ +ol.structs.LRUCache = function() { + + /** + * @private + * @type {number} + */ + this.count_ = 0; + + /** + * @private + * @type {!Object.<string, ol.LRUCacheEntry>} + */ + this.entries_ = {}; + + /** + * @private + * @type {?ol.LRUCacheEntry} + */ + this.oldest_ = null; + + /** + * @private + * @type {?ol.LRUCacheEntry} + */ + this.newest_ = null; + +}; + + +if (ol.DEBUG) { + /** + * FIXME empty description for jsdoc + */ + ol.structs.LRUCache.prototype.assertValid = function() { + if (this.count_ === 0) { + console.assert(ol.obj.isEmpty(this.entries_), + 'entries must be an empty object (count = 0)'); + console.assert(!this.oldest_, + 'oldest must be null (count = 0)'); + console.assert(!this.newest_, + 'newest must be null (count = 0)'); + } else { + console.assert(Object.keys(this.entries_).length == this.count_, + 'number of entries matches count'); + console.assert(this.oldest_, + 'we have an oldest entry'); + console.assert(!this.oldest_.older, + 'no entry is older than oldest'); + console.assert(this.newest_, + 'we have a newest entry'); + console.assert(!this.newest_.newer, + 'no entry is newer than newest'); + var i, entry; + var older = null; + i = 0; + for (entry = this.oldest_; entry; entry = entry.newer) { + console.assert(entry.older === older, + 'entry.older links to correct older'); + older = entry; + ++i; + } + console.assert(i == this.count_, 'iterated correct amount of times'); + var newer = null; + i = 0; + for (entry = this.newest_; entry; entry = entry.older) { + console.assert(entry.newer === newer, + 'entry.newer links to correct newer'); + newer = entry; + ++i; + } + console.assert(i == this.count_, 'iterated correct amount of times'); + } + }; +} + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.LRUCache.prototype.clear = function() { + this.count_ = 0; + this.entries_ = {}; + this.oldest_ = null; + this.newest_ = null; +}; + + +/** + * @param {string} key Key. + * @return {boolean} Contains key. + */ +ol.structs.LRUCache.prototype.containsKey = function(key) { + return this.entries_.hasOwnProperty(key); +}; + + +/** + * @param {function(this: S, T, string, ol.structs.LRUCache): ?} f The function + * to call for every entry from the oldest to the newer. This function takes + * 3 arguments (the entry value, the entry key and the LRUCache object). + * The return value is ignored. + * @param {S=} opt_this The object to use as `this` in `f`. + * @template S + */ +ol.structs.LRUCache.prototype.forEach = function(f, opt_this) { + var entry = this.oldest_; + while (entry) { + f.call(opt_this, entry.value_, entry.key_, this); + entry = entry.newer; + } +}; + + +/** + * @param {string} key Key. + * @return {T} Value. + */ +ol.structs.LRUCache.prototype.get = function(key) { + var entry = this.entries_[key]; + ol.asserts.assert(entry !== undefined, + 15); // Tried to get a value for a key that does not exist in the cache + if (entry === this.newest_) { + return entry.value_; + } else if (entry === this.oldest_) { + this.oldest_ = /** @type {ol.LRUCacheEntry} */ (this.oldest_.newer); + this.oldest_.older = null; + } else { + entry.newer.older = entry.older; + entry.older.newer = entry.newer; + } + entry.newer = null; + entry.older = this.newest_; + this.newest_.newer = entry; + this.newest_ = entry; + return entry.value_; +}; + + +/** + * @return {number} Count. + */ +ol.structs.LRUCache.prototype.getCount = function() { + return this.count_; +}; + + +/** + * @return {Array.<string>} Keys. + */ +ol.structs.LRUCache.prototype.getKeys = function() { + var keys = new Array(this.count_); + var i = 0; + var entry; + for (entry = this.newest_; entry; entry = entry.older) { + keys[i++] = entry.key_; + } + ol.DEBUG && console.assert(i == this.count_, 'iterated correct number of times'); + return keys; +}; + + +/** + * @return {Array.<T>} Values. + */ +ol.structs.LRUCache.prototype.getValues = function() { + var values = new Array(this.count_); + var i = 0; + var entry; + for (entry = this.newest_; entry; entry = entry.older) { + values[i++] = entry.value_; + } + ol.DEBUG && console.assert(i == this.count_, 'iterated correct number of times'); + return values; +}; + + +/** + * @return {T} Last value. + */ +ol.structs.LRUCache.prototype.peekLast = function() { + ol.DEBUG && console.assert(this.oldest_, 'oldest must not be null'); + return this.oldest_.value_; +}; + + +/** + * @return {string} Last key. + */ +ol.structs.LRUCache.prototype.peekLastKey = function() { + ol.DEBUG && console.assert(this.oldest_, 'oldest must not be null'); + return this.oldest_.key_; +}; + + +/** + * @return {T} value Value. + */ +ol.structs.LRUCache.prototype.pop = function() { + ol.DEBUG && console.assert(this.oldest_, 'oldest must not be null'); + ol.DEBUG && console.assert(this.newest_, 'newest must not be null'); + var entry = this.oldest_; + ol.DEBUG && console.assert(entry.key_ in this.entries_, + 'oldest is indexed in entries'); + delete this.entries_[entry.key_]; + if (entry.newer) { + entry.newer.older = null; + } + this.oldest_ = /** @type {ol.LRUCacheEntry} */ (entry.newer); + if (!this.oldest_) { + this.newest_ = null; + } + --this.count_; + return entry.value_; +}; + + +/** + * @param {string} key Key. + * @param {T} value Value. + */ +ol.structs.LRUCache.prototype.replace = function(key, value) { + this.get(key); // update `newest_` + this.entries_[key].value_ = value; +}; + + +/** + * @param {string} key Key. + * @param {T} value Value. + */ +ol.structs.LRUCache.prototype.set = function(key, value) { + ol.DEBUG && console.assert(!(key in {}), + 'key is not a standard property of objects (e.g. "__proto__")'); + ol.asserts.assert(!(key in this.entries_), + 16); // Tried to set a value for a key that is used already + var entry = /** @type {ol.LRUCacheEntry} */ ({ + key_: key, + newer: null, + older: this.newest_, + value_: value + }); + if (!this.newest_) { + this.oldest_ = entry; + } else { + this.newest_.newer = entry; + } + this.newest_ = entry; + this.entries_[key] = entry; + ++this.count_; +}; + +// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE) + +goog.provide('ol.renderer.webgl.Map'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.css'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.layer.Image'); +goog.require('ol.layer.Layer'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); +goog.require('ol.render.Event'); +goog.require('ol.render.webgl.Immediate'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.Type'); +goog.require('ol.renderer.webgl.ImageLayer'); +goog.require('ol.renderer.webgl.TileLayer'); +goog.require('ol.renderer.webgl.VectorLayer'); +goog.require('ol.source.State'); +goog.require('ol.structs.LRUCache'); +goog.require('ol.structs.PriorityQueue'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Context'); +goog.require('ol.webgl.ContextEventType'); + + +/** + * @constructor + * @extends {ol.renderer.Map} + * @param {Element} container Container. + * @param {ol.Map} map Map. + */ +ol.renderer.webgl.Map = function(container, map) { + + ol.renderer.Map.call(this, container, map); + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = /** @type {HTMLCanvasElement} */ + (document.createElement('CANVAS')); + this.canvas_.style.width = '100%'; + this.canvas_.style.height = '100%'; + this.canvas_.className = ol.css.CLASS_UNSELECTABLE; + container.insertBefore(this.canvas_, container.childNodes[0] || null); + + /** + * @private + * @type {number} + */ + this.clipTileCanvasWidth_ = 0; + + /** + * @private + * @type {number} + */ + this.clipTileCanvasHeight_ = 0; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.clipTileContext_ = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = true; + + /** + * @private + * @type {WebGLRenderingContext} + */ + this.gl_ = ol.webgl.getContext(this.canvas_, { + antialias: true, + depth: true, + failIfMajorPerformanceCaveat: true, + preserveDrawingBuffer: false, + stencil: true + }); + ol.DEBUG && console.assert(this.gl_, 'got a WebGLRenderingContext'); + + /** + * @private + * @type {ol.webgl.Context} + */ + this.context_ = new ol.webgl.Context(this.canvas_, this.gl_); + + ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST, + this.handleWebGLContextLost, this); + ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED, + this.handleWebGLContextRestored, this); + + /** + * @private + * @type {ol.structs.LRUCache.<ol.WebglTextureCacheEntry|null>} + */ + this.textureCache_ = new ol.structs.LRUCache(); + + /** + * @private + * @type {ol.Coordinate} + */ + this.focus_ = null; + + /** + * @private + * @type {ol.structs.PriorityQueue.<Array>} + */ + this.tileTextureQueue_ = new ol.structs.PriorityQueue( + /** + * @param {Array.<*>} element Element. + * @return {number} Priority. + * @this {ol.renderer.webgl.Map} + */ + (function(element) { + var tileCenter = /** @type {ol.Coordinate} */ (element[1]); + var tileResolution = /** @type {number} */ (element[2]); + var deltaX = tileCenter[0] - this.focus_[0]; + var deltaY = tileCenter[1] - this.focus_[1]; + return 65536 * Math.log(tileResolution) + + Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; + }).bind(this), + /** + * @param {Array.<*>} element Element. + * @return {string} Key. + */ + function(element) { + return /** @type {ol.Tile} */ (element[0]).getKey(); + }); + + + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + * @return {boolean} false. + * @this {ol.renderer.webgl.Map} + */ + this.loadNextTileTexture_ = + function(map, frameState) { + if (!this.tileTextureQueue_.isEmpty()) { + this.tileTextureQueue_.reprioritize(); + var element = this.tileTextureQueue_.dequeue(); + var tile = /** @type {ol.Tile} */ (element[0]); + var tileSize = /** @type {ol.Size} */ (element[3]); + var tileGutter = /** @type {number} */ (element[4]); + this.bindTileTexture( + tile, tileSize, tileGutter, ol.webgl.LINEAR, ol.webgl.LINEAR); + } + return false; + }.bind(this); + + + /** + * @private + * @type {number} + */ + this.textureCacheFrameMarkerCount_ = 0; + + this.initializeGL_(); + +}; +ol.inherits(ol.renderer.webgl.Map, ol.renderer.Map); + + +/** + * @param {ol.Tile} tile Tile. + * @param {ol.Size} tileSize Tile size. + * @param {number} tileGutter Tile gutter. + * @param {number} magFilter Mag filter. + * @param {number} minFilter Min filter. + */ +ol.renderer.webgl.Map.prototype.bindTileTexture = function(tile, tileSize, tileGutter, magFilter, minFilter) { + var gl = this.getGL(); + var tileKey = tile.getKey(); + if (this.textureCache_.containsKey(tileKey)) { + var textureCacheEntry = this.textureCache_.get(tileKey); + gl.bindTexture(ol.webgl.TEXTURE_2D, textureCacheEntry.texture); + if (textureCacheEntry.magFilter != magFilter) { + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter); + textureCacheEntry.magFilter = magFilter; + } + if (textureCacheEntry.minFilter != minFilter) { + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter); + textureCacheEntry.minFilter = minFilter; + } + } else { + var texture = gl.createTexture(); + gl.bindTexture(ol.webgl.TEXTURE_2D, texture); + if (tileGutter > 0) { + var clipTileCanvas = this.clipTileContext_.canvas; + var clipTileContext = this.clipTileContext_; + if (this.clipTileCanvasWidth_ !== tileSize[0] || + this.clipTileCanvasHeight_ !== tileSize[1]) { + clipTileCanvas.width = tileSize[0]; + clipTileCanvas.height = tileSize[1]; + this.clipTileCanvasWidth_ = tileSize[0]; + this.clipTileCanvasHeight_ = tileSize[1]; + } else { + clipTileContext.clearRect(0, 0, tileSize[0], tileSize[1]); + } + clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter, + tileSize[0], tileSize[1], 0, 0, tileSize[0], tileSize[1]); + gl.texImage2D(ol.webgl.TEXTURE_2D, 0, + ol.webgl.RGBA, ol.webgl.RGBA, + ol.webgl.UNSIGNED_BYTE, clipTileCanvas); + } else { + gl.texImage2D(ol.webgl.TEXTURE_2D, 0, + ol.webgl.RGBA, ol.webgl.RGBA, + ol.webgl.UNSIGNED_BYTE, tile.getImage()); + } + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter); + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter); + gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S, + ol.webgl.CLAMP_TO_EDGE); + gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T, + ol.webgl.CLAMP_TO_EDGE); + this.textureCache_.set(tileKey, { + texture: texture, + magFilter: magFilter, + minFilter: minFilter + }); + } +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) { + if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) { + return new ol.renderer.webgl.ImageLayer(this, layer); + } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) { + return new ol.renderer.webgl.TileLayer(this, layer); + } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) { + return new ol.renderer.webgl.VectorLayer(this, layer); + } else { + ol.DEBUG && console.assert(false, 'unexpected layer configuration'); + return null; + } +}; + + +/** + * @param {ol.render.Event.Type} type Event type. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ = function(type, frameState) { + var map = this.getMap(); + if (map.hasListener(type)) { + var context = this.context_; + + var extent = frameState.extent; + var size = frameState.size; + var viewState = frameState.viewState; + var pixelRatio = frameState.pixelRatio; + + var resolution = viewState.resolution; + var center = viewState.center; + var rotation = viewState.rotation; + + var vectorContext = new ol.render.webgl.Immediate(context, + center, resolution, rotation, size, extent, pixelRatio); + var composeEvent = new ol.render.Event(type, vectorContext, + frameState, null, context); + map.dispatchEvent(composeEvent); + } +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.disposeInternal = function() { + var gl = this.getGL(); + if (!gl.isContextLost()) { + this.textureCache_.forEach( + /** + * @param {?ol.WebglTextureCacheEntry} textureCacheEntry + * Texture cache entry. + */ + function(textureCacheEntry) { + if (textureCacheEntry) { + gl.deleteTexture(textureCacheEntry.texture); + } + }); + } + this.context_.dispose(); + ol.renderer.Map.prototype.disposeInternal.call(this); +}; + + +/** + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.webgl.Map.prototype.expireCache_ = function(map, frameState) { + var gl = this.getGL(); + var textureCacheEntry; + while (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > + ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { + textureCacheEntry = this.textureCache_.peekLast(); + if (!textureCacheEntry) { + if (+this.textureCache_.peekLastKey() == frameState.index) { + break; + } else { + --this.textureCacheFrameMarkerCount_; + } + } else { + gl.deleteTexture(textureCacheEntry.texture); + } + this.textureCache_.pop(); + } +}; + + +/** + * @return {ol.webgl.Context} The context. + */ +ol.renderer.webgl.Map.prototype.getContext = function() { + return this.context_; +}; + + +/** + * @return {WebGLRenderingContext} GL. + */ +ol.renderer.webgl.Map.prototype.getGL = function() { + return this.gl_; +}; + + +/** + * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue. + */ +ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() { + return this.tileTextureQueue_; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.getType = function() { + return ol.renderer.Type.WEBGL; +}; + + +/** + * @param {ol.events.Event} event Event. + * @protected + */ +ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) { + event.preventDefault(); + this.textureCache_.clear(); + this.textureCacheFrameMarkerCount_ = 0; + + var renderers = this.getLayerRenderers(); + for (var id in renderers) { + var renderer = /** @type {ol.renderer.webgl.Layer} */ (renderers[id]); + renderer.handleWebGLContextLost(); + } +}; + + +/** + * @protected + */ +ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() { + this.initializeGL_(); + this.getMap().render(); +}; + + +/** + * @private + */ +ol.renderer.webgl.Map.prototype.initializeGL_ = function() { + var gl = this.gl_; + gl.activeTexture(ol.webgl.TEXTURE0); + gl.blendFuncSeparate( + ol.webgl.SRC_ALPHA, ol.webgl.ONE_MINUS_SRC_ALPHA, + ol.webgl.ONE, ol.webgl.ONE_MINUS_SRC_ALPHA); + gl.disable(ol.webgl.CULL_FACE); + gl.disable(ol.webgl.DEPTH_TEST); + gl.disable(ol.webgl.SCISSOR_TEST); + gl.disable(ol.webgl.STENCIL_TEST); +}; + + +/** + * @param {ol.Tile} tile Tile. + * @return {boolean} Is tile texture loaded. + */ +ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) { + return this.textureCache_.containsKey(tile.getKey()); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { + + var context = this.getContext(); + var gl = this.getGL(); + + if (gl.isContextLost()) { + return false; + } + + if (!frameState) { + if (this.renderedVisible_) { + this.canvas_.style.display = 'none'; + this.renderedVisible_ = false; + } + return false; + } + + this.focus_ = frameState.focus; + + this.textureCache_.set((-frameState.index).toString(), null); + ++this.textureCacheFrameMarkerCount_; + + this.dispatchComposeEvent_(ol.render.Event.Type.PRECOMPOSE, frameState); + + /** @type {Array.<ol.LayerState>} */ + var layerStatesToDraw = []; + var layerStatesArray = frameState.layerStatesArray; + ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex); + + var viewResolution = frameState.viewState.resolution; + var i, ii, layerRenderer, layerState; + for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerState = layerStatesArray[i]; + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerState.sourceState == ol.source.State.READY) { + layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer)); + if (layerRenderer.prepareFrame(frameState, layerState, context)) { + layerStatesToDraw.push(layerState); + } + } + } + + var width = frameState.size[0] * frameState.pixelRatio; + var height = frameState.size[1] * frameState.pixelRatio; + if (this.canvas_.width != width || this.canvas_.height != height) { + this.canvas_.width = width; + this.canvas_.height = height; + } + + gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, null); + + gl.clearColor(0, 0, 0, 0); + gl.clear(ol.webgl.COLOR_BUFFER_BIT); + gl.enable(ol.webgl.BLEND); + gl.viewport(0, 0, this.canvas_.width, this.canvas_.height); + + for (i = 0, ii = layerStatesToDraw.length; i < ii; ++i) { + layerState = layerStatesToDraw[i]; + layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer)); + layerRenderer.composeFrame(frameState, layerState, context); + } + + if (!this.renderedVisible_) { + this.canvas_.style.display = ''; + this.renderedVisible_ = true; + } + + this.calculateMatrices2D(frameState); + + if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > + ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (this.expireCache_.bind(this)) + ); + } + + if (!this.tileTextureQueue_.isEmpty()) { + frameState.postRenderFunctions.push(this.loadNextTileTexture_); + frameState.animate = true; + } + + this.dispatchComposeEvent_(ol.render.Event.Type.POSTCOMPOSE, frameState); + + this.scheduleRemoveUnusedLayerRenderers(frameState); + this.scheduleExpireIconCache(frameState); + +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg, + layerFilter, thisArg2) { + var result; + + if (this.getGL().isContextLost()) { + return false; + } + + var viewState = frameState.viewState; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + result = layerRenderer.forEachFeatureAtCoordinate( + coordinate, frameState, hitTolerance, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) { + var hasFeature = false; + + if (this.getGL().isContextLost()) { + return false; + } + + var viewState = frameState.viewState; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + hasFeature = + layerRenderer.hasFeatureAtCoordinate(coordinate, frameState); + if (hasFeature) { + return true; + } + } + } + return hasFeature; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg, + layerFilter, thisArg2) { + if (this.getGL().isContextLost()) { + return false; + } + + var viewState = frameState.viewState; + var result; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg, layer)) { + var layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layer)); + result = layerRenderer.forEachLayerAtPixel( + pixel, frameState, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; +}; + +// FIXME recheck layer/map projection compatibility when projection changes +// FIXME layer renderers should skip when they can't reproject +// FIXME add tilt and height? + +goog.provide('ol.Map'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserEventHandler'); +goog.require('ol.MapEvent'); +goog.require('ol.Object'); +goog.require('ol.TileQueue'); +goog.require('ol.View'); +goog.require('ol.asserts'); +goog.require('ol.control'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.functions'); +goog.require('ol.has'); +goog.require('ol.interaction'); +goog.require('ol.layer.Group'); +goog.require('ol.obj'); +goog.require('ol.proj.common'); +goog.require('ol.renderer.Type'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.canvas.Map'); +goog.require('ol.renderer.webgl.Map'); +goog.require('ol.size'); +goog.require('ol.structs.PriorityQueue'); +goog.require('ol.transform'); + + +/** + * @const + * @type {string} + */ +ol.OL3_URL = 'https://openlayers.org/'; + + +/** + * @const + * @type {string} + */ +ol.OL3_LOGO_URL = 'data:image/png;base64,' + + 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBI' + + 'WXMAAAHGAAABxgEXwfpGAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAA' + + 'AhNQTFRF////AP//AICAgP//AFVVQECA////K1VVSbbbYL/fJ05idsTYJFtbbcjbJllmZszW' + + 'WMTOIFhoHlNiZszTa9DdUcHNHlNlV8XRIVdiasrUHlZjIVZjaMnVH1RlIFRkH1RkH1ZlasvY' + + 'asvXVsPQH1VkacnVa8vWIVZjIFRjVMPQa8rXIVVkXsXRsNveIFVkIFZlIVVj3eDeh6GmbMvX' + + 'H1ZkIFRka8rWbMvXIFVkIFVjIFVkbMvWH1VjbMvWIFVlbcvWIFVla8vVIFVkbMvWbMvVH1Vk' + + 'bMvWIFVlbcvWIFVkbcvVbMvWjNPbIFVkU8LPwMzNIFVkbczWIFVkbsvWbMvXIFVkRnB8bcvW' + + '2+TkW8XRIFVkIlZlJVloJlpoKlxrLl9tMmJwOWd0Omh1RXF8TneCT3iDUHiDU8LPVMLPVcLP' + + 'VcPQVsPPVsPQV8PQWMTQWsTQW8TQXMXSXsXRX4SNX8bSYMfTYcfTYsfTY8jUZcfSZsnUaIqT' + + 'acrVasrVa8jTa8rWbI2VbMvWbcvWdJObdcvUdszUd8vVeJaee87Yfc3WgJyjhqGnitDYjaar' + + 'ldPZnrK2oNbborW5o9bbo9fbpLa6q9ndrL3ArtndscDDutzfu8fJwN7gwt7gxc/QyuHhy+Hi' + + 'zeHi0NfX0+Pj19zb1+Tj2uXk29/e3uLg3+Lh3+bl4uXj4ufl4+fl5Ofl5ufl5ujm5+jmySDn' + + 'BAAAAFp0Uk5TAAECAgMEBAYHCA0NDg4UGRogIiMmKSssLzU7PkJJT1JTVFliY2hrdHZ3foSF' + + 'hYeJjY2QkpugqbG1tre5w8zQ09XY3uXn6+zx8vT09vf4+Pj5+fr6/P39/f3+gz7SsAAAAVVJ' + + 'REFUOMtjYKA7EBDnwCPLrObS1BRiLoJLnte6CQy8FLHLCzs2QUG4FjZ5GbcmBDDjxJBXDWxC' + + 'Brb8aM4zbkIDzpLYnAcE9VXlJSWlZRU13koIeW57mGx5XjoMZEUqwxWYQaQbSzLSkYGfKFSe' + + '0QMsX5WbjgY0YS4MBplemI4BdGBW+DQ11eZiymfqQuXZIjqwyadPNoSZ4L+0FVM6e+oGI6g8' + + 'a9iKNT3o8kVzNkzRg5lgl7p4wyRUL9Yt2jAxVh6mQCogae6GmflI8p0r13VFWTHBQ0rWPW7a' + + 'hgWVcPm+9cuLoyy4kCJDzCm6d8PSFoh0zvQNC5OjDJhQopPPJqph1doJBUD5tnkbZiUEqaCn' + + 'B3bTqLTFG1bPn71kw4b+GFdpLElKIzRxxgYgWNYc5SCENVHKeUaltHdXx0dZ8uBI1hJ2UUDg' + + 'q82CM2MwKeibqAvSO7MCABq0wXEPiqWEAAAAAElFTkSuQmCC'; + + +/** + * @type {Array.<ol.renderer.Type>} + * @const + */ +ol.DEFAULT_RENDERER_TYPES = [ + ol.renderer.Type.CANVAS, + ol.renderer.Type.WEBGL +]; + + +/** + * @classdesc + * The map is the core component of OpenLayers. For a map to render, a view, + * one or more layers, and a target container are needed: + * + * var map = new ol.Map({ + * view: new ol.View({ + * center: [0, 0], + * zoom: 1 + * }), + * layers: [ + * new ol.layer.Tile({ + * source: new ol.source.OSM() + * }) + * ], + * target: 'map' + * }); + * + * The above snippet creates a map using a {@link ol.layer.Tile} to display + * {@link ol.source.OSM} OSM data and render it to a DOM element with the + * id `map`. + * + * The constructor places a viewport container (with CSS class name + * `ol-viewport`) in the target element (see `getViewport()`), and then two + * further elements within the viewport: one with CSS class name + * `ol-overlaycontainer-stopevent` for controls and some overlays, and one with + * CSS class name `ol-overlaycontainer` for other overlays (see the `stopEvent` + * option of {@link ol.Overlay} for the difference). The map itself is placed in + * a further element within the viewport. + * + * Layers are stored as a `ol.Collection` in layerGroups. A top-level group is + * provided by the library. This is what is accessed by `getLayerGroup` and + * `setLayerGroup`. Layers entered in the options are added to this group, and + * `addLayer` and `removeLayer` change the layer collection in the group. + * `getLayers` is a convenience function for `getLayerGroup().getLayers()`. + * Note that `ol.layer.Group` is a subclass of `ol.layer.Base`, so layers + * entered in the options or added with `addLayer` can be groups, which can + * contain further groups, and so on. + * + * @constructor + * @extends {ol.Object} + * @param {olx.MapOptions} options Map options. + * @fires ol.MapBrowserEvent + * @fires ol.MapEvent + * @fires ol.render.Event#postcompose + * @fires ol.render.Event#precompose + * @api stable + */ +ol.Map = function(options) { + + ol.Object.call(this); + + var optionsInternal = ol.Map.createOptionsInternal(options); + + /** + * @type {boolean} + * @private + */ + this.loadTilesWhileAnimating_ = + options.loadTilesWhileAnimating !== undefined ? + options.loadTilesWhileAnimating : false; + + /** + * @type {boolean} + * @private + */ + this.loadTilesWhileInteracting_ = + options.loadTilesWhileInteracting !== undefined ? + options.loadTilesWhileInteracting : false; + + /** + * @private + * @type {number} + */ + this.pixelRatio_ = options.pixelRatio !== undefined ? + options.pixelRatio : ol.has.DEVICE_PIXEL_RATIO; + + /** + * @private + * @type {Object.<string, string>} + */ + this.logos_ = optionsInternal.logos; + + /** + * @private + * @type {number|undefined} + */ + this.animationDelayKey_; + + /** + * @private + */ + this.animationDelay_ = function() { + this.animationDelayKey_ = undefined; + this.renderFrame_.call(this, Date.now()); + }.bind(this); + + /** + * @private + * @type {ol.Transform} + */ + this.coordinateToPixelTransform_ = ol.transform.create(); + + /** + * @private + * @type {ol.Transform} + */ + this.pixelToCoordinateTransform_ = ol.transform.create(); + + /** + * @private + * @type {number} + */ + this.frameIndex_ = 0; + + /** + * @private + * @type {?olx.FrameState} + */ + this.frameState_ = null; + + /** + * The extent at the previous 'moveend' event. + * @private + * @type {ol.Extent} + */ + this.previousExtent_ = ol.extent.createEmpty(); + + /** + * @private + * @type {?ol.EventsKey} + */ + this.viewPropertyListenerKey_ = null; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.viewChangeListenerKey_ = null; + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.layerGroupPropertyListenerKeys_ = null; + + /** + * @private + * @type {Element} + */ + this.viewport_ = document.createElement('DIV'); + this.viewport_.className = 'ol-viewport' + (ol.has.TOUCH ? ' ol-touch' : ''); + this.viewport_.style.position = 'relative'; + this.viewport_.style.overflow = 'hidden'; + this.viewport_.style.width = '100%'; + this.viewport_.style.height = '100%'; + // prevent page zoom on IE >= 10 browsers + this.viewport_.style.msTouchAction = 'none'; + this.viewport_.style.touchAction = 'none'; + + /** + * @private + * @type {!Element} + */ + this.overlayContainer_ = document.createElement('DIV'); + this.overlayContainer_.className = 'ol-overlaycontainer'; + this.viewport_.appendChild(this.overlayContainer_); + + /** + * @private + * @type {!Element} + */ + this.overlayContainerStopEvent_ = document.createElement('DIV'); + this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent'; + var overlayEvents = [ + ol.events.EventType.CLICK, + ol.events.EventType.DBLCLICK, + ol.events.EventType.MOUSEDOWN, + ol.events.EventType.TOUCHSTART, + ol.events.EventType.MSPOINTERDOWN, + ol.MapBrowserEvent.EventType.POINTERDOWN, + ol.events.EventType.MOUSEWHEEL, + ol.events.EventType.WHEEL + ]; + for (var i = 0, ii = overlayEvents.length; i < ii; ++i) { + ol.events.listen(this.overlayContainerStopEvent_, overlayEvents[i], + ol.events.Event.stopPropagation); + } + this.viewport_.appendChild(this.overlayContainerStopEvent_); + + /** + * @private + * @type {ol.MapBrowserEventHandler} + */ + this.mapBrowserEventHandler_ = new ol.MapBrowserEventHandler(this); + for (var key in ol.MapBrowserEvent.EventType) { + ol.events.listen(this.mapBrowserEventHandler_, ol.MapBrowserEvent.EventType[key], + this.handleMapBrowserEvent, this); + } + + /** + * @private + * @type {Element|Document} + */ + this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget; + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.keyHandlerKeys_ = null; + + ol.events.listen(this.viewport_, ol.events.EventType.WHEEL, + this.handleBrowserEvent, this); + ol.events.listen(this.viewport_, ol.events.EventType.MOUSEWHEEL, + this.handleBrowserEvent, this); + + /** + * @type {ol.Collection.<ol.control.Control>} + * @private + */ + this.controls_ = optionsInternal.controls; + + /** + * @type {ol.Collection.<ol.interaction.Interaction>} + * @private + */ + this.interactions_ = optionsInternal.interactions; + + /** + * @type {ol.Collection.<ol.Overlay>} + * @private + */ + this.overlays_ = optionsInternal.overlays; + + /** + * A lookup of overlays by id. + * @private + * @type {Object.<string, ol.Overlay>} + */ + this.overlayIdIndex_ = {}; + + /** + * @type {ol.renderer.Map} + * @private + */ + this.renderer_ = new optionsInternal.rendererConstructor(this.viewport_, this); + + /** + * @type {function(Event)|undefined} + * @private + */ + this.handleResize_; + + /** + * @private + * @type {ol.Coordinate} + */ + this.focus_ = null; + + /** + * @private + * @type {Array.<ol.PreRenderFunction>} + */ + this.preRenderFunctions_ = []; + + /** + * @private + * @type {Array.<ol.PostRenderFunction>} + */ + this.postRenderFunctions_ = []; + + /** + * @private + * @type {ol.TileQueue} + */ + this.tileQueue_ = new ol.TileQueue( + this.getTilePriority.bind(this), + this.handleTileChange_.bind(this)); + + /** + * Uids of features to skip at rendering time. + * @type {Object.<string, boolean>} + * @private + */ + this.skippedFeatureUids_ = {}; + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Map.Property.LAYERGROUP), + this.handleLayerGroupChanged_, this); + ol.events.listen(this, ol.Object.getChangeEventType(ol.Map.Property.VIEW), + this.handleViewChanged_, this); + ol.events.listen(this, ol.Object.getChangeEventType(ol.Map.Property.SIZE), + this.handleSizeChanged_, this); + ol.events.listen(this, ol.Object.getChangeEventType(ol.Map.Property.TARGET), + this.handleTargetChanged_, this); + + // setProperties will trigger the rendering of the map if the map + // is "defined" already. + this.setProperties(optionsInternal.values); + + this.controls_.forEach( + /** + * @param {ol.control.Control} control Control. + * @this {ol.Map} + */ + function(control) { + control.setMap(this); + }, this); + + ol.events.listen(this.controls_, ol.Collection.EventType.ADD, + /** + * @param {ol.Collection.Event} event Collection event. + */ + function(event) { + event.element.setMap(this); + }, this); + + ol.events.listen(this.controls_, ol.Collection.EventType.REMOVE, + /** + * @param {ol.Collection.Event} event Collection event. + */ + function(event) { + event.element.setMap(null); + }, this); + + this.interactions_.forEach( + /** + * @param {ol.interaction.Interaction} interaction Interaction. + * @this {ol.Map} + */ + function(interaction) { + interaction.setMap(this); + }, this); + + ol.events.listen(this.interactions_, ol.Collection.EventType.ADD, + /** + * @param {ol.Collection.Event} event Collection event. + */ + function(event) { + event.element.setMap(this); + }, this); + + ol.events.listen(this.interactions_, ol.Collection.EventType.REMOVE, + /** + * @param {ol.Collection.Event} event Collection event. + */ + function(event) { + event.element.setMap(null); + }, this); + + this.overlays_.forEach(this.addOverlayInternal_, this); + + ol.events.listen(this.overlays_, ol.Collection.EventType.ADD, + /** + * @param {ol.Collection.Event} event Collection event. + */ + function(event) { + this.addOverlayInternal_(/** @type {ol.Overlay} */ (event.element)); + }, this); + + ol.events.listen(this.overlays_, ol.Collection.EventType.REMOVE, + /** + * @param {ol.Collection.Event} event Collection event. + */ + function(event) { + var overlay = /** @type {ol.Overlay} */ (event.element); + var id = overlay.getId(); + if (id !== undefined) { + delete this.overlayIdIndex_[id.toString()]; + } + event.element.setMap(null); + }, this); + +}; +ol.inherits(ol.Map, ol.Object); + + +/** + * Add the given control to the map. + * @param {ol.control.Control} control Control. + * @api stable + */ +ol.Map.prototype.addControl = function(control) { + this.getControls().push(control); +}; + + +/** + * Add the given interaction to the map. + * @param {ol.interaction.Interaction} interaction Interaction to add. + * @api stable + */ +ol.Map.prototype.addInteraction = function(interaction) { + this.getInteractions().push(interaction); +}; + + +/** + * Adds the given layer to the top of this map. If you want to add a layer + * elsewhere in the stack, use `getLayers()` and the methods available on + * {@link ol.Collection}. + * @param {ol.layer.Base} layer Layer. + * @api stable + */ +ol.Map.prototype.addLayer = function(layer) { + var layers = this.getLayerGroup().getLayers(); + layers.push(layer); +}; + + +/** + * Add the given overlay to the map. + * @param {ol.Overlay} overlay Overlay. + * @api stable + */ +ol.Map.prototype.addOverlay = function(overlay) { + this.getOverlays().push(overlay); +}; + + +/** + * This deals with map's overlay collection changes. + * @param {ol.Overlay} overlay Overlay. + * @private + */ +ol.Map.prototype.addOverlayInternal_ = function(overlay) { + var id = overlay.getId(); + if (id !== undefined) { + this.overlayIdIndex_[id.toString()] = overlay; + } + overlay.setMap(this); +}; + + +/** + * Deprecated (use {@link ol.View#animate} instead). + * Add functions to be called before rendering. This can be used for attaching + * animations before updating the map's view. The {@link ol.animation} + * namespace provides several static methods for creating prerender functions. + * @param {...ol.PreRenderFunction} var_args Any number of pre-render functions. + * @api + */ +ol.Map.prototype.beforeRender = function(var_args) { + ol.DEBUG && console.warn('map.beforeRender() is deprecated. Use view.animate() instead.'); + this.render(); + Array.prototype.push.apply(this.preRenderFunctions_, arguments); +}; + + +/** + * + * @inheritDoc + */ +ol.Map.prototype.disposeInternal = function() { + this.mapBrowserEventHandler_.dispose(); + this.renderer_.dispose(); + ol.events.unlisten(this.viewport_, ol.events.EventType.WHEEL, + this.handleBrowserEvent, this); + ol.events.unlisten(this.viewport_, ol.events.EventType.MOUSEWHEEL, + this.handleBrowserEvent, this); + if (this.handleResize_ !== undefined) { + window.removeEventListener(ol.events.EventType.RESIZE, + this.handleResize_, false); + this.handleResize_ = undefined; + } + if (this.animationDelayKey_) { + cancelAnimationFrame(this.animationDelayKey_); + this.animationDelayKey_ = undefined; + } + this.setTarget(null); + ol.Object.prototype.disposeInternal.call(this); +}; + + +/** + * Detect features that intersect a pixel on the viewport, and execute a + * callback with each intersecting feature. Layers included in the detection can + * be configured through `opt_layerFilter`. + * @param {ol.Pixel} pixel Pixel. + * @param {function(this: S, (ol.Feature|ol.render.Feature), + * ol.layer.Layer): T} callback Feature callback. The callback will be + * called with two arguments. The first argument is one + * {@link ol.Feature feature} or + * {@link ol.render.Feature render feature} at the pixel, the second is + * the {@link ol.layer.Layer layer} of the feature and will be null for + * unmanaged layers. To stop detection, callback functions can return a + * truthy value. + * @param {olx.AtPixelOptions=} opt_options Optional options. + * @return {T|undefined} Callback result, i.e. the return value of last + * callback execution, or the first truthy callback return value. + * @template S,T + * @api stable + */ +ol.Map.prototype.forEachFeatureAtPixel = function(pixel, callback, opt_options) { + if (!this.frameState_) { + return; + } + var coordinate = this.getCoordinateFromPixel(pixel); + opt_options = opt_options !== undefined ? opt_options : {}; + var hitTolerance = opt_options.hitTolerance !== undefined ? + opt_options.hitTolerance * this.frameState_.pixelRatio : 0; + var layerFilter = opt_options.layerFilter !== undefined ? + opt_options.layerFilter : ol.functions.TRUE; + return this.renderer_.forEachFeatureAtCoordinate( + coordinate, this.frameState_, hitTolerance, callback, null, + layerFilter, null); +}; + + +/** + * Detect layers that have a color value at a pixel on the viewport, and + * execute a callback with each matching layer. Layers included in the + * detection can be configured through `opt_layerFilter`. + * @param {ol.Pixel} pixel Pixel. + * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback + * Layer callback. This callback will recieve two arguments: first is the + * {@link ol.layer.Layer layer}, second argument is an array representing + * [R, G, B, A] pixel values (0 - 255) and will be `null` for layer types + * that do not currently support this argument. To stop detection, callback + * functions can return a truthy value. + * @param {S=} opt_this Value to use as `this` when executing `callback`. + * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer + * filter function. The filter function will receive one argument, the + * {@link ol.layer.Layer layer-candidate} and it should return a boolean + * value. Only layers which are visible and for which this function returns + * `true` will be tested for features. By default, all visible layers will + * be tested. + * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result, i.e. the return value of last + * callback execution, or the first truthy callback return value. + * @template S,T,U + * @api stable + */ +ol.Map.prototype.forEachLayerAtPixel = function(pixel, callback, opt_this, opt_layerFilter, opt_this2) { + if (!this.frameState_) { + return; + } + var thisArg = opt_this !== undefined ? opt_this : null; + var layerFilter = opt_layerFilter !== undefined ? + opt_layerFilter : ol.functions.TRUE; + var thisArg2 = opt_this2 !== undefined ? opt_this2 : null; + return this.renderer_.forEachLayerAtPixel( + pixel, this.frameState_, callback, thisArg, + layerFilter, thisArg2); +}; + + +/** + * Detect if features intersect a pixel on the viewport. Layers included in the + * detection can be configured through `opt_layerFilter`. + * @param {ol.Pixel} pixel Pixel. + * @param {olx.AtPixelOptions=} opt_options Optional options. + * @return {boolean} Is there a feature at the given pixel? + * @template U + * @api + */ +ol.Map.prototype.hasFeatureAtPixel = function(pixel, opt_options) { + if (!this.frameState_) { + return false; + } + var coordinate = this.getCoordinateFromPixel(pixel); + opt_options = opt_options !== undefined ? opt_options : {}; + var layerFilter = opt_options.layerFilter !== undefined ? + opt_options.layerFilter : ol.functions.TRUE; + var hitTolerance = opt_options.hitTolerance !== undefined ? + opt_options.hitTolerance * this.frameState_.pixelRatio : 0; + return this.renderer_.hasFeatureAtCoordinate( + coordinate, this.frameState_, hitTolerance, layerFilter, null); +}; + + +/** + * Returns the coordinate in view projection for a browser event. + * @param {Event} event Event. + * @return {ol.Coordinate} Coordinate. + * @api stable + */ +ol.Map.prototype.getEventCoordinate = function(event) { + return this.getCoordinateFromPixel(this.getEventPixel(event)); +}; + + +/** + * Returns the map pixel position for a browser event relative to the viewport. + * @param {Event} event Event. + * @return {ol.Pixel} Pixel. + * @api stable + */ +ol.Map.prototype.getEventPixel = function(event) { + var viewportPosition = this.viewport_.getBoundingClientRect(); + var eventPosition = event.changedTouches ? event.changedTouches[0] : event; + return [ + eventPosition.clientX - viewportPosition.left, + eventPosition.clientY - viewportPosition.top + ]; +}; + + +/** + * Get the target in which this map is rendered. + * Note that this returns what is entered as an option or in setTarget: + * if that was an element, it returns an element; if a string, it returns that. + * @return {Element|string|undefined} The Element or id of the Element that the + * map is rendered in. + * @observable + * @api stable + */ +ol.Map.prototype.getTarget = function() { + return /** @type {Element|string|undefined} */ ( + this.get(ol.Map.Property.TARGET)); +}; + + +/** + * Get the DOM element into which this map is rendered. In contrast to + * `getTarget` this method always return an `Element`, or `null` if the + * map has no target. + * @return {Element} The element that the map is rendered in. + * @api + */ +ol.Map.prototype.getTargetElement = function() { + var target = this.getTarget(); + if (target !== undefined) { + return typeof target === 'string' ? + document.getElementById(target) : + target; + } else { + return null; + } +}; + + +/** + * Get the coordinate for a given pixel. This returns a coordinate in the + * map view projection. + * @param {ol.Pixel} pixel Pixel position in the map viewport. + * @return {ol.Coordinate} The coordinate for the pixel position. + * @api stable + */ +ol.Map.prototype.getCoordinateFromPixel = function(pixel) { + var frameState = this.frameState_; + if (!frameState) { + return null; + } else { + return ol.transform.apply(frameState.pixelToCoordinateTransform, pixel.slice()); + } +}; + + +/** + * Get the map controls. Modifying this collection changes the controls + * associated with the map. + * @return {ol.Collection.<ol.control.Control>} Controls. + * @api stable + */ +ol.Map.prototype.getControls = function() { + return this.controls_; +}; + + +/** + * Get the map overlays. Modifying this collection changes the overlays + * associated with the map. + * @return {ol.Collection.<ol.Overlay>} Overlays. + * @api stable + */ +ol.Map.prototype.getOverlays = function() { + return this.overlays_; +}; + + +/** + * Get an overlay by its identifier (the value returned by overlay.getId()). + * Note that the index treats string and numeric identifiers as the same. So + * `map.getOverlayById(2)` will return an overlay with id `'2'` or `2`. + * @param {string|number} id Overlay identifier. + * @return {ol.Overlay} Overlay. + * @api + */ +ol.Map.prototype.getOverlayById = function(id) { + var overlay = this.overlayIdIndex_[id.toString()]; + return overlay !== undefined ? overlay : null; +}; + + +/** + * Get the map interactions. Modifying this collection changes the interactions + * associated with the map. + * + * Interactions are used for e.g. pan, zoom and rotate. + * @return {ol.Collection.<ol.interaction.Interaction>} Interactions. + * @api stable + */ +ol.Map.prototype.getInteractions = function() { + return this.interactions_; +}; + + +/** + * Get the layergroup associated with this map. + * @return {ol.layer.Group} A layer group containing the layers in this map. + * @observable + * @api stable + */ +ol.Map.prototype.getLayerGroup = function() { + return /** @type {ol.layer.Group} */ (this.get(ol.Map.Property.LAYERGROUP)); +}; + + +/** + * Get the collection of layers associated with this map. + * @return {!ol.Collection.<ol.layer.Base>} Layers. + * @api stable + */ +ol.Map.prototype.getLayers = function() { + var layers = this.getLayerGroup().getLayers(); + return layers; +}; + + +/** + * Get the pixel for a coordinate. This takes a coordinate in the map view + * projection and returns the corresponding pixel. + * @param {ol.Coordinate} coordinate A map coordinate. + * @return {ol.Pixel} A pixel position in the map viewport. + * @api stable + */ +ol.Map.prototype.getPixelFromCoordinate = function(coordinate) { + var frameState = this.frameState_; + if (!frameState) { + return null; + } else { + return ol.transform.apply(frameState.coordinateToPixelTransform, + coordinate.slice(0, 2)); + } +}; + + +/** + * Get the map renderer. + * @return {ol.renderer.Map} Renderer + */ +ol.Map.prototype.getRenderer = function() { + return this.renderer_; +}; + + +/** + * Get the size of this map. + * @return {ol.Size|undefined} The size in pixels of the map in the DOM. + * @observable + * @api stable + */ +ol.Map.prototype.getSize = function() { + return /** @type {ol.Size|undefined} */ (this.get(ol.Map.Property.SIZE)); +}; + + +/** + * Get the view associated with this map. A view manages properties such as + * center and resolution. + * @return {ol.View} The view that controls this map. + * @observable + * @api stable + */ +ol.Map.prototype.getView = function() { + return /** @type {ol.View} */ (this.get(ol.Map.Property.VIEW)); +}; + + +/** + * Get the element that serves as the map viewport. + * @return {Element} Viewport. + * @api stable + */ +ol.Map.prototype.getViewport = function() { + return this.viewport_; +}; + + +/** + * Get the element that serves as the container for overlays. Elements added to + * this container will let mousedown and touchstart events through to the map, + * so clicks and gestures on an overlay will trigger {@link ol.MapBrowserEvent} + * events. + * @return {!Element} The map's overlay container. + */ +ol.Map.prototype.getOverlayContainer = function() { + return this.overlayContainer_; +}; + + +/** + * Get the element that serves as a container for overlays that don't allow + * event propagation. Elements added to this container won't let mousedown and + * touchstart events through to the map, so clicks and gestures on an overlay + * don't trigger any {@link ol.MapBrowserEvent}. + * @return {!Element} The map's overlay container that stops events. + */ +ol.Map.prototype.getOverlayContainerStopEvent = function() { + return this.overlayContainerStopEvent_; +}; + + +/** + * @param {ol.Tile} tile Tile. + * @param {string} tileSourceKey Tile source key. + * @param {ol.Coordinate} tileCenter Tile center. + * @param {number} tileResolution Tile resolution. + * @return {number} Tile priority. + */ +ol.Map.prototype.getTilePriority = function(tile, tileSourceKey, tileCenter, tileResolution) { + // Filter out tiles at higher zoom levels than the current zoom level, or that + // are outside the visible extent. + var frameState = this.frameState_; + if (!frameState || !(tileSourceKey in frameState.wantedTiles)) { + return ol.structs.PriorityQueue.DROP; + } + if (!frameState.wantedTiles[tileSourceKey][tile.getKey()]) { + return ol.structs.PriorityQueue.DROP; + } + // Prioritize the highest zoom level tiles closest to the focus. + // Tiles at higher zoom levels are prioritized using Math.log(tileResolution). + // Within a zoom level, tiles are prioritized by the distance in pixels + // between the center of the tile and the focus. The factor of 65536 means + // that the prioritization should behave as desired for tiles up to + // 65536 * Math.log(2) = 45426 pixels from the focus. + var deltaX = tileCenter[0] - frameState.focus[0]; + var deltaY = tileCenter[1] - frameState.focus[1]; + return 65536 * Math.log(tileResolution) + + Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; +}; + + +/** + * @param {Event} browserEvent Browser event. + * @param {string=} opt_type Type. + */ +ol.Map.prototype.handleBrowserEvent = function(browserEvent, opt_type) { + var type = opt_type || browserEvent.type; + var mapBrowserEvent = new ol.MapBrowserEvent(type, this, browserEvent); + this.handleMapBrowserEvent(mapBrowserEvent); +}; + + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle. + */ +ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) { + if (!this.frameState_) { + // With no view defined, we cannot translate pixels into geographical + // coordinates so interactions cannot be used. + return; + } + this.focus_ = mapBrowserEvent.coordinate; + mapBrowserEvent.frameState = this.frameState_; + var interactionsArray = this.getInteractions().getArray(); + var i; + if (this.dispatchEvent(mapBrowserEvent) !== false) { + for (i = interactionsArray.length - 1; i >= 0; i--) { + var interaction = interactionsArray[i]; + if (!interaction.getActive()) { + continue; + } + var cont = interaction.handleEvent(mapBrowserEvent); + if (!cont) { + break; + } + } + } +}; + + +/** + * @protected + */ +ol.Map.prototype.handlePostRender = function() { + + var frameState = this.frameState_; + + // Manage the tile queue + // Image loads are expensive and a limited resource, so try to use them + // efficiently: + // * When the view is static we allow a large number of parallel tile loads + // to complete the frame as quickly as possible. + // * When animating or interacting, image loads can cause janks, so we reduce + // the maximum number of loads per frame and limit the number of parallel + // tile loads to remain reactive to view changes and to reduce the chance of + // loading tiles that will quickly disappear from view. + var tileQueue = this.tileQueue_; + if (!tileQueue.isEmpty()) { + var maxTotalLoading = 16; + var maxNewLoads = maxTotalLoading; + if (frameState) { + var hints = frameState.viewHints; + if (hints[ol.View.Hint.ANIMATING]) { + maxTotalLoading = this.loadTilesWhileAnimating_ ? 8 : 0; + maxNewLoads = 2; + } + if (hints[ol.View.Hint.INTERACTING]) { + maxTotalLoading = this.loadTilesWhileInteracting_ ? 8 : 0; + maxNewLoads = 2; + } + } + if (tileQueue.getTilesLoading() < maxTotalLoading) { + tileQueue.reprioritize(); // FIXME only call if view has changed + tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads); + } + } + + var postRenderFunctions = this.postRenderFunctions_; + var i, ii; + for (i = 0, ii = postRenderFunctions.length; i < ii; ++i) { + postRenderFunctions[i](this, frameState); + } + postRenderFunctions.length = 0; +}; + + +/** + * @private + */ +ol.Map.prototype.handleSizeChanged_ = function() { + this.render(); +}; + + +/** + * @private + */ +ol.Map.prototype.handleTargetChanged_ = function() { + // target may be undefined, null, a string or an Element. + // If it's a string we convert it to an Element before proceeding. + // If it's not now an Element we remove the viewport from the DOM. + // If it's an Element we append the viewport element to it. + + var targetElement; + if (this.getTarget()) { + targetElement = this.getTargetElement(); + ol.DEBUG && console.assert(targetElement !== null, + 'expects a non-null value for targetElement'); + } + + if (this.keyHandlerKeys_) { + for (var i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) { + ol.events.unlistenByKey(this.keyHandlerKeys_[i]); + } + this.keyHandlerKeys_ = null; + } + + if (!targetElement) { + ol.dom.removeNode(this.viewport_); + if (this.handleResize_ !== undefined) { + window.removeEventListener(ol.events.EventType.RESIZE, + this.handleResize_, false); + this.handleResize_ = undefined; + } + } else { + targetElement.appendChild(this.viewport_); + + var keyboardEventTarget = !this.keyboardEventTarget_ ? + targetElement : this.keyboardEventTarget_; + this.keyHandlerKeys_ = [ + ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYDOWN, + this.handleBrowserEvent, this), + ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYPRESS, + this.handleBrowserEvent, this) + ]; + + if (!this.handleResize_) { + this.handleResize_ = this.updateSize.bind(this); + window.addEventListener(ol.events.EventType.RESIZE, + this.handleResize_, false); + } + } + + this.updateSize(); + // updateSize calls setSize, so no need to call this.render + // ourselves here. +}; + + +/** + * @private + */ +ol.Map.prototype.handleTileChange_ = function() { + this.render(); +}; + + +/** + * @private + */ +ol.Map.prototype.handleViewPropertyChanged_ = function() { + this.render(); +}; + + +/** + * @private + */ +ol.Map.prototype.handleViewChanged_ = function() { + if (this.viewPropertyListenerKey_) { + ol.events.unlistenByKey(this.viewPropertyListenerKey_); + this.viewPropertyListenerKey_ = null; + } + if (this.viewChangeListenerKey_) { + ol.events.unlistenByKey(this.viewChangeListenerKey_); + this.viewChangeListenerKey_ = null; + } + var view = this.getView(); + if (view) { + this.viewPropertyListenerKey_ = ol.events.listen( + view, ol.Object.EventType.PROPERTYCHANGE, + this.handleViewPropertyChanged_, this); + this.viewChangeListenerKey_ = ol.events.listen( + view, ol.events.EventType.CHANGE, + this.handleViewPropertyChanged_, this); + } + this.render(); +}; + + +/** + * @private + */ +ol.Map.prototype.handleLayerGroupChanged_ = function() { + if (this.layerGroupPropertyListenerKeys_) { + this.layerGroupPropertyListenerKeys_.forEach(ol.events.unlistenByKey); + this.layerGroupPropertyListenerKeys_ = null; + } + var layerGroup = this.getLayerGroup(); + if (layerGroup) { + this.layerGroupPropertyListenerKeys_ = [ + ol.events.listen( + layerGroup, ol.Object.EventType.PROPERTYCHANGE, + this.render, this), + ol.events.listen( + layerGroup, ol.events.EventType.CHANGE, + this.render, this) + ]; + } + this.render(); +}; + + +/** + * @return {boolean} Is rendered. + */ +ol.Map.prototype.isRendered = function() { + return !!this.frameState_; +}; + + +/** + * Requests an immediate render in a synchronous manner. + * @api stable + */ +ol.Map.prototype.renderSync = function() { + if (this.animationDelayKey_) { + cancelAnimationFrame(this.animationDelayKey_); + } + this.animationDelay_(); +}; + + +/** + * Request a map rendering (at the next animation frame). + * @api stable + */ +ol.Map.prototype.render = function() { + if (this.animationDelayKey_ === undefined) { + this.animationDelayKey_ = requestAnimationFrame( + this.animationDelay_); + } +}; + + +/** + * Remove the given control from the map. + * @param {ol.control.Control} control Control. + * @return {ol.control.Control|undefined} The removed control (or undefined + * if the control was not found). + * @api stable + */ +ol.Map.prototype.removeControl = function(control) { + return this.getControls().remove(control); +}; + + +/** + * Remove the given interaction from the map. + * @param {ol.interaction.Interaction} interaction Interaction to remove. + * @return {ol.interaction.Interaction|undefined} The removed interaction (or + * undefined if the interaction was not found). + * @api stable + */ +ol.Map.prototype.removeInteraction = function(interaction) { + return this.getInteractions().remove(interaction); +}; + + +/** + * Removes the given layer from the map. + * @param {ol.layer.Base} layer Layer. + * @return {ol.layer.Base|undefined} The removed layer (or undefined if the + * layer was not found). + * @api stable + */ +ol.Map.prototype.removeLayer = function(layer) { + var layers = this.getLayerGroup().getLayers(); + return layers.remove(layer); +}; + + +/** + * Remove the given overlay from the map. + * @param {ol.Overlay} overlay Overlay. + * @return {ol.Overlay|undefined} The removed overlay (or undefined + * if the overlay was not found). + * @api stable + */ +ol.Map.prototype.removeOverlay = function(overlay) { + return this.getOverlays().remove(overlay); +}; + + +/** + * @param {number} time Time. + * @private + */ +ol.Map.prototype.renderFrame_ = function(time) { + var i, ii, viewState; + + var size = this.getSize(); + var view = this.getView(); + var extent = ol.extent.createEmpty(); + /** @type {?olx.FrameState} */ + var frameState = null; + if (size !== undefined && ol.size.hasArea(size) && view && view.isDef()) { + var viewHints = view.getHints(this.frameState_ ? this.frameState_.viewHints : undefined); + var layerStatesArray = this.getLayerGroup().getLayerStatesArray(); + var layerStates = {}; + for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerStates[ol.getUid(layerStatesArray[i].layer)] = layerStatesArray[i]; + } + viewState = view.getState(); + frameState = /** @type {olx.FrameState} */ ({ + animate: false, + attributions: {}, + coordinateToPixelTransform: this.coordinateToPixelTransform_, + extent: extent, + focus: !this.focus_ ? viewState.center : this.focus_, + index: this.frameIndex_++, + layerStates: layerStates, + layerStatesArray: layerStatesArray, + logos: ol.obj.assign({}, this.logos_), + pixelRatio: this.pixelRatio_, + pixelToCoordinateTransform: this.pixelToCoordinateTransform_, + postRenderFunctions: [], + size: size, + skippedFeatureUids: this.skippedFeatureUids_, + tileQueue: this.tileQueue_, + time: time, + usedTiles: {}, + viewState: viewState, + viewHints: viewHints, + wantedTiles: {} + }); + } + + if (frameState) { + var preRenderFunctions = this.preRenderFunctions_; + var n = 0, preRenderFunction; + for (i = 0, ii = preRenderFunctions.length; i < ii; ++i) { + preRenderFunction = preRenderFunctions[i]; + if (preRenderFunction(this, frameState)) { + preRenderFunctions[n++] = preRenderFunction; + } + } + preRenderFunctions.length = n; + + frameState.extent = ol.extent.getForViewAndSize(viewState.center, + viewState.resolution, viewState.rotation, frameState.size, extent); + } + + this.frameState_ = frameState; + this.renderer_.renderFrame(frameState); + + if (frameState) { + if (frameState.animate) { + this.render(); + } + Array.prototype.push.apply( + this.postRenderFunctions_, frameState.postRenderFunctions); + + var idle = this.preRenderFunctions_.length === 0 && + !frameState.viewHints[ol.View.Hint.ANIMATING] && + !frameState.viewHints[ol.View.Hint.INTERACTING] && + !ol.extent.equals(frameState.extent, this.previousExtent_); + + if (idle) { + this.dispatchEvent( + new ol.MapEvent(ol.MapEvent.Type.MOVEEND, this, frameState)); + ol.extent.clone(frameState.extent, this.previousExtent_); + } + } + + this.dispatchEvent( + new ol.MapEvent(ol.MapEvent.Type.POSTRENDER, this, frameState)); + + setTimeout(this.handlePostRender.bind(this), 0); + +}; + + +/** + * Sets the layergroup of this map. + * @param {ol.layer.Group} layerGroup A layer group containing the layers in + * this map. + * @observable + * @api stable + */ +ol.Map.prototype.setLayerGroup = function(layerGroup) { + this.set(ol.Map.Property.LAYERGROUP, layerGroup); +}; + + +/** + * Set the size of this map. + * @param {ol.Size|undefined} size The size in pixels of the map in the DOM. + * @observable + * @api + */ +ol.Map.prototype.setSize = function(size) { + this.set(ol.Map.Property.SIZE, size); +}; + + +/** + * Set the target element to render this map into. + * @param {Element|string|undefined} target The Element or id of the Element + * that the map is rendered in. + * @observable + * @api stable + */ +ol.Map.prototype.setTarget = function(target) { + this.set(ol.Map.Property.TARGET, target); +}; + + +/** + * Set the view for this map. + * @param {ol.View} view The view that controls this map. + * @observable + * @api stable + */ +ol.Map.prototype.setView = function(view) { + this.set(ol.Map.Property.VIEW, view); +}; + + +/** + * @param {ol.Feature} feature Feature. + */ +ol.Map.prototype.skipFeature = function(feature) { + var featureUid = ol.getUid(feature).toString(); + this.skippedFeatureUids_[featureUid] = true; + this.render(); +}; + + +/** + * Force a recalculation of the map viewport size. This should be called when + * third-party code changes the size of the map viewport. + * @api stable + */ +ol.Map.prototype.updateSize = function() { + var targetElement = this.getTargetElement(); + + if (!targetElement) { + this.setSize(undefined); + } else { + var computedStyle = getComputedStyle(targetElement); + this.setSize([ + targetElement.offsetWidth - + parseFloat(computedStyle['borderLeftWidth']) - + parseFloat(computedStyle['paddingLeft']) - + parseFloat(computedStyle['paddingRight']) - + parseFloat(computedStyle['borderRightWidth']), + targetElement.offsetHeight - + parseFloat(computedStyle['borderTopWidth']) - + parseFloat(computedStyle['paddingTop']) - + parseFloat(computedStyle['paddingBottom']) - + parseFloat(computedStyle['borderBottomWidth']) + ]); + } +}; + + +/** + * @param {ol.Feature} feature Feature. + */ +ol.Map.prototype.unskipFeature = function(feature) { + var featureUid = ol.getUid(feature).toString(); + delete this.skippedFeatureUids_[featureUid]; + this.render(); +}; + + +/** + * @param {olx.MapOptions} options Map options. + * @return {ol.MapOptionsInternal} Internal map options. + */ +ol.Map.createOptionsInternal = function(options) { + + /** + * @type {Element|Document} + */ + var keyboardEventTarget = null; + if (options.keyboardEventTarget !== undefined) { + keyboardEventTarget = typeof options.keyboardEventTarget === 'string' ? + document.getElementById(options.keyboardEventTarget) : + options.keyboardEventTarget; + } + + /** + * @type {Object.<string, *>} + */ + var values = {}; + + var logos = {}; + if (options.logo === undefined || + (typeof options.logo === 'boolean' && options.logo)) { + logos[ol.OL3_LOGO_URL] = ol.OL3_URL; + } else { + var logo = options.logo; + if (typeof logo === 'string') { + logos[logo] = ''; + } else if (logo instanceof HTMLElement) { + logos[ol.getUid(logo).toString()] = logo; + } else if (logo) { + ol.asserts.assert(typeof logo.href == 'string', 44); // `logo.href` should be a string. + ol.asserts.assert(typeof logo.src == 'string', 45); // `logo.src` should be a string. + logos[logo.src] = logo.href; + } + } + + var layerGroup = (options.layers instanceof ol.layer.Group) ? + options.layers : new ol.layer.Group({layers: options.layers}); + values[ol.Map.Property.LAYERGROUP] = layerGroup; + + values[ol.Map.Property.TARGET] = options.target; + + values[ol.Map.Property.VIEW] = options.view !== undefined ? + options.view : new ol.View(); + + /** + * @type {function(new: ol.renderer.Map, Element, ol.Map)} + */ + var rendererConstructor = ol.renderer.Map; + + /** + * @type {Array.<ol.renderer.Type>} + */ + var rendererTypes; + if (options.renderer !== undefined) { + if (Array.isArray(options.renderer)) { + rendererTypes = options.renderer; + } else if (typeof options.renderer === 'string') { + rendererTypes = [options.renderer]; + } else { + ol.asserts.assert(false, 46); // Incorrect format for `renderer` option + } + if (rendererTypes.indexOf(/** @type {ol.renderer.Type} */ ('dom')) >= 0) { + ol.DEBUG && console.assert(false, 'The DOM render has been removed'); + rendererTypes = rendererTypes.concat(ol.DEFAULT_RENDERER_TYPES); + } + } else { + rendererTypes = ol.DEFAULT_RENDERER_TYPES; + } + + var i, ii; + for (i = 0, ii = rendererTypes.length; i < ii; ++i) { + /** @type {ol.renderer.Type} */ + var rendererType = rendererTypes[i]; + if (ol.ENABLE_CANVAS && rendererType == ol.renderer.Type.CANVAS) { + if (ol.has.CANVAS) { + rendererConstructor = ol.renderer.canvas.Map; + break; + } + } else if (ol.ENABLE_WEBGL && rendererType == ol.renderer.Type.WEBGL) { + if (ol.has.WEBGL) { + rendererConstructor = ol.renderer.webgl.Map; + break; + } + } + } + + var controls; + if (options.controls !== undefined) { + if (Array.isArray(options.controls)) { + controls = new ol.Collection(options.controls.slice()); + } else { + ol.asserts.assert(options.controls instanceof ol.Collection, + 47); // Expected `controls` to be an array or an `ol.Collection` + controls = options.controls; + } + } else { + controls = ol.control.defaults(); + } + + var interactions; + if (options.interactions !== undefined) { + if (Array.isArray(options.interactions)) { + interactions = new ol.Collection(options.interactions.slice()); + } else { + ol.asserts.assert(options.interactions instanceof ol.Collection, + 48); // Expected `interactions` to be an array or an `ol.Collection` + interactions = options.interactions; + } + } else { + interactions = ol.interaction.defaults(); + } + + var overlays; + if (options.overlays !== undefined) { + if (Array.isArray(options.overlays)) { + overlays = new ol.Collection(options.overlays.slice()); + } else { + ol.asserts.assert(options.overlays instanceof ol.Collection, + 49); // Expected `overlays` to be an array or an `ol.Collection` + overlays = options.overlays; + } + } else { + overlays = new ol.Collection(); + } + + return { + controls: controls, + interactions: interactions, + keyboardEventTarget: keyboardEventTarget, + logos: logos, + overlays: overlays, + rendererConstructor: rendererConstructor, + values: values + }; + +}; + +/** + * @enum {string} + */ +ol.Map.Property = { + LAYERGROUP: 'layergroup', + SIZE: 'size', + TARGET: 'target', + VIEW: 'view' +}; + + +ol.proj.common.add(); + +goog.provide('ol.Overlay'); + +goog.require('ol'); +goog.require('ol.MapEvent'); +goog.require('ol.Object'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.extent'); + + +/** + * @classdesc + * An element to be displayed over the map and attached to a single map + * location. Like {@link ol.control.Control}, Overlays are visible widgets. + * Unlike Controls, they are not in a fixed position on the screen, but are tied + * to a geographical coordinate, so panning the map will move an Overlay but not + * a Control. + * + * Example: + * + * var popup = new ol.Overlay({ + * element: document.getElementById('popup') + * }); + * popup.setPosition(coordinate); + * map.addOverlay(popup); + * + * @constructor + * @extends {ol.Object} + * @param {olx.OverlayOptions} options Overlay options. + * @api stable + */ +ol.Overlay = function(options) { + + ol.Object.call(this); + + /** + * @private + * @type {number|string|undefined} + */ + this.id_ = options.id; + + /** + * @private + * @type {boolean} + */ + this.insertFirst_ = options.insertFirst !== undefined ? + options.insertFirst : true; + + /** + * @private + * @type {boolean} + */ + this.stopEvent_ = options.stopEvent !== undefined ? options.stopEvent : true; + + /** + * @private + * @type {Element} + */ + this.element_ = document.createElement('DIV'); + this.element_.className = 'ol-overlay-container'; + this.element_.style.position = 'absolute'; + + /** + * @protected + * @type {boolean} + */ + this.autoPan = options.autoPan !== undefined ? options.autoPan : false; + + /** + * @private + * @type {olx.OverlayPanOptions} + */ + this.autoPanAnimation_ = options.autoPanAnimation || + /** @type {olx.OverlayPanOptions} */ ({}); + + /** + * @private + * @type {number} + */ + this.autoPanMargin_ = options.autoPanMargin !== undefined ? + options.autoPanMargin : 20; + + /** + * @private + * @type {{bottom_: string, + * left_: string, + * right_: string, + * top_: string, + * visible: boolean}} + */ + this.rendered_ = { + bottom_: '', + left_: '', + right_: '', + top_: '', + visible: true + }; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.mapPostrenderListenerKey_ = null; + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Overlay.Property.ELEMENT), + this.handleElementChanged, this); + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Overlay.Property.MAP), + this.handleMapChanged, this); + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Overlay.Property.OFFSET), + this.handleOffsetChanged, this); + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Overlay.Property.POSITION), + this.handlePositionChanged, this); + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Overlay.Property.POSITIONING), + this.handlePositioningChanged, this); + + if (options.element !== undefined) { + this.setElement(options.element); + } + + this.setOffset(options.offset !== undefined ? options.offset : [0, 0]); + + this.setPositioning(options.positioning !== undefined ? + /** @type {ol.Overlay.Positioning} */ (options.positioning) : + ol.Overlay.Positioning.TOP_LEFT); + + if (options.position !== undefined) { + this.setPosition(options.position); + } + +}; +ol.inherits(ol.Overlay, ol.Object); + + +/** + * Get the DOM element of this overlay. + * @return {Element|undefined} The Element containing the overlay. + * @observable + * @api stable + */ +ol.Overlay.prototype.getElement = function() { + return /** @type {Element|undefined} */ ( + this.get(ol.Overlay.Property.ELEMENT)); +}; + + +/** + * Get the overlay identifier which is set on constructor. + * @return {number|string|undefined} Id. + * @api + */ +ol.Overlay.prototype.getId = function() { + return this.id_; +}; + + +/** + * Get the map associated with this overlay. + * @return {ol.Map|undefined} The map that the overlay is part of. + * @observable + * @api stable + */ +ol.Overlay.prototype.getMap = function() { + return /** @type {ol.Map|undefined} */ ( + this.get(ol.Overlay.Property.MAP)); +}; + + +/** + * Get the offset of this overlay. + * @return {Array.<number>} The offset. + * @observable + * @api stable + */ +ol.Overlay.prototype.getOffset = function() { + return /** @type {Array.<number>} */ ( + this.get(ol.Overlay.Property.OFFSET)); +}; + + +/** + * Get the current position of this overlay. + * @return {ol.Coordinate|undefined} The spatial point that the overlay is + * anchored at. + * @observable + * @api stable + */ +ol.Overlay.prototype.getPosition = function() { + return /** @type {ol.Coordinate|undefined} */ ( + this.get(ol.Overlay.Property.POSITION)); +}; + + +/** + * Get the current positioning of this overlay. + * @return {ol.Overlay.Positioning} How the overlay is positioned + * relative to its point on the map. + * @observable + * @api stable + */ +ol.Overlay.prototype.getPositioning = function() { + return /** @type {ol.Overlay.Positioning} */ ( + this.get(ol.Overlay.Property.POSITIONING)); +}; + + +/** + * @protected + */ +ol.Overlay.prototype.handleElementChanged = function() { + ol.dom.removeChildren(this.element_); + var element = this.getElement(); + if (element) { + this.element_.appendChild(element); + } +}; + + +/** + * @protected + */ +ol.Overlay.prototype.handleMapChanged = function() { + if (this.mapPostrenderListenerKey_) { + ol.dom.removeNode(this.element_); + ol.events.unlistenByKey(this.mapPostrenderListenerKey_); + this.mapPostrenderListenerKey_ = null; + } + var map = this.getMap(); + if (map) { + this.mapPostrenderListenerKey_ = ol.events.listen(map, + ol.MapEvent.Type.POSTRENDER, this.render, this); + this.updatePixelPosition(); + var container = this.stopEvent_ ? + map.getOverlayContainerStopEvent() : map.getOverlayContainer(); + if (this.insertFirst_) { + container.insertBefore(this.element_, container.childNodes[0] || null); + } else { + container.appendChild(this.element_); + } + } +}; + + +/** + * @protected + */ +ol.Overlay.prototype.render = function() { + this.updatePixelPosition(); +}; + + +/** + * @protected + */ +ol.Overlay.prototype.handleOffsetChanged = function() { + this.updatePixelPosition(); +}; + + +/** + * @protected + */ +ol.Overlay.prototype.handlePositionChanged = function() { + this.updatePixelPosition(); + if (this.get(ol.Overlay.Property.POSITION) !== undefined && this.autoPan) { + this.panIntoView_(); + } +}; + + +/** + * @protected + */ +ol.Overlay.prototype.handlePositioningChanged = function() { + this.updatePixelPosition(); +}; + + +/** + * Set the DOM element to be associated with this overlay. + * @param {Element|undefined} element The Element containing the overlay. + * @observable + * @api stable + */ +ol.Overlay.prototype.setElement = function(element) { + this.set(ol.Overlay.Property.ELEMENT, element); +}; + + +/** + * Set the map to be associated with this overlay. + * @param {ol.Map|undefined} map The map that the overlay is part of. + * @observable + * @api stable + */ +ol.Overlay.prototype.setMap = function(map) { + this.set(ol.Overlay.Property.MAP, map); +}; + + +/** + * Set the offset for this overlay. + * @param {Array.<number>} offset Offset. + * @observable + * @api stable + */ +ol.Overlay.prototype.setOffset = function(offset) { + this.set(ol.Overlay.Property.OFFSET, offset); +}; + + +/** + * Set the position for this overlay. If the position is `undefined` the + * overlay is hidden. + * @param {ol.Coordinate|undefined} position The spatial point that the overlay + * is anchored at. + * @observable + * @api stable + */ +ol.Overlay.prototype.setPosition = function(position) { + this.set(ol.Overlay.Property.POSITION, position); +}; + + +/** + * Pan the map so that the overlay is entirely visible in the current viewport + * (if necessary). + * @private + */ +ol.Overlay.prototype.panIntoView_ = function() { + var map = this.getMap(); + + if (map === undefined || !map.getTargetElement()) { + return; + } + + var mapRect = this.getRect_(map.getTargetElement(), map.getSize()); + var element = /** @type {!Element} */ (this.getElement()); + var overlayRect = this.getRect_(element, + [ol.dom.outerWidth(element), ol.dom.outerHeight(element)]); + + var margin = this.autoPanMargin_; + if (!ol.extent.containsExtent(mapRect, overlayRect)) { + // the overlay is not completely inside the viewport, so pan the map + var offsetLeft = overlayRect[0] - mapRect[0]; + var offsetRight = mapRect[2] - overlayRect[2]; + var offsetTop = overlayRect[1] - mapRect[1]; + var offsetBottom = mapRect[3] - overlayRect[3]; + + var delta = [0, 0]; + if (offsetLeft < 0) { + // move map to the left + delta[0] = offsetLeft - margin; + } else if (offsetRight < 0) { + // move map to the right + delta[0] = Math.abs(offsetRight) + margin; + } + if (offsetTop < 0) { + // move map up + delta[1] = offsetTop - margin; + } else if (offsetBottom < 0) { + // move map down + delta[1] = Math.abs(offsetBottom) + margin; + } + + if (delta[0] !== 0 || delta[1] !== 0) { + var center = /** @type {ol.Coordinate} */ (map.getView().getCenter()); + var centerPx = map.getPixelFromCoordinate(center); + var newCenterPx = [ + centerPx[0] + delta[0], + centerPx[1] + delta[1] + ]; + + map.getView().animate({ + center: map.getCoordinateFromPixel(newCenterPx), + duration: this.autoPanAnimation_.duration, + easing: this.autoPanAnimation_.easing + }); + } + } +}; + + +/** + * Get the extent of an element relative to the document + * @param {Element|undefined} element The element. + * @param {ol.Size|undefined} size The size of the element. + * @return {ol.Extent} The extent. + * @private + */ +ol.Overlay.prototype.getRect_ = function(element, size) { + var box = element.getBoundingClientRect(); + var offsetX = box.left + window.pageXOffset; + var offsetY = box.top + window.pageYOffset; + return [ + offsetX, + offsetY, + offsetX + size[0], + offsetY + size[1] + ]; +}; + + +/** + * Set the positioning for this overlay. + * @param {ol.Overlay.Positioning} positioning how the overlay is + * positioned relative to its point on the map. + * @observable + * @api stable + */ +ol.Overlay.prototype.setPositioning = function(positioning) { + this.set(ol.Overlay.Property.POSITIONING, positioning); +}; + + +/** + * Modify the visibility of the element. + * @param {boolean} visible Element visibility. + * @protected + */ +ol.Overlay.prototype.setVisible = function(visible) { + if (this.rendered_.visible !== visible) { + this.element_.style.display = visible ? '' : 'none'; + this.rendered_.visible = visible; + } +}; + + +/** + * Update pixel position. + * @protected + */ +ol.Overlay.prototype.updatePixelPosition = function() { + var map = this.getMap(); + var position = this.getPosition(); + if (map === undefined || !map.isRendered() || position === undefined) { + this.setVisible(false); + return; + } + + var pixel = map.getPixelFromCoordinate(position); + var mapSize = map.getSize(); + this.updateRenderedPosition(pixel, mapSize); +}; + + +/** + * @param {ol.Pixel} pixel The pixel location. + * @param {ol.Size|undefined} mapSize The map size. + * @protected + */ +ol.Overlay.prototype.updateRenderedPosition = function(pixel, mapSize) { + var style = this.element_.style; + var offset = this.getOffset(); + + var positioning = this.getPositioning(); + ol.DEBUG && console.assert(positioning !== undefined, + 'positioning should be defined'); + + var offsetX = offset[0]; + var offsetY = offset[1]; + if (positioning == ol.Overlay.Positioning.BOTTOM_RIGHT || + positioning == ol.Overlay.Positioning.CENTER_RIGHT || + positioning == ol.Overlay.Positioning.TOP_RIGHT) { + if (this.rendered_.left_ !== '') { + this.rendered_.left_ = style.left = ''; + } + var right = Math.round(mapSize[0] - pixel[0] - offsetX) + 'px'; + if (this.rendered_.right_ != right) { + this.rendered_.right_ = style.right = right; + } + } else { + if (this.rendered_.right_ !== '') { + this.rendered_.right_ = style.right = ''; + } + if (positioning == ol.Overlay.Positioning.BOTTOM_CENTER || + positioning == ol.Overlay.Positioning.CENTER_CENTER || + positioning == ol.Overlay.Positioning.TOP_CENTER) { + offsetX -= this.element_.offsetWidth / 2; + } + var left = Math.round(pixel[0] + offsetX) + 'px'; + if (this.rendered_.left_ != left) { + this.rendered_.left_ = style.left = left; + } + } + if (positioning == ol.Overlay.Positioning.BOTTOM_LEFT || + positioning == ol.Overlay.Positioning.BOTTOM_CENTER || + positioning == ol.Overlay.Positioning.BOTTOM_RIGHT) { + if (this.rendered_.top_ !== '') { + this.rendered_.top_ = style.top = ''; + } + var bottom = Math.round(mapSize[1] - pixel[1] - offsetY) + 'px'; + if (this.rendered_.bottom_ != bottom) { + this.rendered_.bottom_ = style.bottom = bottom; + } + } else { + if (this.rendered_.bottom_ !== '') { + this.rendered_.bottom_ = style.bottom = ''; + } + if (positioning == ol.Overlay.Positioning.CENTER_LEFT || + positioning == ol.Overlay.Positioning.CENTER_CENTER || + positioning == ol.Overlay.Positioning.CENTER_RIGHT) { + offsetY -= this.element_.offsetHeight / 2; + } + var top = Math.round(pixel[1] + offsetY) + 'px'; + if (this.rendered_.top_ != top) { + this.rendered_.top_ = style.top = top; + } + } + + this.setVisible(true); +}; + + +/** + * Overlay position: `'bottom-left'`, `'bottom-center'`, `'bottom-right'`, + * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`, + * `'top-center'`, `'top-right'` + * @enum {string} + */ +ol.Overlay.Positioning = { + BOTTOM_LEFT: 'bottom-left', + BOTTOM_CENTER: 'bottom-center', + BOTTOM_RIGHT: 'bottom-right', + CENTER_LEFT: 'center-left', + CENTER_CENTER: 'center-center', + CENTER_RIGHT: 'center-right', + TOP_LEFT: 'top-left', + TOP_CENTER: 'top-center', + TOP_RIGHT: 'top-right' +}; + + +/** + * @enum {string} + */ +ol.Overlay.Property = { + ELEMENT: 'element', + MAP: 'map', + OFFSET: 'offset', + POSITION: 'position', + POSITIONING: 'positioning' +}; + +goog.provide('ol.control.OverviewMap'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.Map'); +goog.require('ol.MapEvent'); +goog.require('ol.Object'); +goog.require('ol.Overlay'); +goog.require('ol.View'); +goog.require('ol.control.Control'); +goog.require('ol.coordinate'); +goog.require('ol.css'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); + + +/** + * Create a new control with a map acting as an overview map for an other + * defined map. + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.OverviewMapOptions=} opt_options OverviewMap options. + * @api + */ +ol.control.OverviewMap = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + /** + * @type {boolean} + * @private + */ + this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true; + + /** + * @private + * @type {boolean} + */ + this.collapsible_ = options.collapsible !== undefined ? + options.collapsible : true; + + if (!this.collapsible_) { + this.collapsed_ = false; + } + + var className = options.className !== undefined ? options.className : 'ol-overviewmap'; + + var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Overview map'; + + var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00AB'; + + if (typeof collapseLabel === 'string') { + /** + * @private + * @type {Node} + */ + this.collapseLabel_ = document.createElement('span'); + this.collapseLabel_.textContent = collapseLabel; + } else { + this.collapseLabel_ = collapseLabel; + } + + var label = options.label !== undefined ? options.label : '\u00BB'; + + + if (typeof label === 'string') { + /** + * @private + * @type {Node} + */ + this.label_ = document.createElement('span'); + this.label_.textContent = label; + } else { + this.label_ = label; + } + + var activeLabel = (this.collapsible_ && !this.collapsed_) ? + this.collapseLabel_ : this.label_; + var button = document.createElement('button'); + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild(activeLabel); + + ol.events.listen(button, ol.events.EventType.CLICK, + this.handleClick_, this); + + var ovmapDiv = document.createElement('DIV'); + ovmapDiv.className = 'ol-overviewmap-map'; + + /** + * @type {ol.Map} + * @private + */ + this.ovmap_ = new ol.Map({ + controls: new ol.Collection(), + interactions: new ol.Collection(), + target: ovmapDiv, + view: options.view + }); + var ovmap = this.ovmap_; + + if (options.layers) { + options.layers.forEach( + /** + * @param {ol.layer.Layer} layer Layer. + */ + function(layer) { + ovmap.addLayer(layer); + }, this); + } + + var box = document.createElement('DIV'); + box.className = 'ol-overviewmap-box'; + box.style.boxSizing = 'border-box'; + + /** + * @type {ol.Overlay} + * @private + */ + this.boxOverlay_ = new ol.Overlay({ + position: [0, 0], + positioning: ol.Overlay.Positioning.BOTTOM_LEFT, + element: box + }); + this.ovmap_.addOverlay(this.boxOverlay_); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL + + (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') + + (this.collapsible_ ? '' : ' ol-uncollapsible'); + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(ovmapDiv); + element.appendChild(button); + + var render = options.render ? options.render : ol.control.OverviewMap.render; + + ol.control.Control.call(this, { + element: element, + render: render, + target: options.target + }); +}; +ol.inherits(ol.control.OverviewMap, ol.control.Control); + + +/** + * @inheritDoc + * @api + */ +ol.control.OverviewMap.prototype.setMap = function(map) { + var oldMap = this.getMap(); + if (map === oldMap) { + return; + } + if (oldMap) { + var oldView = oldMap.getView(); + if (oldView) { + this.unbindView_(oldView); + } + } + ol.control.Control.prototype.setMap.call(this, map); + + if (map) { + this.listenerKeys.push(ol.events.listen( + map, ol.Object.EventType.PROPERTYCHANGE, + this.handleMapPropertyChange_, this)); + + // TODO: to really support map switching, this would need to be reworked + if (this.ovmap_.getLayers().getLength() === 0) { + this.ovmap_.setLayerGroup(map.getLayerGroup()); + } + + var view = map.getView(); + if (view) { + this.bindView_(view); + if (view.isDef()) { + this.ovmap_.updateSize(); + this.resetExtent_(); + } + } + } +}; + + +/** + * Handle map property changes. This only deals with changes to the map's view. + * @param {ol.Object.Event} event The propertychange event. + * @private + */ +ol.control.OverviewMap.prototype.handleMapPropertyChange_ = function(event) { + if (event.key === ol.Map.Property.VIEW) { + var oldView = /** @type {ol.View} */ (event.oldValue); + if (oldView) { + this.unbindView_(oldView); + } + var newView = this.getMap().getView(); + this.bindView_(newView); + } +}; + + +/** + * Register listeners for view property changes. + * @param {ol.View} view The view. + * @private + */ +ol.control.OverviewMap.prototype.bindView_ = function(view) { + ol.events.listen(view, + ol.Object.getChangeEventType(ol.View.Property.ROTATION), + this.handleRotationChanged_, this); +}; + + +/** + * Unregister listeners for view property changes. + * @param {ol.View} view The view. + * @private + */ +ol.control.OverviewMap.prototype.unbindView_ = function(view) { + ol.events.unlisten(view, + ol.Object.getChangeEventType(ol.View.Property.ROTATION), + this.handleRotationChanged_, this); +}; + + +/** + * Handle rotation changes to the main map. + * TODO: This should rotate the extent rectrangle instead of the + * overview map's view. + * @private + */ +ol.control.OverviewMap.prototype.handleRotationChanged_ = function() { + this.ovmap_.getView().setRotation(this.getMap().getView().getRotation()); +}; + + +/** + * Update the overview map element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.OverviewMap} + * @api + */ +ol.control.OverviewMap.render = function(mapEvent) { + this.validateExtent_(); + this.updateBox_(); +}; + + +/** + * Reset the overview map extent if the box size (width or + * height) is less than the size of the overview map size times minRatio + * or is greater than the size of the overview size times maxRatio. + * + * If the map extent was not reset, the box size can fits in the defined + * ratio sizes. This method then checks if is contained inside the overview + * map current extent. If not, recenter the overview map to the current + * main map center location. + * @private + */ +ol.control.OverviewMap.prototype.validateExtent_ = function() { + var map = this.getMap(); + var ovmap = this.ovmap_; + + if (!map.isRendered() || !ovmap.isRendered()) { + return; + } + + var mapSize = /** @type {ol.Size} */ (map.getSize()); + + var view = map.getView(); + var extent = view.calculateExtent(mapSize); + + var ovmapSize = /** @type {ol.Size} */ (ovmap.getSize()); + + var ovview = ovmap.getView(); + var ovextent = ovview.calculateExtent(ovmapSize); + + var topLeftPixel = + ovmap.getPixelFromCoordinate(ol.extent.getTopLeft(extent)); + var bottomRightPixel = + ovmap.getPixelFromCoordinate(ol.extent.getBottomRight(extent)); + + var boxWidth = Math.abs(topLeftPixel[0] - bottomRightPixel[0]); + var boxHeight = Math.abs(topLeftPixel[1] - bottomRightPixel[1]); + + var ovmapWidth = ovmapSize[0]; + var ovmapHeight = ovmapSize[1]; + + if (boxWidth < ovmapWidth * ol.OVERVIEWMAP_MIN_RATIO || + boxHeight < ovmapHeight * ol.OVERVIEWMAP_MIN_RATIO || + boxWidth > ovmapWidth * ol.OVERVIEWMAP_MAX_RATIO || + boxHeight > ovmapHeight * ol.OVERVIEWMAP_MAX_RATIO) { + this.resetExtent_(); + } else if (!ol.extent.containsExtent(ovextent, extent)) { + this.recenter_(); + } +}; + + +/** + * Reset the overview map extent to half calculated min and max ratio times + * the extent of the main map. + * @private + */ +ol.control.OverviewMap.prototype.resetExtent_ = function() { + if (ol.OVERVIEWMAP_MAX_RATIO === 0 || ol.OVERVIEWMAP_MIN_RATIO === 0) { + return; + } + + var map = this.getMap(); + var ovmap = this.ovmap_; + + var mapSize = /** @type {ol.Size} */ (map.getSize()); + + var view = map.getView(); + var extent = view.calculateExtent(mapSize); + + var ovmapSize = /** @type {ol.Size} */ (ovmap.getSize()); + + var ovview = ovmap.getView(); + + // get how many times the current map overview could hold different + // box sizes using the min and max ratio, pick the step in the middle used + // to calculate the extent from the main map to set it to the overview map, + var steps = Math.log( + ol.OVERVIEWMAP_MAX_RATIO / ol.OVERVIEWMAP_MIN_RATIO) / Math.LN2; + var ratio = 1 / (Math.pow(2, steps / 2) * ol.OVERVIEWMAP_MIN_RATIO); + ol.extent.scaleFromCenter(extent, ratio); + ovview.fit(extent, ovmapSize); +}; + + +/** + * Set the center of the overview map to the map center without changing its + * resolution. + * @private + */ +ol.control.OverviewMap.prototype.recenter_ = function() { + var map = this.getMap(); + var ovmap = this.ovmap_; + + var view = map.getView(); + + var ovview = ovmap.getView(); + + ovview.setCenter(view.getCenter()); +}; + + +/** + * Update the box using the main map extent + * @private + */ +ol.control.OverviewMap.prototype.updateBox_ = function() { + var map = this.getMap(); + var ovmap = this.ovmap_; + + if (!map.isRendered() || !ovmap.isRendered()) { + return; + } + + var mapSize = /** @type {ol.Size} */ (map.getSize()); + + var view = map.getView(); + + var ovview = ovmap.getView(); + + var rotation = view.getRotation(); + + var overlay = this.boxOverlay_; + var box = this.boxOverlay_.getElement(); + var extent = view.calculateExtent(mapSize); + var ovresolution = ovview.getResolution(); + var bottomLeft = ol.extent.getBottomLeft(extent); + var topRight = ol.extent.getTopRight(extent); + + // set position using bottom left coordinates + var rotateBottomLeft = this.calculateCoordinateRotate_(rotation, bottomLeft); + overlay.setPosition(rotateBottomLeft); + + // set box size calculated from map extent size and overview map resolution + if (box) { + box.style.width = Math.abs((bottomLeft[0] - topRight[0]) / ovresolution) + 'px'; + box.style.height = Math.abs((topRight[1] - bottomLeft[1]) / ovresolution) + 'px'; + } +}; + + +/** + * @param {number} rotation Target rotation. + * @param {ol.Coordinate} coordinate Coordinate. + * @return {ol.Coordinate|undefined} Coordinate for rotation and center anchor. + * @private + */ +ol.control.OverviewMap.prototype.calculateCoordinateRotate_ = function( + rotation, coordinate) { + var coordinateRotate; + + var map = this.getMap(); + var view = map.getView(); + + var currentCenter = view.getCenter(); + + if (currentCenter) { + coordinateRotate = [ + coordinate[0] - currentCenter[0], + coordinate[1] - currentCenter[1] + ]; + ol.coordinate.rotate(coordinateRotate, rotation); + ol.coordinate.add(coordinateRotate, currentCenter); + } + return coordinateRotate; +}; + + +/** + * @param {Event} event The event to handle + * @private + */ +ol.control.OverviewMap.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleToggle_(); +}; + + +/** + * @private + */ +ol.control.OverviewMap.prototype.handleToggle_ = function() { + this.element.classList.toggle('ol-collapsed'); + if (this.collapsed_) { + ol.dom.replaceNode(this.collapseLabel_, this.label_); + } else { + ol.dom.replaceNode(this.label_, this.collapseLabel_); + } + this.collapsed_ = !this.collapsed_; + + // manage overview map if it had not been rendered before and control + // is expanded + var ovmap = this.ovmap_; + if (!this.collapsed_ && !ovmap.isRendered()) { + ovmap.updateSize(); + this.resetExtent_(); + ol.events.listenOnce(ovmap, ol.MapEvent.Type.POSTRENDER, + function(event) { + this.updateBox_(); + }, + this); + } +}; + + +/** + * Return `true` if the overview map is collapsible, `false` otherwise. + * @return {boolean} True if the widget is collapsible. + * @api stable + */ +ol.control.OverviewMap.prototype.getCollapsible = function() { + return this.collapsible_; +}; + + +/** + * Set whether the overview map should be collapsible. + * @param {boolean} collapsible True if the widget is collapsible. + * @api stable + */ +ol.control.OverviewMap.prototype.setCollapsible = function(collapsible) { + if (this.collapsible_ === collapsible) { + return; + } + this.collapsible_ = collapsible; + this.element.classList.toggle('ol-uncollapsible'); + if (!collapsible && this.collapsed_) { + this.handleToggle_(); + } +}; + + +/** + * Collapse or expand the overview map according to the passed parameter. Will + * not do anything if the overview map isn't collapsible or if the current + * collapsed state is already the one requested. + * @param {boolean} collapsed True if the widget is collapsed. + * @api stable + */ +ol.control.OverviewMap.prototype.setCollapsed = function(collapsed) { + if (!this.collapsible_ || this.collapsed_ === collapsed) { + return; + } + this.handleToggle_(); +}; + + +/** + * Determine if the overview map is collapsed. + * @return {boolean} The overview map is collapsed. + * @api stable + */ +ol.control.OverviewMap.prototype.getCollapsed = function() { + return this.collapsed_; +}; + + +/** + * Return the overview map. + * @return {ol.Map} Overview map. + * @api + */ +ol.control.OverviewMap.prototype.getOverviewMap = function() { + return this.ovmap_; +}; + +goog.provide('ol.control.ScaleLine'); + +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.asserts'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.events'); +goog.require('ol.proj'); +goog.require('ol.proj.Units'); + + +/** + * @classdesc + * A control displaying rough y-axis distances, calculated for the center of the + * viewport. For conformal projections (e.g. EPSG:3857, the default view + * projection in OpenLayers), the scale is valid for all directions. + * No scale line will be shown when the y-axis distance of a pixel at the + * viewport center cannot be calculated in the view projection. + * By default the scale line will show in the bottom left portion of the map, + * but this can be changed by using the css selector `.ol-scale-line`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ScaleLineOptions=} opt_options Scale line options. + * @api stable + */ +ol.control.ScaleLine = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + var className = options.className !== undefined ? options.className : 'ol-scale-line'; + + /** + * @private + * @type {Element} + */ + this.innerElement_ = document.createElement('DIV'); + this.innerElement_.className = className + '-inner'; + + /** + * @private + * @type {Element} + */ + this.element_ = document.createElement('DIV'); + this.element_.className = className + ' ' + ol.css.CLASS_UNSELECTABLE; + this.element_.appendChild(this.innerElement_); + + /** + * @private + * @type {?olx.ViewState} + */ + this.viewState_ = null; + + /** + * @private + * @type {number} + */ + this.minWidth_ = options.minWidth !== undefined ? options.minWidth : 64; + + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = false; + + /** + * @private + * @type {number|undefined} + */ + this.renderedWidth_ = undefined; + + /** + * @private + * @type {string} + */ + this.renderedHTML_ = ''; + + var render = options.render ? options.render : ol.control.ScaleLine.render; + + ol.control.Control.call(this, { + element: this.element_, + render: render, + target: options.target + }); + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.control.ScaleLine.Property.UNITS), + this.handleUnitsChanged_, this); + + this.setUnits(/** @type {ol.control.ScaleLine.Units} */ (options.units) || + ol.control.ScaleLine.Units.METRIC); + +}; +ol.inherits(ol.control.ScaleLine, ol.control.Control); + + +/** + * @const + * @type {Array.<number>} + */ +ol.control.ScaleLine.LEADING_DIGITS = [1, 2, 5]; + + +/** + * Return the units to use in the scale line. + * @return {ol.control.ScaleLine.Units|undefined} The units to use in the scale + * line. + * @observable + * @api stable + */ +ol.control.ScaleLine.prototype.getUnits = function() { + return /** @type {ol.control.ScaleLine.Units|undefined} */ ( + this.get(ol.control.ScaleLine.Property.UNITS)); +}; + + +/** + * Update the scale line element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.ScaleLine} + * @api + */ +ol.control.ScaleLine.render = function(mapEvent) { + var frameState = mapEvent.frameState; + if (!frameState) { + this.viewState_ = null; + } else { + this.viewState_ = frameState.viewState; + } + this.updateElement_(); +}; + + +/** + * @private + */ +ol.control.ScaleLine.prototype.handleUnitsChanged_ = function() { + this.updateElement_(); +}; + + +/** + * Set the units to use in the scale line. + * @param {ol.control.ScaleLine.Units} units The units to use in the scale line. + * @observable + * @api stable + */ +ol.control.ScaleLine.prototype.setUnits = function(units) { + this.set(ol.control.ScaleLine.Property.UNITS, units); +}; + + +/** + * @private + */ +ol.control.ScaleLine.prototype.updateElement_ = function() { + var viewState = this.viewState_; + + if (!viewState) { + if (this.renderedVisible_) { + this.element_.style.display = 'none'; + this.renderedVisible_ = false; + } + return; + } + + var center = viewState.center; + var projection = viewState.projection; + var metersPerUnit = projection.getMetersPerUnit(); + var pointResolution = + ol.proj.getPointResolution(projection, viewState.resolution, center) * + metersPerUnit; + + var nominalCount = this.minWidth_ * pointResolution; + var suffix = ''; + var units = this.getUnits(); + if (units == ol.control.ScaleLine.Units.DEGREES) { + var metersPerDegree = ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES]; + pointResolution /= metersPerDegree; + if (nominalCount < metersPerDegree / 60) { + suffix = '\u2033'; // seconds + pointResolution *= 3600; + } else if (nominalCount < metersPerDegree) { + suffix = '\u2032'; // minutes + pointResolution *= 60; + } else { + suffix = '\u00b0'; // degrees + } + } else if (units == ol.control.ScaleLine.Units.IMPERIAL) { + if (nominalCount < 0.9144) { + suffix = 'in'; + pointResolution /= 0.0254; + } else if (nominalCount < 1609.344) { + suffix = 'ft'; + pointResolution /= 0.3048; + } else { + suffix = 'mi'; + pointResolution /= 1609.344; + } + } else if (units == ol.control.ScaleLine.Units.NAUTICAL) { + pointResolution /= 1852; + suffix = 'nm'; + } else if (units == ol.control.ScaleLine.Units.METRIC) { + if (nominalCount < 1) { + suffix = 'mm'; + pointResolution *= 1000; + } else if (nominalCount < 1000) { + suffix = 'm'; + } else { + suffix = 'km'; + pointResolution /= 1000; + } + } else if (units == ol.control.ScaleLine.Units.US) { + if (nominalCount < 0.9144) { + suffix = 'in'; + pointResolution *= 39.37; + } else if (nominalCount < 1609.344) { + suffix = 'ft'; + pointResolution /= 0.30480061; + } else { + suffix = 'mi'; + pointResolution /= 1609.3472; + } + } else { + ol.asserts.assert(false, 33); // Invalid units + } + + var i = 3 * Math.floor( + Math.log(this.minWidth_ * pointResolution) / Math.log(10)); + var count, width; + while (true) { + count = ol.control.ScaleLine.LEADING_DIGITS[((i % 3) + 3) % 3] * + Math.pow(10, Math.floor(i / 3)); + width = Math.round(count / pointResolution); + if (isNaN(width)) { + this.element_.style.display = 'none'; + this.renderedVisible_ = false; + return; + } else if (width >= this.minWidth_) { + break; + } + ++i; + } + + var html = count + ' ' + suffix; + if (this.renderedHTML_ != html) { + this.innerElement_.innerHTML = html; + this.renderedHTML_ = html; + } + + if (this.renderedWidth_ != width) { + this.innerElement_.style.width = width + 'px'; + this.renderedWidth_ = width; + } + + if (!this.renderedVisible_) { + this.element_.style.display = ''; + this.renderedVisible_ = true; + } + +}; + + +/** + * @enum {string} + * @api + */ +ol.control.ScaleLine.Property = { + UNITS: 'units' +}; + + +/** + * Units for the scale line. Supported values are `'degrees'`, `'imperial'`, + * `'nautical'`, `'metric'`, `'us'`. + * @enum {string} + */ +ol.control.ScaleLine.Units = { + DEGREES: 'degrees', + IMPERIAL: 'imperial', + NAUTICAL: 'nautical', + METRIC: 'metric', + US: 'us' +}; + +// FIXME should possibly show tooltip when dragging? + +goog.provide('ol.control.ZoomSlider'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.easing'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.EventType'); +goog.require('ol.math'); +goog.require('ol.pointer.EventType'); +goog.require('ol.pointer.PointerEventHandler'); + + +/** + * @classdesc + * A slider type of control for zooming. + * + * Example: + * + * map.addControl(new ol.control.ZoomSlider()); + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ZoomSliderOptions=} opt_options Zoom slider options. + * @api stable + */ +ol.control.ZoomSlider = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + /** + * Will hold the current resolution of the view. + * + * @type {number|undefined} + * @private + */ + this.currentResolution_ = undefined; + + /** + * The direction of the slider. Will be determined from actual display of the + * container and defaults to ol.control.ZoomSlider.direction.VERTICAL. + * + * @type {ol.control.ZoomSlider.direction} + * @private + */ + this.direction_ = ol.control.ZoomSlider.direction.VERTICAL; + + /** + * @type {boolean} + * @private + */ + this.dragging_; + + /** + * @type {!Array.<ol.EventsKey>} + * @private + */ + this.dragListenerKeys_ = []; + + /** + * @type {number} + * @private + */ + this.heightLimit_ = 0; + + /** + * @type {number} + * @private + */ + this.widthLimit_ = 0; + + /** + * @type {number|undefined} + * @private + */ + this.previousX_; + + /** + * @type {number|undefined} + * @private + */ + this.previousY_; + + /** + * The calculated thumb size (border box plus margins). Set when initSlider_ + * is called. + * @type {ol.Size} + * @private + */ + this.thumbSize_ = null; + + /** + * Whether the slider is initialized. + * @type {boolean} + * @private + */ + this.sliderInitialized_ = false; + + /** + * @type {number} + * @private + */ + this.duration_ = options.duration !== undefined ? options.duration : 200; + + var className = options.className !== undefined ? options.className : 'ol-zoomslider'; + var thumbElement = document.createElement('button'); + thumbElement.setAttribute('type', 'button'); + thumbElement.className = className + '-thumb ' + ol.css.CLASS_UNSELECTABLE; + var containerElement = document.createElement('div'); + containerElement.className = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + ol.css.CLASS_CONTROL; + containerElement.appendChild(thumbElement); + /** + * @type {ol.pointer.PointerEventHandler} + * @private + */ + this.dragger_ = new ol.pointer.PointerEventHandler(containerElement); + + ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERDOWN, + this.handleDraggerStart_, this); + ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERMOVE, + this.handleDraggerDrag_, this); + ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERUP, + this.handleDraggerEnd_, this); + + ol.events.listen(containerElement, ol.events.EventType.CLICK, + this.handleContainerClick_, this); + ol.events.listen(thumbElement, ol.events.EventType.CLICK, + ol.events.Event.stopPropagation); + + var render = options.render ? options.render : ol.control.ZoomSlider.render; + + ol.control.Control.call(this, { + element: containerElement, + render: render + }); +}; +ol.inherits(ol.control.ZoomSlider, ol.control.Control); + + +/** + * @inheritDoc + */ +ol.control.ZoomSlider.prototype.disposeInternal = function() { + this.dragger_.dispose(); + ol.control.Control.prototype.disposeInternal.call(this); +}; + + +/** + * The enum for available directions. + * + * @enum {number} + */ +ol.control.ZoomSlider.direction = { + VERTICAL: 0, + HORIZONTAL: 1 +}; + + +/** + * @inheritDoc + */ +ol.control.ZoomSlider.prototype.setMap = function(map) { + ol.control.Control.prototype.setMap.call(this, map); + if (map) { + map.render(); + } +}; + + +/** + * Initializes the slider element. This will determine and set this controls + * direction_ and also constrain the dragging of the thumb to always be within + * the bounds of the container. + * + * @private + */ +ol.control.ZoomSlider.prototype.initSlider_ = function() { + var container = this.element; + var containerSize = { + width: container.offsetWidth, height: container.offsetHeight + }; + + var thumb = container.firstElementChild; + var computedStyle = getComputedStyle(thumb); + var thumbWidth = thumb.offsetWidth + + parseFloat(computedStyle['marginRight']) + + parseFloat(computedStyle['marginLeft']); + var thumbHeight = thumb.offsetHeight + + parseFloat(computedStyle['marginTop']) + + parseFloat(computedStyle['marginBottom']); + this.thumbSize_ = [thumbWidth, thumbHeight]; + + if (containerSize.width > containerSize.height) { + this.direction_ = ol.control.ZoomSlider.direction.HORIZONTAL; + this.widthLimit_ = containerSize.width - thumbWidth; + } else { + this.direction_ = ol.control.ZoomSlider.direction.VERTICAL; + this.heightLimit_ = containerSize.height - thumbHeight; + } + this.sliderInitialized_ = true; +}; + + +/** + * Update the zoomslider element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.ZoomSlider} + * @api + */ +ol.control.ZoomSlider.render = function(mapEvent) { + if (!mapEvent.frameState) { + return; + } + if (!this.sliderInitialized_) { + this.initSlider_(); + } + var res = mapEvent.frameState.viewState.resolution; + if (res !== this.currentResolution_) { + this.currentResolution_ = res; + this.setThumbPosition_(res); + } +}; + + +/** + * @param {Event} event The browser event to handle. + * @private + */ +ol.control.ZoomSlider.prototype.handleContainerClick_ = function(event) { + var view = this.getMap().getView(); + + var relativePosition = this.getRelativePosition_( + event.offsetX - this.thumbSize_[0] / 2, + event.offsetY - this.thumbSize_[1] / 2); + + var resolution = this.getResolutionForPosition_(relativePosition); + + view.animate({ + resolution: view.constrainResolution(resolution), + duration: this.duration_, + easing: ol.easing.easeOut + }); +}; + + +/** + * Handle dragger start events. + * @param {ol.pointer.PointerEvent} event The drag event. + * @private + */ +ol.control.ZoomSlider.prototype.handleDraggerStart_ = function(event) { + if (!this.dragging_ && + event.originalEvent.target === this.element.firstElementChild) { + this.getMap().getView().setHint(ol.View.Hint.INTERACTING, 1); + this.previousX_ = event.clientX; + this.previousY_ = event.clientY; + this.dragging_ = true; + + if (this.dragListenerKeys_.length === 0) { + var drag = this.handleDraggerDrag_; + var end = this.handleDraggerEnd_; + this.dragListenerKeys_.push( + ol.events.listen(document, ol.events.EventType.MOUSEMOVE, drag, this), + ol.events.listen(document, ol.events.EventType.TOUCHMOVE, drag, this), + ol.events.listen(document, ol.pointer.EventType.POINTERMOVE, drag, this), + ol.events.listen(document, ol.events.EventType.MOUSEUP, end, this), + ol.events.listen(document, ol.events.EventType.TOUCHEND, end, this), + ol.events.listen(document, ol.pointer.EventType.POINTERUP, end, this) + ); + } + } +}; + + +/** + * Handle dragger drag events. + * + * @param {ol.pointer.PointerEvent|Event} event The drag event. + * @private + */ +ol.control.ZoomSlider.prototype.handleDraggerDrag_ = function(event) { + if (this.dragging_) { + var element = this.element.firstElementChild; + var deltaX = event.clientX - this.previousX_ + parseInt(element.style.left, 10); + var deltaY = event.clientY - this.previousY_ + parseInt(element.style.top, 10); + var relativePosition = this.getRelativePosition_(deltaX, deltaY); + this.currentResolution_ = this.getResolutionForPosition_(relativePosition); + this.getMap().getView().setResolution(this.currentResolution_); + this.setThumbPosition_(this.currentResolution_); + this.previousX_ = event.clientX; + this.previousY_ = event.clientY; + } +}; + + +/** + * Handle dragger end events. + * @param {ol.pointer.PointerEvent|Event} event The drag event. + * @private + */ +ol.control.ZoomSlider.prototype.handleDraggerEnd_ = function(event) { + if (this.dragging_) { + var view = this.getMap().getView(); + view.setHint(ol.View.Hint.INTERACTING, -1); + + view.animate({ + resolution: view.constrainResolution(this.currentResolution_), + duration: this.duration_, + easing: ol.easing.easeOut + }); + + this.dragging_ = false; + this.previousX_ = undefined; + this.previousY_ = undefined; + this.dragListenerKeys_.forEach(ol.events.unlistenByKey); + this.dragListenerKeys_.length = 0; + } +}; + + +/** + * Positions the thumb inside its container according to the given resolution. + * + * @param {number} res The res. + * @private + */ +ol.control.ZoomSlider.prototype.setThumbPosition_ = function(res) { + var position = this.getPositionForResolution_(res); + var thumb = this.element.firstElementChild; + + if (this.direction_ == ol.control.ZoomSlider.direction.HORIZONTAL) { + thumb.style.left = this.widthLimit_ * position + 'px'; + } else { + thumb.style.top = this.heightLimit_ * position + 'px'; + } +}; + + +/** + * Calculates the relative position of the thumb given x and y offsets. The + * relative position scales from 0 to 1. The x and y offsets are assumed to be + * in pixel units within the dragger limits. + * + * @param {number} x Pixel position relative to the left of the slider. + * @param {number} y Pixel position relative to the top of the slider. + * @return {number} The relative position of the thumb. + * @private + */ +ol.control.ZoomSlider.prototype.getRelativePosition_ = function(x, y) { + var amount; + if (this.direction_ === ol.control.ZoomSlider.direction.HORIZONTAL) { + amount = x / this.widthLimit_; + } else { + amount = y / this.heightLimit_; + } + return ol.math.clamp(amount, 0, 1); +}; + + +/** + * Calculates the corresponding resolution of the thumb given its relative + * position (where 0 is the minimum and 1 is the maximum). + * + * @param {number} position The relative position of the thumb. + * @return {number} The corresponding resolution. + * @private + */ +ol.control.ZoomSlider.prototype.getResolutionForPosition_ = function(position) { + var fn = this.getMap().getView().getResolutionForValueFunction(); + return fn(1 - position); +}; + + +/** + * Determines the relative position of the slider for the given resolution. A + * relative position of 0 corresponds to the minimum view resolution. A + * relative position of 1 corresponds to the maximum view resolution. + * + * @param {number} res The resolution. + * @return {number} The relative position value (between 0 and 1). + * @private + */ +ol.control.ZoomSlider.prototype.getPositionForResolution_ = function(res) { + var fn = this.getMap().getView().getValueForResolutionFunction(); + return 1 - fn(res); +}; + +goog.provide('ol.control.ZoomToExtent'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.control.Control'); +goog.require('ol.css'); + + +/** + * @classdesc + * A button control which, when pressed, changes the map view to a specific + * extent. To style this control use the css selector `.ol-zoom-extent`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ZoomToExtentOptions=} opt_options Options. + * @api stable + */ +ol.control.ZoomToExtent = function(opt_options) { + var options = opt_options ? opt_options : {}; + + /** + * @type {ol.Extent} + * @private + */ + this.extent_ = options.extent ? options.extent : null; + + var className = options.className !== undefined ? options.className : + 'ol-zoom-extent'; + + var label = options.label !== undefined ? options.label : 'E'; + var tipLabel = options.tipLabel !== undefined ? + options.tipLabel : 'Fit to extent'; + var button = document.createElement('button'); + button.setAttribute('type', 'button'); + button.title = tipLabel; + button.appendChild( + typeof label === 'string' ? document.createTextNode(label) : label + ); + + ol.events.listen(button, ol.events.EventType.CLICK, + this.handleClick_, this); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = document.createElement('div'); + element.className = cssClasses; + element.appendChild(button); + + ol.control.Control.call(this, { + element: element, + target: options.target + }); +}; +ol.inherits(ol.control.ZoomToExtent, ol.control.Control); + + +/** + * @param {Event} event The event to handle + * @private + */ +ol.control.ZoomToExtent.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleZoomToExtent_(); +}; + + +/** + * @private + */ +ol.control.ZoomToExtent.prototype.handleZoomToExtent_ = function() { + var map = this.getMap(); + var view = map.getView(); + var extent = !this.extent_ ? view.getProjection().getExtent() : this.extent_; + var size = /** @type {ol.Size} */ (map.getSize()); + view.fit(extent, size); +}; + +goog.provide('ol.DeviceOrientation'); + +goog.require('ol.events'); +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.has'); +goog.require('ol.math'); + + +/** + * @classdesc + * The ol.DeviceOrientation class provides access to information from + * DeviceOrientation events. See the [HTML 5 DeviceOrientation Specification]( + * http://www.w3.org/TR/orientation-event/) for more details. + * + * Many new computers, and especially mobile phones + * and tablets, provide hardware support for device orientation. Web + * developers targeting mobile devices will be especially interested in this + * class. + * + * Device orientation data are relative to a common starting point. For mobile + * devices, the starting point is to lay your phone face up on a table with the + * top of the phone pointing north. This represents the zero state. All + * angles are then relative to this state. For computers, it is the same except + * the screen is open at 90 degrees. + * + * Device orientation is reported as three angles - `alpha`, `beta`, and + * `gamma` - relative to the starting position along the three planar axes X, Y + * and Z. The X axis runs from the left edge to the right edge through the + * middle of the device. Similarly, the Y axis runs from the bottom to the top + * of the device through the middle. The Z axis runs from the back to the front + * through the middle. In the starting position, the X axis points to the + * right, the Y axis points away from you and the Z axis points straight up + * from the device lying flat. + * + * The three angles representing the device orientation are relative to the + * three axes. `alpha` indicates how much the device has been rotated around the + * Z axis, which is commonly interpreted as the compass heading (see note + * below). `beta` indicates how much the device has been rotated around the X + * axis, or how much it is tilted from front to back. `gamma` indicates how + * much the device has been rotated around the Y axis, or how much it is tilted + * from left to right. + * + * For most browsers, the `alpha` value returns the compass heading so if the + * device points north, it will be 0. With Safari on iOS, the 0 value of + * `alpha` is calculated from when device orientation was first requested. + * ol.DeviceOrientation provides the `heading` property which normalizes this + * behavior across all browsers for you. + * + * It is important to note that the HTML 5 DeviceOrientation specification + * indicates that `alpha`, `beta` and `gamma` are in degrees while the + * equivalent properties in ol.DeviceOrientation are in radians for consistency + * with all other uses of angles throughout OpenLayers. + * + * To get notified of device orientation changes, register a listener for the + * generic `change` event on your `ol.DeviceOrientation` instance. + * + * @see {@link http://www.w3.org/TR/orientation-event/} + * + * @constructor + * @extends {ol.Object} + * @param {olx.DeviceOrientationOptions=} opt_options Options. + * @api + */ +ol.DeviceOrientation = function(opt_options) { + + ol.Object.call(this); + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.listenerKey_ = null; + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.DeviceOrientation.Property.TRACKING), + this.handleTrackingChanged_, this); + + this.setTracking(options.tracking !== undefined ? options.tracking : false); + +}; +ol.inherits(ol.DeviceOrientation, ol.Object); + + +/** + * @inheritDoc + */ +ol.DeviceOrientation.prototype.disposeInternal = function() { + this.setTracking(false); + ol.Object.prototype.disposeInternal.call(this); +}; + + +/** + * @private + * @param {Event} originalEvent Event. + */ +ol.DeviceOrientation.prototype.orientationChange_ = function(originalEvent) { + var event = /** @type {DeviceOrientationEvent} */ (originalEvent); + if (event.alpha !== null) { + var alpha = ol.math.toRadians(event.alpha); + this.set(ol.DeviceOrientation.Property.ALPHA, alpha); + // event.absolute is undefined in iOS. + if (typeof event.absolute === 'boolean' && event.absolute) { + this.set(ol.DeviceOrientation.Property.HEADING, alpha); + } else if (typeof event.webkitCompassHeading === 'number' && + event.webkitCompassAccuracy != -1) { + var heading = ol.math.toRadians(event.webkitCompassHeading); + this.set(ol.DeviceOrientation.Property.HEADING, heading); + } + } + if (event.beta !== null) { + this.set(ol.DeviceOrientation.Property.BETA, + ol.math.toRadians(event.beta)); + } + if (event.gamma !== null) { + this.set(ol.DeviceOrientation.Property.GAMMA, + ol.math.toRadians(event.gamma)); + } + this.changed(); +}; + + +/** + * Rotation around the device z-axis (in radians). + * @return {number|undefined} The euler angle in radians of the device from the + * standard Z axis. + * @observable + * @api + */ +ol.DeviceOrientation.prototype.getAlpha = function() { + return /** @type {number|undefined} */ ( + this.get(ol.DeviceOrientation.Property.ALPHA)); +}; + + +/** + * Rotation around the device x-axis (in radians). + * @return {number|undefined} The euler angle in radians of the device from the + * planar X axis. + * @observable + * @api + */ +ol.DeviceOrientation.prototype.getBeta = function() { + return /** @type {number|undefined} */ ( + this.get(ol.DeviceOrientation.Property.BETA)); +}; + + +/** + * Rotation around the device y-axis (in radians). + * @return {number|undefined} The euler angle in radians of the device from the + * planar Y axis. + * @observable + * @api + */ +ol.DeviceOrientation.prototype.getGamma = function() { + return /** @type {number|undefined} */ ( + this.get(ol.DeviceOrientation.Property.GAMMA)); +}; + + +/** + * The heading of the device relative to north (in radians). + * @return {number|undefined} The heading of the device relative to north, in + * radians, normalizing for different browser behavior. + * @observable + * @api + */ +ol.DeviceOrientation.prototype.getHeading = function() { + return /** @type {number|undefined} */ ( + this.get(ol.DeviceOrientation.Property.HEADING)); +}; + + +/** + * Determine if orientation is being tracked. + * @return {boolean} Changes in device orientation are being tracked. + * @observable + * @api + */ +ol.DeviceOrientation.prototype.getTracking = function() { + return /** @type {boolean} */ ( + this.get(ol.DeviceOrientation.Property.TRACKING)); +}; + + +/** + * @private + */ +ol.DeviceOrientation.prototype.handleTrackingChanged_ = function() { + if (ol.has.DEVICE_ORIENTATION) { + var tracking = this.getTracking(); + if (tracking && !this.listenerKey_) { + this.listenerKey_ = ol.events.listen(window, 'deviceorientation', + this.orientationChange_, this); + } else if (!tracking && this.listenerKey_ !== null) { + ol.events.unlistenByKey(this.listenerKey_); + this.listenerKey_ = null; + } + } +}; + + +/** + * Enable or disable tracking of device orientation events. + * @param {boolean} tracking The status of tracking changes to alpha, beta and + * gamma. If true, changes are tracked and reported immediately. + * @observable + * @api + */ +ol.DeviceOrientation.prototype.setTracking = function(tracking) { + this.set(ol.DeviceOrientation.Property.TRACKING, tracking); +}; + + +/** + * @enum {string} + */ +ol.DeviceOrientation.Property = { + ALPHA: 'alpha', + BETA: 'beta', + GAMMA: 'gamma', + HEADING: 'heading', + TRACKING: 'tracking' +}; + +goog.provide('ol.Feature'); + +goog.require('ol.asserts'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.geom.Geometry'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * A vector object for geographic features with a geometry and other + * attribute properties, similar to the features in vector file formats like + * GeoJSON. + * + * Features can be styled individually with `setStyle`; otherwise they use the + * style of their vector layer. + * + * Note that attribute properties are set as {@link ol.Object} properties on + * the feature object, so they are observable, and have get/set accessors. + * + * Typically, a feature has a single geometry property. You can set the + * geometry using the `setGeometry` method and get it with `getGeometry`. + * It is possible to store more than one geometry on a feature using attribute + * properties. By default, the geometry used for rendering is identified by + * the property name `geometry`. If you want to use another geometry property + * for rendering, use the `setGeometryName` method to change the attribute + * property associated with the geometry for the feature. For example: + * + * ```js + * var feature = new ol.Feature({ + * geometry: new ol.geom.Polygon(polyCoords), + * labelPoint: new ol.geom.Point(labelCoords), + * name: 'My Polygon' + * }); + * + * // get the polygon geometry + * var poly = feature.getGeometry(); + * + * // Render the feature as a point using the coordinates from labelPoint + * feature.setGeometryName('labelPoint'); + * + * // get the point geometry + * var point = feature.getGeometry(); + * ``` + * + * @constructor + * @extends {ol.Object} + * @param {ol.geom.Geometry|Object.<string, *>=} opt_geometryOrProperties + * You may pass a Geometry object directly, or an object literal + * containing properties. If you pass an object literal, you may + * include a Geometry associated with a `geometry` key. + * @api stable + */ +ol.Feature = function(opt_geometryOrProperties) { + + ol.Object.call(this); + + /** + * @private + * @type {number|string|undefined} + */ + this.id_ = undefined; + + /** + * @type {string} + * @private + */ + this.geometryName_ = 'geometry'; + + /** + * User provided style. + * @private + * @type {ol.style.Style|Array.<ol.style.Style>| + * ol.FeatureStyleFunction} + */ + this.style_ = null; + + /** + * @private + * @type {ol.FeatureStyleFunction|undefined} + */ + this.styleFunction_ = undefined; + + /** + * @private + * @type {?ol.EventsKey} + */ + this.geometryChangeKey_ = null; + + ol.events.listen( + this, ol.Object.getChangeEventType(this.geometryName_), + this.handleGeometryChanged_, this); + + if (opt_geometryOrProperties !== undefined) { + if (opt_geometryOrProperties instanceof ol.geom.Geometry || + !opt_geometryOrProperties) { + var geometry = opt_geometryOrProperties; + this.setGeometry(geometry); + } else { + /** @type {Object.<string, *>} */ + var properties = opt_geometryOrProperties; + this.setProperties(properties); + } + } +}; +ol.inherits(ol.Feature, ol.Object); + + +/** + * Clone this feature. If the original feature has a geometry it + * is also cloned. The feature id is not set in the clone. + * @return {ol.Feature} The clone. + * @api stable + */ +ol.Feature.prototype.clone = function() { + var clone = new ol.Feature(this.getProperties()); + clone.setGeometryName(this.getGeometryName()); + var geometry = this.getGeometry(); + if (geometry) { + clone.setGeometry(geometry.clone()); + } + var style = this.getStyle(); + if (style) { + clone.setStyle(style); + } + return clone; +}; + + +/** + * Get the feature's default geometry. A feature may have any number of named + * geometries. The "default" geometry (the one that is rendered by default) is + * set when calling {@link ol.Feature#setGeometry}. + * @return {ol.geom.Geometry|undefined} The default geometry for the feature. + * @api stable + * @observable + */ +ol.Feature.prototype.getGeometry = function() { + return /** @type {ol.geom.Geometry|undefined} */ ( + this.get(this.geometryName_)); +}; + + +/** + * Get the feature identifier. This is a stable identifier for the feature and + * is either set when reading data from a remote source or set explicitly by + * calling {@link ol.Feature#setId}. + * @return {number|string|undefined} Id. + * @api stable + * @observable + */ +ol.Feature.prototype.getId = function() { + return this.id_; +}; + + +/** + * Get the name of the feature's default geometry. By default, the default + * geometry is named `geometry`. + * @return {string} Get the property name associated with the default geometry + * for this feature. + * @api stable + */ +ol.Feature.prototype.getGeometryName = function() { + return this.geometryName_; +}; + + +/** + * Get the feature's style. Will return what was provided to the + * {@link ol.Feature#setStyle} method. + * @return {ol.style.Style|Array.<ol.style.Style>| + * ol.FeatureStyleFunction} The feature style. + * @api stable + * @observable + */ +ol.Feature.prototype.getStyle = function() { + return this.style_; +}; + + +/** + * Get the feature's style function. + * @return {ol.FeatureStyleFunction|undefined} Return a function + * representing the current style of this feature. + * @api stable + */ +ol.Feature.prototype.getStyleFunction = function() { + return this.styleFunction_; +}; + + +/** + * @private + */ +ol.Feature.prototype.handleGeometryChange_ = function() { + this.changed(); +}; + + +/** + * @private + */ +ol.Feature.prototype.handleGeometryChanged_ = function() { + if (this.geometryChangeKey_) { + ol.events.unlistenByKey(this.geometryChangeKey_); + this.geometryChangeKey_ = null; + } + var geometry = this.getGeometry(); + if (geometry) { + this.geometryChangeKey_ = ol.events.listen(geometry, + ol.events.EventType.CHANGE, this.handleGeometryChange_, this); + } + this.changed(); +}; + + +/** + * Set the default geometry for the feature. This will update the property + * with the name returned by {@link ol.Feature#getGeometryName}. + * @param {ol.geom.Geometry|undefined} geometry The new geometry. + * @api stable + * @observable + */ +ol.Feature.prototype.setGeometry = function(geometry) { + this.set(this.geometryName_, geometry); +}; + + +/** + * Set the style for the feature. This can be a single style object, an array + * of styles, or a function that takes a resolution and returns an array of + * styles. If it is `null` the feature has no style (a `null` style). + * @param {ol.style.Style|Array.<ol.style.Style>| + * ol.FeatureStyleFunction} style Style for this feature. + * @api stable + * @observable + */ +ol.Feature.prototype.setStyle = function(style) { + this.style_ = style; + this.styleFunction_ = !style ? + undefined : ol.Feature.createStyleFunction(style); + this.changed(); +}; + + +/** + * Set the feature id. The feature id is considered stable and may be used when + * requesting features or comparing identifiers returned from a remote source. + * The feature id can be used with the {@link ol.source.Vector#getFeatureById} + * method. + * @param {number|string|undefined} id The feature id. + * @api stable + * @observable + */ +ol.Feature.prototype.setId = function(id) { + this.id_ = id; + this.changed(); +}; + + +/** + * Set the property name to be used when getting the feature's default geometry. + * When calling {@link ol.Feature#getGeometry}, the value of the property with + * this name will be returned. + * @param {string} name The property name of the default geometry. + * @api stable + */ +ol.Feature.prototype.setGeometryName = function(name) { + ol.events.unlisten( + this, ol.Object.getChangeEventType(this.geometryName_), + this.handleGeometryChanged_, this); + this.geometryName_ = name; + ol.events.listen( + this, ol.Object.getChangeEventType(this.geometryName_), + this.handleGeometryChanged_, this); + this.handleGeometryChanged_(); +}; + + +/** + * Convert the provided object into a feature style function. Functions passed + * through unchanged. Arrays of ol.style.Style or single style objects wrapped + * in a new feature style function. + * @param {ol.FeatureStyleFunction|!Array.<ol.style.Style>|!ol.style.Style} obj + * A feature style function, a single style, or an array of styles. + * @return {ol.FeatureStyleFunction} A style function. + */ +ol.Feature.createStyleFunction = function(obj) { + var styleFunction; + + if (typeof obj === 'function') { + styleFunction = obj; + } else { + /** + * @type {Array.<ol.style.Style>} + */ + var styles; + if (Array.isArray(obj)) { + styles = obj; + } else { + ol.asserts.assert(obj instanceof ol.style.Style, + 41); // Expected an `ol.style.Style` or an array of `ol.style.Style` + styles = [obj]; + } + styleFunction = function() { + return styles; + }; + } + return styleFunction; +}; + +goog.provide('ol.format.FormatType'); + + +/** + * @enum {string} + */ +ol.format.FormatType = { + ARRAY_BUFFER: 'arraybuffer', + JSON: 'json', + TEXT: 'text', + XML: 'xml' +}; + +goog.provide('ol.xml'); + +goog.require('ol'); +goog.require('ol.array'); + + +/** + * This document should be used when creating nodes for XML serializations. This + * document is also used by {@link ol.xml.createElementNS} and + * {@link ol.xml.setAttributeNS} + * @const + * @type {Document} + */ +ol.xml.DOCUMENT = document.implementation.createDocument('', '', null); + + +/** + * @param {string} namespaceURI Namespace URI. + * @param {string} qualifiedName Qualified name. + * @return {Node} Node. + */ +ol.xml.createElementNS = function(namespaceURI, qualifiedName) { + return ol.xml.DOCUMENT.createElementNS(namespaceURI, qualifiedName); +}; + + +/** + * Recursively grab all text content of child nodes into a single string. + * @param {Node} node Node. + * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line + * breaks. + * @return {string} All text content. + * @api + */ +ol.xml.getAllTextContent = function(node, normalizeWhitespace) { + return ol.xml.getAllTextContent_(node, normalizeWhitespace, []).join(''); +}; + + +/** + * Recursively grab all text content of child nodes into a single string. + * @param {Node} node Node. + * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line + * breaks. + * @param {Array.<string>} accumulator Accumulator. + * @private + * @return {Array.<string>} Accumulator. + */ +ol.xml.getAllTextContent_ = function(node, normalizeWhitespace, accumulator) { + if (node.nodeType == Node.CDATA_SECTION_NODE || + node.nodeType == Node.TEXT_NODE) { + if (normalizeWhitespace) { + accumulator.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, '')); + } else { + accumulator.push(node.nodeValue); + } + } else { + var n; + for (n = node.firstChild; n; n = n.nextSibling) { + ol.xml.getAllTextContent_(n, normalizeWhitespace, accumulator); + } + } + return accumulator; +}; + + +/** + * @param {?} value Value. + * @return {boolean} Is document. + */ +ol.xml.isDocument = function(value) { + return value instanceof Document; +}; + + +/** + * @param {?} value Value. + * @return {boolean} Is node. + */ +ol.xml.isNode = function(value) { + return value instanceof Node; +}; + + +/** + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @return {string} Value + */ +ol.xml.getAttributeNS = function(node, namespaceURI, name) { + return node.getAttributeNS(namespaceURI, name) || ''; +}; + + +/** + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @param {string|number} value Value. + */ +ol.xml.setAttributeNS = function(node, namespaceURI, name, value) { + node.setAttributeNS(namespaceURI, name, value); +}; + + +/** + * Parse an XML string to an XML Document. + * @param {string} xml XML. + * @return {Document} Document. + * @api + */ +ol.xml.parse = function(xml) { + return new DOMParser().parseFromString(xml, 'application/xml'); +}; + + +/** + * Make an array extender function for extending the array at the top of the + * object stack. + * @param {function(this: T, Node, Array.<*>): (Array.<*>|undefined)} + * valueReader Value reader. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.XmlParser} Parser. + * @template T + */ +ol.xml.makeArrayExtender = function(valueReader, opt_this) { + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(opt_this, node, objectStack); + if (value !== undefined) { + ol.DEBUG && console.assert(Array.isArray(value), + 'valueReader function is expected to return an array of values'); + var array = /** @type {Array.<*>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(array), + 'objectStack is supposed to be an array of arrays'); + ol.array.extend(array, value); + } + }); +}; + + +/** + * Make an array pusher function for pushing to the array at the top of the + * object stack. + * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.XmlParser} Parser. + * @template T + */ +ol.xml.makeArrayPusher = function(valueReader, opt_this) { + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(opt_this !== undefined ? opt_this : this, + node, objectStack); + if (value !== undefined) { + var array = objectStack[objectStack.length - 1]; + ol.DEBUG && console.assert(Array.isArray(array), + 'objectStack is supposed to be an array of arrays'); + array.push(value); + } + }); +}; + + +/** + * Make an object stack replacer function for replacing the object at the + * top of the stack. + * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.XmlParser} Parser. + * @template T + */ +ol.xml.makeReplacer = function(valueReader, opt_this) { + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(opt_this !== undefined ? opt_this : this, + node, objectStack); + if (value !== undefined) { + objectStack[objectStack.length - 1] = value; + } + }); +}; + + +/** + * Make an object property pusher function for adding a property to the + * object at the top of the stack. + * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. + * @param {string=} opt_property Property. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.XmlParser} Parser. + * @template T + */ +ol.xml.makeObjectPropertyPusher = function(valueReader, opt_property, opt_this) { + ol.DEBUG && console.assert(valueReader !== undefined, + 'undefined valueReader, expected function(this: T, Node, Array.<*>)'); + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(opt_this !== undefined ? opt_this : this, + node, objectStack); + if (value !== undefined) { + var object = /** @type {Object} */ + (objectStack[objectStack.length - 1]); + var property = opt_property !== undefined ? + opt_property : node.localName; + var array; + if (property in object) { + array = object[property]; + } else { + array = object[property] = []; + } + array.push(value); + } + }); +}; + + +/** + * Make an object property setter function. + * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. + * @param {string=} opt_property Property. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.XmlParser} Parser. + * @template T + */ +ol.xml.makeObjectPropertySetter = function(valueReader, opt_property, opt_this) { + ol.DEBUG && console.assert(valueReader !== undefined, + 'undefined valueReader, expected function(this: T, Node, Array.<*>)'); + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(opt_this !== undefined ? opt_this : this, + node, objectStack); + if (value !== undefined) { + var object = /** @type {Object} */ + (objectStack[objectStack.length - 1]); + var property = opt_property !== undefined ? + opt_property : node.localName; + object[property] = value; + } + }); +}; + + +/** + * Create a serializer that appends nodes written by its `nodeWriter` to its + * designated parent. The parent is the `node` of the + * {@link ol.XmlNodeStackItem} at the top of the `objectStack`. + * @param {function(this: T, Node, V, Array.<*>)} + * nodeWriter Node writer. + * @param {T=} opt_this The object to use as `this` in `nodeWriter`. + * @return {ol.XmlSerializer} Serializer. + * @template T, V + */ +ol.xml.makeChildAppender = function(nodeWriter, opt_this) { + return function(node, value, objectStack) { + nodeWriter.call(opt_this !== undefined ? opt_this : this, + node, value, objectStack); + var parent = objectStack[objectStack.length - 1]; + var parentNode = parent.node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode) || + ol.xml.isDocument(parentNode), + 'expected parentNode %s to be a Node or a Document', parentNode); + parentNode.appendChild(node); + }; +}; + + +/** + * Create a serializer that calls the provided `nodeWriter` from + * {@link ol.xml.serialize}. This can be used by the parent writer to have the + * 'nodeWriter' called with an array of values when the `nodeWriter` was + * designed to serialize a single item. An example would be a LineString + * geometry writer, which could be reused for writing MultiLineString + * geometries. + * @param {function(this: T, Node, V, Array.<*>)} + * nodeWriter Node writer. + * @param {T=} opt_this The object to use as `this` in `nodeWriter`. + * @return {ol.XmlSerializer} Serializer. + * @template T, V + */ +ol.xml.makeArraySerializer = function(nodeWriter, opt_this) { + var serializersNS, nodeFactory; + return function(node, value, objectStack) { + if (serializersNS === undefined) { + serializersNS = {}; + var serializers = {}; + serializers[node.localName] = nodeWriter; + serializersNS[node.namespaceURI] = serializers; + nodeFactory = ol.xml.makeSimpleNodeFactory(node.localName); + } + ol.xml.serialize(serializersNS, nodeFactory, value, objectStack); + }; +}; + + +/** + * Create a node factory which can use the `opt_keys` passed to + * {@link ol.xml.serialize} or {@link ol.xml.pushSerializeAndPop} as node names, + * or a fixed node name. The namespace of the created nodes can either be fixed, + * or the parent namespace will be used. + * @param {string=} opt_nodeName Fixed node name which will be used for all + * created nodes. If not provided, the 3rd argument to the resulting node + * factory needs to be provided and will be the nodeName. + * @param {string=} opt_namespaceURI Fixed namespace URI which will be used for + * all created nodes. If not provided, the namespace of the parent node will + * be used. + * @return {function(*, Array.<*>, string=): (Node|undefined)} Node factory. + */ +ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) { + var fixedNodeName = opt_nodeName; + return ( + /** + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node} Node. + */ + function(value, objectStack, opt_nodeName) { + var context = objectStack[objectStack.length - 1]; + var node = context.node; + ol.DEBUG && console.assert(ol.xml.isNode(node) || ol.xml.isDocument(node), + 'expected node %s to be a Node or a Document', node); + var nodeName = fixedNodeName; + if (nodeName === undefined) { + nodeName = opt_nodeName; + } + var namespaceURI = opt_namespaceURI; + if (opt_namespaceURI === undefined) { + namespaceURI = node.namespaceURI; + } + ol.DEBUG && console.assert(nodeName !== undefined, 'nodeName was undefined'); + return ol.xml.createElementNS(namespaceURI, /** @type {string} */ (nodeName)); + } + ); +}; + + +/** + * A node factory that creates a node using the parent's `namespaceURI` and the + * `nodeName` passed by {@link ol.xml.serialize} or + * {@link ol.xml.pushSerializeAndPop} to the node factory. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + */ +ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory(); + + +/** + * Create an array of `values` to be used with {@link ol.xml.serialize} or + * {@link ol.xml.pushSerializeAndPop}, where `orderedKeys` has to be provided as + * `opt_key` argument. + * @param {Object.<string, V>} object Key-value pairs for the sequence. Keys can + * be a subset of the `orderedKeys`. + * @param {Array.<string>} orderedKeys Keys in the order of the sequence. + * @return {Array.<V>} Values in the order of the sequence. The resulting array + * has the same length as the `orderedKeys` array. Values that are not + * present in `object` will be `undefined` in the resulting array. + * @template V + */ +ol.xml.makeSequence = function(object, orderedKeys) { + var length = orderedKeys.length; + var sequence = new Array(length); + for (var i = 0; i < length; ++i) { + sequence[i] = object[orderedKeys[i]]; + } + return sequence; +}; + + +/** + * Create a namespaced structure, using the same values for each namespace. + * This can be used as a starting point for versioned parsers, when only a few + * values are version specific. + * @param {Array.<string>} namespaceURIs Namespace URIs. + * @param {T} structure Structure. + * @param {Object.<string, T>=} opt_structureNS Namespaced structure to add to. + * @return {Object.<string, T>} Namespaced structure. + * @template T + */ +ol.xml.makeStructureNS = function(namespaceURIs, structure, opt_structureNS) { + /** + * @type {Object.<string, *>} + */ + var structureNS = opt_structureNS !== undefined ? opt_structureNS : {}; + var i, ii; + for (i = 0, ii = namespaceURIs.length; i < ii; ++i) { + structureNS[namespaceURIs[i]] = structure; + } + return structureNS; +}; + + +/** + * Parse a node using the parsers and object stack. + * @param {Object.<string, Object.<string, ol.XmlParser>>} parsersNS + * Parsers by namespace. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @param {*=} opt_this The object to use as `this`. + */ +ol.xml.parseNode = function(parsersNS, node, objectStack, opt_this) { + var n; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var parsers = parsersNS[n.namespaceURI]; + if (parsers !== undefined) { + var parser = parsers[n.localName]; + if (parser !== undefined) { + parser.call(opt_this, n, objectStack); + } + } + } +}; + + +/** + * Push an object on top of the stack, parse and return the popped object. + * @param {T} object Object. + * @param {Object.<string, Object.<string, ol.XmlParser>>} parsersNS + * Parsers by namespace. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @param {*=} opt_this The object to use as `this`. + * @return {T} Object. + * @template T + */ +ol.xml.pushParseAndPop = function( + object, parsersNS, node, objectStack, opt_this) { + objectStack.push(object); + ol.xml.parseNode(parsersNS, node, objectStack, opt_this); + return objectStack.pop(); +}; + + +/** + * Walk through an array of `values` and call a serializer for each value. + * @param {Object.<string, Object.<string, ol.XmlSerializer>>} serializersNS + * Namespaced serializers. + * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory + * Node factory. The `nodeFactory` creates the node whose namespace and name + * will be used to choose a node writer from `serializersNS`. This + * separation allows us to decide what kind of node to create, depending on + * the value we want to serialize. An example for this would be different + * geometry writers based on the geometry type. + * @param {Array.<*>} values Values to serialize. An example would be an array + * of {@link ol.Feature} instances. + * @param {Array.<*>} objectStack Node stack. + * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the + * `nodeFactory`. This is used for serializing object literals where the + * node name relates to the property key. The array length of `opt_keys` has + * to match the length of `values`. For serializing a sequence, `opt_keys` + * determines the order of the sequence. + * @param {T=} opt_this The object to use as `this` for the node factory and + * serializers. + * @template T + */ +ol.xml.serialize = function( + serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) { + var length = (opt_keys !== undefined ? opt_keys : values).length; + var value, node; + for (var i = 0; i < length; ++i) { + value = values[i]; + if (value !== undefined) { + node = nodeFactory.call(opt_this, value, objectStack, + opt_keys !== undefined ? opt_keys[i] : undefined); + if (node !== undefined) { + serializersNS[node.namespaceURI][node.localName] + .call(opt_this, node, value, objectStack); + } + } + } +}; + + +/** + * @param {O} object Object. + * @param {Object.<string, Object.<string, ol.XmlSerializer>>} serializersNS + * Namespaced serializers. + * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory + * Node factory. The `nodeFactory` creates the node whose namespace and name + * will be used to choose a node writer from `serializersNS`. This + * separation allows us to decide what kind of node to create, depending on + * the value we want to serialize. An example for this would be different + * geometry writers based on the geometry type. + * @param {Array.<*>} values Values to serialize. An example would be an array + * of {@link ol.Feature} instances. + * @param {Array.<*>} objectStack Node stack. + * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the + * `nodeFactory`. This is used for serializing object literals where the + * node name relates to the property key. The array length of `opt_keys` has + * to match the length of `values`. For serializing a sequence, `opt_keys` + * determines the order of the sequence. + * @param {T=} opt_this The object to use as `this` for the node factory and + * serializers. + * @return {O|undefined} Object. + * @template O, T + */ +ol.xml.pushSerializeAndPop = function(object, + serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) { + objectStack.push(object); + ol.xml.serialize( + serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this); + return objectStack.pop(); +}; + +goog.provide('ol.featureloader'); + +goog.require('ol'); +goog.require('ol.format.FormatType'); +goog.require('ol.xml'); + + +/** + * @param {string|ol.FeatureUrlFunction} url Feature URL service. + * @param {ol.format.Feature} format Feature format. + * @param {function(this:ol.VectorTile, Array.<ol.Feature>, ol.proj.Projection)|function(this:ol.source.Vector, Array.<ol.Feature>)} success + * Function called with the loaded features and optionally with the data + * projection. Called with the vector tile or source as `this`. + * @param {function(this:ol.VectorTile)|function(this:ol.source.Vector)} failure + * Function called when loading failed. Called with the vector tile or + * source as `this`. + * @return {ol.FeatureLoader} The feature loader. + */ +ol.featureloader.loadFeaturesXhr = function(url, format, success, failure) { + return ( + /** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {ol.proj.Projection} projection Projection. + * @this {ol.source.Vector|ol.VectorTile} + */ + function(extent, resolution, projection) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', + typeof url === 'function' ? url(extent, resolution, projection) : url, + true); + if (format.getType() == ol.format.FormatType.ARRAY_BUFFER) { + xhr.responseType = 'arraybuffer'; + } + /** + * @param {Event} event Event. + * @private + */ + xhr.onload = function(event) { + // status will be 0 for file:// urls + if (!xhr.status || xhr.status >= 200 && xhr.status < 300) { + var type = format.getType(); + /** @type {Document|Node|Object|string|undefined} */ + var source; + if (type == ol.format.FormatType.JSON || + type == ol.format.FormatType.TEXT) { + source = xhr.responseText; + } else if (type == ol.format.FormatType.XML) { + source = xhr.responseXML; + if (!source) { + source = ol.xml.parse(xhr.responseText); + } + } else if (type == ol.format.FormatType.ARRAY_BUFFER) { + source = /** @type {ArrayBuffer} */ (xhr.response); + } + if (source) { + success.call(this, format.readFeatures(source, + {featureProjection: projection}), + format.readProjection(source)); + } else { + failure.call(this); + } + } else { + failure.call(this); + } + }.bind(this); + xhr.send(); + }); +}; + + +/** + * Create an XHR feature loader for a `url` and `format`. The feature loader + * loads features (with XHR), parses the features, and adds them to the + * vector source. + * @param {string|ol.FeatureUrlFunction} url Feature URL service. + * @param {ol.format.Feature} format Feature format. + * @return {ol.FeatureLoader} The feature loader. + * @api + */ +ol.featureloader.xhr = function(url, format) { + return ol.featureloader.loadFeaturesXhr(url, format, + /** + * @param {Array.<ol.Feature>} features The loaded features. + * @param {ol.proj.Projection} dataProjection Data projection. + * @this {ol.source.Vector} + */ + function(features, dataProjection) { + this.addFeatures(features); + }, /* FIXME handle error */ ol.nullFunction); +}; + +goog.provide('ol.format.Feature'); + +goog.require('ol.geom.Geometry'); +goog.require('ol.obj'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for feature formats. + * {ol.format.Feature} subclasses provide the ability to decode and encode + * {@link ol.Feature} objects from a variety of commonly used geospatial + * file formats. See the documentation for each format for more details. + * + * @constructor + * @api stable + */ +ol.format.Feature = function() { + + /** + * @protected + * @type {ol.proj.Projection} + */ + this.defaultDataProjection = null; + + /** + * @protected + * @type {ol.proj.Projection} + */ + this.defaultFeatureProjection = null; + +}; + + +/** + * @abstract + * @return {Array.<string>} Extensions. + */ +ol.format.Feature.prototype.getExtensions = function() {}; + + +/** + * Adds the data projection to the read options. + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {olx.format.ReadOptions|undefined} Options. + * @protected + */ +ol.format.Feature.prototype.getReadOptions = function(source, opt_options) { + var options; + if (opt_options) { + options = { + dataProjection: opt_options.dataProjection ? + opt_options.dataProjection : this.readProjection(source), + featureProjection: opt_options.featureProjection + }; + } + return this.adaptOptions(options); +}; + + +/** + * Sets the `defaultDataProjection` on the options, if no `dataProjection` + * is set. + * @param {olx.format.WriteOptions|olx.format.ReadOptions|undefined} options + * Options. + * @protected + * @return {olx.format.WriteOptions|olx.format.ReadOptions|undefined} + * Updated options. + */ +ol.format.Feature.prototype.adaptOptions = function(options) { + return ol.obj.assign({ + dataProjection: this.defaultDataProjection, + featureProjection: this.defaultFeatureProjection + }, options); +}; + + +/** + * @abstract + * @return {ol.format.FormatType} Format. + */ +ol.format.Feature.prototype.getType = function() {}; + + +/** + * Read a single feature from a source. + * + * @abstract + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + */ +ol.format.Feature.prototype.readFeature = function(source, opt_options) {}; + + +/** + * Read all features from a source. + * + * @abstract + * @param {Document|Node|ArrayBuffer|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + */ +ol.format.Feature.prototype.readFeatures = function(source, opt_options) {}; + + +/** + * Read a single geometry from a source. + * + * @abstract + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.Feature.prototype.readGeometry = function(source, opt_options) {}; + + +/** + * Read the projection from a source. + * + * @abstract + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + */ +ol.format.Feature.prototype.readProjection = function(source) {}; + + +/** + * Encode a feature in this format. + * + * @abstract + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Result. + */ +ol.format.Feature.prototype.writeFeature = function(feature, opt_options) {}; + + +/** + * Encode an array of features in this format. + * + * @abstract + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Result. + */ +ol.format.Feature.prototype.writeFeatures = function(features, opt_options) {}; + + +/** + * Write a single geometry in this format. + * + * @abstract + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Result. + */ +ol.format.Feature.prototype.writeGeometry = function(geometry, opt_options) {}; + + +/** + * @param {ol.geom.Geometry|ol.Extent} geometry Geometry. + * @param {boolean} write Set to true for writing, false for reading. + * @param {(olx.format.WriteOptions|olx.format.ReadOptions)=} opt_options + * Options. + * @return {ol.geom.Geometry|ol.Extent} Transformed geometry. + * @protected + */ +ol.format.Feature.transformWithOptions = function( + geometry, write, opt_options) { + var featureProjection = opt_options ? + ol.proj.get(opt_options.featureProjection) : null; + var dataProjection = opt_options ? + ol.proj.get(opt_options.dataProjection) : null; + /** + * @type {ol.geom.Geometry|ol.Extent} + */ + var transformed; + if (featureProjection && dataProjection && + !ol.proj.equivalent(featureProjection, dataProjection)) { + if (geometry instanceof ol.geom.Geometry) { + transformed = (write ? geometry.clone() : geometry).transform( + write ? featureProjection : dataProjection, + write ? dataProjection : featureProjection); + } else { + // FIXME this is necessary because ol.format.GML treats extents + // as geometries + transformed = ol.proj.transformExtent( + write ? geometry.slice() : geometry, + write ? featureProjection : dataProjection, + write ? dataProjection : featureProjection); + } + } else { + transformed = geometry; + } + if (write && opt_options && opt_options.decimals) { + var power = Math.pow(10, opt_options.decimals); + // if decimals option on write, round each coordinate appropriately + /** + * @param {Array.<number>} coordinates Coordinates. + * @return {Array.<number>} Transformed coordinates. + */ + var transform = function(coordinates) { + for (var i = 0, ii = coordinates.length; i < ii; ++i) { + coordinates[i] = Math.round(coordinates[i] * power) / power; + } + return coordinates; + }; + if (Array.isArray(transformed)) { + transform(transformed); + } else { + transformed.applyTransform(transform); + } + } + return transformed; +}; + +goog.provide('ol.format.JSONFeature'); + +goog.require('ol'); +goog.require('ol.format.Feature'); +goog.require('ol.format.FormatType'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for JSON feature formats. + * + * @constructor + * @extends {ol.format.Feature} + */ +ol.format.JSONFeature = function() { + ol.format.Feature.call(this); +}; +ol.inherits(ol.format.JSONFeature, ol.format.Feature); + + +/** + * @param {Document|Node|Object|string} source Source. + * @private + * @return {Object} Object. + */ +ol.format.JSONFeature.prototype.getObject_ = function(source) { + if (typeof source === 'string') { + var object = JSON.parse(source); + return object ? /** @type {Object} */ (object) : null; + } else if (source !== null) { + return source; + } else { + return null; + } +}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.getType = function() { + return ol.format.FormatType.JSON; +}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.readFeature = function(source, opt_options) { + return this.readFeatureFromObject( + this.getObject_(source), this.getReadOptions(source, opt_options)); +}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.readFeatures = function(source, opt_options) { + return this.readFeaturesFromObject( + this.getObject_(source), this.getReadOptions(source, opt_options)); +}; + + +/** + * @abstract + * @param {Object} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {ol.Feature} Feature. + */ +ol.format.JSONFeature.prototype.readFeatureFromObject = function(object, opt_options) {}; + + +/** + * @abstract + * @param {Object} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {Array.<ol.Feature>} Features. + */ +ol.format.JSONFeature.prototype.readFeaturesFromObject = function(object, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.readGeometry = function(source, opt_options) { + return this.readGeometryFromObject( + this.getObject_(source), this.getReadOptions(source, opt_options)); +}; + + +/** + * @abstract + * @param {Object} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.JSONFeature.prototype.readGeometryFromObject = function(object, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.readProjection = function(source) { + return this.readProjectionFromObject(this.getObject_(source)); +}; + + +/** + * @abstract + * @param {Object} object Object. + * @protected + * @return {ol.proj.Projection} Projection. + */ +ol.format.JSONFeature.prototype.readProjectionFromObject = function(object) {}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.writeFeature = function(feature, opt_options) { + return JSON.stringify(this.writeFeatureObject(feature, opt_options)); +}; + + +/** + * @abstract + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. + */ +ol.format.JSONFeature.prototype.writeFeatureObject = function(feature, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.writeFeatures = function(features, opt_options) { + return JSON.stringify(this.writeFeaturesObject(features, opt_options)); +}; + + +/** + * @abstract + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. + */ +ol.format.JSONFeature.prototype.writeFeaturesObject = function(features, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.JSONFeature.prototype.writeGeometry = function(geometry, opt_options) { + return JSON.stringify(this.writeGeometryObject(geometry, opt_options)); +}; + + +/** + * @abstract + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. + */ +ol.format.JSONFeature.prototype.writeGeometryObject = function(geometry, opt_options) {}; + +goog.provide('ol.geom.flat.interpolate'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.math'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} fraction Fraction. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Destination. + */ +ol.geom.flat.interpolate.lineString = function(flatCoordinates, offset, end, stride, fraction, opt_dest) { + // FIXME does not work when vertices are repeated + // FIXME interpolate extra dimensions + ol.DEBUG && console.assert(0 <= fraction && fraction <= 1, + 'fraction should be in between 0 and 1'); + var pointX = NaN; + var pointY = NaN; + var n = (end - offset) / stride; + if (n === 0) { + ol.DEBUG && console.assert(false, 'n cannot be 0'); + } else if (n == 1) { + pointX = flatCoordinates[offset]; + pointY = flatCoordinates[offset + 1]; + } else if (n == 2) { + pointX = (1 - fraction) * flatCoordinates[offset] + + fraction * flatCoordinates[offset + stride]; + pointY = (1 - fraction) * flatCoordinates[offset + 1] + + fraction * flatCoordinates[offset + stride + 1]; + } else { + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + var length = 0; + var cumulativeLengths = [0]; + var i; + for (i = offset + stride; i < end; i += stride) { + var x2 = flatCoordinates[i]; + var y2 = flatCoordinates[i + 1]; + length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + cumulativeLengths.push(length); + x1 = x2; + y1 = y2; + } + var target = fraction * length; + var index = ol.array.binarySearch(cumulativeLengths, target); + if (index < 0) { + var t = (target - cumulativeLengths[-index - 2]) / + (cumulativeLengths[-index - 1] - cumulativeLengths[-index - 2]); + var o = offset + (-index - 2) * stride; + pointX = ol.math.lerp( + flatCoordinates[o], flatCoordinates[o + stride], t); + pointY = ol.math.lerp( + flatCoordinates[o + 1], flatCoordinates[o + stride + 1], t); + } else { + pointX = flatCoordinates[offset + index * stride]; + pointY = flatCoordinates[offset + index * stride + 1]; + } + } + if (opt_dest) { + opt_dest[0] = pointX; + opt_dest[1] = pointY; + return opt_dest; + } else { + return [pointX, pointY]; + } +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} m M. + * @param {boolean} extrapolate Extrapolate. + * @return {ol.Coordinate} Coordinate. + */ +ol.geom.flat.interpolate.lineStringCoordinateAtM = function(flatCoordinates, offset, end, stride, m, extrapolate) { + if (end == offset) { + return null; + } + var coordinate; + if (m < flatCoordinates[offset + stride - 1]) { + if (extrapolate) { + coordinate = flatCoordinates.slice(offset, offset + stride); + coordinate[stride - 1] = m; + return coordinate; + } else { + return null; + } + } else if (flatCoordinates[end - 1] < m) { + if (extrapolate) { + coordinate = flatCoordinates.slice(end - stride, end); + coordinate[stride - 1] = m; + return coordinate; + } else { + return null; + } + } + // FIXME use O(1) search + if (m == flatCoordinates[offset + stride - 1]) { + return flatCoordinates.slice(offset, offset + stride); + } + var lo = offset / stride; + var hi = end / stride; + while (lo < hi) { + var mid = (lo + hi) >> 1; + if (m < flatCoordinates[(mid + 1) * stride - 1]) { + hi = mid; + } else { + lo = mid + 1; + } + } + var m0 = flatCoordinates[lo * stride - 1]; + if (m == m0) { + return flatCoordinates.slice((lo - 1) * stride, (lo - 1) * stride + stride); + } + var m1 = flatCoordinates[(lo + 1) * stride - 1]; + ol.DEBUG && console.assert(m0 < m, 'm0 should be less than m'); + ol.DEBUG && console.assert(m <= m1, 'm should be less than or equal to m1'); + var t = (m - m0) / (m1 - m0); + coordinate = []; + var i; + for (i = 0; i < stride - 1; ++i) { + coordinate.push(ol.math.lerp(flatCoordinates[(lo - 1) * stride + i], + flatCoordinates[lo * stride + i], t)); + } + coordinate.push(m); + ol.DEBUG && console.assert(coordinate.length == stride, + 'length of coordinate array should match stride'); + return coordinate; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} m M. + * @param {boolean} extrapolate Extrapolate. + * @param {boolean} interpolate Interpolate. + * @return {ol.Coordinate} Coordinate. + */ +ol.geom.flat.interpolate.lineStringsCoordinateAtM = function( + flatCoordinates, offset, ends, stride, m, extrapolate, interpolate) { + if (interpolate) { + return ol.geom.flat.interpolate.lineStringCoordinateAtM( + flatCoordinates, offset, ends[ends.length - 1], stride, m, extrapolate); + } + var coordinate; + if (m < flatCoordinates[stride - 1]) { + if (extrapolate) { + coordinate = flatCoordinates.slice(0, stride); + coordinate[stride - 1] = m; + return coordinate; + } else { + return null; + } + } + if (flatCoordinates[flatCoordinates.length - 1] < m) { + if (extrapolate) { + coordinate = flatCoordinates.slice(flatCoordinates.length - stride); + coordinate[stride - 1] = m; + return coordinate; + } else { + return null; + } + } + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + if (offset == end) { + continue; + } + if (m < flatCoordinates[offset + stride - 1]) { + return null; + } else if (m <= flatCoordinates[end - 1]) { + return ol.geom.flat.interpolate.lineStringCoordinateAtM( + flatCoordinates, offset, end, stride, m, false); + } + offset = end; + } + ol.DEBUG && console.assert(false, + 'ol.geom.flat.interpolate.lineStringsCoordinateAtM should have returned'); + return null; +}; + +goog.provide('ol.geom.flat.length'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} Length. + */ +ol.geom.flat.length.lineString = function(flatCoordinates, offset, end, stride) { + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + var length = 0; + var i; + for (i = offset + stride; i < end; i += stride) { + var x2 = flatCoordinates[i]; + var y2 = flatCoordinates[i + 1]; + length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + x1 = x2; + y1 = y2; + } + return length; +}; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} Perimeter. + */ +ol.geom.flat.length.linearRing = function(flatCoordinates, offset, end, stride) { + var perimeter = + ol.geom.flat.length.lineString(flatCoordinates, offset, end, stride); + var dx = flatCoordinates[end - stride] - flatCoordinates[offset]; + var dy = flatCoordinates[end - stride + 1] - flatCoordinates[offset + 1]; + perimeter += Math.sqrt(dx * dx + dy * dy); + return perimeter; +}; + +goog.provide('ol.geom.LineString'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.interpolate'); +goog.require('ol.geom.flat.intersectsextent'); +goog.require('ol.geom.flat.length'); +goog.require('ol.geom.flat.segments'); +goog.require('ol.geom.flat.simplify'); + + +/** + * @classdesc + * Linestring geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.LineString = function(coordinates, opt_layout) { + + ol.geom.SimpleGeometry.call(this); + + /** + * @private + * @type {ol.Coordinate} + */ + this.flatMidpoint_ = null; + + /** + * @private + * @type {number} + */ + this.flatMidpointRevision_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDelta_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDeltaRevision_ = -1; + + this.setCoordinates(coordinates, opt_layout); + +}; +ol.inherits(ol.geom.LineString, ol.geom.SimpleGeometry); + + +/** + * Append the passed coordinate to the coordinates of the linestring. + * @param {ol.Coordinate} coordinate Coordinate. + * @api stable + */ +ol.geom.LineString.prototype.appendCoordinate = function(coordinate) { + ol.DEBUG && console.assert(coordinate.length == this.stride, + 'length of coordinate array should match stride'); + if (!this.flatCoordinates) { + this.flatCoordinates = coordinate.slice(); + } else { + ol.array.extend(this.flatCoordinates, coordinate); + } + this.changed(); +}; + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.LineString} Clone. + * @api stable + */ +ol.geom.LineString.prototype.clone = function() { + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return lineString; +}; + + +/** + * @inheritDoc + */ +ol.geom.LineString.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getClosestPoint( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + this.maxDelta_, false, x, y, closestPoint, minSquaredDistance); +}; + + +/** + * Iterate over each segment, calling the provided callback. + * If the callback returns a truthy value the function returns that + * value immediately. Otherwise the function returns `false`. + * + * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function + * called for each segment. + * @param {S=} opt_this The object to be used as the value of 'this' + * within callback. + * @return {T|boolean} Value. + * @template T,S + * @api + */ +ol.geom.LineString.prototype.forEachSegment = function(callback, opt_this) { + return ol.geom.flat.segments.forEach(this.flatCoordinates, 0, + this.flatCoordinates.length, this.stride, callback, opt_this); +}; + + +/** + * Returns the coordinate at `m` using linear interpolation, or `null` if no + * such coordinate exists. + * + * `opt_extrapolate` controls extrapolation beyond the range of Ms in the + * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first + * M will return the first coordinate and Ms greater than the last M will + * return the last coordinate. + * + * @param {number} m M. + * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`. + * @return {ol.Coordinate} Coordinate. + * @api stable + */ +ol.geom.LineString.prototype.getCoordinateAtM = function(m, opt_extrapolate) { + if (this.layout != ol.geom.GeometryLayout.XYM && + this.layout != ol.geom.GeometryLayout.XYZM) { + return null; + } + var extrapolate = opt_extrapolate !== undefined ? opt_extrapolate : false; + return ol.geom.flat.interpolate.lineStringCoordinateAtM(this.flatCoordinates, 0, + this.flatCoordinates.length, this.stride, m, extrapolate); +}; + + +/** + * Return the coordinates of the linestring. + * @return {Array.<ol.Coordinate>} Coordinates. + * @api stable + */ +ol.geom.LineString.prototype.getCoordinates = function() { + return ol.geom.flat.inflate.coordinates( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; + + +/** + * Return the coordinate at the provided fraction along the linestring. + * The `fraction` is a number between 0 and 1, where 0 is the start of the + * linestring and 1 is the end. + * @param {number} fraction Fraction. + * @param {ol.Coordinate=} opt_dest Optional coordinate whose values will + * be modified. If not provided, a new coordinate will be returned. + * @return {ol.Coordinate} Coordinate of the interpolated point. + * @api + */ +ol.geom.LineString.prototype.getCoordinateAt = function(fraction, opt_dest) { + return ol.geom.flat.interpolate.lineString( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + fraction, opt_dest); +}; + + +/** + * Return the length of the linestring on projected plane. + * @return {number} Length (on projected plane). + * @api stable + */ +ol.geom.LineString.prototype.getLength = function() { + return ol.geom.flat.length.lineString( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; + + +/** + * @return {Array.<number>} Flat midpoint. + */ +ol.geom.LineString.prototype.getFlatMidpoint = function() { + if (this.flatMidpointRevision_ != this.getRevision()) { + this.flatMidpoint_ = this.getCoordinateAt(0.5, this.flatMidpoint_); + this.flatMidpointRevision_ = this.getRevision(); + } + return this.flatMidpoint_; +}; + + +/** + * @inheritDoc + */ +ol.geom.LineString.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + squaredTolerance, simplifiedFlatCoordinates, 0); + var simplifiedLineString = new ol.geom.LineString(null); + simplifiedLineString.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates); + return simplifiedLineString; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.LineString.prototype.getType = function() { + return ol.geom.GeometryType.LINE_STRING; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.LineString.prototype.intersectsExtent = function(extent) { + return ol.geom.flat.intersectsextent.lineString( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + extent); +}; + + +/** + * Set the coordinates of the linestring. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.LineString.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, coordinates, 1); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + this.flatCoordinates.length = ol.geom.flat.deflate.coordinates( + this.flatCoordinates, 0, coordinates, this.stride); + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + */ +ol.geom.LineString.prototype.setFlatCoordinates = function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; + +goog.provide('ol.geom.MultiLineString'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.interpolate'); +goog.require('ol.geom.flat.intersectsextent'); +goog.require('ol.geom.flat.simplify'); + + +/** + * @classdesc + * Multi-linestring geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.MultiLineString = function(coordinates, opt_layout) { + + ol.geom.SimpleGeometry.call(this); + + /** + * @type {Array.<number>} + * @private + */ + this.ends_ = []; + + /** + * @private + * @type {number} + */ + this.maxDelta_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDeltaRevision_ = -1; + + this.setCoordinates(coordinates, opt_layout); + +}; +ol.inherits(ol.geom.MultiLineString, ol.geom.SimpleGeometry); + + +/** + * Append the passed linestring to the multilinestring. + * @param {ol.geom.LineString} lineString LineString. + * @api stable + */ +ol.geom.MultiLineString.prototype.appendLineString = function(lineString) { + ol.DEBUG && console.assert(lineString.getLayout() == this.layout, + 'layout of lineString should match the layout'); + if (!this.flatCoordinates) { + this.flatCoordinates = lineString.getFlatCoordinates().slice(); + } else { + ol.array.extend( + this.flatCoordinates, lineString.getFlatCoordinates().slice()); + } + this.ends_.push(this.flatCoordinates.length); + this.changed(); +}; + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.MultiLineString} Clone. + * @api stable + */ +ol.geom.MultiLineString.prototype.clone = function() { + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setFlatCoordinates( + this.layout, this.flatCoordinates.slice(), this.ends_.slice()); + return multiLineString; +}; + + +/** + * @inheritDoc + */ +ol.geom.MultiLineString.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta( + this.flatCoordinates, 0, this.ends_, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getsClosestPoint( + this.flatCoordinates, 0, this.ends_, this.stride, + this.maxDelta_, false, x, y, closestPoint, minSquaredDistance); +}; + + +/** + * Returns the coordinate at `m` using linear interpolation, or `null` if no + * such coordinate exists. + * + * `opt_extrapolate` controls extrapolation beyond the range of Ms in the + * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first + * M will return the first coordinate and Ms greater than the last M will + * return the last coordinate. + * + * `opt_interpolate` controls interpolation between consecutive LineStrings + * within the MultiLineString. If `opt_interpolate` is `true` the coordinates + * will be linearly interpolated between the last coordinate of one LineString + * and the first coordinate of the next LineString. If `opt_interpolate` is + * `false` then the function will return `null` for Ms falling between + * LineStrings. + * + * @param {number} m M. + * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`. + * @param {boolean=} opt_interpolate Interpolate. Default is `false`. + * @return {ol.Coordinate} Coordinate. + * @api stable + */ +ol.geom.MultiLineString.prototype.getCoordinateAtM = function(m, opt_extrapolate, opt_interpolate) { + if ((this.layout != ol.geom.GeometryLayout.XYM && + this.layout != ol.geom.GeometryLayout.XYZM) || + this.flatCoordinates.length === 0) { + return null; + } + var extrapolate = opt_extrapolate !== undefined ? opt_extrapolate : false; + var interpolate = opt_interpolate !== undefined ? opt_interpolate : false; + return ol.geom.flat.interpolate.lineStringsCoordinateAtM(this.flatCoordinates, 0, + this.ends_, this.stride, m, extrapolate, interpolate); +}; + + +/** + * Return the coordinates of the multilinestring. + * @return {Array.<Array.<ol.Coordinate>>} Coordinates. + * @api stable + */ +ol.geom.MultiLineString.prototype.getCoordinates = function() { + return ol.geom.flat.inflate.coordinatess( + this.flatCoordinates, 0, this.ends_, this.stride); +}; + + +/** + * @return {Array.<number>} Ends. + */ +ol.geom.MultiLineString.prototype.getEnds = function() { + return this.ends_; +}; + + +/** + * Return the linestring at the specified index. + * @param {number} index Index. + * @return {ol.geom.LineString} LineString. + * @api stable + */ +ol.geom.MultiLineString.prototype.getLineString = function(index) { + ol.DEBUG && console.assert(0 <= index && index < this.ends_.length, + 'index should be in between 0 and length of the this.ends_ array'); + if (index < 0 || this.ends_.length <= index) { + return null; + } + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice( + index === 0 ? 0 : this.ends_[index - 1], this.ends_[index])); + return lineString; +}; + + +/** + * Return the linestrings of this multilinestring. + * @return {Array.<ol.geom.LineString>} LineStrings. + * @api stable + */ +ol.geom.MultiLineString.prototype.getLineStrings = function() { + var flatCoordinates = this.flatCoordinates; + var ends = this.ends_; + var layout = this.layout; + /** @type {Array.<ol.geom.LineString>} */ + var lineStrings = []; + var offset = 0; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(layout, flatCoordinates.slice(offset, end)); + lineStrings.push(lineString); + offset = end; + } + return lineStrings; +}; + + +/** + * @return {Array.<number>} Flat midpoints. + */ +ol.geom.MultiLineString.prototype.getFlatMidpoints = function() { + var midpoints = []; + var flatCoordinates = this.flatCoordinates; + var offset = 0; + var ends = this.ends_; + var stride = this.stride; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var midpoint = ol.geom.flat.interpolate.lineString( + flatCoordinates, offset, end, stride, 0.5); + ol.array.extend(midpoints, midpoint); + offset = end; + } + return midpoints; +}; + + +/** + * @inheritDoc + */ +ol.geom.MultiLineString.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + var simplifiedEnds = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeuckers( + this.flatCoordinates, 0, this.ends_, this.stride, squaredTolerance, + simplifiedFlatCoordinates, 0, simplifiedEnds); + var simplifiedMultiLineString = new ol.geom.MultiLineString(null); + simplifiedMultiLineString.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds); + return simplifiedMultiLineString; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.MultiLineString.prototype.getType = function() { + return ol.geom.GeometryType.MULTI_LINE_STRING; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.MultiLineString.prototype.intersectsExtent = function(extent) { + return ol.geom.flat.intersectsextent.lineStrings( + this.flatCoordinates, 0, this.ends_, this.stride, extent); +}; + + +/** + * Set the coordinates of the multilinestring. + * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.MultiLineString.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_); + } else { + this.setLayout(opt_layout, coordinates, 2); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + var ends = ol.geom.flat.deflate.coordinatess( + this.flatCoordinates, 0, coordinates, this.stride, this.ends_); + this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1]; + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<number>} ends Ends. + */ +ol.geom.MultiLineString.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) { + if (!flatCoordinates) { + ol.DEBUG && console.assert(ends && ends.length === 0, + 'ends must be truthy and ends.length should be 0'); + } else if (ends.length === 0) { + ol.DEBUG && console.assert(flatCoordinates.length === 0, + 'flatCoordinates should be an empty array'); + } else { + ol.DEBUG && console.assert(flatCoordinates.length == ends[ends.length - 1], + 'length of flatCoordinates array should match the last value of ends'); + } + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.ends_ = ends; + this.changed(); +}; + + +/** + * @param {Array.<ol.geom.LineString>} lineStrings LineStrings. + */ +ol.geom.MultiLineString.prototype.setLineStrings = function(lineStrings) { + var layout = this.getLayout(); + var flatCoordinates = []; + var ends = []; + var i, ii; + for (i = 0, ii = lineStrings.length; i < ii; ++i) { + var lineString = lineStrings[i]; + if (i === 0) { + layout = lineString.getLayout(); + } else { + // FIXME better handle the case of non-matching layouts + ol.DEBUG && console.assert(lineString.getLayout() == layout, + 'layout of lineString should match layout'); + } + ol.array.extend(flatCoordinates, lineString.getFlatCoordinates()); + ends.push(flatCoordinates.length); + } + this.setFlatCoordinates(layout, flatCoordinates, ends); +}; + +goog.provide('ol.geom.MultiPoint'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.math'); + + +/** + * @classdesc + * Multi-point geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.MultiPoint = function(coordinates, opt_layout) { + ol.geom.SimpleGeometry.call(this); + this.setCoordinates(coordinates, opt_layout); +}; +ol.inherits(ol.geom.MultiPoint, ol.geom.SimpleGeometry); + + +/** + * Append the passed point to this multipoint. + * @param {ol.geom.Point} point Point. + * @api stable + */ +ol.geom.MultiPoint.prototype.appendPoint = function(point) { + ol.DEBUG && console.assert(point.getLayout() == this.layout, + 'the layout of point should match layout'); + if (!this.flatCoordinates) { + this.flatCoordinates = point.getFlatCoordinates().slice(); + } else { + ol.array.extend(this.flatCoordinates, point.getFlatCoordinates()); + } + this.changed(); +}; + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.MultiPoint} Clone. + * @api stable + */ +ol.geom.MultiPoint.prototype.clone = function() { + var multiPoint = new ol.geom.MultiPoint(null); + multiPoint.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return multiPoint; +}; + + +/** + * @inheritDoc + */ +ol.geom.MultiPoint.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + var flatCoordinates = this.flatCoordinates; + var stride = this.stride; + var i, ii, j; + for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { + var squaredDistance = ol.math.squaredDistance( + x, y, flatCoordinates[i], flatCoordinates[i + 1]); + if (squaredDistance < minSquaredDistance) { + minSquaredDistance = squaredDistance; + for (j = 0; j < stride; ++j) { + closestPoint[j] = flatCoordinates[i + j]; + } + closestPoint.length = stride; + } + } + return minSquaredDistance; +}; + + +/** + * Return the coordinates of the multipoint. + * @return {Array.<ol.Coordinate>} Coordinates. + * @api stable + */ +ol.geom.MultiPoint.prototype.getCoordinates = function() { + return ol.geom.flat.inflate.coordinates( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; + + +/** + * Return the point at the specified index. + * @param {number} index Index. + * @return {ol.geom.Point} Point. + * @api stable + */ +ol.geom.MultiPoint.prototype.getPoint = function(index) { + var n = !this.flatCoordinates ? + 0 : this.flatCoordinates.length / this.stride; + ol.DEBUG && console.assert(0 <= index && index < n, + 'index should be in between 0 and n'); + if (index < 0 || n <= index) { + return null; + } + var point = new ol.geom.Point(null); + point.setFlatCoordinates(this.layout, this.flatCoordinates.slice( + index * this.stride, (index + 1) * this.stride)); + return point; +}; + + +/** + * Return the points of this multipoint. + * @return {Array.<ol.geom.Point>} Points. + * @api stable + */ +ol.geom.MultiPoint.prototype.getPoints = function() { + var flatCoordinates = this.flatCoordinates; + var layout = this.layout; + var stride = this.stride; + /** @type {Array.<ol.geom.Point>} */ + var points = []; + var i, ii; + for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { + var point = new ol.geom.Point(null); + point.setFlatCoordinates(layout, flatCoordinates.slice(i, i + stride)); + points.push(point); + } + return points; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.MultiPoint.prototype.getType = function() { + return ol.geom.GeometryType.MULTI_POINT; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.MultiPoint.prototype.intersectsExtent = function(extent) { + var flatCoordinates = this.flatCoordinates; + var stride = this.stride; + var i, ii, x, y; + for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { + x = flatCoordinates[i]; + y = flatCoordinates[i + 1]; + if (ol.extent.containsXY(extent, x, y)) { + return true; + } + } + return false; +}; + + +/** + * Set the coordinates of the multipoint. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.MultiPoint.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, coordinates, 1); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + this.flatCoordinates.length = ol.geom.flat.deflate.coordinates( + this.flatCoordinates, 0, coordinates, this.stride); + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + */ +ol.geom.MultiPoint.prototype.setFlatCoordinates = function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; + +goog.provide('ol.geom.flat.center'); + +goog.require('ol.extent'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @return {Array.<number>} Flat centers. + */ +ol.geom.flat.center.linearRingss = function(flatCoordinates, offset, endss, stride) { + var flatCenters = []; + var i, ii; + var extent = ol.extent.createEmpty(); + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + extent = ol.extent.createOrUpdateFromFlatCoordinates( + flatCoordinates, offset, ends[0], stride); + flatCenters.push((extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2); + offset = ends[ends.length - 1]; + } + return flatCenters; +}; + +goog.provide('ol.geom.MultiPolygon'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.area'); +goog.require('ol.geom.flat.center'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.contains'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.interiorpoint'); +goog.require('ol.geom.flat.intersectsextent'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.simplify'); + + +/** + * @classdesc + * Multi-polygon geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.MultiPolygon = function(coordinates, opt_layout) { + + ol.geom.SimpleGeometry.call(this); + + /** + * @type {Array.<Array.<number>>} + * @private + */ + this.endss_ = []; + + /** + * @private + * @type {number} + */ + this.flatInteriorPointsRevision_ = -1; + + /** + * @private + * @type {Array.<number>} + */ + this.flatInteriorPoints_ = null; + + /** + * @private + * @type {number} + */ + this.maxDelta_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDeltaRevision_ = -1; + + /** + * @private + * @type {number} + */ + this.orientedRevision_ = -1; + + /** + * @private + * @type {Array.<number>} + */ + this.orientedFlatCoordinates_ = null; + + this.setCoordinates(coordinates, opt_layout); + +}; +ol.inherits(ol.geom.MultiPolygon, ol.geom.SimpleGeometry); + + +/** + * Append the passed polygon to this multipolygon. + * @param {ol.geom.Polygon} polygon Polygon. + * @api stable + */ +ol.geom.MultiPolygon.prototype.appendPolygon = function(polygon) { + ol.DEBUG && console.assert(polygon.getLayout() == this.layout, + 'layout of polygon should match layout'); + /** @type {Array.<number>} */ + var ends; + if (!this.flatCoordinates) { + this.flatCoordinates = polygon.getFlatCoordinates().slice(); + ends = polygon.getEnds().slice(); + this.endss_.push(); + } else { + var offset = this.flatCoordinates.length; + ol.array.extend(this.flatCoordinates, polygon.getFlatCoordinates()); + ends = polygon.getEnds().slice(); + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + ends[i] += offset; + } + } + this.endss_.push(ends); + this.changed(); +}; + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.MultiPolygon} Clone. + * @api stable + */ +ol.geom.MultiPolygon.prototype.clone = function() { + var multiPolygon = new ol.geom.MultiPolygon(null); + + var len = this.endss_.length; + var newEndss = new Array(len); + for (var i = 0; i < len; ++i) { + newEndss[i] = this.endss_[i].slice(); + } + + multiPolygon.setFlatCoordinates( + this.layout, this.flatCoordinates.slice(), newEndss); + return multiPolygon; +}; + + +/** + * @inheritDoc + */ +ol.geom.MultiPolygon.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getssMaxSquaredDelta( + this.flatCoordinates, 0, this.endss_, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getssClosestPoint( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, + this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); +}; + + +/** + * @inheritDoc + */ +ol.geom.MultiPolygon.prototype.containsXY = function(x, y) { + return ol.geom.flat.contains.linearRingssContainsXY( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, x, y); +}; + + +/** + * Return the area of the multipolygon on projected plane. + * @return {number} Area (on projected plane). + * @api stable + */ +ol.geom.MultiPolygon.prototype.getArea = function() { + return ol.geom.flat.area.linearRingss( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride); +}; + + +/** + * Get the coordinate array for this geometry. This array has the structure + * of a GeoJSON coordinate array for multi-polygons. + * + * @param {boolean=} opt_right Orient coordinates according to the right-hand + * rule (counter-clockwise for exterior and clockwise for interior rings). + * If `false`, coordinates will be oriented according to the left-hand rule + * (clockwise for exterior and counter-clockwise for interior rings). + * By default, coordinate orientation will depend on how the geometry was + * constructed. + * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinates. + * @api stable + */ +ol.geom.MultiPolygon.prototype.getCoordinates = function(opt_right) { + var flatCoordinates; + if (opt_right !== undefined) { + flatCoordinates = this.getOrientedFlatCoordinates().slice(); + ol.geom.flat.orient.orientLinearRingss( + flatCoordinates, 0, this.endss_, this.stride, opt_right); + } else { + flatCoordinates = this.flatCoordinates; + } + + return ol.geom.flat.inflate.coordinatesss( + flatCoordinates, 0, this.endss_, this.stride); +}; + + +/** + * @return {Array.<Array.<number>>} Endss. + */ +ol.geom.MultiPolygon.prototype.getEndss = function() { + return this.endss_; +}; + + +/** + * @return {Array.<number>} Flat interior points. + */ +ol.geom.MultiPolygon.prototype.getFlatInteriorPoints = function() { + if (this.flatInteriorPointsRevision_ != this.getRevision()) { + var flatCenters = ol.geom.flat.center.linearRingss( + this.flatCoordinates, 0, this.endss_, this.stride); + this.flatInteriorPoints_ = ol.geom.flat.interiorpoint.linearRingss( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, + flatCenters); + this.flatInteriorPointsRevision_ = this.getRevision(); + } + return this.flatInteriorPoints_; +}; + + +/** + * Return the interior points as {@link ol.geom.MultiPoint multipoint}. + * @return {ol.geom.MultiPoint} Interior points. + * @api stable + */ +ol.geom.MultiPolygon.prototype.getInteriorPoints = function() { + var interiorPoints = new ol.geom.MultiPoint(null); + interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XY, + this.getFlatInteriorPoints().slice()); + return interiorPoints; +}; + + +/** + * @return {Array.<number>} Oriented flat coordinates. + */ +ol.geom.MultiPolygon.prototype.getOrientedFlatCoordinates = function() { + if (this.orientedRevision_ != this.getRevision()) { + var flatCoordinates = this.flatCoordinates; + if (ol.geom.flat.orient.linearRingssAreOriented( + flatCoordinates, 0, this.endss_, this.stride)) { + this.orientedFlatCoordinates_ = flatCoordinates; + } else { + this.orientedFlatCoordinates_ = flatCoordinates.slice(); + this.orientedFlatCoordinates_.length = + ol.geom.flat.orient.orientLinearRingss( + this.orientedFlatCoordinates_, 0, this.endss_, this.stride); + } + this.orientedRevision_ = this.getRevision(); + } + return this.orientedFlatCoordinates_; +}; + + +/** + * @inheritDoc + */ +ol.geom.MultiPolygon.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + var simplifiedEndss = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizess( + this.flatCoordinates, 0, this.endss_, this.stride, + Math.sqrt(squaredTolerance), + simplifiedFlatCoordinates, 0, simplifiedEndss); + var simplifiedMultiPolygon = new ol.geom.MultiPolygon(null); + simplifiedMultiPolygon.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEndss); + return simplifiedMultiPolygon; +}; + + +/** + * Return the polygon at the specified index. + * @param {number} index Index. + * @return {ol.geom.Polygon} Polygon. + * @api stable + */ +ol.geom.MultiPolygon.prototype.getPolygon = function(index) { + ol.DEBUG && console.assert(0 <= index && index < this.endss_.length, + 'index should be in between 0 and the length of this.endss_'); + if (index < 0 || this.endss_.length <= index) { + return null; + } + var offset; + if (index === 0) { + offset = 0; + } else { + var prevEnds = this.endss_[index - 1]; + offset = prevEnds[prevEnds.length - 1]; + } + var ends = this.endss_[index].slice(); + var end = ends[ends.length - 1]; + if (offset !== 0) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + ends[i] -= offset; + } + } + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + this.layout, this.flatCoordinates.slice(offset, end), ends); + return polygon; +}; + + +/** + * Return the polygons of this multipolygon. + * @return {Array.<ol.geom.Polygon>} Polygons. + * @api stable + */ +ol.geom.MultiPolygon.prototype.getPolygons = function() { + var layout = this.layout; + var flatCoordinates = this.flatCoordinates; + var endss = this.endss_; + var polygons = []; + var offset = 0; + var i, ii, j, jj; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i].slice(); + var end = ends[ends.length - 1]; + if (offset !== 0) { + for (j = 0, jj = ends.length; j < jj; ++j) { + ends[j] -= offset; + } + } + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + layout, flatCoordinates.slice(offset, end), ends); + polygons.push(polygon); + offset = end; + } + return polygons; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.MultiPolygon.prototype.getType = function() { + return ol.geom.GeometryType.MULTI_POLYGON; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.MultiPolygon.prototype.intersectsExtent = function(extent) { + return ol.geom.flat.intersectsextent.linearRingss( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, extent); +}; + + +/** + * Set the coordinates of the multipolygon. + * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.MultiPolygon.prototype.setCoordinates = function(coordinates, opt_layout) { + if (!coordinates) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.endss_); + } else { + this.setLayout(opt_layout, coordinates, 3); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + var endss = ol.geom.flat.deflate.coordinatesss( + this.flatCoordinates, 0, coordinates, this.stride, this.endss_); + if (endss.length === 0) { + this.flatCoordinates.length = 0; + } else { + var lastEnds = endss[endss.length - 1]; + this.flatCoordinates.length = lastEnds.length === 0 ? + 0 : lastEnds[lastEnds.length - 1]; + } + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<Array.<number>>} endss Endss. + */ +ol.geom.MultiPolygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, endss) { + ol.DEBUG && console.assert(endss, 'endss must be truthy'); + if (!flatCoordinates || flatCoordinates.length === 0) { + ol.DEBUG && console.assert(endss.length === 0, 'the length of endss should be 0'); + } else { + ol.DEBUG && console.assert(endss.length > 0, 'endss cannot be an empty array'); + var ends = endss[endss.length - 1]; + ol.DEBUG && console.assert(flatCoordinates.length == ends[ends.length - 1], + 'the length of flatCoordinates should be the last value of ends'); + } + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.endss_ = endss; + this.changed(); +}; + + +/** + * @param {Array.<ol.geom.Polygon>} polygons Polygons. + */ +ol.geom.MultiPolygon.prototype.setPolygons = function(polygons) { + var layout = this.getLayout(); + var flatCoordinates = []; + var endss = []; + var i, ii, ends; + for (i = 0, ii = polygons.length; i < ii; ++i) { + var polygon = polygons[i]; + if (i === 0) { + layout = polygon.getLayout(); + } else { + // FIXME better handle the case of non-matching layouts + ol.DEBUG && console.assert(polygon.getLayout() == layout, + 'layout of polygon should be layout'); + } + var offset = flatCoordinates.length; + ends = polygon.getEnds(); + var j, jj; + for (j = 0, jj = ends.length; j < jj; ++j) { + ends[j] += offset; + } + ol.array.extend(flatCoordinates, polygon.getFlatCoordinates()); + endss.push(ends); + } + this.setFlatCoordinates(layout, flatCoordinates, endss); +}; + +goog.provide('ol.format.EsriJSON'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.array'); +goog.require('ol.asserts'); +goog.require('ol.extent'); +goog.require('ol.format.Feature'); +goog.require('ol.format.JSONFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.obj'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Feature format for reading and writing data in the EsriJSON format. + * + * @constructor + * @extends {ol.format.JSONFeature} + * @param {olx.format.EsriJSONOptions=} opt_options Options. + * @api + */ +ol.format.EsriJSON = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.JSONFeature.call(this); + + /** + * Name of the geometry attribute for features. + * @type {string|undefined} + * @private + */ + this.geometryName_ = options.geometryName; + +}; +ol.inherits(ol.format.EsriJSON, ol.format.JSONFeature); + + +/** + * @param {EsriJSONGeometry} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @private + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.EsriJSON.readGeometry_ = function(object, opt_options) { + if (!object) { + return null; + } + /** @type {ol.geom.GeometryType} */ + var type; + if (typeof object.x === 'number' && typeof object.y === 'number') { + type = ol.geom.GeometryType.POINT; + } else if (object.points) { + type = ol.geom.GeometryType.MULTI_POINT; + } else if (object.paths) { + if (object.paths.length === 1) { + type = ol.geom.GeometryType.LINE_STRING; + } else { + type = ol.geom.GeometryType.MULTI_LINE_STRING; + } + } else if (object.rings) { + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + var rings = ol.format.EsriJSON.convertRings_(object.rings, layout); + object = /** @type {EsriJSONGeometry} */(ol.obj.assign({}, object)); + if (rings.length === 1) { + type = ol.geom.GeometryType.POLYGON; + object.rings = rings[0]; + } else { + type = ol.geom.GeometryType.MULTI_POLYGON; + object.rings = rings; + } + } + var geometryReader = ol.format.EsriJSON.GEOMETRY_READERS_[type]; + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions( + geometryReader(object), false, opt_options)); +}; + + +/** + * Determines inner and outer rings. + * Checks if any polygons in this array contain any other polygons in this + * array. It is used for checking for holes. + * Logic inspired by: https://github.com/Esri/terraformer-arcgis-parser + * @param {Array.<!Array.<!Array.<number>>>} rings Rings. + * @param {ol.geom.GeometryLayout} layout Geometry layout. + * @private + * @return {Array.<!Array.<!Array.<number>>>} Transoformed rings. + */ +ol.format.EsriJSON.convertRings_ = function(rings, layout) { + var outerRings = []; + var holes = []; + var i, ii; + for (i = 0, ii = rings.length; i < ii; ++i) { + var flatRing = ol.array.flatten(rings[i]); + // is this ring an outer ring? is it clockwise? + var clockwise = ol.geom.flat.orient.linearRingIsClockwise(flatRing, 0, + flatRing.length, layout.length); + if (clockwise) { + outerRings.push([rings[i]]); + } else { + holes.push(rings[i]); + } + } + while (holes.length) { + var hole = holes.shift(); + var matched = false; + // loop over all outer rings and see if they contain our hole. + for (i = outerRings.length - 1; i >= 0; i--) { + var outerRing = outerRings[i][0]; + if (ol.extent.containsExtent(new ol.geom.LinearRing( + outerRing).getExtent(), + new ol.geom.LinearRing(hole).getExtent())) { + // the hole is contained push it into our polygon + outerRings[i].push(hole); + matched = true; + break; + } + } + if (!matched) { + // no outer rings contain this hole turn it into and outer + // ring (reverse it) + outerRings.push([hole.reverse()]); + } + } + return outerRings; +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} Point. + */ +ol.format.EsriJSON.readPointGeometry_ = function(object) { + ol.DEBUG && console.assert(typeof object.x === 'number', 'object.x should be number'); + ol.DEBUG && console.assert(typeof object.y === 'number', 'object.y should be number'); + var point; + if (object.m !== undefined && object.z !== undefined) { + point = new ol.geom.Point([object.x, object.y, object.z, object.m], + ol.geom.GeometryLayout.XYZM); + } else if (object.z !== undefined) { + point = new ol.geom.Point([object.x, object.y, object.z], + ol.geom.GeometryLayout.XYZ); + } else if (object.m !== undefined) { + point = new ol.geom.Point([object.x, object.y, object.m], + ol.geom.GeometryLayout.XYM); + } else { + point = new ol.geom.Point([object.x, object.y]); + } + return point; +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} LineString. + */ +ol.format.EsriJSON.readLineStringGeometry_ = function(object) { + ol.DEBUG && console.assert(Array.isArray(object.paths), + 'object.paths should be an array'); + ol.DEBUG && console.assert(object.paths.length === 1, + 'object.paths array length should be 1'); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.LineString(object.paths[0], layout); +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} MultiLineString. + */ +ol.format.EsriJSON.readMultiLineStringGeometry_ = function(object) { + ol.DEBUG && console.assert(Array.isArray(object.paths), + 'object.paths should be an array'); + ol.DEBUG && console.assert(object.paths.length > 1, + 'object.paths array length should be more than 1'); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.MultiLineString(object.paths, layout); +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.GeometryLayout} The geometry layout to use. + */ +ol.format.EsriJSON.getGeometryLayout_ = function(object) { + var layout = ol.geom.GeometryLayout.XY; + if (object.hasZ === true && object.hasM === true) { + layout = ol.geom.GeometryLayout.XYZM; + } else if (object.hasZ === true) { + layout = ol.geom.GeometryLayout.XYZ; + } else if (object.hasM === true) { + layout = ol.geom.GeometryLayout.XYM; + } + return layout; +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} MultiPoint. + */ +ol.format.EsriJSON.readMultiPointGeometry_ = function(object) { + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.MultiPoint(object.points, layout); +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} MultiPolygon. + */ +ol.format.EsriJSON.readMultiPolygonGeometry_ = function(object) { + ol.DEBUG && console.assert(object.rings.length > 1, + 'object.rings should have length larger than 1'); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.MultiPolygon( + /** @type {Array.<Array.<Array.<Array.<number>>>>} */(object.rings), + layout); +}; + + +/** + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} Polygon. + */ +ol.format.EsriJSON.readPolygonGeometry_ = function(object) { + ol.DEBUG && console.assert(object.rings); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.Polygon(object.rings, layout); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONGeometry} EsriJSON geometry. + */ +ol.format.EsriJSON.writePointGeometry_ = function(geometry, opt_options) { + var coordinates = /** @type {ol.geom.Point} */ (geometry).getCoordinates(); + var esriJSON; + var layout = /** @type {ol.geom.Point} */ (geometry).getLayout(); + if (layout === ol.geom.GeometryLayout.XYZ) { + esriJSON = /** @type {EsriJSONPoint} */ ({ + x: coordinates[0], + y: coordinates[1], + z: coordinates[2] + }); + } else if (layout === ol.geom.GeometryLayout.XYM) { + esriJSON = /** @type {EsriJSONPoint} */ ({ + x: coordinates[0], + y: coordinates[1], + m: coordinates[2] + }); + } else if (layout === ol.geom.GeometryLayout.XYZM) { + esriJSON = /** @type {EsriJSONPoint} */ ({ + x: coordinates[0], + y: coordinates[1], + z: coordinates[2], + m: coordinates[3] + }); + } else if (layout === ol.geom.GeometryLayout.XY) { + esriJSON = /** @type {EsriJSONPoint} */ ({ + x: coordinates[0], + y: coordinates[1] + }); + } else { + ol.asserts.assert(false, 34); // Invalid geometry layout + } + return /** @type {EsriJSONGeometry} */ (esriJSON); +}; + + +/** + * @param {ol.geom.SimpleGeometry} geometry Geometry. + * @private + * @return {Object} Object with boolean hasZ and hasM keys. + */ +ol.format.EsriJSON.getHasZM_ = function(geometry) { + var layout = geometry.getLayout(); + return { + hasZ: (layout === ol.geom.GeometryLayout.XYZ || + layout === ol.geom.GeometryLayout.XYZM), + hasM: (layout === ol.geom.GeometryLayout.XYM || + layout === ol.geom.GeometryLayout.XYZM) + }; +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONPolyline} EsriJSON geometry. + */ +ol.format.EsriJSON.writeLineStringGeometry_ = function(geometry, opt_options) { + var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.LineString} */ (geometry)); + return /** @type {EsriJSONPolyline} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + paths: [ + /** @type {ol.geom.LineString} */ (geometry).getCoordinates() + ] + }); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONPolygon} EsriJSON geometry. + */ +ol.format.EsriJSON.writePolygonGeometry_ = function(geometry, opt_options) { + // Esri geometries use the left-hand rule + var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.Polygon} */ (geometry)); + return /** @type {EsriJSONPolygon} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + rings: /** @type {ol.geom.Polygon} */ (geometry).getCoordinates(false) + }); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONPolyline} EsriJSON geometry. + */ +ol.format.EsriJSON.writeMultiLineStringGeometry_ = function(geometry, opt_options) { + var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiLineString} */ (geometry)); + return /** @type {EsriJSONPolyline} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + paths: /** @type {ol.geom.MultiLineString} */ (geometry).getCoordinates() + }); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONMultipoint} EsriJSON geometry. + */ +ol.format.EsriJSON.writeMultiPointGeometry_ = function(geometry, opt_options) { + var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPoint} */ (geometry)); + return /** @type {EsriJSONMultipoint} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + points: /** @type {ol.geom.MultiPoint} */ (geometry).getCoordinates() + }); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONPolygon} EsriJSON geometry. + */ +ol.format.EsriJSON.writeMultiPolygonGeometry_ = function(geometry, + opt_options) { + var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPolygon} */ (geometry)); + var coordinates = /** @type {ol.geom.MultiPolygon} */ (geometry).getCoordinates(false); + var output = []; + for (var i = 0; i < coordinates.length; i++) { + for (var x = coordinates[i].length - 1; x >= 0; x--) { + output.push(coordinates[i][x]); + } + } + return /** @type {EsriJSONPolygon} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + rings: output + }); +}; + + +/** + * @const + * @private + * @type {Object.<ol.geom.GeometryType, function(EsriJSONGeometry): ol.geom.Geometry>} + */ +ol.format.EsriJSON.GEOMETRY_READERS_ = {}; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.POINT] = + ol.format.EsriJSON.readPointGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.LINE_STRING] = + ol.format.EsriJSON.readLineStringGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.POLYGON] = + ol.format.EsriJSON.readPolygonGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POINT] = + ol.format.EsriJSON.readMultiPointGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_LINE_STRING] = + ol.format.EsriJSON.readMultiLineStringGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POLYGON] = + ol.format.EsriJSON.readMultiPolygonGeometry_; + + +/** + * @const + * @private + * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (EsriJSONGeometry)>} + */ +ol.format.EsriJSON.GEOMETRY_WRITERS_ = {}; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.POINT] = + ol.format.EsriJSON.writePointGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.LINE_STRING] = + ol.format.EsriJSON.writeLineStringGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.POLYGON] = + ol.format.EsriJSON.writePolygonGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_POINT] = + ol.format.EsriJSON.writeMultiPointGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_LINE_STRING] = + ol.format.EsriJSON.writeMultiLineStringGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_POLYGON] = + ol.format.EsriJSON.writeMultiPolygonGeometry_; + + +/** + * Read a feature from a EsriJSON Feature source. Only works for Feature, + * use `readFeatures` to read FeatureCollection source. + * + * @function + * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api + */ +ol.format.EsriJSON.prototype.readFeature; + + +/** + * Read all features from a EsriJSON source. Works with both Feature and + * FeatureCollection sources. + * + * @function + * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api + */ +ol.format.EsriJSON.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.EsriJSON.prototype.readFeatureFromObject = function( + object, opt_options) { + var esriJSONFeature = /** @type {EsriJSONFeature} */ (object); + ol.DEBUG && console.assert(esriJSONFeature.geometry || + esriJSONFeature.attributes, + 'geometry or attributes should be defined'); + var geometry = ol.format.EsriJSON.readGeometry_(esriJSONFeature.geometry, + opt_options); + var feature = new ol.Feature(); + if (this.geometryName_) { + feature.setGeometryName(this.geometryName_); + } + feature.setGeometry(geometry); + if (opt_options && opt_options.idField && + esriJSONFeature.attributes[opt_options.idField]) { + ol.DEBUG && console.assert( + typeof esriJSONFeature.attributes[opt_options.idField] === 'number', + 'objectIdFieldName value should be a number'); + feature.setId(/** @type {number} */( + esriJSONFeature.attributes[opt_options.idField])); + } + if (esriJSONFeature.attributes) { + feature.setProperties(esriJSONFeature.attributes); + } + return feature; +}; + + +/** + * @inheritDoc + */ +ol.format.EsriJSON.prototype.readFeaturesFromObject = function( + object, opt_options) { + var esriJSONObject = /** @type {EsriJSONObject} */ (object); + var options = opt_options ? opt_options : {}; + if (esriJSONObject.features) { + var esriJSONFeatureCollection = /** @type {EsriJSONFeatureCollection} */ + (object); + /** @type {Array.<ol.Feature>} */ + var features = []; + var esriJSONFeatures = esriJSONFeatureCollection.features; + var i, ii; + options.idField = object.objectIdFieldName; + for (i = 0, ii = esriJSONFeatures.length; i < ii; ++i) { + features.push(this.readFeatureFromObject(esriJSONFeatures[i], + options)); + } + return features; + } else { + return [this.readFeatureFromObject(object, options)]; + } +}; + + +/** + * Read a geometry from a EsriJSON source. + * + * @function + * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. + * @api + */ +ol.format.EsriJSON.prototype.readGeometry; + + +/** + * @inheritDoc + */ +ol.format.EsriJSON.prototype.readGeometryFromObject = function( + object, opt_options) { + return ol.format.EsriJSON.readGeometry_( + /** @type {EsriJSONGeometry} */ (object), opt_options); +}; + + +/** + * Read the projection from a EsriJSON source. + * + * @function + * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api + */ +ol.format.EsriJSON.prototype.readProjection; + + +/** + * @inheritDoc + */ +ol.format.EsriJSON.prototype.readProjectionFromObject = function(object) { + var esriJSONObject = /** @type {EsriJSONObject} */ (object); + if (esriJSONObject.spatialReference && esriJSONObject.spatialReference.wkid) { + var crs = esriJSONObject.spatialReference.wkid; + return ol.proj.get('EPSG:' + crs); + } else { + return null; + } +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONGeometry} EsriJSON geometry. + */ +ol.format.EsriJSON.writeGeometry_ = function(geometry, opt_options) { + var geometryWriter = ol.format.EsriJSON.GEOMETRY_WRITERS_[geometry.getType()]; + return geometryWriter(/** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, true, opt_options)), + opt_options); +}; + + +/** + * Encode a geometry as a EsriJSON string. + * + * @function + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} EsriJSON. + * @api + */ +ol.format.EsriJSON.prototype.writeGeometry; + + +/** + * Encode a geometry as a EsriJSON object. + * + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {EsriJSONGeometry} Object. + * @api + */ +ol.format.EsriJSON.prototype.writeGeometryObject = function(geometry, + opt_options) { + return ol.format.EsriJSON.writeGeometry_(geometry, + this.adaptOptions(opt_options)); +}; + + +/** + * Encode a feature as a EsriJSON Feature string. + * + * @function + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} EsriJSON. + * @api + */ +ol.format.EsriJSON.prototype.writeFeature; + + +/** + * Encode a feature as a esriJSON Feature object. + * + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. + * @api + */ +ol.format.EsriJSON.prototype.writeFeatureObject = function( + feature, opt_options) { + opt_options = this.adaptOptions(opt_options); + var object = {}; + var geometry = feature.getGeometry(); + if (geometry) { + object['geometry'] = + ol.format.EsriJSON.writeGeometry_(geometry, opt_options); + } + var properties = feature.getProperties(); + delete properties[feature.getGeometryName()]; + if (!ol.obj.isEmpty(properties)) { + object['attributes'] = properties; + } else { + object['attributes'] = {}; + } + if (opt_options && opt_options.featureProjection) { + object['spatialReference'] = /** @type {EsriJSONCRS} */({ + wkid: ol.proj.get( + opt_options.featureProjection).getCode().split(':').pop() + }); + } + return object; +}; + + +/** + * Encode an array of features as EsriJSON. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} EsriJSON. + * @api + */ +ol.format.EsriJSON.prototype.writeFeatures; + + +/** + * Encode an array of features as a EsriJSON object. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} EsriJSON Object. + * @api + */ +ol.format.EsriJSON.prototype.writeFeaturesObject = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + var objects = []; + var i, ii; + for (i = 0, ii = features.length; i < ii; ++i) { + objects.push(this.writeFeatureObject(features[i], opt_options)); + } + return /** @type {EsriJSONFeatureCollection} */ ({ + 'features': objects + }); +}; + +goog.provide('ol.format.filter.Filter'); + +goog.require('ol'); + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @struct + * @api + */ +ol.format.filter.Filter = function(tagName) { + + /** + * @private + * @type {!string} + */ + this.tagName_ = tagName; +}; + +/** + * The XML tag name for a filter. + * @returns {!string} Name. + */ +ol.format.filter.Filter.prototype.getTagName = function() { + return this.tagName_; +}; + +goog.provide('ol.format.filter.Logical'); + +goog.require('ol'); +goog.require('ol.format.filter.Filter'); + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature logical filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @extends {ol.format.filter.Filter} + */ +ol.format.filter.Logical = function(tagName) { + ol.format.filter.Filter.call(this, tagName); +}; +ol.inherits(ol.format.filter.Logical, ol.format.filter.Filter); + +goog.provide('ol.format.filter.LogicalBinary'); + +goog.require('ol'); +goog.require('ol.format.filter.Logical'); + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature binary logical filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @param {!ol.format.filter.Filter} conditionA First filter condition. + * @param {!ol.format.filter.Filter} conditionB Second filter condition. + * @extends {ol.format.filter.Logical} + */ +ol.format.filter.LogicalBinary = function(tagName, conditionA, conditionB) { + + ol.format.filter.Logical.call(this, tagName); + + /** + * @public + * @type {!ol.format.filter.Filter} + */ + this.conditionA = conditionA; + + /** + * @public + * @type {!ol.format.filter.Filter} + */ + this.conditionB = conditionB; + +}; +ol.inherits(ol.format.filter.LogicalBinary, ol.format.filter.Logical); + +goog.provide('ol.format.filter.And'); + +goog.require('ol'); +goog.require('ol.format.filter.LogicalBinary'); + +/** + * @classdesc + * Represents a logical `<And>` operator between two filter conditions. + * + * @constructor + * @param {!ol.format.filter.Filter} conditionA First filter condition. + * @param {!ol.format.filter.Filter} conditionB Second filter condition. + * @extends {ol.format.filter.LogicalBinary} + * @api + */ +ol.format.filter.And = function(conditionA, conditionB) { + ol.format.filter.LogicalBinary.call(this, 'And', conditionA, conditionB); +}; +ol.inherits(ol.format.filter.And, ol.format.filter.LogicalBinary); + +goog.provide('ol.format.filter.Bbox'); + +goog.require('ol'); +goog.require('ol.format.filter.Filter'); + + +/** + * @classdesc + * Represents a `<BBOX>` operator to test whether a geometry-valued property + * intersects a fixed bounding box + * + * @constructor + * @param {!string} geometryName Geometry name to use. + * @param {!ol.Extent} extent Extent. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @extends {ol.format.filter.Filter} + * @api + */ +ol.format.filter.Bbox = function(geometryName, extent, opt_srsName) { + + ol.format.filter.Filter.call(this, 'BBOX'); + + /** + * @public + * @type {!string} + */ + this.geometryName = geometryName; + + /** + * @public + * @type {ol.Extent} + */ + this.extent = extent; + + /** + * @public + * @type {string|undefined} + */ + this.srsName = opt_srsName; +}; +ol.inherits(ol.format.filter.Bbox, ol.format.filter.Filter); + +goog.provide('ol.format.filter.Comparison'); + +goog.require('ol'); +goog.require('ol.format.filter.Filter'); + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature property comparison filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @param {!string} propertyName Name of the context property to compare. + * @extends {ol.format.filter.Filter} + * @api + */ +ol.format.filter.Comparison = function(tagName, propertyName) { + + ol.format.filter.Filter.call(this, tagName); + + /** + * @public + * @type {!string} + */ + this.propertyName = propertyName; +}; +ol.inherits(ol.format.filter.Comparison, ol.format.filter.Filter); + +goog.provide('ol.format.filter.ComparisonBinary'); + +goog.require('ol'); +goog.require('ol.format.filter.Comparison'); + + +/** + * @classdesc + * Abstract class; normally only used for creating subclasses and not instantiated in apps. + * Base class for WFS GetFeature property binary comparison filters. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @extends {ol.format.filter.Comparison} + * @api + */ +ol.format.filter.ComparisonBinary = function( + tagName, propertyName, expression, opt_matchCase) { + + ol.format.filter.Comparison.call(this, tagName, propertyName); + + /** + * @public + * @type {!(string|number)} + */ + this.expression = expression; + + /** + * @public + * @type {boolean|undefined} + */ + this.matchCase = opt_matchCase; +}; +ol.inherits(ol.format.filter.ComparisonBinary, ol.format.filter.Comparison); + +goog.provide('ol.format.filter.EqualTo'); + +goog.require('ol'); +goog.require('ol.format.filter.ComparisonBinary'); + + +/** + * @classdesc + * Represents a `<PropertyIsEqualTo>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @extends {ol.format.filter.ComparisonBinary} + * @api + */ +ol.format.filter.EqualTo = function(propertyName, expression, opt_matchCase) { + ol.format.filter.ComparisonBinary.call(this, 'PropertyIsEqualTo', propertyName, expression, opt_matchCase); +}; +ol.inherits(ol.format.filter.EqualTo, ol.format.filter.ComparisonBinary); + +goog.provide('ol.format.filter.GreaterThan'); + +goog.require('ol'); +goog.require('ol.format.filter.ComparisonBinary'); + + +/** + * @classdesc + * Represents a `<PropertyIsGreaterThan>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @extends {ol.format.filter.ComparisonBinary} + * @api + */ +ol.format.filter.GreaterThan = function(propertyName, expression) { + ol.format.filter.ComparisonBinary.call(this, 'PropertyIsGreaterThan', propertyName, expression); +}; +ol.inherits(ol.format.filter.GreaterThan, ol.format.filter.ComparisonBinary); + +goog.provide('ol.format.filter.GreaterThanOrEqualTo'); + +goog.require('ol'); +goog.require('ol.format.filter.ComparisonBinary'); + + +/** + * @classdesc + * Represents a `<PropertyIsGreaterThanOrEqualTo>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @extends {ol.format.filter.ComparisonBinary} + * @api + */ +ol.format.filter.GreaterThanOrEqualTo = function(propertyName, expression) { + ol.format.filter.ComparisonBinary.call(this, 'PropertyIsGreaterThanOrEqualTo', propertyName, expression); +}; +ol.inherits(ol.format.filter.GreaterThanOrEqualTo, ol.format.filter.ComparisonBinary); + +goog.provide('ol.format.filter.Spatial'); + +goog.require('ol'); +goog.require('ol.format.filter.Filter'); + + +/** + * @classdesc + * Represents a spatial operator to test whether a geometry-valued property + * relates to a given geometry. + * + * @constructor + * @param {!string} tagName The XML tag name for this filter. + * @param {!string} geometryName Geometry name to use. + * @param {!ol.geom.Geometry} geometry Geometry. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @extends {ol.format.filter.Filter} + * @api + */ +ol.format.filter.Spatial = function(tagName, geometryName, geometry, opt_srsName) { + + ol.format.filter.Filter.call(this, tagName); + + /** + * @public + * @type {!string} + */ + this.geometryName = geometryName || 'the_geom'; + + /** + * @public + * @type {ol.geom.Geometry} + */ + this.geometry = geometry; + + /** + * @public + * @type {string|undefined} + */ + this.srsName = opt_srsName; +}; +ol.inherits(ol.format.filter.Spatial, ol.format.filter.Filter); + +goog.provide('ol.format.filter.Intersects'); + +goog.require('ol'); +goog.require('ol.format.filter.Spatial'); + + +/** + * @classdesc + * Represents a `<Intersects>` operator to test whether a geometry-valued property + * intersects a given geometry. + * + * @constructor + * @param {!string} geometryName Geometry name to use. + * @param {!ol.geom.Geometry} geometry Geometry. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @extends {ol.format.filter.Spatial} + * @api + */ +ol.format.filter.Intersects = function(geometryName, geometry, opt_srsName) { + + ol.format.filter.Spatial.call(this, 'Intersects', geometryName, geometry, opt_srsName); + +}; +ol.inherits(ol.format.filter.Intersects, ol.format.filter.Spatial); + +goog.provide('ol.format.filter.IsBetween'); + +goog.require('ol'); +goog.require('ol.format.filter.Comparison'); + + +/** + * @classdesc + * Represents a `<PropertyIsBetween>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} lowerBoundary The lower bound of the range. + * @param {!number} upperBoundary The upper bound of the range. + * @extends {ol.format.filter.Comparison} + * @api + */ +ol.format.filter.IsBetween = function(propertyName, lowerBoundary, upperBoundary) { + ol.format.filter.Comparison.call(this, 'PropertyIsBetween', propertyName); + + /** + * @public + * @type {!number} + */ + this.lowerBoundary = lowerBoundary; + + /** + * @public + * @type {!number} + */ + this.upperBoundary = upperBoundary; +}; +ol.inherits(ol.format.filter.IsBetween, ol.format.filter.Comparison); + +goog.provide('ol.format.filter.IsLike'); + +goog.require('ol'); +goog.require('ol.format.filter.Comparison'); + + +/** + * @classdesc + * Represents a `<PropertyIsLike>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!string} pattern Text pattern. + * @param {string=} opt_wildCard Pattern character which matches any sequence of + * zero or more string characters. Default is '*'. + * @param {string=} opt_singleChar pattern character which matches any single + * string character. Default is '.'. + * @param {string=} opt_escapeChar Escape character which can be used to escape + * the pattern characters. Default is '!'. + * @param {boolean=} opt_matchCase Case-sensitive? + * @extends {ol.format.filter.Comparison} + * @api + */ +ol.format.filter.IsLike = function(propertyName, pattern, + opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase) { + ol.format.filter.Comparison.call(this, 'PropertyIsLike', propertyName); + + /** + * @public + * @type {!string} + */ + this.pattern = pattern; + + /** + * @public + * @type {!string} + */ + this.wildCard = (opt_wildCard !== undefined) ? opt_wildCard : '*'; + + /** + * @public + * @type {!string} + */ + this.singleChar = (opt_singleChar !== undefined) ? opt_singleChar : '.'; + + /** + * @public + * @type {!string} + */ + this.escapeChar = (opt_escapeChar !== undefined) ? opt_escapeChar : '!'; + + /** + * @public + * @type {boolean|undefined} + */ + this.matchCase = opt_matchCase; +}; +ol.inherits(ol.format.filter.IsLike, ol.format.filter.Comparison); + +goog.provide('ol.format.filter.IsNull'); + +goog.require('ol'); +goog.require('ol.format.filter.Comparison'); + + +/** + * @classdesc + * Represents a `<PropertyIsNull>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @extends {ol.format.filter.Comparison} + * @api + */ +ol.format.filter.IsNull = function(propertyName) { + ol.format.filter.Comparison.call(this, 'PropertyIsNull', propertyName); +}; +ol.inherits(ol.format.filter.IsNull, ol.format.filter.Comparison); + +goog.provide('ol.format.filter.LessThan'); + +goog.require('ol'); +goog.require('ol.format.filter.ComparisonBinary'); + + +/** + * @classdesc + * Represents a `<PropertyIsLessThan>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @extends {ol.format.filter.ComparisonBinary} + * @api + */ +ol.format.filter.LessThan = function(propertyName, expression) { + ol.format.filter.ComparisonBinary.call(this, 'PropertyIsLessThan', propertyName, expression); +}; +ol.inherits(ol.format.filter.LessThan, ol.format.filter.ComparisonBinary); + +goog.provide('ol.format.filter.LessThanOrEqualTo'); + +goog.require('ol'); +goog.require('ol.format.filter.ComparisonBinary'); + + +/** + * @classdesc + * Represents a `<PropertyIsLessThanOrEqualTo>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @extends {ol.format.filter.ComparisonBinary} + * @api + */ +ol.format.filter.LessThanOrEqualTo = function(propertyName, expression) { + ol.format.filter.ComparisonBinary.call(this, 'PropertyIsLessThanOrEqualTo', propertyName, expression); +}; +ol.inherits(ol.format.filter.LessThanOrEqualTo, ol.format.filter.ComparisonBinary); + +goog.provide('ol.format.filter.Not'); + +goog.require('ol'); +goog.require('ol.format.filter.Logical'); + + +/** + * @classdesc + * Represents a logical `<Not>` operator for a filter condition. + * + * @constructor + * @param {!ol.format.filter.Filter} condition Filter condition. + * @extends {ol.format.filter.Logical} + * @api + */ +ol.format.filter.Not = function(condition) { + + ol.format.filter.Logical.call(this, 'Not'); + + /** + * @public + * @type {!ol.format.filter.Filter} + */ + this.condition = condition; +}; +ol.inherits(ol.format.filter.Not, ol.format.filter.Logical); + +goog.provide('ol.format.filter.NotEqualTo'); + +goog.require('ol'); +goog.require('ol.format.filter.ComparisonBinary'); + + +/** + * @classdesc + * Represents a `<PropertyIsNotEqualTo>` comparison operator. + * + * @constructor + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @extends {ol.format.filter.ComparisonBinary} + * @api + */ +ol.format.filter.NotEqualTo = function(propertyName, expression, opt_matchCase) { + ol.format.filter.ComparisonBinary.call(this, 'PropertyIsNotEqualTo', propertyName, expression, opt_matchCase); +}; +ol.inherits(ol.format.filter.NotEqualTo, ol.format.filter.ComparisonBinary); + +goog.provide('ol.format.filter.Or'); + +goog.require('ol'); +goog.require('ol.format.filter.LogicalBinary'); + + +/** + * @classdesc + * Represents a logical `<Or>` operator between two filter conditions. + * + * @constructor + * @param {!ol.format.filter.Filter} conditionA First filter condition. + * @param {!ol.format.filter.Filter} conditionB Second filter condition. + * @extends {ol.format.filter.LogicalBinary} + * @api + */ +ol.format.filter.Or = function(conditionA, conditionB) { + ol.format.filter.LogicalBinary.call(this, 'Or', conditionA, conditionB); +}; +ol.inherits(ol.format.filter.Or, ol.format.filter.LogicalBinary); + +goog.provide('ol.format.filter.Within'); + +goog.require('ol'); +goog.require('ol.format.filter.Spatial'); + + +/** + * @classdesc + * Represents a `<Within>` operator to test whether a geometry-valued property + * is within a given geometry. + * + * @constructor + * @param {!string} geometryName Geometry name to use. + * @param {!ol.geom.Geometry} geometry Geometry. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @extends {ol.format.filter.Spatial} + * @api + */ +ol.format.filter.Within = function(geometryName, geometry, opt_srsName) { + + ol.format.filter.Spatial.call(this, 'Within', geometryName, geometry, opt_srsName); + +}; +ol.inherits(ol.format.filter.Within, ol.format.filter.Spatial); + +goog.provide('ol.format.filter'); + +goog.require('ol'); +goog.require('ol.format.filter.And'); +goog.require('ol.format.filter.Bbox'); +goog.require('ol.format.filter.EqualTo'); +goog.require('ol.format.filter.GreaterThan'); +goog.require('ol.format.filter.GreaterThanOrEqualTo'); +goog.require('ol.format.filter.Intersects'); +goog.require('ol.format.filter.IsBetween'); +goog.require('ol.format.filter.IsLike'); +goog.require('ol.format.filter.IsNull'); +goog.require('ol.format.filter.LessThan'); +goog.require('ol.format.filter.LessThanOrEqualTo'); +goog.require('ol.format.filter.Not'); +goog.require('ol.format.filter.NotEqualTo'); +goog.require('ol.format.filter.Or'); +goog.require('ol.format.filter.Within'); + + +/** + * Create a logical `<And>` operator between two filter conditions. + * + * @param {!ol.format.filter.Filter} conditionA First filter condition. + * @param {!ol.format.filter.Filter} conditionB Second filter condition. + * @returns {!ol.format.filter.And} `<And>` operator. + * @api + */ +ol.format.filter.and = function(conditionA, conditionB) { + return new ol.format.filter.And(conditionA, conditionB); +}; + + +/** + * Create a logical `<Or>` operator between two filter conditions. + * + * @param {!ol.format.filter.Filter} conditionA First filter condition. + * @param {!ol.format.filter.Filter} conditionB Second filter condition. + * @returns {!ol.format.filter.Or} `<Or>` operator. + * @api + */ +ol.format.filter.or = function(conditionA, conditionB) { + return new ol.format.filter.Or(conditionA, conditionB); +}; + + +/** + * Represents a logical `<Not>` operator for a filter condition. + * + * @param {!ol.format.filter.Filter} condition Filter condition. + * @returns {!ol.format.filter.Not} `<Not>` operator. + * @api + */ +ol.format.filter.not = function(condition) { + return new ol.format.filter.Not(condition); +}; + + +/** + * Create a `<BBOX>` operator to test whether a geometry-valued property + * intersects a fixed bounding box + * + * @param {!string} geometryName Geometry name to use. + * @param {!ol.Extent} extent Extent. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @returns {!ol.format.filter.Bbox} `<BBOX>` operator. + * @api + */ +ol.format.filter.bbox = function(geometryName, extent, opt_srsName) { + return new ol.format.filter.Bbox(geometryName, extent, opt_srsName); +}; + +/** + * Create a `<Intersects>` operator to test whether a geometry-valued property + * intersects a given geometry. + * + * @param {!string} geometryName Geometry name to use. + * @param {!ol.geom.Geometry} geometry Geometry. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @returns {!ol.format.filter.Intersects} `<Intersects>` operator. + * @api + */ +ol.format.filter.intersects = function(geometryName, geometry, opt_srsName) { + return new ol.format.filter.Intersects(geometryName, geometry, opt_srsName); +}; + +/** + * Create a `<Within>` operator to test whether a geometry-valued property + * is within a given geometry. + * + * @param {!string} geometryName Geometry name to use. + * @param {!ol.geom.Geometry} geometry Geometry. + * @param {string=} opt_srsName SRS name. No srsName attribute will be + * set on geometries when this is not provided. + * @returns {!ol.format.filter.Within} `<Within>` operator. + * @api + */ +ol.format.filter.within = function(geometryName, geometry, opt_srsName) { + return new ol.format.filter.Within(geometryName, geometry, opt_srsName); +}; + + +/** + * Creates a `<PropertyIsEqualTo>` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @returns {!ol.format.filter.EqualTo} `<PropertyIsEqualTo>` operator. + * @api + */ +ol.format.filter.equalTo = function(propertyName, expression, opt_matchCase) { + return new ol.format.filter.EqualTo(propertyName, expression, opt_matchCase); +}; + + +/** + * Creates a `<PropertyIsNotEqualTo>` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!(string|number)} expression The value to compare. + * @param {boolean=} opt_matchCase Case-sensitive? + * @returns {!ol.format.filter.NotEqualTo} `<PropertyIsNotEqualTo>` operator. + * @api + */ +ol.format.filter.notEqualTo = function(propertyName, expression, opt_matchCase) { + return new ol.format.filter.NotEqualTo(propertyName, expression, opt_matchCase); +}; + + +/** + * Creates a `<PropertyIsLessThan>` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @returns {!ol.format.filter.LessThan} `<PropertyIsLessThan>` operator. + * @api + */ +ol.format.filter.lessThan = function(propertyName, expression) { + return new ol.format.filter.LessThan(propertyName, expression); +}; + + +/** + * Creates a `<PropertyIsLessThanOrEqualTo>` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @returns {!ol.format.filter.LessThanOrEqualTo} `<PropertyIsLessThanOrEqualTo>` operator. + * @api + */ +ol.format.filter.lessThanOrEqualTo = function(propertyName, expression) { + return new ol.format.filter.LessThanOrEqualTo(propertyName, expression); +}; + + +/** + * Creates a `<PropertyIsGreaterThan>` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @returns {!ol.format.filter.GreaterThan} `<PropertyIsGreaterThan>` operator. + * @api + */ +ol.format.filter.greaterThan = function(propertyName, expression) { + return new ol.format.filter.GreaterThan(propertyName, expression); +}; + + +/** + * Creates a `<PropertyIsGreaterThanOrEqualTo>` comparison operator. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} expression The value to compare. + * @returns {!ol.format.filter.GreaterThanOrEqualTo} `<PropertyIsGreaterThanOrEqualTo>` operator. + * @api + */ +ol.format.filter.greaterThanOrEqualTo = function(propertyName, expression) { + return new ol.format.filter.GreaterThanOrEqualTo(propertyName, expression); +}; + + +/** + * Creates a `<PropertyIsNull>` comparison operator to test whether a property value + * is null. + * + * @param {!string} propertyName Name of the context property to compare. + * @returns {!ol.format.filter.IsNull} `<PropertyIsNull>` operator. + * @api + */ +ol.format.filter.isNull = function(propertyName) { + return new ol.format.filter.IsNull(propertyName); +}; + + +/** + * Creates a `<PropertyIsBetween>` comparison operator to test whether an expression + * value lies within a range given by a lower and upper bound (inclusive). + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!number} lowerBoundary The lower bound of the range. + * @param {!number} upperBoundary The upper bound of the range. + * @returns {!ol.format.filter.IsBetween} `<PropertyIsBetween>` operator. + * @api + */ +ol.format.filter.between = function(propertyName, lowerBoundary, upperBoundary) { + return new ol.format.filter.IsBetween(propertyName, lowerBoundary, upperBoundary); +}; + + +/** + * Represents a `<PropertyIsLike>` comparison operator that matches a string property + * value against a text pattern. + * + * @param {!string} propertyName Name of the context property to compare. + * @param {!string} pattern Text pattern. + * @param {string=} opt_wildCard Pattern character which matches any sequence of + * zero or more string characters. Default is '*'. + * @param {string=} opt_singleChar pattern character which matches any single + * string character. Default is '.'. + * @param {string=} opt_escapeChar Escape character which can be used to escape + * the pattern characters. Default is '!'. + * @param {boolean=} opt_matchCase Case-sensitive? + * @returns {!ol.format.filter.IsLike} `<PropertyIsLike>` operator. + * @api + */ +ol.format.filter.like = function(propertyName, pattern, + opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase) { + return new ol.format.filter.IsLike(propertyName, pattern, + opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase); +}; + +goog.provide('ol.geom.GeometryCollection'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.obj'); + + +/** + * @classdesc + * An array of {@link ol.geom.Geometry} objects. + * + * @constructor + * @extends {ol.geom.Geometry} + * @param {Array.<ol.geom.Geometry>=} opt_geometries Geometries. + * @api stable + */ +ol.geom.GeometryCollection = function(opt_geometries) { + + ol.geom.Geometry.call(this); + + /** + * @private + * @type {Array.<ol.geom.Geometry>} + */ + this.geometries_ = opt_geometries ? opt_geometries : null; + + this.listenGeometriesChange_(); +}; +ol.inherits(ol.geom.GeometryCollection, ol.geom.Geometry); + + +/** + * @param {Array.<ol.geom.Geometry>} geometries Geometries. + * @private + * @return {Array.<ol.geom.Geometry>} Cloned geometries. + */ +ol.geom.GeometryCollection.cloneGeometries_ = function(geometries) { + var clonedGeometries = []; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + clonedGeometries.push(geometries[i].clone()); + } + return clonedGeometries; +}; + + +/** + * @private + */ +ol.geom.GeometryCollection.prototype.unlistenGeometriesChange_ = function() { + var i, ii; + if (!this.geometries_) { + return; + } + for (i = 0, ii = this.geometries_.length; i < ii; ++i) { + ol.events.unlisten( + this.geometries_[i], ol.events.EventType.CHANGE, + this.changed, this); + } +}; + + +/** + * @private + */ +ol.geom.GeometryCollection.prototype.listenGeometriesChange_ = function() { + var i, ii; + if (!this.geometries_) { + return; + } + for (i = 0, ii = this.geometries_.length; i < ii; ++i) { + ol.events.listen( + this.geometries_[i], ol.events.EventType.CHANGE, + this.changed, this); + } +}; + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.GeometryCollection} Clone. + * @api stable + */ +ol.geom.GeometryCollection.prototype.clone = function() { + var geometryCollection = new ol.geom.GeometryCollection(null); + geometryCollection.setGeometries(this.geometries_); + return geometryCollection; +}; + + +/** + * @inheritDoc + */ +ol.geom.GeometryCollection.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + minSquaredDistance = geometries[i].closestPointXY( + x, y, closestPoint, minSquaredDistance); + } + return minSquaredDistance; +}; + + +/** + * @inheritDoc + */ +ol.geom.GeometryCollection.prototype.containsXY = function(x, y) { + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + if (geometries[i].containsXY(x, y)) { + return true; + } + } + return false; +}; + + +/** + * @inheritDoc + */ +ol.geom.GeometryCollection.prototype.computeExtent = function(extent) { + ol.extent.createOrUpdateEmpty(extent); + var geometries = this.geometries_; + for (var i = 0, ii = geometries.length; i < ii; ++i) { + ol.extent.extend(extent, geometries[i].getExtent()); + } + return extent; +}; + + +/** + * Return the geometries that make up this geometry collection. + * @return {Array.<ol.geom.Geometry>} Geometries. + * @api stable + */ +ol.geom.GeometryCollection.prototype.getGeometries = function() { + return ol.geom.GeometryCollection.cloneGeometries_(this.geometries_); +}; + + +/** + * @return {Array.<ol.geom.Geometry>} Geometries. + */ +ol.geom.GeometryCollection.prototype.getGeometriesArray = function() { + return this.geometries_; +}; + + +/** + * @inheritDoc + */ +ol.geom.GeometryCollection.prototype.getSimplifiedGeometry = function(squaredTolerance) { + if (this.simplifiedGeometryRevision != this.getRevision()) { + ol.obj.clear(this.simplifiedGeometryCache); + this.simplifiedGeometryMaxMinSquaredTolerance = 0; + this.simplifiedGeometryRevision = this.getRevision(); + } + if (squaredTolerance < 0 || + (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 && + squaredTolerance < this.simplifiedGeometryMaxMinSquaredTolerance)) { + return this; + } + var key = squaredTolerance.toString(); + if (this.simplifiedGeometryCache.hasOwnProperty(key)) { + return this.simplifiedGeometryCache[key]; + } else { + var simplifiedGeometries = []; + var geometries = this.geometries_; + var simplified = false; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + var geometry = geometries[i]; + var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance); + simplifiedGeometries.push(simplifiedGeometry); + if (simplifiedGeometry !== geometry) { + simplified = true; + } + } + if (simplified) { + var simplifiedGeometryCollection = new ol.geom.GeometryCollection(null); + simplifiedGeometryCollection.setGeometriesArray(simplifiedGeometries); + this.simplifiedGeometryCache[key] = simplifiedGeometryCollection; + return simplifiedGeometryCollection; + } else { + this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance; + return this; + } + } +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.GeometryCollection.prototype.getType = function() { + return ol.geom.GeometryType.GEOMETRY_COLLECTION; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.GeometryCollection.prototype.intersectsExtent = function(extent) { + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + if (geometries[i].intersectsExtent(extent)) { + return true; + } + } + return false; +}; + + +/** + * @return {boolean} Is empty. + */ +ol.geom.GeometryCollection.prototype.isEmpty = function() { + return this.geometries_.length === 0; +}; + + +/** + * @inheritDoc + * @api + */ +ol.geom.GeometryCollection.prototype.rotate = function(angle, anchor) { + var geometries = this.geometries_; + for (var i = 0, ii = geometries.length; i < ii; ++i) { + geometries[i].rotate(angle, anchor); + } + this.changed(); +}; + + +/** + * @inheritDoc + * @api + */ +ol.geom.GeometryCollection.prototype.scale = function(sx, opt_sy, opt_anchor) { + var anchor = opt_anchor; + if (!anchor) { + anchor = ol.extent.getCenter(this.getExtent()); + } + var geometries = this.geometries_; + for (var i = 0, ii = geometries.length; i < ii; ++i) { + geometries[i].scale(sx, opt_sy, anchor); + } + this.changed(); +}; + + +/** + * Set the geometries that make up this geometry collection. + * @param {Array.<ol.geom.Geometry>} geometries Geometries. + * @api stable + */ +ol.geom.GeometryCollection.prototype.setGeometries = function(geometries) { + this.setGeometriesArray( + ol.geom.GeometryCollection.cloneGeometries_(geometries)); +}; + + +/** + * @param {Array.<ol.geom.Geometry>} geometries Geometries. + */ +ol.geom.GeometryCollection.prototype.setGeometriesArray = function(geometries) { + this.unlistenGeometriesChange_(); + this.geometries_ = geometries; + this.listenGeometriesChange_(); + this.changed(); +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.GeometryCollection.prototype.applyTransform = function(transformFn) { + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + geometries[i].applyTransform(transformFn); + } + this.changed(); +}; + + +/** + * Translate the geometry. + * @param {number} deltaX Delta X. + * @param {number} deltaY Delta Y. + * @api + */ +ol.geom.GeometryCollection.prototype.translate = function(deltaX, deltaY) { + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + geometries[i].translate(deltaX, deltaY); + } + this.changed(); +}; + + +/** + * @inheritDoc + */ +ol.geom.GeometryCollection.prototype.disposeInternal = function() { + this.unlistenGeometriesChange_(); + ol.geom.Geometry.prototype.disposeInternal.call(this); +}; + +// TODO: serialize dataProjection as crs member when writing +// see https://github.com/openlayers/ol3/issues/2078 + +goog.provide('ol.format.GeoJSON'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.JSONFeature'); +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.obj'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Feature format for reading and writing data in the GeoJSON format. + * + * @constructor + * @extends {ol.format.JSONFeature} + * @param {olx.format.GeoJSONOptions=} opt_options Options. + * @api stable + */ +ol.format.GeoJSON = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.JSONFeature.call(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get( + options.defaultDataProjection ? + options.defaultDataProjection : 'EPSG:4326'); + + + if (options.featureProjection) { + this.defaultFeatureProjection = ol.proj.get(options.featureProjection); + } + + /** + * Name of the geometry attribute for features. + * @type {string|undefined} + * @private + */ + this.geometryName_ = options.geometryName; + +}; +ol.inherits(ol.format.GeoJSON, ol.format.JSONFeature); + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.GeoJSON.EXTENSIONS_ = ['.geojson']; + + +/** + * @param {GeoJSONGeometry|GeoJSONGeometryCollection} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @private + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.GeoJSON.readGeometry_ = function(object, opt_options) { + if (!object) { + return null; + } + var geometryReader = ol.format.GeoJSON.GEOMETRY_READERS_[object.type]; + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions( + geometryReader(object), false, opt_options)); +}; + + +/** + * @param {GeoJSONGeometryCollection} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @private + * @return {ol.geom.GeometryCollection} Geometry collection. + */ +ol.format.GeoJSON.readGeometryCollectionGeometry_ = function( + object, opt_options) { + ol.DEBUG && console.assert(object.type == 'GeometryCollection', + 'object.type should be GeometryCollection'); + var geometries = object.geometries.map( + /** + * @param {GeoJSONGeometry} geometry Geometry. + * @return {ol.geom.Geometry} geometry Geometry. + */ + function(geometry) { + return ol.format.GeoJSON.readGeometry_(geometry, opt_options); + }); + return new ol.geom.GeometryCollection(geometries); +}; + + +/** + * @param {GeoJSONGeometry} object Object. + * @private + * @return {ol.geom.Point} Point. + */ +ol.format.GeoJSON.readPointGeometry_ = function(object) { + ol.DEBUG && console.assert(object.type == 'Point', + 'object.type should be Point'); + return new ol.geom.Point(object.coordinates); +}; + + +/** + * @param {GeoJSONGeometry} object Object. + * @private + * @return {ol.geom.LineString} LineString. + */ +ol.format.GeoJSON.readLineStringGeometry_ = function(object) { + ol.DEBUG && console.assert(object.type == 'LineString', + 'object.type should be LineString'); + return new ol.geom.LineString(object.coordinates); +}; + + +/** + * @param {GeoJSONGeometry} object Object. + * @private + * @return {ol.geom.MultiLineString} MultiLineString. + */ +ol.format.GeoJSON.readMultiLineStringGeometry_ = function(object) { + ol.DEBUG && console.assert(object.type == 'MultiLineString', + 'object.type should be MultiLineString'); + return new ol.geom.MultiLineString(object.coordinates); +}; + + +/** + * @param {GeoJSONGeometry} object Object. + * @private + * @return {ol.geom.MultiPoint} MultiPoint. + */ +ol.format.GeoJSON.readMultiPointGeometry_ = function(object) { + ol.DEBUG && console.assert(object.type == 'MultiPoint', + 'object.type should be MultiPoint'); + return new ol.geom.MultiPoint(object.coordinates); +}; + + +/** + * @param {GeoJSONGeometry} object Object. + * @private + * @return {ol.geom.MultiPolygon} MultiPolygon. + */ +ol.format.GeoJSON.readMultiPolygonGeometry_ = function(object) { + ol.DEBUG && console.assert(object.type == 'MultiPolygon', + 'object.type should be MultiPolygon'); + return new ol.geom.MultiPolygon(object.coordinates); +}; + + +/** + * @param {GeoJSONGeometry} object Object. + * @private + * @return {ol.geom.Polygon} Polygon. + */ +ol.format.GeoJSON.readPolygonGeometry_ = function(object) { + ol.DEBUG && console.assert(object.type == 'Polygon', + 'object.type should be Polygon'); + return new ol.geom.Polygon(object.coordinates); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry|GeoJSONGeometryCollection} GeoJSON geometry. + */ +ol.format.GeoJSON.writeGeometry_ = function(geometry, opt_options) { + var geometryWriter = ol.format.GeoJSON.GEOMETRY_WRITERS_[geometry.getType()]; + return geometryWriter(/** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, true, opt_options)), + opt_options); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @private + * @return {GeoJSONGeometryCollection} Empty GeoJSON geometry collection. + */ +ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ = function(geometry) { + return /** @type {GeoJSONGeometryCollection} */ ({ + type: 'GeometryCollection', + geometries: [] + }); +}; + + +/** + * @param {ol.geom.GeometryCollection} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometryCollection} GeoJSON geometry collection. + */ +ol.format.GeoJSON.writeGeometryCollectionGeometry_ = function( + geometry, opt_options) { + var geometries = geometry.getGeometriesArray().map(function(geometry) { + var options = ol.obj.assign({}, opt_options); + delete options.featureProjection; + return ol.format.GeoJSON.writeGeometry_(geometry, options); + }); + return /** @type {GeoJSONGeometryCollection} */ ({ + type: 'GeometryCollection', + geometries: geometries + }); +}; + + +/** + * @param {ol.geom.LineString} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writeLineStringGeometry_ = function(geometry, opt_options) { + return /** @type {GeoJSONGeometry} */ ({ + type: 'LineString', + coordinates: geometry.getCoordinates() + }); +}; + + +/** + * @param {ol.geom.MultiLineString} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writeMultiLineStringGeometry_ = function(geometry, opt_options) { + return /** @type {GeoJSONGeometry} */ ({ + type: 'MultiLineString', + coordinates: geometry.getCoordinates() + }); +}; + + +/** + * @param {ol.geom.MultiPoint} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writeMultiPointGeometry_ = function(geometry, opt_options) { + return /** @type {GeoJSONGeometry} */ ({ + type: 'MultiPoint', + coordinates: geometry.getCoordinates() + }); +}; + + +/** + * @param {ol.geom.MultiPolygon} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writeMultiPolygonGeometry_ = function(geometry, opt_options) { + var right; + if (opt_options) { + right = opt_options.rightHanded; + } + return /** @type {GeoJSONGeometry} */ ({ + type: 'MultiPolygon', + coordinates: geometry.getCoordinates(right) + }); +}; + + +/** + * @param {ol.geom.Point} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writePointGeometry_ = function(geometry, opt_options) { + return /** @type {GeoJSONGeometry} */ ({ + type: 'Point', + coordinates: geometry.getCoordinates() + }); +}; + + +/** + * @param {ol.geom.Polygon} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writePolygonGeometry_ = function(geometry, opt_options) { + var right; + if (opt_options) { + right = opt_options.rightHanded; + } + return /** @type {GeoJSONGeometry} */ ({ + type: 'Polygon', + coordinates: geometry.getCoordinates(right) + }); +}; + + +/** + * @const + * @private + * @type {Object.<string, function(GeoJSONObject): ol.geom.Geometry>} + */ +ol.format.GeoJSON.GEOMETRY_READERS_ = { + 'Point': ol.format.GeoJSON.readPointGeometry_, + 'LineString': ol.format.GeoJSON.readLineStringGeometry_, + 'Polygon': ol.format.GeoJSON.readPolygonGeometry_, + 'MultiPoint': ol.format.GeoJSON.readMultiPointGeometry_, + 'MultiLineString': ol.format.GeoJSON.readMultiLineStringGeometry_, + 'MultiPolygon': ol.format.GeoJSON.readMultiPolygonGeometry_, + 'GeometryCollection': ol.format.GeoJSON.readGeometryCollectionGeometry_ +}; + + +/** + * @const + * @private + * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (GeoJSONGeometry|GeoJSONGeometryCollection)>} + */ +ol.format.GeoJSON.GEOMETRY_WRITERS_ = { + 'Point': ol.format.GeoJSON.writePointGeometry_, + 'LineString': ol.format.GeoJSON.writeLineStringGeometry_, + 'Polygon': ol.format.GeoJSON.writePolygonGeometry_, + 'MultiPoint': ol.format.GeoJSON.writeMultiPointGeometry_, + 'MultiLineString': ol.format.GeoJSON.writeMultiLineStringGeometry_, + 'MultiPolygon': ol.format.GeoJSON.writeMultiPolygonGeometry_, + 'GeometryCollection': ol.format.GeoJSON.writeGeometryCollectionGeometry_, + 'Circle': ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ +}; + + +/** + * @inheritDoc + */ +ol.format.GeoJSON.prototype.getExtensions = function() { + return ol.format.GeoJSON.EXTENSIONS_; +}; + + +/** + * Read a feature from a GeoJSON Feature source. Only works for Feature or + * geometry types. Use {@link ol.format.GeoJSON#readFeatures} to read + * FeatureCollection source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable + */ +ol.format.GeoJSON.prototype.readFeature; + + +/** + * Read all features from a GeoJSON source. Works for all GeoJSON types. + * If the source includes only geometries, features will be created with those + * geometries. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.GeoJSON.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.GeoJSON.prototype.readFeatureFromObject = function( + object, opt_options) { + + ol.DEBUG && console.assert(object.type !== 'FeatureCollection', 'Expected a Feature or geometry'); + + /** + * @type {GeoJSONFeature} + */ + var geoJSONFeature = null; + if (object.type === 'Feature') { + geoJSONFeature = /** @type {GeoJSONFeature} */ (object); + } else { + geoJSONFeature = /** @type {GeoJSONFeature} */ ({ + type: 'Feature', + geometry: /** @type {GeoJSONGeometry|GeoJSONGeometryCollection} */ (object) + }); + } + + var geometry = ol.format.GeoJSON.readGeometry_(geoJSONFeature.geometry, opt_options); + var feature = new ol.Feature(); + if (this.geometryName_) { + feature.setGeometryName(this.geometryName_); + } + feature.setGeometry(geometry); + if (geoJSONFeature.id !== undefined) { + feature.setId(geoJSONFeature.id); + } + if (geoJSONFeature.properties) { + feature.setProperties(geoJSONFeature.properties); + } + return feature; +}; + + +/** + * @inheritDoc + */ +ol.format.GeoJSON.prototype.readFeaturesFromObject = function( + object, opt_options) { + var geoJSONObject = /** @type {GeoJSONObject} */ (object); + /** @type {Array.<ol.Feature>} */ + var features = null; + if (geoJSONObject.type === 'FeatureCollection') { + var geoJSONFeatureCollection = /** @type {GeoJSONFeatureCollection} */ + (object); + features = []; + var geoJSONFeatures = geoJSONFeatureCollection.features; + var i, ii; + for (i = 0, ii = geoJSONFeatures.length; i < ii; ++i) { + features.push(this.readFeatureFromObject(geoJSONFeatures[i], + opt_options)); + } + } else { + features = [this.readFeatureFromObject(object, opt_options)]; + } + return features; +}; + + +/** + * Read a geometry from a GeoJSON source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. + * @api stable + */ +ol.format.GeoJSON.prototype.readGeometry; + + +/** + * @inheritDoc + */ +ol.format.GeoJSON.prototype.readGeometryFromObject = function( + object, opt_options) { + return ol.format.GeoJSON.readGeometry_( + /** @type {GeoJSONGeometry} */ (object), opt_options); +}; + + +/** + * Read the projection from a GeoJSON source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.GeoJSON.prototype.readProjection; + + +/** + * @inheritDoc + */ +ol.format.GeoJSON.prototype.readProjectionFromObject = function(object) { + var geoJSONObject = /** @type {GeoJSONObject} */ (object); + var crs = geoJSONObject.crs; + var projection; + if (crs) { + if (crs.type == 'name') { + projection = ol.proj.get(crs.properties.name); + } else if (crs.type == 'EPSG') { + // 'EPSG' is not part of the GeoJSON specification, but is generated by + // GeoServer. + // TODO: remove this when http://jira.codehaus.org/browse/GEOS-5996 + // is fixed and widely deployed. + projection = ol.proj.get('EPSG:' + crs.properties.code); + } else { + ol.asserts.assert(false, 36); // Unknown SRS type + } + } else { + projection = this.defaultDataProjection; + } + return /** @type {ol.proj.Projection} */ (projection); +}; + + +/** + * Encode a feature as a GeoJSON Feature string. + * + * @function + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} GeoJSON. + * @api stable + */ +ol.format.GeoJSON.prototype.writeFeature; + + +/** + * Encode a feature as a GeoJSON Feature object. + * + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {GeoJSONFeature} Object. + * @api stable + */ +ol.format.GeoJSON.prototype.writeFeatureObject = function(feature, opt_options) { + opt_options = this.adaptOptions(opt_options); + + var object = /** @type {GeoJSONFeature} */ ({ + 'type': 'Feature' + }); + var id = feature.getId(); + if (id !== undefined) { + object.id = id; + } + var geometry = feature.getGeometry(); + if (geometry) { + object.geometry = + ol.format.GeoJSON.writeGeometry_(geometry, opt_options); + } else { + object.geometry = null; + } + var properties = feature.getProperties(); + delete properties[feature.getGeometryName()]; + if (!ol.obj.isEmpty(properties)) { + object.properties = properties; + } else { + object.properties = null; + } + return object; +}; + + +/** + * Encode an array of features as GeoJSON. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} GeoJSON. + * @api stable + */ +ol.format.GeoJSON.prototype.writeFeatures; + + +/** + * Encode an array of features as a GeoJSON object. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {GeoJSONFeatureCollection} GeoJSON Object. + * @api stable + */ +ol.format.GeoJSON.prototype.writeFeaturesObject = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + var objects = []; + var i, ii; + for (i = 0, ii = features.length; i < ii; ++i) { + objects.push(this.writeFeatureObject(features[i], opt_options)); + } + return /** @type {GeoJSONFeatureCollection} */ ({ + type: 'FeatureCollection', + features: objects + }); +}; + + +/** + * Encode a geometry as a GeoJSON string. + * + * @function + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} GeoJSON. + * @api stable + */ +ol.format.GeoJSON.prototype.writeGeometry; + + +/** + * Encode a geometry as a GeoJSON object. + * + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {GeoJSONGeometry|GeoJSONGeometryCollection} Object. + * @api stable + */ +ol.format.GeoJSON.prototype.writeGeometryObject = function(geometry, + opt_options) { + return ol.format.GeoJSON.writeGeometry_(geometry, + this.adaptOptions(opt_options)); +}; + +goog.provide('ol.format.XMLFeature'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.format.Feature'); +goog.require('ol.format.FormatType'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for XML feature formats. + * + * @constructor + * @extends {ol.format.Feature} + */ +ol.format.XMLFeature = function() { + + /** + * @type {XMLSerializer} + * @private + */ + this.xmlSerializer_ = new XMLSerializer(); + + ol.format.Feature.call(this); +}; +ol.inherits(ol.format.XMLFeature, ol.format.Feature); + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.getType = function() { + return ol.format.FormatType.XML; +}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.readFeature = function(source, opt_options) { + if (ol.xml.isDocument(source)) { + return this.readFeatureFromDocument( + /** @type {Document} */ (source), opt_options); + } else if (ol.xml.isNode(source)) { + return this.readFeatureFromNode(/** @type {Node} */ (source), opt_options); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readFeatureFromDocument(doc, opt_options); + } else { + return null; + } +}; + + +/** + * @param {Document} doc Document. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {ol.Feature} Feature. + */ +ol.format.XMLFeature.prototype.readFeatureFromDocument = function( + doc, opt_options) { + var features = this.readFeaturesFromDocument(doc, opt_options); + if (features.length > 0) { + return features[0]; + } else { + return null; + } +}; + + +/** + * @abstract + * @param {Node} node Node. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {ol.Feature} Feature. + */ +ol.format.XMLFeature.prototype.readFeatureFromNode = function(node, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.readFeatures = function(source, opt_options) { + if (ol.xml.isDocument(source)) { + return this.readFeaturesFromDocument( + /** @type {Document} */ (source), opt_options); + } else if (ol.xml.isNode(source)) { + return this.readFeaturesFromNode(/** @type {Node} */ (source), opt_options); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readFeaturesFromDocument(doc, opt_options); + } else { + return []; + } +}; + + +/** + * @param {Document} doc Document. + * @param {olx.format.ReadOptions=} opt_options Options. + * @protected + * @return {Array.<ol.Feature>} Features. + */ +ol.format.XMLFeature.prototype.readFeaturesFromDocument = function( + doc, opt_options) { + /** @type {Array.<ol.Feature>} */ + var features = []; + var n; + for (n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + ol.array.extend(features, this.readFeaturesFromNode(n, opt_options)); + } + } + return features; +}; + + +/** + * @abstract + * @param {Node} node Node. + * @param {olx.format.ReadOptions=} opt_options Options. + * @protected + * @return {Array.<ol.Feature>} Features. + */ +ol.format.XMLFeature.prototype.readFeaturesFromNode = function(node, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.readGeometry = function(source, opt_options) { + if (ol.xml.isDocument(source)) { + return this.readGeometryFromDocument( + /** @type {Document} */ (source), opt_options); + } else if (ol.xml.isNode(source)) { + return this.readGeometryFromNode(/** @type {Node} */ (source), opt_options); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readGeometryFromDocument(doc, opt_options); + } else { + return null; + } +}; + + +/** + * @abstract + * @param {Document} doc Document. + * @param {olx.format.ReadOptions=} opt_options Options. + * @protected + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.XMLFeature.prototype.readGeometryFromDocument = function(doc, opt_options) {}; + + +/** + * @abstract + * @param {Node} node Node. + * @param {olx.format.ReadOptions=} opt_options Options. + * @protected + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.XMLFeature.prototype.readGeometryFromNode = function(node, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.readProjection = function(source) { + if (ol.xml.isDocument(source)) { + return this.readProjectionFromDocument(/** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readProjectionFromNode(/** @type {Node} */ (source)); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readProjectionFromDocument(doc); + } else { + return null; + } +}; + + +/** + * @param {Document} doc Document. + * @protected + * @return {ol.proj.Projection} Projection. + */ +ol.format.XMLFeature.prototype.readProjectionFromDocument = function(doc) { + return this.defaultDataProjection; +}; + + +/** + * @param {Node} node Node. + * @protected + * @return {ol.proj.Projection} Projection. + */ +ol.format.XMLFeature.prototype.readProjectionFromNode = function(node) { + return this.defaultDataProjection; +}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.writeFeature = function(feature, opt_options) { + var node = this.writeFeatureNode(feature, opt_options); + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return this.xmlSerializer_.serializeToString(node); +}; + + +/** + * @abstract + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Options. + * @protected + * @return {Node} Node. + */ +ol.format.XMLFeature.prototype.writeFeatureNode = function(feature, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.writeFeatures = function(features, opt_options) { + var node = this.writeFeaturesNode(features, opt_options); + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return this.xmlSerializer_.serializeToString(node); +}; + + +/** + * @abstract + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + */ +ol.format.XMLFeature.prototype.writeFeaturesNode = function(features, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.writeGeometry = function(geometry, opt_options) { + var node = this.writeGeometryNode(geometry, opt_options); + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return this.xmlSerializer_.serializeToString(node); +}; + + +/** + * @abstract + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + */ +ol.format.XMLFeature.prototype.writeGeometryNode = function(geometry, opt_options) {}; + +// FIXME Envelopes should not be treated as geometries! readEnvelope_ is part +// of GEOMETRY_PARSERS_ and methods using GEOMETRY_PARSERS_ do not expect +// envelopes/extents, only geometries! +goog.provide('ol.format.GMLBase'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Feature base format for reading and writing data in the GML format. + * This class cannot be instantiated, it contains only base content that + * is shared with versioned format classes ol.format.GML2 and + * ol.format.GML3. + * + * @constructor + * @param {olx.format.GMLOptions=} opt_options + * Optional configuration object. + * @extends {ol.format.XMLFeature} + */ +ol.format.GMLBase = function(opt_options) { + var options = /** @type {olx.format.GMLOptions} */ + (opt_options ? opt_options : {}); + + /** + * @protected + * @type {Array.<string>|string|undefined} + */ + this.featureType = options.featureType; + + /** + * @protected + * @type {Object.<string, string>|string|undefined} + */ + this.featureNS = options.featureNS; + + /** + * @protected + * @type {string} + */ + this.srsName = options.srsName; + + /** + * @protected + * @type {string} + */ + this.schemaLocation = ''; + + /** + * @type {Object.<string, Object.<string, Object>>} + */ + this.FEATURE_COLLECTION_PARSERS = {}; + this.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS] = { + 'featureMember': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readFeaturesInternal), + 'featureMembers': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readFeaturesInternal) + }; + + ol.format.XMLFeature.call(this); +}; +ol.inherits(ol.format.GMLBase, ol.format.XMLFeature); + + +/** + * @const + * @type {string} + */ +ol.format.GMLBase.GMLNS = 'http://www.opengis.net/gml'; + + +/** + * A regular expression that matches if a string only contains whitespace + * characters. It will e.g. match `''`, `' '`, `'\n'` etc. The non-breaking + * space (0xa0) is explicitly included as IE doesn't include it in its + * definition of `\s`. + * + * Information from `goog.string.isEmptyOrWhitespace`: https://github.com/google/closure-library/blob/e877b1e/closure/goog/string/string.js#L156-L160 + * + * @const + * @type {RegExp} + * @private + */ +ol.format.GMLBase.ONLY_WHITESPACE_RE_ = /^[\s\xa0]*$/; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Array.<ol.Feature> | undefined} Features. + */ +ol.format.GMLBase.prototype.readFeaturesInternal = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + var localName = node.localName; + var features = null; + if (localName == 'FeatureCollection') { + if (node.namespaceURI === 'http://www.opengis.net/wfs') { + features = ol.xml.pushParseAndPop([], + this.FEATURE_COLLECTION_PARSERS, node, + objectStack, this); + } else { + features = ol.xml.pushParseAndPop(null, + this.FEATURE_COLLECTION_PARSERS, node, + objectStack, this); + } + } else if (localName == 'featureMembers' || localName == 'featureMember') { + var context = objectStack[0]; + var featureType = context['featureType']; + var featureNS = context['featureNS']; + var i, ii, prefix = 'p', defaultPrefix = 'p0'; + if (!featureType && node.childNodes) { + featureType = [], featureNS = {}; + for (i = 0, ii = node.childNodes.length; i < ii; ++i) { + var child = node.childNodes[i]; + if (child.nodeType === 1) { + var ft = child.nodeName.split(':').pop(); + if (featureType.indexOf(ft) === -1) { + var key = ''; + var count = 0; + var uri = child.namespaceURI; + for (var candidate in featureNS) { + if (featureNS[candidate] === uri) { + key = candidate; + break; + } + ++count; + } + if (!key) { + key = prefix + count; + featureNS[key] = uri; + } + featureType.push(key + ':' + ft); + } + } + } + if (localName != 'featureMember') { + // recheck featureType for each featureMember + context['featureType'] = featureType; + context['featureNS'] = featureNS; + } + } + if (typeof featureNS === 'string') { + var ns = featureNS; + featureNS = {}; + featureNS[defaultPrefix] = ns; + } + var parsersNS = {}; + var featureTypes = Array.isArray(featureType) ? featureType : [featureType]; + for (var p in featureNS) { + var parsers = {}; + for (i = 0, ii = featureTypes.length; i < ii; ++i) { + var featurePrefix = featureTypes[i].indexOf(':') === -1 ? + defaultPrefix : featureTypes[i].split(':')[0]; + if (featurePrefix === p) { + parsers[featureTypes[i].split(':').pop()] = + (localName == 'featureMembers') ? + ol.xml.makeArrayPusher(this.readFeatureElement, this) : + ol.xml.makeReplacer(this.readFeatureElement, this); + } + } + parsersNS[featureNS[p]] = parsers; + } + if (localName == 'featureMember') { + features = ol.xml.pushParseAndPop(undefined, parsersNS, node, objectStack); + } else { + features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack); + } + } + if (features === null) { + features = []; + } + return features; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.Geometry|undefined} Geometry. + */ +ol.format.GMLBase.prototype.readGeometryElement = function(node, objectStack) { + var context = /** @type {Object} */ (objectStack[0]); + context['srsName'] = node.firstElementChild.getAttribute('srsName'); + /** @type {ol.geom.Geometry} */ + var geometry = ol.xml.pushParseAndPop(null, + this.GEOMETRY_PARSERS_, node, objectStack, this); + if (geometry) { + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, false, context)); + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.Feature} Feature. + */ +ol.format.GMLBase.prototype.readFeatureElement = function(node, objectStack) { + var n; + var fid = node.getAttribute('fid') || + ol.xml.getAttributeNS(node, ol.format.GMLBase.GMLNS, 'id'); + var values = {}, geometryName; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var localName = n.localName; + // Assume attribute elements have one child node and that the child + // is a text or CDATA node (to be treated as text). + // Otherwise assume it is a geometry node. + if (n.childNodes.length === 0 || + (n.childNodes.length === 1 && + (n.firstChild.nodeType === 3 || n.firstChild.nodeType === 4))) { + var value = ol.xml.getAllTextContent(n, false); + if (ol.format.GMLBase.ONLY_WHITESPACE_RE_.test(value)) { + value = undefined; + } + values[localName] = value; + } else { + // boundedBy is an extent and must not be considered as a geometry + if (localName !== 'boundedBy') { + geometryName = localName; + } + values[localName] = this.readGeometryElement(n, objectStack); + } + } + var feature = new ol.Feature(values); + if (geometryName) { + feature.setGeometryName(geometryName); + } + if (fid) { + feature.setId(fid); + } + return feature; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.Point|undefined} Point. + */ +ol.format.GMLBase.prototype.readPoint = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Point', 'localName should be Point'); + var flatCoordinates = + this.readFlatCoordinatesFromNode_(node, objectStack); + if (flatCoordinates) { + var point = new ol.geom.Point(null); + ol.DEBUG && console.assert(flatCoordinates.length == 3, + 'flatCoordinates should have a length of 3'); + point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + return point; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.MultiPoint|undefined} MultiPoint. + */ +ol.format.GMLBase.prototype.readMultiPoint = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MultiPoint', + 'localName should be MultiPoint'); + /** @type {Array.<Array.<number>>} */ + var coordinates = ol.xml.pushParseAndPop([], + this.MULTIPOINT_PARSERS_, node, objectStack, this); + if (coordinates) { + return new ol.geom.MultiPoint(coordinates); + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.MultiLineString|undefined} MultiLineString. + */ +ol.format.GMLBase.prototype.readMultiLineString = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MultiLineString', + 'localName should be MultiLineString'); + /** @type {Array.<ol.geom.LineString>} */ + var lineStrings = ol.xml.pushParseAndPop([], + this.MULTILINESTRING_PARSERS_, node, objectStack, this); + if (lineStrings) { + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setLineStrings(lineStrings); + return multiLineString; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.MultiPolygon|undefined} MultiPolygon. + */ +ol.format.GMLBase.prototype.readMultiPolygon = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MultiPolygon', + 'localName should be MultiPolygon'); + /** @type {Array.<ol.geom.Polygon>} */ + var polygons = ol.xml.pushParseAndPop([], + this.MULTIPOLYGON_PARSERS_, node, objectStack, this); + if (polygons) { + var multiPolygon = new ol.geom.MultiPolygon(null); + multiPolygon.setPolygons(polygons); + return multiPolygon; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GMLBase.prototype.pointMemberParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'pointMember' || + node.localName == 'pointMembers', + 'localName should be pointMember or pointMembers'); + ol.xml.parseNode(this.POINTMEMBER_PARSERS_, + node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GMLBase.prototype.lineStringMemberParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'lineStringMember' || + node.localName == 'lineStringMembers', + 'localName should be LineStringMember or LineStringMembers'); + ol.xml.parseNode(this.LINESTRINGMEMBER_PARSERS_, + node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GMLBase.prototype.polygonMemberParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'polygonMember' || + node.localName == 'polygonMembers', + 'localName should be polygonMember or polygonMembers'); + ol.xml.parseNode(this.POLYGONMEMBER_PARSERS_, node, + objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.LineString|undefined} LineString. + */ +ol.format.GMLBase.prototype.readLineString = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LineString', + 'localName should be LineString'); + var flatCoordinates = + this.readFlatCoordinatesFromNode_(node, objectStack); + if (flatCoordinates) { + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + return lineString; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} LinearRing flat coordinates. + */ +ol.format.GMLBase.prototype.readFlatLinearRing_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LinearRing', + 'localName should be LinearRing'); + var ring = ol.xml.pushParseAndPop(null, + this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, + objectStack, this); + if (ring) { + return ring; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.LinearRing|undefined} LinearRing. + */ +ol.format.GMLBase.prototype.readLinearRing = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LinearRing', + 'localName should be LinearRing'); + var flatCoordinates = + this.readFlatCoordinatesFromNode_(node, objectStack); + if (flatCoordinates) { + var ring = new ol.geom.LinearRing(null); + ring.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + return ring; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.Polygon|undefined} Polygon. + */ +ol.format.GMLBase.prototype.readPolygon = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Polygon', + 'localName should be Polygon'); + /** @type {Array.<Array.<number>>} */ + var flatLinearRings = ol.xml.pushParseAndPop([null], + this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this); + if (flatLinearRings && flatLinearRings[0]) { + var polygon = new ol.geom.Polygon(null); + var flatCoordinates = flatLinearRings[0]; + var ends = [flatCoordinates.length]; + var i, ii; + for (i = 1, ii = flatLinearRings.length; i < ii; ++i) { + ol.array.extend(flatCoordinates, flatLinearRings[i]); + ends.push(flatCoordinates.length); + } + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XYZ, flatCoordinates, ends); + return polygon; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>} Flat coordinates. + */ +ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return ol.xml.pushParseAndPop(null, + this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, + objectStack, this); +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GMLBase.prototype.MULTIPOINT_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'pointMember': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.pointMemberParser_), + 'pointMembers': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.pointMemberParser_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GMLBase.prototype.MULTILINESTRING_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'lineStringMember': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.lineStringMemberParser_), + 'lineStringMembers': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.lineStringMemberParser_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GMLBase.prototype.MULTIPOLYGON_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'polygonMember': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.polygonMemberParser_), + 'polygonMembers': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.polygonMemberParser_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GMLBase.prototype.POINTMEMBER_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'Point': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GMLBase.prototype.LINESTRINGMEMBER_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'LineString': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.readLineString) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GMLBase.prototype.POLYGONMEMBER_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'Polygon': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.readPolygon) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @protected + */ +ol.format.GMLBase.prototype.RING_PARSERS = { + 'http://www.opengis.net/gml' : { + 'LinearRing': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readFlatLinearRing_) + } +}; + + +/** + * @inheritDoc + */ +ol.format.GMLBase.prototype.readGeometryFromNode = function(node, opt_options) { + var geometry = this.readGeometryElement(node, + [this.getReadOptions(node, opt_options ? opt_options : {})]); + return geometry ? geometry : null; +}; + + +/** + * Read all features from a GML FeatureCollection. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.GMLBase.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.GMLBase.prototype.readFeaturesFromNode = function(node, opt_options) { + var options = { + featureType: this.featureType, + featureNS: this.featureNS + }; + if (opt_options) { + ol.obj.assign(options, this.getReadOptions(node, opt_options)); + } + var features = this.readFeaturesInternal(node, [options]); + return features || []; +}; + + +/** + * @inheritDoc + */ +ol.format.GMLBase.prototype.readProjectionFromNode = function(node) { + return ol.proj.get(this.srsName ? this.srsName : + node.firstElementChild.getAttribute('srsName')); +}; + +goog.provide('ol.format.XSD'); + +goog.require('ol'); +goog.require('ol.xml'); +goog.require('ol.string'); + + +/** + * @const + * @type {string} + */ +ol.format.XSD.NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema'; + + +/** + * @param {Node} node Node. + * @return {boolean|undefined} Boolean. + */ +ol.format.XSD.readBoolean = function(node) { + var s = ol.xml.getAllTextContent(node, false); + return ol.format.XSD.readBooleanString(s); +}; + + +/** + * @param {string} string String. + * @return {boolean|undefined} Boolean. + */ +ol.format.XSD.readBooleanString = function(string) { + var m = /^\s*(true|1)|(false|0)\s*$/.exec(string); + if (m) { + return m[1] !== undefined || false; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @return {number|undefined} DateTime in seconds. + */ +ol.format.XSD.readDateTime = function(node) { + var s = ol.xml.getAllTextContent(node, false); + var dateTime = Date.parse(s); + return isNaN(dateTime) ? undefined : dateTime / 1000; +}; + + +/** + * @param {Node} node Node. + * @return {number|undefined} Decimal. + */ +ol.format.XSD.readDecimal = function(node) { + var s = ol.xml.getAllTextContent(node, false); + return ol.format.XSD.readDecimalString(s); +}; + + +/** + * @param {string} string String. + * @return {number|undefined} Decimal. + */ +ol.format.XSD.readDecimalString = function(string) { + // FIXME check spec + var m = /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(string); + if (m) { + return parseFloat(m[1]); + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @return {number|undefined} Non negative integer. + */ +ol.format.XSD.readNonNegativeInteger = function(node) { + var s = ol.xml.getAllTextContent(node, false); + return ol.format.XSD.readNonNegativeIntegerString(s); +}; + + +/** + * @param {string} string String. + * @return {number|undefined} Non negative integer. + */ +ol.format.XSD.readNonNegativeIntegerString = function(string) { + var m = /^\s*(\d+)\s*$/.exec(string); + if (m) { + return parseInt(m[1], 10); + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @return {string|undefined} String. + */ +ol.format.XSD.readString = function(node) { + return ol.xml.getAllTextContent(node, false).trim(); +}; + + +/** + * @param {Node} node Node to append a TextNode with the boolean to. + * @param {boolean} bool Boolean. + */ +ol.format.XSD.writeBooleanTextNode = function(node, bool) { + ol.format.XSD.writeStringTextNode(node, (bool) ? '1' : '0'); +}; + + +/** + * @param {Node} node Node to append a CDATA Section with the string to. + * @param {string} string String. + */ +ol.format.XSD.writeCDATASection = function(node, string) { + node.appendChild(ol.xml.DOCUMENT.createCDATASection(string)); +}; + + +/** + * @param {Node} node Node to append a TextNode with the dateTime to. + * @param {number} dateTime DateTime in seconds. + */ +ol.format.XSD.writeDateTimeTextNode = function(node, dateTime) { + var date = new Date(dateTime * 1000); + var string = date.getUTCFullYear() + '-' + + ol.string.padNumber(date.getUTCMonth() + 1, 2) + '-' + + ol.string.padNumber(date.getUTCDate(), 2) + 'T' + + ol.string.padNumber(date.getUTCHours(), 2) + ':' + + ol.string.padNumber(date.getUTCMinutes(), 2) + ':' + + ol.string.padNumber(date.getUTCSeconds(), 2) + 'Z'; + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; + + +/** + * @param {Node} node Node to append a TextNode with the decimal to. + * @param {number} decimal Decimal. + */ +ol.format.XSD.writeDecimalTextNode = function(node, decimal) { + var string = decimal.toPrecision(); + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; + + +/** + * @param {Node} node Node to append a TextNode with the decimal to. + * @param {number} nonNegativeInteger Non negative integer. + */ +ol.format.XSD.writeNonNegativeIntegerTextNode = function(node, nonNegativeInteger) { + ol.DEBUG && console.assert(nonNegativeInteger >= 0, 'value should be more than 0'); + ol.DEBUG && console.assert(nonNegativeInteger == (nonNegativeInteger | 0), + 'value should be an integer value'); + var string = nonNegativeInteger.toString(); + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; + + +/** + * @param {Node} node Node to append a TextNode with the string to. + * @param {string} string String. + */ +ol.format.XSD.writeStringTextNode = function(node, string) { + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; + +goog.provide('ol.format.GML3'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.format.Feature'); +goog.require('ol.format.GMLBase'); +goog.require('ol.format.XSD'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Polygon'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Feature format for reading and writing data in the GML format + * version 3.1.1. + * Currently only supports GML 3.1.1 Simple Features profile. + * + * @constructor + * @param {olx.format.GMLOptions=} opt_options + * Optional configuration object. + * @extends {ol.format.GMLBase} + * @api + */ +ol.format.GML3 = function(opt_options) { + var options = /** @type {olx.format.GMLOptions} */ + (opt_options ? opt_options : {}); + + ol.format.GMLBase.call(this, options); + + /** + * @private + * @type {boolean} + */ + this.surface_ = options.surface !== undefined ? options.surface : false; + + /** + * @private + * @type {boolean} + */ + this.curve_ = options.curve !== undefined ? options.curve : false; + + /** + * @private + * @type {boolean} + */ + this.multiCurve_ = options.multiCurve !== undefined ? + options.multiCurve : true; + + /** + * @private + * @type {boolean} + */ + this.multiSurface_ = options.multiSurface !== undefined ? + options.multiSurface : true; + + /** + * @inheritDoc + */ + this.schemaLocation = options.schemaLocation ? + options.schemaLocation : ol.format.GML3.schemaLocation_; + +}; +ol.inherits(ol.format.GML3, ol.format.GMLBase); + + +/** + * @const + * @type {string} + * @private + */ +ol.format.GML3.schemaLocation_ = ol.format.GMLBase.GMLNS + + ' http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' + + '1.0.0/gmlsf.xsd'; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.MultiLineString|undefined} MultiLineString. + */ +ol.format.GML3.prototype.readMultiCurve_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MultiCurve', + 'localName should be MultiCurve'); + /** @type {Array.<ol.geom.LineString>} */ + var lineStrings = ol.xml.pushParseAndPop([], + this.MULTICURVE_PARSERS_, node, objectStack, this); + if (lineStrings) { + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setLineStrings(lineStrings); + return multiLineString; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.MultiPolygon|undefined} MultiPolygon. + */ +ol.format.GML3.prototype.readMultiSurface_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MultiSurface', + 'localName should be MultiSurface'); + /** @type {Array.<ol.geom.Polygon>} */ + var polygons = ol.xml.pushParseAndPop([], + this.MULTISURFACE_PARSERS_, node, objectStack, this); + if (polygons) { + var multiPolygon = new ol.geom.MultiPolygon(null); + multiPolygon.setPolygons(polygons); + return multiPolygon; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GML3.prototype.curveMemberParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'curveMember' || + node.localName == 'curveMembers', + 'localName should be curveMember or curveMembers'); + ol.xml.parseNode(this.CURVEMEMBER_PARSERS_, node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GML3.prototype.surfaceMemberParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'surfaceMember' || + node.localName == 'surfaceMembers', + 'localName should be surfaceMember or surfaceMembers'); + ol.xml.parseNode(this.SURFACEMEMBER_PARSERS_, + node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<(Array.<number>)>|undefined} flat coordinates. + */ +ol.format.GML3.prototype.readPatch_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'patches', + 'localName should be patches'); + return ol.xml.pushParseAndPop([null], + this.PATCHES_PARSERS_, node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} flat coordinates. + */ +ol.format.GML3.prototype.readSegment_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'segments', + 'localName should be segments'); + return ol.xml.pushParseAndPop([null], + this.SEGMENTS_PARSERS_, node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<(Array.<number>)>|undefined} flat coordinates. + */ +ol.format.GML3.prototype.readPolygonPatch_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'npde.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'PolygonPatch', + 'localName should be PolygonPatch'); + return ol.xml.pushParseAndPop([null], + this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} flat coordinates. + */ +ol.format.GML3.prototype.readLineStringSegment_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LineStringSegment', + 'localName should be LineStringSegment'); + return ol.xml.pushParseAndPop([null], + this.GEOMETRY_FLAT_COORDINATES_PARSERS_, + node, objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GML3.prototype.interiorParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'interior', + 'localName should be interior'); + /** @type {Array.<number>|undefined} */ + var flatLinearRing = ol.xml.pushParseAndPop(undefined, + this.RING_PARSERS, node, objectStack, this); + if (flatLinearRing) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + ol.DEBUG && console.assert(flatLinearRings.length > 0, + 'flatLinearRings should have an array length of 1 or more'); + flatLinearRings.push(flatLinearRing); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GML3.prototype.exteriorParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'exterior', + 'localName should be exterior'); + /** @type {Array.<number>|undefined} */ + var flatLinearRing = ol.xml.pushParseAndPop(undefined, + this.RING_PARSERS, node, objectStack, this); + if (flatLinearRing) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + ol.DEBUG && console.assert(flatLinearRings.length > 0, + 'flatLinearRings should have an array length of 1 or more'); + flatLinearRings[0] = flatLinearRing; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Polygon|undefined} Polygon. + */ +ol.format.GML3.prototype.readSurface_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Surface', + 'localName should be Surface'); + /** @type {Array.<Array.<number>>} */ + var flatLinearRings = ol.xml.pushParseAndPop([null], + this.SURFACE_PARSERS_, node, objectStack, this); + if (flatLinearRings && flatLinearRings[0]) { + var polygon = new ol.geom.Polygon(null); + var flatCoordinates = flatLinearRings[0]; + var ends = [flatCoordinates.length]; + var i, ii; + for (i = 1, ii = flatLinearRings.length; i < ii; ++i) { + ol.array.extend(flatCoordinates, flatLinearRings[i]); + ends.push(flatCoordinates.length); + } + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XYZ, flatCoordinates, ends); + return polygon; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.LineString|undefined} LineString. + */ +ol.format.GML3.prototype.readCurve_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Curve', 'localName should be Curve'); + /** @type {Array.<number>} */ + var flatCoordinates = ol.xml.pushParseAndPop([null], + this.CURVE_PARSERS_, node, objectStack, this); + if (flatCoordinates) { + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + return lineString; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Extent|undefined} Envelope. + */ +ol.format.GML3.prototype.readEnvelope_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Envelope', + 'localName should be Envelope'); + /** @type {Array.<number>} */ + var flatCoordinates = ol.xml.pushParseAndPop([null], + this.ENVELOPE_PARSERS_, node, objectStack, this); + return ol.extent.createOrUpdate(flatCoordinates[1][0], + flatCoordinates[1][1], flatCoordinates[2][0], + flatCoordinates[2][1]); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} Flat coordinates. + */ +ol.format.GML3.prototype.readFlatPos_ = function(node, objectStack) { + var s = ol.xml.getAllTextContent(node, false); + var re = /^\s*([+\-]?\d*\.?\d+(?:[eE][+\-]?\d+)?)\s*/; + /** @type {Array.<number>} */ + var flatCoordinates = []; + var m; + while ((m = re.exec(s))) { + flatCoordinates.push(parseFloat(m[1])); + s = s.substr(m[0].length); + } + if (s !== '') { + return undefined; + } + var context = objectStack[0]; + var containerSrs = context['srsName']; + var axisOrientation = 'enu'; + if (containerSrs) { + var proj = ol.proj.get(containerSrs); + axisOrientation = proj.getAxisOrientation(); + } + if (axisOrientation === 'neu') { + var i, ii; + for (i = 0, ii = flatCoordinates.length; i < ii; i += 3) { + var y = flatCoordinates[i]; + var x = flatCoordinates[i + 1]; + flatCoordinates[i] = x; + flatCoordinates[i + 1] = y; + } + } + var len = flatCoordinates.length; + if (len == 2) { + flatCoordinates.push(0); + } + if (len === 0) { + return undefined; + } + return flatCoordinates; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} Flat coordinates. + */ +ol.format.GML3.prototype.readFlatPosList_ = function(node, objectStack) { + var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, ''); + var context = objectStack[0]; + var containerSrs = context['srsName']; + var containerDimension = node.parentNode.getAttribute('srsDimension'); + var axisOrientation = 'enu'; + if (containerSrs) { + var proj = ol.proj.get(containerSrs); + axisOrientation = proj.getAxisOrientation(); + } + var coords = s.split(/\s+/); + // The "dimension" attribute is from the GML 3.0.1 spec. + var dim = 2; + if (node.getAttribute('srsDimension')) { + dim = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('srsDimension')); + } else if (node.getAttribute('dimension')) { + dim = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('dimension')); + } else if (containerDimension) { + dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension); + } + var x, y, z; + var flatCoordinates = []; + for (var i = 0, ii = coords.length; i < ii; i += dim) { + x = parseFloat(coords[i]); + y = parseFloat(coords[i + 1]); + z = (dim === 3) ? parseFloat(coords[i + 2]) : 0; + if (axisOrientation.substr(0, 2) === 'en') { + flatCoordinates.push(x, y, z); + } else { + flatCoordinates.push(y, x, z); + } + } + return flatCoordinates; +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'pos': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPos_), + 'posList': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPosList_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.FLAT_LINEAR_RINGS_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'interior': ol.format.GML3.prototype.interiorParser_, + 'exterior': ol.format.GML3.prototype.exteriorParser_ + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.GEOMETRY_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint), + 'MultiPoint': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiPoint), + 'LineString': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readLineString), + 'MultiLineString': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiLineString), + 'LinearRing' : ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readLinearRing), + 'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon), + 'MultiPolygon': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiPolygon), + 'Surface': ol.xml.makeReplacer(ol.format.GML3.prototype.readSurface_), + 'MultiSurface': ol.xml.makeReplacer( + ol.format.GML3.prototype.readMultiSurface_), + 'Curve': ol.xml.makeReplacer(ol.format.GML3.prototype.readCurve_), + 'MultiCurve': ol.xml.makeReplacer( + ol.format.GML3.prototype.readMultiCurve_), + 'Envelope': ol.xml.makeReplacer(ol.format.GML3.prototype.readEnvelope_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.MULTICURVE_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'curveMember': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.curveMemberParser_), + 'curveMembers': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.curveMemberParser_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.MULTISURFACE_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'surfaceMember': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.surfaceMemberParser_), + 'surfaceMembers': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.surfaceMemberParser_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.CURVEMEMBER_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'LineString': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.readLineString), + 'Curve': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readCurve_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.SURFACEMEMBER_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'Polygon': ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readPolygon), + 'Surface': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readSurface_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.SURFACE_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'patches': ol.xml.makeReplacer(ol.format.GML3.prototype.readPatch_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.CURVE_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'segments': ol.xml.makeReplacer(ol.format.GML3.prototype.readSegment_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.ENVELOPE_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'lowerCorner': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.readFlatPosList_), + 'upperCorner': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.readFlatPosList_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.PATCHES_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'PolygonPatch': ol.xml.makeReplacer( + ol.format.GML3.prototype.readPolygonPatch_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML3.prototype.SEGMENTS_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'LineStringSegment': ol.xml.makeReplacer( + ol.format.GML3.prototype.readLineStringSegment_) + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Point} value Point geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writePos_ = function(node, value, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + var axisOrientation = 'enu'; + if (srsName) { + axisOrientation = ol.proj.get(srsName).getAxisOrientation(); + } + var point = value.getCoordinates(); + var coords; + // only 2d for simple features profile + if (axisOrientation.substr(0, 2) === 'en') { + coords = (point[0] + ' ' + point[1]); + } else { + coords = (point[1] + ' ' + point[0]); + } + ol.format.XSD.writeStringTextNode(node, coords); +}; + + +/** + * @param {Array.<number>} point Point geometry. + * @param {string=} opt_srsName Optional srsName + * @return {string} The coords string. + * @private + */ +ol.format.GML3.prototype.getCoords_ = function(point, opt_srsName) { + var axisOrientation = 'enu'; + if (opt_srsName) { + axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation(); + } + return ((axisOrientation.substr(0, 2) === 'en') ? + point[0] + ' ' + point[1] : + point[1] + ' ' + point[0]); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LineString|ol.geom.LinearRing} value Geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writePosList_ = function(node, value, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + // only 2d for simple features profile + var points = value.getCoordinates(); + var len = points.length; + var parts = new Array(len); + var point; + for (var i = 0; i < len; ++i) { + point = points[i]; + parts[i] = this.getCoords_(point, srsName); + } + ol.format.XSD.writeStringTextNode(node, parts.join(' ')); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Point} geometry Point geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writePoint_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + if (srsName) { + node.setAttribute('srsName', srsName); + } + var pos = ol.xml.createElementNS(node.namespaceURI, 'pos'); + node.appendChild(pos); + this.writePos_(pos, geometry, objectStack); +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GML3.ENVELOPE_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'lowerCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'upperCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.Extent} extent Extent. + * @param {Array.<*>} objectStack Node stack. + */ +ol.format.GML3.prototype.writeEnvelope = function(node, extent, objectStack) { + ol.DEBUG && console.assert(extent.length == 4, 'extent should have 4 items'); + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + if (srsName) { + node.setAttribute('srsName', srsName); + } + var keys = ['lowerCorner', 'upperCorner']; + var values = [extent[0] + ' ' + extent[1], extent[2] + ' ' + extent[3]]; + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ + ({node: node}), ol.format.GML3.ENVELOPE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, + objectStack, keys, this); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LinearRing} geometry LinearRing geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeLinearRing_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + if (srsName) { + node.setAttribute('srsName', srsName); + } + var posList = ol.xml.createElementNS(node.namespaceURI, 'posList'); + node.appendChild(posList); + this.writePosList_(posList, geometry, objectStack); +}; + + +/** + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node} Node. + * @private + */ +ol.format.GML3.prototype.RING_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + var context = objectStack[objectStack.length - 1]; + var parentNode = context.node; + var exteriorWritten = context['exteriorWritten']; + if (exteriorWritten === undefined) { + context['exteriorWritten'] = true; + } + return ol.xml.createElementNS(parentNode.namespaceURI, + exteriorWritten !== undefined ? 'interior' : 'exterior'); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Polygon} geometry Polygon geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeSurfaceOrPolygon_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + if (node.nodeName !== 'PolygonPatch' && srsName) { + node.setAttribute('srsName', srsName); + } + if (node.nodeName === 'Polygon' || node.nodeName === 'PolygonPatch') { + var rings = geometry.getLinearRings(); + ol.xml.pushSerializeAndPop( + {node: node, srsName: srsName}, + ol.format.GML3.RING_SERIALIZERS_, + this.RING_NODE_FACTORY_, + rings, objectStack, undefined, this); + } else if (node.nodeName === 'Surface') { + var patches = ol.xml.createElementNS(node.namespaceURI, 'patches'); + node.appendChild(patches); + this.writeSurfacePatches_( + patches, geometry, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LineString} geometry LineString geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeCurveOrLineString_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + if (node.nodeName !== 'LineStringSegment' && srsName) { + node.setAttribute('srsName', srsName); + } + if (node.nodeName === 'LineString' || + node.nodeName === 'LineStringSegment') { + var posList = ol.xml.createElementNS(node.namespaceURI, 'posList'); + node.appendChild(posList); + this.writePosList_(posList, geometry, objectStack); + } else if (node.nodeName === 'Curve') { + var segments = ol.xml.createElementNS(node.namespaceURI, 'segments'); + node.appendChild(segments); + this.writeCurveSegments_(segments, + geometry, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + var surface = context['surface']; + if (srsName) { + node.setAttribute('srsName', srsName); + } + var polygons = geometry.getPolygons(); + ol.xml.pushSerializeAndPop({node: node, srsName: srsName, surface: surface}, + ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_, + this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, polygons, + objectStack, undefined, this); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.MultiPoint} geometry MultiPoint geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeMultiPoint_ = function(node, geometry, + objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + if (srsName) { + node.setAttribute('srsName', srsName); + } + var points = geometry.getPoints(); + ol.xml.pushSerializeAndPop({node: node, srsName: srsName}, + ol.format.GML3.POINTMEMBER_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('pointMember'), points, + objectStack, undefined, this); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.MultiLineString} geometry MultiLineString geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeMultiCurveOrLineString_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + var srsName = context['srsName']; + var curve = context['curve']; + if (srsName) { + node.setAttribute('srsName', srsName); + } + var lines = geometry.getLineStrings(); + ol.xml.pushSerializeAndPop({node: node, srsName: srsName, curve: curve}, + ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_, + this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, lines, + objectStack, undefined, this); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LinearRing} ring LinearRing geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeRing_ = function(node, ring, objectStack) { + var linearRing = ol.xml.createElementNS(node.namespaceURI, 'LinearRing'); + node.appendChild(linearRing); + this.writeLinearRing_(linearRing, ring, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Polygon} polygon Polygon geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeSurfaceOrPolygonMember_ = function(node, polygon, objectStack) { + var child = this.GEOMETRY_NODE_FACTORY_( + polygon, objectStack); + if (child) { + node.appendChild(child); + this.writeSurfaceOrPolygon_(child, polygon, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Point} point Point geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writePointMember_ = function(node, point, objectStack) { + var child = ol.xml.createElementNS(node.namespaceURI, 'Point'); + node.appendChild(child); + this.writePoint_(child, point, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LineString} line LineString geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeLineStringOrCurveMember_ = function(node, line, objectStack) { + var child = this.GEOMETRY_NODE_FACTORY_(line, objectStack); + if (child) { + node.appendChild(child); + this.writeCurveOrLineString_(child, line, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Polygon} polygon Polygon geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeSurfacePatches_ = function(node, polygon, objectStack) { + var child = ol.xml.createElementNS(node.namespaceURI, 'PolygonPatch'); + node.appendChild(child); + this.writeSurfaceOrPolygon_(child, polygon, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LineString} line LineString geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeCurveSegments_ = function(node, line, objectStack) { + var child = ol.xml.createElementNS(node.namespaceURI, + 'LineStringSegment'); + node.appendChild(child); + this.writeCurveOrLineString_(child, line, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Geometry|ol.Extent} geometry Geometry. + * @param {Array.<*>} objectStack Node stack. + */ +ol.format.GML3.prototype.writeGeometryElement = function(node, geometry, objectStack) { + var context = /** @type {olx.format.WriteOptions} */ (objectStack[objectStack.length - 1]); + var item = ol.obj.assign({}, context); + item.node = node; + var value; + if (Array.isArray(geometry)) { + if (context.dataProjection) { + value = ol.proj.transformExtent( + geometry, context.featureProjection, context.dataProjection); + } else { + value = geometry; + } + } else { + value = + ol.format.Feature.transformWithOptions(/** @type {ol.geom.Geometry} */ (geometry), true, context); + } + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ + (item), ol.format.GML3.GEOMETRY_SERIALIZERS_, + this.GEOMETRY_NODE_FACTORY_, [value], + objectStack, undefined, this); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + */ +ol.format.GML3.prototype.writeFeatureElement = function(node, feature, objectStack) { + var fid = feature.getId(); + if (fid) { + node.setAttribute('fid', fid); + } + var context = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var featureNS = context['featureNS']; + var geometryName = feature.getGeometryName(); + if (!context.serializers) { + context.serializers = {}; + context.serializers[featureNS] = {}; + } + var properties = feature.getProperties(); + var keys = [], values = []; + for (var key in properties) { + var value = properties[key]; + if (value !== null) { + keys.push(key); + values.push(value); + if (key == geometryName || value instanceof ol.geom.Geometry) { + if (!(key in context.serializers[featureNS])) { + context.serializers[featureNS][key] = ol.xml.makeChildAppender( + this.writeGeometryElement, this); + } + } else { + if (!(key in context.serializers[featureNS])) { + context.serializers[featureNS][key] = ol.xml.makeChildAppender( + ol.format.XSD.writeStringTextNode); + } + } + } + } + var item = ol.obj.assign({}, context); + item.node = node; + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ + (item), context.serializers, + ol.xml.makeSimpleNodeFactory(undefined, featureNS), + values, + objectStack, keys); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<ol.Feature>} features Features. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeFeatureMembers_ = function(node, features, objectStack) { + var context = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var featureType = context['featureType']; + var featureNS = context['featureNS']; + var serializers = {}; + serializers[featureNS] = {}; + serializers[featureNS][featureType] = ol.xml.makeChildAppender( + this.writeFeatureElement, this); + var item = ol.obj.assign({}, context); + item.node = node; + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ + (item), + serializers, + ol.xml.makeSimpleNodeFactory(featureType, featureNS), features, + objectStack); +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'surfaceMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeSurfaceOrPolygonMember_), + 'polygonMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeSurfaceOrPolygonMember_) + } +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GML3.POINTMEMBER_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'pointMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writePointMember_) + } +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'lineStringMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeLineStringOrCurveMember_), + 'curveMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeLineStringOrCurveMember_) + } +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GML3.RING_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'exterior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_), + 'interior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_) + } +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GML3.GEOMETRY_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'Curve': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeCurveOrLineString_), + 'MultiCurve': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiCurveOrLineString_), + 'Point': ol.xml.makeChildAppender(ol.format.GML3.prototype.writePoint_), + 'MultiPoint': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiPoint_), + 'LineString': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeCurveOrLineString_), + 'MultiLineString': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiCurveOrLineString_), + 'LinearRing': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeLinearRing_), + 'Polygon': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeSurfaceOrPolygon_), + 'MultiPolygon': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_), + 'Surface': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeSurfaceOrPolygon_), + 'MultiSurface': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_), + 'Envelope': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeEnvelope) + } +}; + + +/** + * @const + * @type {Object.<string, string>} + * @private + */ +ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_ = { + 'MultiLineString': 'lineStringMember', + 'MultiCurve': 'curveMember', + 'MultiPolygon': 'polygonMember', + 'MultiSurface': 'surfaceMember' +}; + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private + */ +ol.format.GML3.prototype.MULTIGEOMETRY_MEMBER_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + var parentNode = objectStack[objectStack.length - 1].node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode), + 'parentNode should be a node'); + return ol.xml.createElementNS('http://www.opengis.net/gml', + ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_[parentNode.nodeName]); +}; + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private + */ +ol.format.GML3.prototype.GEOMETRY_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + var context = objectStack[objectStack.length - 1]; + var multiSurface = context['multiSurface']; + var surface = context['surface']; + var curve = context['curve']; + var multiCurve = context['multiCurve']; + var parentNode = objectStack[objectStack.length - 1].node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode), + 'parentNode should be a node'); + var nodeName; + if (!Array.isArray(value)) { + nodeName = /** @type {ol.geom.Geometry} */ (value).getType(); + if (nodeName === 'MultiPolygon' && multiSurface === true) { + nodeName = 'MultiSurface'; + } else if (nodeName === 'Polygon' && surface === true) { + nodeName = 'Surface'; + } else if (nodeName === 'LineString' && curve === true) { + nodeName = 'Curve'; + } else if (nodeName === 'MultiLineString' && multiCurve === true) { + nodeName = 'MultiCurve'; + } + } else { + nodeName = 'Envelope'; + } + return ol.xml.createElementNS('http://www.opengis.net/gml', + nodeName); +}; + + +/** + * Encode a geometry in GML 3.1.1 Simple Features. + * + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api + */ +ol.format.GML3.prototype.writeGeometryNode = function(geometry, opt_options) { + opt_options = this.adaptOptions(opt_options); + var geom = ol.xml.createElementNS('http://www.opengis.net/gml', 'geom'); + var context = {node: geom, srsName: this.srsName, + curve: this.curve_, surface: this.surface_, + multiSurface: this.multiSurface_, multiCurve: this.multiCurve_}; + if (opt_options) { + ol.obj.assign(context, opt_options); + } + this.writeGeometryElement(geom, geometry, [context]); + return geom; +}; + + +/** + * Encode an array of features in GML 3.1.1 Simple Features. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {string} Result. + * @api stable + */ +ol.format.GML3.prototype.writeFeatures; + + +/** + * Encode an array of features in the GML 3.1.1 format as an XML node. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api + */ +ol.format.GML3.prototype.writeFeaturesNode = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + var node = ol.xml.createElementNS('http://www.opengis.net/gml', + 'featureMembers'); + ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation); + var context = { + srsName: this.srsName, + curve: this.curve_, + surface: this.surface_, + multiSurface: this.multiSurface_, + multiCurve: this.multiCurve_, + featureNS: this.featureNS, + featureType: this.featureType + }; + if (opt_options) { + ol.obj.assign(context, opt_options); + } + this.writeFeatureMembers_(node, features, [context]); + return node; +}; + +goog.provide('ol.format.GML'); + +goog.require('ol.format.GML3'); + + +/** + * @classdesc + * Feature format for reading and writing data in the GML format + * version 3.1.1. + * Currently only supports GML 3.1.1 Simple Features profile. + * + * @constructor + * @param {olx.format.GMLOptions=} opt_options + * Optional configuration object. + * @extends {ol.format.GMLBase} + * @api stable + */ +ol.format.GML = ol.format.GML3; + + +/** + * Encode an array of features in GML 3.1.1 Simple Features. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {string} Result. + * @api stable + */ +ol.format.GML.prototype.writeFeatures; + + +/** + * Encode an array of features in the GML 3.1.1 format as an XML node. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api + */ +ol.format.GML.prototype.writeFeaturesNode; + +goog.provide('ol.format.GML2'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.format.GMLBase'); +goog.require('ol.format.XSD'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Feature format for reading and writing data in the GML format, + * version 2.1.2. + * + * @constructor + * @param {olx.format.GMLOptions=} opt_options Optional configuration object. + * @extends {ol.format.GMLBase} + * @api + */ +ol.format.GML2 = function(opt_options) { + var options = /** @type {olx.format.GMLOptions} */ + (opt_options ? opt_options : {}); + + ol.format.GMLBase.call(this, options); + + this.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS][ + 'featureMember'] = + ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readFeaturesInternal); + + /** + * @inheritDoc + */ + this.schemaLocation = options.schemaLocation ? + options.schemaLocation : ol.format.GML2.schemaLocation_; + +}; +ol.inherits(ol.format.GML2, ol.format.GMLBase); + + +/** + * @const + * @type {string} + * @private + */ +ol.format.GML2.schemaLocation_ = ol.format.GMLBase.GMLNS + + ' http://schemas.opengis.net/gml/2.1.2/feature.xsd'; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} Flat coordinates. + */ +ol.format.GML2.prototype.readFlatCoordinates_ = function(node, objectStack) { + var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, ''); + var context = /** @type {ol.XmlNodeStackItem} */ (objectStack[0]); + var containerSrs = context['srsName']; + var containerDimension = node.parentNode.getAttribute('srsDimension'); + var axisOrientation = 'enu'; + if (containerSrs) { + var proj = ol.proj.get(containerSrs); + if (proj) { + axisOrientation = proj.getAxisOrientation(); + } + } + var coords = s.split(/[\s,]+/); + // The "dimension" attribute is from the GML 3.0.1 spec. + var dim = 2; + if (node.getAttribute('srsDimension')) { + dim = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('srsDimension')); + } else if (node.getAttribute('dimension')) { + dim = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('dimension')); + } else if (containerDimension) { + dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension); + } + var x, y, z; + var flatCoordinates = []; + for (var i = 0, ii = coords.length; i < ii; i += dim) { + x = parseFloat(coords[i]); + y = parseFloat(coords[i + 1]); + z = (dim === 3) ? parseFloat(coords[i + 2]) : 0; + if (axisOrientation.substr(0, 2) === 'en') { + flatCoordinates.push(x, y, z); + } else { + flatCoordinates.push(y, x, z); + } + } + return flatCoordinates; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Extent|undefined} Envelope. + */ +ol.format.GML2.prototype.readBox_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Box', 'localName should be Box'); + /** @type {Array.<number>} */ + var flatCoordinates = ol.xml.pushParseAndPop([null], + this.BOX_PARSERS_, node, objectStack, this); + return ol.extent.createOrUpdate(flatCoordinates[1][0], + flatCoordinates[1][1], flatCoordinates[1][3], + flatCoordinates[1][4]); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GML2.prototype.innerBoundaryIsParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'innerBoundaryIs', + 'localName should be innerBoundaryIs'); + /** @type {Array.<number>|undefined} */ + var flatLinearRing = ol.xml.pushParseAndPop(undefined, + this.RING_PARSERS, node, objectStack, this); + if (flatLinearRing) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + ol.DEBUG && console.assert(flatLinearRings.length > 0, + 'flatLinearRings should have an array length larger than 0'); + flatLinearRings.push(flatLinearRing); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GML2.prototype.outerBoundaryIsParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'outerBoundaryIs', + 'localName should be outerBoundaryIs'); + /** @type {Array.<number>|undefined} */ + var flatLinearRing = ol.xml.pushParseAndPop(undefined, + this.RING_PARSERS, node, objectStack, this); + if (flatLinearRing) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + ol.DEBUG && console.assert(flatLinearRings.length > 0, + 'flatLinearRings should have an array length larger than 0'); + flatLinearRings[0] = flatLinearRing; + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML2.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'coordinates': ol.xml.makeReplacer( + ol.format.GML2.prototype.readFlatCoordinates_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML2.prototype.FLAT_LINEAR_RINGS_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'innerBoundaryIs': ol.format.GML2.prototype.innerBoundaryIsParser_, + 'outerBoundaryIs': ol.format.GML2.prototype.outerBoundaryIsParser_ + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML2.prototype.BOX_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'coordinates': ol.xml.makeArrayPusher( + ol.format.GML2.prototype.readFlatCoordinates_) + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GML2.prototype.GEOMETRY_PARSERS_ = { + 'http://www.opengis.net/gml' : { + 'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint), + 'MultiPoint': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiPoint), + 'LineString': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readLineString), + 'MultiLineString': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiLineString), + 'LinearRing' : ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readLinearRing), + 'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon), + 'MultiPolygon': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiPolygon), + 'Box': ol.xml.makeReplacer(ol.format.GML2.prototype.readBox_) + } +}; + +goog.provide('ol.format.GPX'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.array'); +goog.require('ol.format.Feature'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.format.XSD'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.Point'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Feature format for reading and writing data in the GPX format. + * + * @constructor + * @extends {ol.format.XMLFeature} + * @param {olx.format.GPXOptions=} opt_options Options. + * @api stable + */ +ol.format.GPX = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.XMLFeature.call(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); + + /** + * @type {function(ol.Feature, Node)|undefined} + * @private + */ + this.readExtensions_ = options.readExtensions; +}; +ol.inherits(ol.format.GPX, ol.format.XMLFeature); + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.format.GPX.NAMESPACE_URIS_ = [ + null, + 'http://www.topografix.com/GPX/1/0', + 'http://www.topografix.com/GPX/1/1' +]; + + +/** + * @const + * @type {string} + * @private + */ +ol.format.GPX.SCHEMA_LOCATION_ = 'http://www.topografix.com/GPX/1/1 ' + + 'http://www.topografix.com/GPX/1/1/gpx.xsd'; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {ol.LayoutOptions} layoutOptions Layout options. + * @param {Node} node Node. + * @param {Object} values Values. + * @private + * @return {Array.<number>} Flat coordinates. + */ +ol.format.GPX.appendCoordinate_ = function(flatCoordinates, layoutOptions, node, values) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + flatCoordinates.push( + parseFloat(node.getAttribute('lon')), + parseFloat(node.getAttribute('lat'))); + if ('ele' in values) { + flatCoordinates.push(/** @type {number} */ (values['ele'])); + delete values['ele']; + layoutOptions.hasZ = true; + } else { + flatCoordinates.push(0); + } + if ('time' in values) { + flatCoordinates.push(/** @type {number} */ (values['time'])); + delete values['time']; + layoutOptions.hasM = true; + } else { + flatCoordinates.push(0); + } + return flatCoordinates; +}; + + +/** + * Choose GeometryLayout based on flags in layoutOptions and adjust flatCoordinates + * and ends arrays by shrinking them accordingly (removing unused zero entries). + * + * @param {ol.LayoutOptions} layoutOptions Layout options. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<number>=} ends Ends. + * @return {ol.geom.GeometryLayout} Layout. + */ +ol.format.GPX.applyLayoutOptions_ = function(layoutOptions, flatCoordinates, ends) { + var layout = ol.geom.GeometryLayout.XY; + var stride = 2; + if (layoutOptions.hasZ && layoutOptions.hasM) { + layout = ol.geom.GeometryLayout.XYZM; + stride = 4; + } else if (layoutOptions.hasZ) { + layout = ol.geom.GeometryLayout.XYZ; + stride = 3; + } else if (layoutOptions.hasM) { + layout = ol.geom.GeometryLayout.XYM; + stride = 3; + } + if (stride !== 4) { + var i, ii; + for (i = 0, ii = flatCoordinates.length / 4; i < ii; i++) { + flatCoordinates[i * stride] = flatCoordinates[i * 4]; + flatCoordinates[i * stride + 1] = flatCoordinates[i * 4 + 1]; + if (layoutOptions.hasZ) { + flatCoordinates[i * stride + 2] = flatCoordinates[i * 4 + 2]; + } + if (layoutOptions.hasM) { + flatCoordinates[i * stride + 2] = flatCoordinates[i * 4 + 3]; + } + } + flatCoordinates.length = flatCoordinates.length / 4 * stride; + if (ends) { + for (i = 0, ii = ends.length; i < ii; i++) { + ends[i] = ends[i] / 4 * stride; + } + } + } + return layout; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.parseLink_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'link', 'localName should be link'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var href = node.getAttribute('href'); + if (href !== null) { + values['link'] = href; + } + ol.xml.parseNode(ol.format.GPX.LINK_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.parseExtensions_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'extensions', + 'localName should be extensions'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + values['extensionsNode_'] = node; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.parseRtePt_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'rtept', 'localName should be rtept'); + var values = ol.xml.pushParseAndPop( + {}, ol.format.GPX.RTEPT_PARSERS_, node, objectStack); + if (values) { + var rteValues = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var flatCoordinates = /** @type {Array.<number>} */ + (rteValues['flatCoordinates']); + var layoutOptions = /** @type {ol.LayoutOptions} */ + (rteValues['layoutOptions']); + ol.format.GPX.appendCoordinate_(flatCoordinates, layoutOptions, node, values); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.parseTrkPt_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'trkpt', 'localName should be trkpt'); + var values = ol.xml.pushParseAndPop( + {}, ol.format.GPX.TRKPT_PARSERS_, node, objectStack); + if (values) { + var trkValues = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var flatCoordinates = /** @type {Array.<number>} */ + (trkValues['flatCoordinates']); + var layoutOptions = /** @type {ol.LayoutOptions} */ + (trkValues['layoutOptions']); + ol.format.GPX.appendCoordinate_(flatCoordinates, layoutOptions, node, values); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.parseTrkSeg_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'trkseg', + 'localName should be trkseg'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + ol.xml.parseNode(ol.format.GPX.TRKSEG_PARSERS_, node, objectStack); + var flatCoordinates = /** @type {Array.<number>} */ + (values['flatCoordinates']); + var ends = /** @type {Array.<number>} */ (values['ends']); + ends.push(flatCoordinates.length); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Feature|undefined} Track. + */ +ol.format.GPX.readRte_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'rte', 'localName should be rte'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var values = ol.xml.pushParseAndPop({ + 'flatCoordinates': [], + 'layoutOptions': {} + }, ol.format.GPX.RTE_PARSERS_, node, objectStack); + if (!values) { + return undefined; + } + var flatCoordinates = /** @type {Array.<number>} */ + (values['flatCoordinates']); + delete values['flatCoordinates']; + var layoutOptions = /** @type {ol.LayoutOptions} */ (values['layoutOptions']); + delete values['layoutOptions']; + var layout = ol.format.GPX.applyLayoutOptions_(layoutOptions, flatCoordinates); + var geometry = new ol.geom.LineString(null); + geometry.setFlatCoordinates(layout, flatCoordinates); + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setProperties(values); + return feature; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Feature|undefined} Track. + */ +ol.format.GPX.readTrk_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'trk', 'localName should be trk'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var values = ol.xml.pushParseAndPop({ + 'flatCoordinates': [], + 'ends': [], + 'layoutOptions': {} + }, ol.format.GPX.TRK_PARSERS_, node, objectStack); + if (!values) { + return undefined; + } + var flatCoordinates = /** @type {Array.<number>} */ + (values['flatCoordinates']); + delete values['flatCoordinates']; + var ends = /** @type {Array.<number>} */ (values['ends']); + delete values['ends']; + var layoutOptions = /** @type {ol.LayoutOptions} */ (values['layoutOptions']); + delete values['layoutOptions']; + var layout = ol.format.GPX.applyLayoutOptions_(layoutOptions, flatCoordinates, ends); + var geometry = new ol.geom.MultiLineString(null); + geometry.setFlatCoordinates(layout, flatCoordinates, ends); + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setProperties(values); + return feature; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Feature|undefined} Waypoint. + */ +ol.format.GPX.readWpt_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'wpt', 'localName should be wpt'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var values = ol.xml.pushParseAndPop( + {}, ol.format.GPX.WPT_PARSERS_, node, objectStack); + if (!values) { + return undefined; + } + var layoutOptions = /** @type {ol.LayoutOptions} */ ({}); + var coordinates = ol.format.GPX.appendCoordinate_([], layoutOptions, node, values); + var layout = ol.format.GPX.applyLayoutOptions_(layoutOptions, coordinates); + var geometry = new ol.geom.Point(coordinates, layout); + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setProperties(values); + return feature; +}; + + +/** + * @const + * @type {Object.<string, function(Node, Array.<*>): (ol.Feature|undefined)>} + * @private + */ +ol.format.GPX.FEATURE_READER_ = { + 'rte': ol.format.GPX.readRte_, + 'trk': ol.format.GPX.readTrk_, + 'wpt': ol.format.GPX.readWpt_ +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.GPX_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'rte': ol.xml.makeArrayPusher(ol.format.GPX.readRte_), + 'trk': ol.xml.makeArrayPusher(ol.format.GPX.readTrk_), + 'wpt': ol.xml.makeArrayPusher(ol.format.GPX.readWpt_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.LINK_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'text': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkText'), + 'type': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkType') + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.RTE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'link': ol.format.GPX.parseLink_, + 'number': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), + 'extensions': ol.format.GPX.parseExtensions_, + 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'rtept': ol.format.GPX.parseRtePt_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.RTEPT_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.TRK_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'link': ol.format.GPX.parseLink_, + 'number': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), + 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'extensions': ol.format.GPX.parseExtensions_, + 'trkseg': ol.format.GPX.parseTrkSeg_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.TRKSEG_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'trkpt': ol.format.GPX.parseTrkPt_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.TRKPT_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.GPX.WPT_PARSERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime), + 'magvar': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'geoidheight': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'link': ol.format.GPX.parseLink_, + 'sym': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'fix': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'sat': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'hdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'vdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'pdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'ageofdgpsdata': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'dgpsid': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), + 'extensions': ol.format.GPX.parseExtensions_ + }); + + +/** + * @param {Array.<ol.Feature>} features List of features. + * @private + */ +ol.format.GPX.prototype.handleReadExtensions_ = function(features) { + if (!features) { + features = []; + } + for (var i = 0, ii = features.length; i < ii; ++i) { + var feature = features[i]; + if (this.readExtensions_) { + var extensionsNode = feature.get('extensionsNode_') || null; + this.readExtensions_(feature, extensionsNode); + } + feature.set('extensionsNode_', undefined); + } +}; + + +/** + * Read the first feature from a GPX source. + * Routes (`<rte>`) are converted into LineString geometries, and tracks (`<trk>`) + * into MultiLineString. Any properties on route and track waypoints are ignored. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable + */ +ol.format.GPX.prototype.readFeature; + + +/** + * @inheritDoc + */ +ol.format.GPX.prototype.readFeatureFromNode = function(node, opt_options) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + if (!ol.array.includes(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) { + return null; + } + var featureReader = ol.format.GPX.FEATURE_READER_[node.localName]; + if (!featureReader) { + return null; + } + var feature = featureReader(node, [this.getReadOptions(node, opt_options)]); + if (!feature) { + return null; + } + this.handleReadExtensions_([feature]); + return feature; +}; + + +/** + * Read all features from a GPX source. + * Routes (`<rte>`) are converted into LineString geometries, and tracks (`<trk>`) + * into MultiLineString. Any properties on route and track waypoints are ignored. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.GPX.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.GPX.prototype.readFeaturesFromNode = function(node, opt_options) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + if (!ol.array.includes(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) { + return []; + } + if (node.localName == 'gpx') { + /** @type {Array.<ol.Feature>} */ + var features = ol.xml.pushParseAndPop([], ol.format.GPX.GPX_PARSERS_, + node, [this.getReadOptions(node, opt_options)]); + if (features) { + this.handleReadExtensions_(features); + return features; + } else { + return []; + } + } + return []; +}; + + +/** + * Read the projection from a GPX source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.GPX.prototype.readProjection; + + +/** + * @param {Node} node Node. + * @param {string} value Value for the link's `href` attribute. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GPX.writeLink_ = function(node, value, objectStack) { + node.setAttribute('href', value); + var context = objectStack[objectStack.length - 1]; + var properties = context['properties']; + var link = [ + properties['linkText'], + properties['linkType'] + ]; + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ ({node: node}), + ol.format.GPX.LINK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + link, objectStack, ol.format.GPX.LINK_SEQUENCE_); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) { + var context = objectStack[objectStack.length - 1]; + var parentNode = context.node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode), + 'parentNode should be an XML node'); + var namespaceURI = parentNode.namespaceURI; + var properties = context['properties']; + //FIXME Projection handling + ol.xml.setAttributeNS(node, null, 'lat', coordinate[1]); + ol.xml.setAttributeNS(node, null, 'lon', coordinate[0]); + var geometryLayout = context['geometryLayout']; + switch (geometryLayout) { + case ol.geom.GeometryLayout.XYZM: + if (coordinate[3] !== 0) { + properties['time'] = coordinate[3]; + } + // fall through + case ol.geom.GeometryLayout.XYZ: + if (coordinate[2] !== 0) { + properties['ele'] = coordinate[2]; + } + break; + case ol.geom.GeometryLayout.XYM: + if (coordinate[2] !== 0) { + properties['time'] = coordinate[2]; + } + break; + default: + // pass + } + var orderedKeys = (node.nodeName == 'rtept') ? + ol.format.GPX.RTEPT_TYPE_SEQUENCE_[namespaceURI] : + ol.format.GPX.WPT_TYPE_SEQUENCE_[namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ + ({node: node, 'properties': properties}), + ol.format.GPX.WPT_TYPE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.writeRte_ = function(node, feature, objectStack) { + var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); + var properties = feature.getProperties(); + var context = {node: node, 'properties': properties}; + var geometry = feature.getGeometry(); + if (geometry) { + geometry = /** @type {ol.geom.LineString} */ + (ol.format.Feature.transformWithOptions(geometry, true, options)); + context['geometryLayout'] = geometry.getLayout(); + properties['rtept'] = geometry.getCoordinates(); + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.GPX.RTE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, + ol.format.GPX.RTE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.writeTrk_ = function(node, feature, objectStack) { + var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); + var properties = feature.getProperties(); + /** @type {ol.XmlNodeStackItem} */ + var context = {node: node, 'properties': properties}; + var geometry = feature.getGeometry(); + if (geometry) { + geometry = /** @type {ol.geom.MultiLineString} */ + (ol.format.Feature.transformWithOptions(geometry, true, options)); + properties['trkseg'] = geometry.getLineStrings(); + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.GPX.TRK_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, + ol.format.GPX.TRK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LineString} lineString LineString. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.writeTrkSeg_ = function(node, lineString, objectStack) { + /** @type {ol.XmlNodeStackItem} */ + var context = {node: node, 'geometryLayout': lineString.getLayout(), + 'properties': {}}; + ol.xml.pushSerializeAndPop(context, + ol.format.GPX.TRKSEG_SERIALIZERS_, ol.format.GPX.TRKSEG_NODE_FACTORY_, + lineString.getCoordinates(), objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.writeWpt_ = function(node, feature, objectStack) { + var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); + var context = objectStack[objectStack.length - 1]; + context['properties'] = feature.getProperties(); + var geometry = feature.getGeometry(); + if (geometry) { + geometry = /** @type {ol.geom.Point} */ + (ol.format.Feature.transformWithOptions(geometry, true, options)); + context['geometryLayout'] = geometry.getLayout(); + ol.format.GPX.writeWptType_(node, geometry.getCoordinates(), objectStack); + } +}; + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type']; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GPX.LINK_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'text': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'rtept' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GPX.RTE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), + 'number': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'rtept': ol.xml.makeArraySerializer(ol.xml.makeChildAppender( + ol.format.GPX.writeWptType_)) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.GPX.RTEPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'ele', 'time' + ]); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'trkseg' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GPX.TRK_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), + 'number': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'trkseg': ol.xml.makeArraySerializer(ol.xml.makeChildAppender( + ol.format.GPX.writeTrkSeg_)) + }); + + +/** + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt'); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GPX.TRKSEG_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'trkpt': ol.xml.makeChildAppender(ol.format.GPX.writeWptType_) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.GPX.WPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'ele', 'time', 'magvar', 'geoidheight', 'name', 'cmt', 'desc', 'src', + 'link', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop', 'pdop', + 'ageofdgpsdata', 'dgpsid' + ]); + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GPX.WPT_TYPE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'time': ol.xml.makeChildAppender(ol.format.XSD.writeDateTimeTextNode), + 'magvar': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'geoidheight': ol.xml.makeChildAppender( + ol.format.XSD.writeDecimalTextNode), + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), + 'sym': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'fix': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'sat': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode), + 'hdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'vdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'pdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'ageofdgpsdata': ol.xml.makeChildAppender( + ol.format.XSD.writeDecimalTextNode), + 'dgpsid': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode) + }); + + +/** + * @const + * @type {Object.<string, string>} + * @private + */ +ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = { + 'Point': 'wpt', + 'LineString': 'rte', + 'MultiLineString': 'trk' +}; + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private + */ +ol.format.GPX.GPX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + var geometry = /** @type {ol.Feature} */ (value).getGeometry(); + if (geometry) { + var nodeName = ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_[geometry.getType()]; + if (nodeName) { + var parentNode = objectStack[objectStack.length - 1].node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode), + 'parentNode should be an XML node'); + return ol.xml.createElementNS(parentNode.namespaceURI, nodeName); + } + } +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.GPX.GPX_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'rte': ol.xml.makeChildAppender(ol.format.GPX.writeRte_), + 'trk': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_), + 'wpt': ol.xml.makeChildAppender(ol.format.GPX.writeWpt_) + }); + + +/** + * Encode an array of features in the GPX format. + * LineString geometries are output as routes (`<rte>`), and MultiLineString + * as tracks (`<trk>`). + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Result. + * @api stable + */ +ol.format.GPX.prototype.writeFeatures; + + +/** + * Encode an array of features in the GPX format as an XML node. + * LineString geometries are output as routes (`<rte>`), and MultiLineString + * as tracks (`<trk>`). + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api + */ +ol.format.GPX.prototype.writeFeaturesNode = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + //FIXME Serialize metadata + var gpx = ol.xml.createElementNS('http://www.topografix.com/GPX/1/1', 'gpx'); + var xmlnsUri = 'http://www.w3.org/2000/xmlns/'; + var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance'; + ol.xml.setAttributeNS(gpx, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri); + ol.xml.setAttributeNS(gpx, xmlSchemaInstanceUri, 'xsi:schemaLocation', + ol.format.GPX.SCHEMA_LOCATION_); + gpx.setAttribute('version', '1.1'); + gpx.setAttribute('creator', 'OpenLayers 3'); + + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ + ({node: gpx}), ol.format.GPX.GPX_SERIALIZERS_, + ol.format.GPX.GPX_NODE_FACTORY_, features, [opt_options]); + return gpx; +}; + +goog.provide('ol.format.TextFeature'); + +goog.require('ol'); +goog.require('ol.format.Feature'); +goog.require('ol.format.FormatType'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for text feature formats. + * + * @constructor + * @extends {ol.format.Feature} + */ +ol.format.TextFeature = function() { + ol.format.Feature.call(this); +}; +ol.inherits(ol.format.TextFeature, ol.format.Feature); + + +/** + * @param {Document|Node|Object|string} source Source. + * @private + * @return {string} Text. + */ +ol.format.TextFeature.prototype.getText_ = function(source) { + if (typeof source === 'string') { + return source; + } else { + return ''; + } +}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.getType = function() { + return ol.format.FormatType.TEXT; +}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.readFeature = function(source, opt_options) { + return this.readFeatureFromText( + this.getText_(source), this.adaptOptions(opt_options)); +}; + + +/** + * @abstract + * @param {string} text Text. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {ol.Feature} Feature. + */ +ol.format.TextFeature.prototype.readFeatureFromText = function(text, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.readFeatures = function(source, opt_options) { + return this.readFeaturesFromText( + this.getText_(source), this.adaptOptions(opt_options)); +}; + + +/** + * @abstract + * @param {string} text Text. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {Array.<ol.Feature>} Features. + */ +ol.format.TextFeature.prototype.readFeaturesFromText = function(text, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.readGeometry = function(source, opt_options) { + return this.readGeometryFromText( + this.getText_(source), this.adaptOptions(opt_options)); +}; + + +/** + * @abstract + * @param {string} text Text. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.TextFeature.prototype.readGeometryFromText = function(text, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.readProjection = function(source) { + return this.readProjectionFromText(this.getText_(source)); +}; + + +/** + * @param {string} text Text. + * @protected + * @return {ol.proj.Projection} Projection. + */ +ol.format.TextFeature.prototype.readProjectionFromText = function(text) { + return this.defaultDataProjection; +}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.writeFeature = function(feature, opt_options) { + return this.writeFeatureText(feature, this.adaptOptions(opt_options)); +}; + + +/** + * @abstract + * @param {ol.Feature} feature Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @protected + * @return {string} Text. + */ +ol.format.TextFeature.prototype.writeFeatureText = function(feature, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.writeFeatures = function( + features, opt_options) { + return this.writeFeaturesText(features, this.adaptOptions(opt_options)); +}; + + +/** + * @abstract + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @protected + * @return {string} Text. + */ +ol.format.TextFeature.prototype.writeFeaturesText = function(features, opt_options) {}; + + +/** + * @inheritDoc + */ +ol.format.TextFeature.prototype.writeGeometry = function( + geometry, opt_options) { + return this.writeGeometryText(geometry, this.adaptOptions(opt_options)); +}; + + +/** + * @abstract + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @protected + * @return {string} Text. + */ +ol.format.TextFeature.prototype.writeGeometryText = function(geometry, opt_options) {}; + +goog.provide('ol.format.IGC'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.TextFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Feature format for `*.igc` flight recording files. + * + * @constructor + * @extends {ol.format.TextFeature} + * @param {olx.format.IGCOptions=} opt_options Options. + * @api + */ +ol.format.IGC = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.TextFeature.call(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); + + /** + * @private + * @type {ol.format.IGC.Z} + */ + this.altitudeMode_ = options.altitudeMode ? + options.altitudeMode : ol.format.IGC.Z.NONE; + +}; +ol.inherits(ol.format.IGC, ol.format.TextFeature); + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.IGC.EXTENSIONS_ = ['.igc']; + + +/** + * @const + * @type {RegExp} + * @private + */ +ol.format.IGC.B_RECORD_RE_ = + /^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/; + + +/** + * @const + * @type {RegExp} + * @private + */ +ol.format.IGC.H_RECORD_RE_ = /^H.([A-Z]{3}).*?:(.*)/; + + +/** + * @const + * @type {RegExp} + * @private + */ +ol.format.IGC.HFDTE_RECORD_RE_ = /^HFDTE(\d{2})(\d{2})(\d{2})/; + + +/** + * A regular expression matching the newline characters `\r\n`, `\r` and `\n`. + * + * @const + * @type {RegExp} + * @private + */ +ol.format.IGC.NEWLINE_RE_ = /\r\n|\r|\n/; + + +/** + * @inheritDoc + */ +ol.format.IGC.prototype.getExtensions = function() { + return ol.format.IGC.EXTENSIONS_; +}; + + +/** + * Read the feature from the IGC source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api + */ +ol.format.IGC.prototype.readFeature; + + +/** + * @inheritDoc + */ +ol.format.IGC.prototype.readFeatureFromText = function(text, opt_options) { + var altitudeMode = this.altitudeMode_; + var lines = text.split(ol.format.IGC.NEWLINE_RE_); + /** @type {Object.<string, string>} */ + var properties = {}; + var flatCoordinates = []; + var year = 2000; + var month = 0; + var day = 1; + var lastDateTime = -1; + var i, ii; + for (i = 0, ii = lines.length; i < ii; ++i) { + var line = lines[i]; + var m; + if (line.charAt(0) == 'B') { + m = ol.format.IGC.B_RECORD_RE_.exec(line); + if (m) { + var hour = parseInt(m[1], 10); + var minute = parseInt(m[2], 10); + var second = parseInt(m[3], 10); + var y = parseInt(m[4], 10) + parseInt(m[5], 10) / 60000; + if (m[6] == 'S') { + y = -y; + } + var x = parseInt(m[7], 10) + parseInt(m[8], 10) / 60000; + if (m[9] == 'W') { + x = -x; + } + flatCoordinates.push(x, y); + if (altitudeMode != ol.format.IGC.Z.NONE) { + var z; + if (altitudeMode == ol.format.IGC.Z.GPS) { + z = parseInt(m[11], 10); + } else if (altitudeMode == ol.format.IGC.Z.BAROMETRIC) { + z = parseInt(m[12], 10); + } else { + ol.DEBUG && console.assert(false, 'Unknown altitude mode.'); + z = 0; + } + flatCoordinates.push(z); + } + var dateTime = Date.UTC(year, month, day, hour, minute, second); + // Detect UTC midnight wrap around. + if (dateTime < lastDateTime) { + dateTime = Date.UTC(year, month, day + 1, hour, minute, second); + } + flatCoordinates.push(dateTime / 1000); + lastDateTime = dateTime; + } + } else if (line.charAt(0) == 'H') { + m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line); + if (m) { + day = parseInt(m[1], 10); + month = parseInt(m[2], 10) - 1; + year = 2000 + parseInt(m[3], 10); + } else { + m = ol.format.IGC.H_RECORD_RE_.exec(line); + if (m) { + properties[m[1]] = m[2].trim(); + } + } + } + } + if (flatCoordinates.length === 0) { + return null; + } + var lineString = new ol.geom.LineString(null); + var layout = altitudeMode == ol.format.IGC.Z.NONE ? + ol.geom.GeometryLayout.XYM : ol.geom.GeometryLayout.XYZM; + lineString.setFlatCoordinates(layout, flatCoordinates); + var feature = new ol.Feature(ol.format.Feature.transformWithOptions( + lineString, false, opt_options)); + feature.setProperties(properties); + return feature; +}; + + +/** + * Read the feature from the source. As IGC sources contain a single + * feature, this will return the feature in an array. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api + */ +ol.format.IGC.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.IGC.prototype.readFeaturesFromText = function(text, opt_options) { + var feature = this.readFeatureFromText(text, opt_options); + if (feature) { + return [feature]; + } else { + return []; + } +}; + + +/** + * Read the projection from the IGC source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api + */ +ol.format.IGC.prototype.readProjection; + + +/** + * IGC altitude/z. One of 'barometric', 'gps', 'none'. + * @enum {string} + */ +ol.format.IGC.Z = { + BAROMETRIC: 'barometric', + GPS: 'gps', + NONE: 'none' +}; + +goog.provide('ol.style.IconImage'); + +goog.require('ol'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); +goog.require('ol.events.EventType'); +goog.require('ol.Image'); +goog.require('ol.style'); + + +/** + * @constructor + * @param {Image|HTMLCanvasElement} image Image. + * @param {string|undefined} src Src. + * @param {ol.Size} size Size. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Image.State} imageState Image state. + * @param {ol.Color} color Color. + * @extends {ol.events.EventTarget} + */ +ol.style.IconImage = function(image, src, size, crossOrigin, imageState, + color) { + + ol.events.EventTarget.call(this); + + /** + * @private + * @type {Image|HTMLCanvasElement} + */ + this.hitDetectionImage_ = null; + + /** + * @private + * @type {Image|HTMLCanvasElement} + */ + this.image_ = !image ? new Image() : image; + + if (crossOrigin !== null) { + this.image_.crossOrigin = crossOrigin; + } + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = color ? + /** @type {HTMLCanvasElement} */ (document.createElement('CANVAS')) : + null; + + /** + * @private + * @type {ol.Color} + */ + this.color_ = color; + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.imageListenerKeys_ = null; + + /** + * @private + * @type {ol.Image.State} + */ + this.imageState_ = imageState; + + /** + * @private + * @type {ol.Size} + */ + this.size_ = size; + + /** + * @private + * @type {string|undefined} + */ + this.src_ = src; + + /** + * @private + * @type {boolean} + */ + this.tainting_ = false; + if (this.imageState_ == ol.Image.State.LOADED) { + this.determineTainting_(); + } + +}; +ol.inherits(ol.style.IconImage, ol.events.EventTarget); + + +/** + * @param {Image|HTMLCanvasElement} image Image. + * @param {string} src Src. + * @param {ol.Size} size Size. + * @param {?string} crossOrigin Cross origin. + * @param {ol.Image.State} imageState Image state. + * @param {ol.Color} color Color. + * @return {ol.style.IconImage} Icon image. + */ +ol.style.IconImage.get = function(image, src, size, crossOrigin, imageState, + color) { + var iconImageCache = ol.style.iconImageCache; + var iconImage = iconImageCache.get(src, crossOrigin, color); + if (!iconImage) { + iconImage = new ol.style.IconImage( + image, src, size, crossOrigin, imageState, color); + iconImageCache.set(src, crossOrigin, color, iconImage); + } + return iconImage; +}; + + +/** + * @private + */ +ol.style.IconImage.prototype.determineTainting_ = function() { + var context = ol.dom.createCanvasContext2D(1, 1); + try { + context.drawImage(this.image_, 0, 0); + context.getImageData(0, 0, 1, 1); + } catch (e) { + this.tainting_ = true; + } +}; + + +/** + * @private + */ +ol.style.IconImage.prototype.dispatchChangeEvent_ = function() { + this.dispatchEvent(ol.events.EventType.CHANGE); +}; + + +/** + * @private + */ +ol.style.IconImage.prototype.handleImageError_ = function() { + this.imageState_ = ol.Image.State.ERROR; + this.unlistenImage_(); + this.dispatchChangeEvent_(); +}; + + +/** + * @private + */ +ol.style.IconImage.prototype.handleImageLoad_ = function() { + this.imageState_ = ol.Image.State.LOADED; + if (this.size_) { + this.image_.width = this.size_[0]; + this.image_.height = this.size_[1]; + } + this.size_ = [this.image_.width, this.image_.height]; + this.unlistenImage_(); + this.determineTainting_(); + this.replaceColor_(); + this.dispatchChangeEvent_(); +}; + + +/** + * @param {number} pixelRatio Pixel ratio. + * @return {Image|HTMLCanvasElement} Image or Canvas element. + */ +ol.style.IconImage.prototype.getImage = function(pixelRatio) { + return this.canvas_ ? this.canvas_ : this.image_; +}; + + +/** + * @return {ol.Image.State} Image state. + */ +ol.style.IconImage.prototype.getImageState = function() { + return this.imageState_; +}; + + +/** + * @param {number} pixelRatio Pixel ratio. + * @return {Image|HTMLCanvasElement} Image element. + */ +ol.style.IconImage.prototype.getHitDetectionImage = function(pixelRatio) { + if (!this.hitDetectionImage_) { + if (this.tainting_) { + var width = this.size_[0]; + var height = this.size_[1]; + var context = ol.dom.createCanvasContext2D(width, height); + context.fillRect(0, 0, width, height); + this.hitDetectionImage_ = context.canvas; + } else { + this.hitDetectionImage_ = this.image_; + } + } + return this.hitDetectionImage_; +}; + + +/** + * @return {ol.Size} Image size. + */ +ol.style.IconImage.prototype.getSize = function() { + return this.size_; +}; + + +/** + * @return {string|undefined} Image src. + */ +ol.style.IconImage.prototype.getSrc = function() { + return this.src_; +}; + + +/** + * Load not yet loaded URI. + */ +ol.style.IconImage.prototype.load = function() { + if (this.imageState_ == ol.Image.State.IDLE) { + ol.DEBUG && console.assert(this.src_ !== undefined, + 'this.src_ must not be undefined'); + ol.DEBUG && console.assert(!this.imageListenerKeys_, + 'no listener keys existing'); + this.imageState_ = ol.Image.State.LOADING; + this.imageListenerKeys_ = [ + ol.events.listenOnce(this.image_, ol.events.EventType.ERROR, + this.handleImageError_, this), + ol.events.listenOnce(this.image_, ol.events.EventType.LOAD, + this.handleImageLoad_, this) + ]; + try { + this.image_.src = this.src_; + } catch (e) { + this.handleImageError_(); + } + } +}; + + +/** + * @private + */ +ol.style.IconImage.prototype.replaceColor_ = function() { + if (this.tainting_ || this.color_ === null) { + return; + } + + this.canvas_.width = this.image_.width; + this.canvas_.height = this.image_.height; + + var ctx = this.canvas_.getContext('2d'); + ctx.drawImage(this.image_, 0, 0); + + var imgData = ctx.getImageData(0, 0, this.image_.width, this.image_.height); + var data = imgData.data; + var r = this.color_[0] / 255.0; + var g = this.color_[1] / 255.0; + var b = this.color_[2] / 255.0; + + for (var i = 0, ii = data.length; i < ii; i += 4) { + data[i] *= r; + data[i + 1] *= g; + data[i + 2] *= b; + } + ctx.putImageData(imgData, 0, 0); +}; + + +/** + * Discards event handlers which listen for load completion or errors. + * + * @private + */ +ol.style.IconImage.prototype.unlistenImage_ = function() { + this.imageListenerKeys_.forEach(ol.events.unlistenByKey); + this.imageListenerKeys_ = null; +}; + +goog.provide('ol.style.Icon'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.color'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.Image'); +goog.require('ol.style.IconImage'); +goog.require('ol.style.Image'); + + +/** + * @classdesc + * Set icon style for vector features. + * + * @constructor + * @param {olx.style.IconOptions=} opt_options Options. + * @extends {ol.style.Image} + * @api + */ +ol.style.Icon = function(opt_options) { + + var options = opt_options || {}; + + /** + * @private + * @type {Array.<number>} + */ + this.anchor_ = options.anchor !== undefined ? options.anchor : [0.5, 0.5]; + + /** + * @private + * @type {Array.<number>} + */ + this.normalizedAnchor_ = null; + + /** + * @private + * @type {ol.style.Icon.Origin} + */ + this.anchorOrigin_ = options.anchorOrigin !== undefined ? + options.anchorOrigin : ol.style.Icon.Origin.TOP_LEFT; + + /** + * @private + * @type {ol.style.Icon.AnchorUnits} + */ + this.anchorXUnits_ = options.anchorXUnits !== undefined ? + options.anchorXUnits : ol.style.Icon.AnchorUnits.FRACTION; + + /** + * @private + * @type {ol.style.Icon.AnchorUnits} + */ + this.anchorYUnits_ = options.anchorYUnits !== undefined ? + options.anchorYUnits : ol.style.Icon.AnchorUnits.FRACTION; + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + options.crossOrigin !== undefined ? options.crossOrigin : null; + + /** + * @type {Image|HTMLCanvasElement} + */ + var image = options.img !== undefined ? options.img : null; + + /** + * @type {ol.Size} + */ + var imgSize = options.imgSize !== undefined ? options.imgSize : null; + + /** + * @type {string|undefined} + */ + var src = options.src; + + ol.asserts.assert(!(src !== undefined && image), + 4); // `image` and `src` cannot be provided at the same time + ol.asserts.assert(!image || (image && imgSize), + 5); // `imgSize` must be set when `image` is provided + + if ((src === undefined || src.length === 0) && image) { + src = image.src || ol.getUid(image).toString(); + } + ol.asserts.assert(src !== undefined && src.length > 0, + 6); // A defined and non-empty `src` or `image` must be provided + + /** + * @type {ol.Image.State} + */ + var imageState = options.src !== undefined ? + ol.Image.State.IDLE : ol.Image.State.LOADED; + + /** + * @private + * @type {ol.Color} + */ + this.color_ = options.color !== undefined ? ol.color.asArray(options.color) : + null; + + /** + * @private + * @type {ol.style.IconImage} + */ + this.iconImage_ = ol.style.IconImage.get( + image, /** @type {string} */ (src), imgSize, this.crossOrigin_, imageState, this.color_); + + /** + * @private + * @type {Array.<number>} + */ + this.offset_ = options.offset !== undefined ? options.offset : [0, 0]; + + /** + * @private + * @type {ol.style.Icon.Origin} + */ + this.offsetOrigin_ = options.offsetOrigin !== undefined ? + options.offsetOrigin : ol.style.Icon.Origin.TOP_LEFT; + + /** + * @private + * @type {Array.<number>} + */ + this.origin_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.size_ = options.size !== undefined ? options.size : null; + + /** + * @type {number} + */ + var opacity = options.opacity !== undefined ? options.opacity : 1; + + /** + * @type {boolean} + */ + var rotateWithView = options.rotateWithView !== undefined ? + options.rotateWithView : false; + + /** + * @type {number} + */ + var rotation = options.rotation !== undefined ? options.rotation : 0; + + /** + * @type {number} + */ + var scale = options.scale !== undefined ? options.scale : 1; + + /** + * @type {boolean} + */ + var snapToPixel = options.snapToPixel !== undefined ? + options.snapToPixel : true; + + ol.style.Image.call(this, { + opacity: opacity, + rotation: rotation, + scale: scale, + snapToPixel: snapToPixel, + rotateWithView: rotateWithView + }); + +}; +ol.inherits(ol.style.Icon, ol.style.Image); + + +/** + * Clones the style. + * @return {ol.style.Icon} The cloned style. + * @api + */ +ol.style.Icon.prototype.clone = function() { + var oldImage = this.getImage(1); + var newImage; + if (this.iconImage_.getImageState() === ol.Image.State.LOADED) { + if (oldImage.tagName.toUpperCase() === 'IMG') { + newImage = /** @type {Image} */ (oldImage.cloneNode(true)); + } else { + newImage = /** @type {HTMLCanvasElement} */ (document.createElement('canvas')); + var context = newImage.getContext('2d'); + newImage.width = oldImage.width; + newImage.height = oldImage.height; + context.drawImage(oldImage, 0, 0); + } + } + return new ol.style.Icon({ + anchor: this.anchor_.slice(), + anchorOrigin: this.anchorOrigin_, + anchorXUnits: this.anchorXUnits_, + anchorYUnits: this.anchorYUnits_, + crossOrigin: this.crossOrigin_, + color: (this.color_ && this.color_.slice) ? this.color_.slice() : this.color_ || undefined, + img: newImage ? newImage : undefined, + imgSize: newImage ? this.iconImage_.getSize().slice() : undefined, + src: newImage ? undefined : this.getSrc(), + offset: this.offset_.slice(), + offsetOrigin: this.offsetOrigin_, + size: this.size_ !== null ? this.size_.slice() : undefined, + opacity: this.getOpacity(), + scale: this.getScale(), + snapToPixel: this.getSnapToPixel(), + rotation: this.getRotation(), + rotateWithView: this.getRotateWithView() + }); +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.Icon.prototype.getAnchor = function() { + if (this.normalizedAnchor_) { + return this.normalizedAnchor_; + } + var anchor = this.anchor_; + var size = this.getSize(); + if (this.anchorXUnits_ == ol.style.Icon.AnchorUnits.FRACTION || + this.anchorYUnits_ == ol.style.Icon.AnchorUnits.FRACTION) { + if (!size) { + return null; + } + anchor = this.anchor_.slice(); + if (this.anchorXUnits_ == ol.style.Icon.AnchorUnits.FRACTION) { + anchor[0] *= size[0]; + } + if (this.anchorYUnits_ == ol.style.Icon.AnchorUnits.FRACTION) { + anchor[1] *= size[1]; + } + } + + if (this.anchorOrigin_ != ol.style.Icon.Origin.TOP_LEFT) { + if (!size) { + return null; + } + if (anchor === this.anchor_) { + anchor = this.anchor_.slice(); + } + if (this.anchorOrigin_ == ol.style.Icon.Origin.TOP_RIGHT || + this.anchorOrigin_ == ol.style.Icon.Origin.BOTTOM_RIGHT) { + anchor[0] = -anchor[0] + size[0]; + } + if (this.anchorOrigin_ == ol.style.Icon.Origin.BOTTOM_LEFT || + this.anchorOrigin_ == ol.style.Icon.Origin.BOTTOM_RIGHT) { + anchor[1] = -anchor[1] + size[1]; + } + } + this.normalizedAnchor_ = anchor; + return this.normalizedAnchor_; +}; + + +/** + * Get the icon color. + * @return {ol.Color} Color. + * @api + */ +ol.style.Icon.prototype.getColor = function() { + return this.color_; +}; + + +/** + * Get the image icon. + * @param {number} pixelRatio Pixel ratio. + * @return {Image|HTMLCanvasElement} Image or Canvas element. + * @api + */ +ol.style.Icon.prototype.getImage = function(pixelRatio) { + return this.iconImage_.getImage(pixelRatio); +}; + + +/** + * Real Image size used. + * @return {ol.Size} Size. + */ +ol.style.Icon.prototype.getImageSize = function() { + return this.iconImage_.getSize(); +}; + + +/** + * @inheritDoc + */ +ol.style.Icon.prototype.getHitDetectionImageSize = function() { + return this.getImageSize(); +}; + + +/** + * @inheritDoc + */ +ol.style.Icon.prototype.getImageState = function() { + return this.iconImage_.getImageState(); +}; + + +/** + * @inheritDoc + */ +ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) { + return this.iconImage_.getHitDetectionImage(pixelRatio); +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.Icon.prototype.getOrigin = function() { + if (this.origin_) { + return this.origin_; + } + var offset = this.offset_; + + if (this.offsetOrigin_ != ol.style.Icon.Origin.TOP_LEFT) { + var size = this.getSize(); + var iconImageSize = this.iconImage_.getSize(); + if (!size || !iconImageSize) { + return null; + } + offset = offset.slice(); + if (this.offsetOrigin_ == ol.style.Icon.Origin.TOP_RIGHT || + this.offsetOrigin_ == ol.style.Icon.Origin.BOTTOM_RIGHT) { + offset[0] = iconImageSize[0] - size[0] - offset[0]; + } + if (this.offsetOrigin_ == ol.style.Icon.Origin.BOTTOM_LEFT || + this.offsetOrigin_ == ol.style.Icon.Origin.BOTTOM_RIGHT) { + offset[1] = iconImageSize[1] - size[1] - offset[1]; + } + } + this.origin_ = offset; + return this.origin_; +}; + + +/** + * Get the image URL. + * @return {string|undefined} Image src. + * @api + */ +ol.style.Icon.prototype.getSrc = function() { + return this.iconImage_.getSrc(); +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.Icon.prototype.getSize = function() { + return !this.size_ ? this.iconImage_.getSize() : this.size_; +}; + + +/** + * @inheritDoc + */ +ol.style.Icon.prototype.listenImageChange = function(listener, thisArg) { + return ol.events.listen(this.iconImage_, ol.events.EventType.CHANGE, + listener, thisArg); +}; + + +/** + * Load not yet loaded URI. + * When rendering a feature with an icon style, the vector renderer will + * automatically call this method. However, you might want to call this + * method yourself for preloading or other purposes. + * @api + */ +ol.style.Icon.prototype.load = function() { + this.iconImage_.load(); +}; + + +/** + * @inheritDoc + */ +ol.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) { + ol.events.unlisten(this.iconImage_, ol.events.EventType.CHANGE, + listener, thisArg); +}; + + +/** + * Icon anchor units. One of 'fraction', 'pixels'. + * @enum {string} + */ +ol.style.Icon.AnchorUnits = { + FRACTION: 'fraction', + PIXELS: 'pixels' +}; + + +/** + * Icon origin. One of 'bottom-left', 'bottom-right', 'top-left', 'top-right'. + * @enum {string} + */ +ol.style.Icon.Origin = { + BOTTOM_LEFT: 'bottom-left', + BOTTOM_RIGHT: 'bottom-right', + TOP_LEFT: 'top-left', + TOP_RIGHT: 'top-right' +}; + +goog.provide('ol.style.Text'); + + +goog.require('ol.style.Fill'); + + +/** + * @classdesc + * Set text style for vector features. + * + * @constructor + * @param {olx.style.TextOptions=} opt_options Options. + * @api + */ +ol.style.Text = function(opt_options) { + + var options = opt_options || {}; + + /** + * @private + * @type {string|undefined} + */ + this.font_ = options.font; + + /** + * @private + * @type {number|undefined} + */ + this.rotation_ = options.rotation; + + /** + * @private + * @type {boolean|undefined} + */ + this.rotateWithView_ = options.rotateWithView; + + /** + * @private + * @type {number|undefined} + */ + this.scale_ = options.scale; + + /** + * @private + * @type {string|undefined} + */ + this.text_ = options.text; + + /** + * @private + * @type {string|undefined} + */ + this.textAlign_ = options.textAlign; + + /** + * @private + * @type {string|undefined} + */ + this.textBaseline_ = options.textBaseline; + + /** + * @private + * @type {ol.style.Fill} + */ + this.fill_ = options.fill !== undefined ? options.fill : + new ol.style.Fill({color: ol.style.Text.DEFAULT_FILL_COLOR_}); + + /** + * @private + * @type {ol.style.Stroke} + */ + this.stroke_ = options.stroke !== undefined ? options.stroke : null; + + /** + * @private + * @type {number} + */ + this.offsetX_ = options.offsetX !== undefined ? options.offsetX : 0; + + /** + * @private + * @type {number} + */ + this.offsetY_ = options.offsetY !== undefined ? options.offsetY : 0; +}; + + +/** + * The default fill color to use if no fill was set at construction time; a + * blackish `#333`. + * + * @const {string} + * @private + */ +ol.style.Text.DEFAULT_FILL_COLOR_ = '#333'; + + +/** + * Clones the style. + * @return {ol.style.Text} The cloned style. + * @api + */ +ol.style.Text.prototype.clone = function() { + return new ol.style.Text({ + font: this.getFont(), + rotation: this.getRotation(), + rotateWithView: this.getRotateWithView(), + scale: this.getScale(), + text: this.getText(), + textAlign: this.getTextAlign(), + textBaseline: this.getTextBaseline(), + fill: this.getFill() ? this.getFill().clone() : undefined, + stroke: this.getStroke() ? this.getStroke().clone() : undefined, + offsetX: this.getOffsetX(), + offsetY: this.getOffsetY() + }); +}; + + +/** + * Get the font name. + * @return {string|undefined} Font. + * @api + */ +ol.style.Text.prototype.getFont = function() { + return this.font_; +}; + + +/** + * Get the x-offset for the text. + * @return {number} Horizontal text offset. + * @api + */ +ol.style.Text.prototype.getOffsetX = function() { + return this.offsetX_; +}; + + +/** + * Get the y-offset for the text. + * @return {number} Vertical text offset. + * @api + */ +ol.style.Text.prototype.getOffsetY = function() { + return this.offsetY_; +}; + + +/** + * Get the fill style for the text. + * @return {ol.style.Fill} Fill style. + * @api + */ +ol.style.Text.prototype.getFill = function() { + return this.fill_; +}; + + +/** + * Determine whether the text rotates with the map. + * @return {boolean|undefined} Rotate with map. + * @api + */ +ol.style.Text.prototype.getRotateWithView = function() { + return this.rotateWithView_; +}; + + +/** + * Get the text rotation. + * @return {number|undefined} Rotation. + * @api + */ +ol.style.Text.prototype.getRotation = function() { + return this.rotation_; +}; + + +/** + * Get the text scale. + * @return {number|undefined} Scale. + * @api + */ +ol.style.Text.prototype.getScale = function() { + return this.scale_; +}; + + +/** + * Get the stroke style for the text. + * @return {ol.style.Stroke} Stroke style. + * @api + */ +ol.style.Text.prototype.getStroke = function() { + return this.stroke_; +}; + + +/** + * Get the text to be rendered. + * @return {string|undefined} Text. + * @api + */ +ol.style.Text.prototype.getText = function() { + return this.text_; +}; + + +/** + * Get the text alignment. + * @return {string|undefined} Text align. + * @api + */ +ol.style.Text.prototype.getTextAlign = function() { + return this.textAlign_; +}; + + +/** + * Get the text baseline. + * @return {string|undefined} Text baseline. + * @api + */ +ol.style.Text.prototype.getTextBaseline = function() { + return this.textBaseline_; +}; + + +/** + * Set the font. + * + * @param {string|undefined} font Font. + * @api + */ +ol.style.Text.prototype.setFont = function(font) { + this.font_ = font; +}; + + +/** + * Set the x offset. + * + * @param {number} offsetX Horizontal text offset. + * @api + */ +ol.style.Text.prototype.setOffsetX = function(offsetX) { + this.offsetX_ = offsetX; +}; + + +/** + * Set the y offset. + * + * @param {number} offsetY Vertical text offset. + * @api + */ +ol.style.Text.prototype.setOffsetY = function(offsetY) { + this.offsetY_ = offsetY; +}; + + +/** + * Set the fill. + * + * @param {ol.style.Fill} fill Fill style. + * @api + */ +ol.style.Text.prototype.setFill = function(fill) { + this.fill_ = fill; +}; + + +/** + * Set the rotation. + * + * @param {number|undefined} rotation Rotation. + * @api + */ +ol.style.Text.prototype.setRotation = function(rotation) { + this.rotation_ = rotation; +}; + + +/** + * Set the scale. + * + * @param {number|undefined} scale Scale. + * @api + */ +ol.style.Text.prototype.setScale = function(scale) { + this.scale_ = scale; +}; + + +/** + * Set the stroke. + * + * @param {ol.style.Stroke} stroke Stroke style. + * @api + */ +ol.style.Text.prototype.setStroke = function(stroke) { + this.stroke_ = stroke; +}; + + +/** + * Set the text. + * + * @param {string|undefined} text Text. + * @api + */ +ol.style.Text.prototype.setText = function(text) { + this.text_ = text; +}; + + +/** + * Set the text alignment. + * + * @param {string|undefined} textAlign Text align. + * @api + */ +ol.style.Text.prototype.setTextAlign = function(textAlign) { + this.textAlign_ = textAlign; +}; + + +/** + * Set the text baseline. + * + * @param {string|undefined} textBaseline Text baseline. + * @api + */ +ol.style.Text.prototype.setTextBaseline = function(textBaseline) { + this.textBaseline_ = textBaseline; +}; + +// FIXME http://earth.google.com/kml/1.0 namespace? +// FIXME why does node.getAttribute return an unknown type? +// FIXME serialize arbitrary feature properties +// FIXME don't parse style if extractStyles is false + +goog.provide('ol.format.KML'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.array'); +goog.require('ol.asserts'); +goog.require('ol.color'); +goog.require('ol.format.Feature'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.format.XSD'); +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.math'); +goog.require('ol.proj'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Icon'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Style'); +goog.require('ol.style.Text'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Feature format for reading and writing data in the KML format. + * + * Note that the KML format uses the URL() constructor. Older browsers such as IE + * which do not support this will need a URL polyfill to be loaded before use. + * + * @constructor + * @extends {ol.format.XMLFeature} + * @param {olx.format.KMLOptions=} opt_options Options. + * @api stable + */ +ol.format.KML = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.XMLFeature.call(this); + + if (!ol.format.KML.DEFAULT_STYLE_ARRAY_) { + ol.format.KML.createStyleDefaults_(); + } + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); + + /** + * @private + * @type {Array.<ol.style.Style>} + */ + this.defaultStyle_ = options.defaultStyle ? + options.defaultStyle : ol.format.KML.DEFAULT_STYLE_ARRAY_; + + /** + * @private + * @type {boolean} + */ + this.extractStyles_ = options.extractStyles !== undefined ? + options.extractStyles : true; + + /** + * @private + * @type {boolean} + */ + this.writeStyles_ = options.writeStyles !== undefined ? + options.writeStyles : true; + + /** + * @private + * @type {Object.<string, (Array.<ol.style.Style>|string)>} + */ + this.sharedStyles_ = {}; + + /** + * @private + * @type {boolean} + */ + this.showPointNames_ = options.showPointNames !== undefined ? + options.showPointNames : true; + +}; +ol.inherits(ol.format.KML, ol.format.XMLFeature); + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.KML.EXTENSIONS_ = ['.kml']; + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.KML.GX_NAMESPACE_URIS_ = [ + 'http://www.google.com/kml/ext/2.2' +]; + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.KML.NAMESPACE_URIS_ = [ + null, + 'http://earth.google.com/kml/2.0', + 'http://earth.google.com/kml/2.1', + 'http://earth.google.com/kml/2.2', + 'http://www.opengis.net/kml/2.2' +]; + + +/** + * @const + * @type {string} + * @private + */ +ol.format.KML.SCHEMA_LOCATION_ = 'http://www.opengis.net/kml/2.2 ' + + 'https://developers.google.com/kml/schema/kml22gx.xsd'; + + +/** + * @return {Array.<ol.style.Style>} Default style. + * @private + */ +ol.format.KML.createStyleDefaults_ = function() { + /** + * @const + * @type {ol.Color} + * @private + */ + ol.format.KML.DEFAULT_COLOR_ = [255, 255, 255, 1]; + + /** + * @const + * @type {ol.style.Fill} + * @private + */ + ol.format.KML.DEFAULT_FILL_STYLE_ = new ol.style.Fill({ + color: ol.format.KML.DEFAULT_COLOR_ + }); + + /** + * @const + * @type {ol.Size} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_ = [20, 2]; // FIXME maybe [8, 32] ? + + /** + * @const + * @type {ol.style.Icon.AnchorUnits} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_ = + ol.style.Icon.AnchorUnits.PIXELS; + + /** + * @const + * @type {ol.style.Icon.AnchorUnits} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_ = + ol.style.Icon.AnchorUnits.PIXELS; + + /** + * @const + * @type {ol.Size} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_ = [64, 64]; + + /** + * @const + * @type {string} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ = + 'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png'; + + /** + * @const + * @type {number} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_ = 0.5; + + /** + * @const + * @type {ol.style.Image} + * @private + */ + ol.format.KML.DEFAULT_IMAGE_STYLE_ = new ol.style.Icon({ + anchor: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_, + anchorOrigin: ol.style.Icon.Origin.BOTTOM_LEFT, + anchorXUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_, + anchorYUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_, + crossOrigin: 'anonymous', + rotation: 0, + scale: ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_, + size: ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_, + src: ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ + }); + + /** + * @const + * @type {string} + * @private + */ + ol.format.KML.DEFAULT_NO_IMAGE_STYLE_ = 'NO_IMAGE'; + + /** + * @const + * @type {ol.style.Stroke} + * @private + */ + ol.format.KML.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({ + color: ol.format.KML.DEFAULT_COLOR_, + width: 1 + }); + + /** + * @const + * @type {ol.style.Stroke} + * @private + */ + ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_ = new ol.style.Stroke({ + color: [51, 51, 51, 1], + width: 2 + }); + + /** + * @const + * @type {ol.style.Text} + * @private + */ + ol.format.KML.DEFAULT_TEXT_STYLE_ = new ol.style.Text({ + font: 'bold 16px Helvetica', + fill: ol.format.KML.DEFAULT_FILL_STYLE_, + stroke: ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_, + scale: 0.8 + }); + + /** + * @const + * @type {ol.style.Style} + * @private + */ + ol.format.KML.DEFAULT_STYLE_ = new ol.style.Style({ + fill: ol.format.KML.DEFAULT_FILL_STYLE_, + image: ol.format.KML.DEFAULT_IMAGE_STYLE_, + text: ol.format.KML.DEFAULT_TEXT_STYLE_, + stroke: ol.format.KML.DEFAULT_STROKE_STYLE_, + zIndex: 0 + }); + + /** + * @const + * @type {Array.<ol.style.Style>} + * @private + */ + ol.format.KML.DEFAULT_STYLE_ARRAY_ = [ol.format.KML.DEFAULT_STYLE_]; + + return ol.format.KML.DEFAULT_STYLE_ARRAY_; +}; + + +/** + * @const + * @type {Object.<string, ol.style.Icon.AnchorUnits>} + * @private + */ +ol.format.KML.ICON_ANCHOR_UNITS_MAP_ = { + 'fraction': ol.style.Icon.AnchorUnits.FRACTION, + 'pixels': ol.style.Icon.AnchorUnits.PIXELS +}; + + +/** + * @param {ol.style.Style|undefined} foundStyle Style. + * @param {string} name Name. + * @return {ol.style.Style} style Style. + * @private + */ +ol.format.KML.createNameStyleFunction_ = function(foundStyle, name) { + var textStyle = null; + var textOffset = [0, 0]; + var textAlign = 'start'; + if (foundStyle.getImage()) { + var imageSize = foundStyle.getImage().getImageSize(); + if (imageSize === null) { + imageSize = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_; + } + if (imageSize.length == 2) { + var imageScale = foundStyle.getImage().getScale(); + // Offset the label to be centered to the right of the icon, if there is + // one. + textOffset[0] = imageScale * imageSize[0] / 2; + textOffset[1] = -imageScale * imageSize[1] / 2; + textAlign = 'left'; + } + } + if (foundStyle.getText() !== null) { + // clone the text style, customizing it with name, alignments and offset. + // Note that kml does not support many text options that OpenLayers does (rotation, textBaseline). + var foundText = foundStyle.getText(); + textStyle = foundText.clone(); + textStyle.setFont(foundText.getFont() || ol.format.KML.DEFAULT_TEXT_STYLE_.getFont()); + textStyle.setScale(foundText.getScale() || ol.format.KML.DEFAULT_TEXT_STYLE_.getScale()); + textStyle.setFill(foundText.getFill() || ol.format.KML.DEFAULT_TEXT_STYLE_.getFill()); + textStyle.setStroke(foundText.getStroke() || ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_); + } else { + textStyle = ol.format.KML.DEFAULT_TEXT_STYLE_.clone(); + } + textStyle.setText(name); + textStyle.setOffsetX(textOffset[0]); + textStyle.setOffsetY(textOffset[1]); + textStyle.setTextAlign(textAlign); + + var nameStyle = new ol.style.Style({ + text: textStyle + }); + return nameStyle; +}; + + +/** + * @param {Array.<ol.style.Style>|undefined} style Style. + * @param {string} styleUrl Style URL. + * @param {Array.<ol.style.Style>} defaultStyle Default style. + * @param {Object.<string, (Array.<ol.style.Style>|string)>} sharedStyles Shared + * styles. + * @param {boolean|undefined} showPointNames true to show names for point + * placemarks. + * @return {ol.FeatureStyleFunction} Feature style function. + * @private + */ +ol.format.KML.createFeatureStyleFunction_ = function(style, styleUrl, + defaultStyle, sharedStyles, showPointNames) { + + return ( + /** + * @param {number} resolution Resolution. + * @return {Array.<ol.style.Style>} Style. + * @this {ol.Feature} + */ + function(resolution) { + var drawName = showPointNames; + /** @type {ol.style.Style|undefined} */ + var nameStyle; + var name = ''; + if (drawName) { + if (this.getGeometry()) { + drawName = (this.getGeometry().getType() === + ol.geom.GeometryType.POINT); + } + } + + if (drawName) { + name = /** @type {string} */ (this.get('name')); + drawName = drawName && name; + } + + if (style) { + if (drawName) { + nameStyle = ol.format.KML.createNameStyleFunction_(style[0], + name); + return style.concat(nameStyle); + } + return style; + } + if (styleUrl) { + var foundStyle = ol.format.KML.findStyle_(styleUrl, defaultStyle, + sharedStyles); + if (drawName) { + nameStyle = ol.format.KML.createNameStyleFunction_(foundStyle[0], + name); + return foundStyle.concat(nameStyle); + } + return foundStyle; + } + if (drawName) { + nameStyle = ol.format.KML.createNameStyleFunction_(defaultStyle[0], + name); + return defaultStyle.concat(nameStyle); + } + return defaultStyle; + }); +}; + + +/** + * @param {Array.<ol.style.Style>|string|undefined} styleValue Style value. + * @param {Array.<ol.style.Style>} defaultStyle Default style. + * @param {Object.<string, (Array.<ol.style.Style>|string)>} sharedStyles + * Shared styles. + * @return {Array.<ol.style.Style>} Style. + * @private + */ +ol.format.KML.findStyle_ = function(styleValue, defaultStyle, sharedStyles) { + if (Array.isArray(styleValue)) { + return styleValue; + } else if (typeof styleValue === 'string') { + // KML files in the wild occasionally forget the leading `#` on styleUrls + // defined in the same document. Add a leading `#` if it enables to find + // a style. + if (!(styleValue in sharedStyles) && ('#' + styleValue in sharedStyles)) { + styleValue = '#' + styleValue; + } + return ol.format.KML.findStyle_( + sharedStyles[styleValue], defaultStyle, sharedStyles); + } else { + return defaultStyle; + } +}; + + +/** + * @param {Node} node Node. + * @private + * @return {ol.Color|undefined} Color. + */ +ol.format.KML.readColor_ = function(node) { + var s = ol.xml.getAllTextContent(node, false); + // The KML specification states that colors should not include a leading `#` + // but we tolerate them. + var m = /^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(s); + if (m) { + var hexColor = m[1]; + return [ + parseInt(hexColor.substr(6, 2), 16), + parseInt(hexColor.substr(4, 2), 16), + parseInt(hexColor.substr(2, 2), 16), + parseInt(hexColor.substr(0, 2), 16) / 255 + ]; + + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @private + * @return {Array.<number>|undefined} Flat coordinates. + */ +ol.format.KML.readFlatCoordinates_ = function(node) { + var s = ol.xml.getAllTextContent(node, false); + var flatCoordinates = []; + // The KML specification states that coordinate tuples should not include + // spaces, but we tolerate them. + var re = + /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i; + var m; + while ((m = re.exec(s))) { + var x = parseFloat(m[1]); + var y = parseFloat(m[2]); + var z = m[3] ? parseFloat(m[3]) : 0; + flatCoordinates.push(x, y, z); + s = s.substr(m[0].length); + } + if (s !== '') { + return undefined; + } + return flatCoordinates; +}; + + +/** + * @param {Node} node Node. + * @private + * @return {string} URI. + */ +ol.format.KML.readURI_ = function(node) { + var s = ol.xml.getAllTextContent(node, false).trim(); + if (node.baseURI) { + var url = new URL(s, node.baseURI); + return url.href; + } else { + return s; + } +}; + + +/** + * @param {Node} node Node. + * @private + * @return {ol.KMLVec2_} Vec2. + */ +ol.format.KML.readVec2_ = function(node) { + var xunits = node.getAttribute('xunits'); + var yunits = node.getAttribute('yunits'); + return { + x: parseFloat(node.getAttribute('x')), + xunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[xunits], + y: parseFloat(node.getAttribute('y')), + yunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[yunits] + }; +}; + + +/** + * @param {Node} node Node. + * @private + * @return {number|undefined} Scale. + */ +ol.format.KML.readScale_ = function(node) { + return ol.format.XSD.readDecimal(node); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<ol.style.Style>|string|undefined} StyleMap. + */ +ol.format.KML.readStyleMapValue_ = function(node, objectStack) { + return ol.xml.pushParseAndPop(undefined, + ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack); +}; + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.IconStyleParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be an ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'IconStyle', + 'localName should be IconStyle'); + // FIXME refreshMode + // FIXME refreshInterval + // FIXME viewRefreshTime + // FIXME viewBoundScale + // FIXME viewFormat + // FIXME httpQuery + var object = ol.xml.pushParseAndPop( + {}, ol.format.KML.ICON_STYLE_PARSERS_, node, objectStack); + if (!object) { + return; + } + var styleObject = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var IconObject = 'Icon' in object ? object['Icon'] : {}; + var drawIcon = (!('Icon' in object) || Object.keys(IconObject).length > 0); + var src; + var href = /** @type {string|undefined} */ + (IconObject['href']); + if (href) { + src = href; + } else if (drawIcon) { + src = ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_; + } + var anchor, anchorXUnits, anchorYUnits; + var hotSpot = /** @type {ol.KMLVec2_|undefined} */ + (object['hotSpot']); + if (hotSpot) { + anchor = [hotSpot.x, hotSpot.y]; + anchorXUnits = hotSpot.xunits; + anchorYUnits = hotSpot.yunits; + } else if (src === ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) { + anchor = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_; + anchorXUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_; + anchorYUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_; + } else if (/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(src)) { + anchor = [0.5, 0]; + anchorXUnits = ol.style.Icon.AnchorUnits.FRACTION; + anchorYUnits = ol.style.Icon.AnchorUnits.FRACTION; + } + + var offset; + var x = /** @type {number|undefined} */ + (IconObject['x']); + var y = /** @type {number|undefined} */ + (IconObject['y']); + if (x !== undefined && y !== undefined) { + offset = [x, y]; + } + + var size; + var w = /** @type {number|undefined} */ + (IconObject['w']); + var h = /** @type {number|undefined} */ + (IconObject['h']); + if (w !== undefined && h !== undefined) { + size = [w, h]; + } + + var rotation; + var heading = /** @type {number} */ + (object['heading']); + if (heading !== undefined) { + rotation = ol.math.toRadians(heading); + } + + var scale = /** @type {number|undefined} */ + (object['scale']); + + if (drawIcon) { + if (src == ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) { + size = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_; + if (scale === undefined) { + scale = ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_; + } + } + + var imageStyle = new ol.style.Icon({ + anchor: anchor, + anchorOrigin: ol.style.Icon.Origin.BOTTOM_LEFT, + anchorXUnits: anchorXUnits, + anchorYUnits: anchorYUnits, + crossOrigin: 'anonymous', // FIXME should this be configurable? + offset: offset, + offsetOrigin: ol.style.Icon.Origin.BOTTOM_LEFT, + rotation: rotation, + scale: scale, + size: size, + src: src + }); + styleObject['imageStyle'] = imageStyle; + } else { + // handle the case when we explicitly want to draw no icon. + styleObject['imageStyle'] = ol.format.KML.DEFAULT_NO_IMAGE_STYLE_; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.LabelStyleParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LabelStyle', + 'localName should be LabelStyle'); + // FIXME colorMode + var object = ol.xml.pushParseAndPop( + {}, ol.format.KML.LABEL_STYLE_PARSERS_, node, objectStack); + if (!object) { + return; + } + var styleObject = objectStack[objectStack.length - 1]; + var textStyle = new ol.style.Text({ + fill: new ol.style.Fill({ + color: /** @type {ol.Color} */ + ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_) + }), + scale: /** @type {number|undefined} */ + (object['scale']) + }); + styleObject['textStyle'] = textStyle; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.LineStyleParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LineStyle', + 'localName should be LineStyle'); + // FIXME colorMode + // FIXME gx:outerColor + // FIXME gx:outerWidth + // FIXME gx:physicalWidth + // FIXME gx:labelVisibility + var object = ol.xml.pushParseAndPop( + {}, ol.format.KML.LINE_STYLE_PARSERS_, node, objectStack); + if (!object) { + return; + } + var styleObject = objectStack[objectStack.length - 1]; + var strokeStyle = new ol.style.Stroke({ + color: /** @type {ol.Color} */ + ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_), + width: /** @type {number} */ ('width' in object ? object['width'] : 1) + }); + styleObject['strokeStyle'] = strokeStyle; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.PolyStyleParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'PolyStyle', + 'localName should be PolyStyle'); + // FIXME colorMode + var object = ol.xml.pushParseAndPop( + {}, ol.format.KML.POLY_STYLE_PARSERS_, node, objectStack); + if (!object) { + return; + } + var styleObject = objectStack[objectStack.length - 1]; + var fillStyle = new ol.style.Fill({ + color: /** @type {ol.Color} */ + ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_) + }); + styleObject['fillStyle'] = fillStyle; + var fill = /** @type {boolean|undefined} */ (object['fill']); + if (fill !== undefined) { + styleObject['fill'] = fill; + } + var outline = + /** @type {boolean|undefined} */ (object['outline']); + if (outline !== undefined) { + styleObject['outline'] = outline; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>} LinearRing flat coordinates. + */ +ol.format.KML.readFlatLinearRing_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LinearRing', + 'localName should be LinearRing'); + return ol.xml.pushParseAndPop(null, + ol.format.KML.FLAT_LINEAR_RING_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.gxCoordParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(ol.array.includes( + ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI), + 'namespaceURI of the node should be known to the KML parser'); + ol.DEBUG && console.assert(node.localName == 'coord', 'localName should be coord'); + var gxTrackObject = /** @type {ol.KMLGxTrackObject_} */ + (objectStack[objectStack.length - 1]); + var flatCoordinates = gxTrackObject.flatCoordinates; + var s = ol.xml.getAllTextContent(node, false); + var re = + /^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i; + var m = re.exec(s); + if (m) { + var x = parseFloat(m[1]); + var y = parseFloat(m[2]); + var z = parseFloat(m[3]); + flatCoordinates.push(x, y, z, 0); + } else { + flatCoordinates.push(0, 0, 0, 0); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.MultiLineString|undefined} MultiLineString. + */ +ol.format.KML.readGxMultiTrack_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(ol.array.includes( + ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI), + 'namespaceURI of the node should be known to the KML parser'); + ol.DEBUG && console.assert(node.localName == 'MultiTrack', + 'localName should be MultiTrack'); + var lineStrings = ol.xml.pushParseAndPop([], + ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_, node, objectStack); + if (!lineStrings) { + return undefined; + } + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setLineStrings(lineStrings); + return multiLineString; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.LineString|undefined} LineString. + */ +ol.format.KML.readGxTrack_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(ol.array.includes( + ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI), + 'namespaceURI of the node should be known to the KML parser'); + ol.DEBUG && console.assert(node.localName == 'Track', 'localName should be Track'); + var gxTrackObject = ol.xml.pushParseAndPop( + /** @type {ol.KMLGxTrackObject_} */ ({ + flatCoordinates: [], + whens: [] + }), ol.format.KML.GX_TRACK_PARSERS_, node, objectStack); + if (!gxTrackObject) { + return undefined; + } + var flatCoordinates = gxTrackObject.flatCoordinates; + var whens = gxTrackObject.whens; + ol.DEBUG && console.assert(flatCoordinates.length / 4 == whens.length, + 'the length of the flatCoordinates array divided by 4 should be the ' + + 'length of the whens array'); + var i, ii; + for (i = 0, ii = Math.min(flatCoordinates.length, whens.length); i < ii; + ++i) { + flatCoordinates[4 * i + 3] = whens[i]; + } + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates); + return lineString; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object} Icon object. + */ +ol.format.KML.readIcon_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Icon', 'localName should be Icon'); + var iconObject = ol.xml.pushParseAndPop( + {}, ol.format.KML.ICON_PARSERS_, node, objectStack); + if (iconObject) { + return iconObject; + } else { + return null; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>} Flat coordinates. + */ +ol.format.KML.readFlatCoordinatesFromNode_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return ol.xml.pushParseAndPop(null, + ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.LineString|undefined} LineString. + */ +ol.format.KML.readLineString_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LineString', + 'localName should be LineString'); + var properties = ol.xml.pushParseAndPop({}, + ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node, + objectStack); + var flatCoordinates = + ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack); + if (flatCoordinates) { + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + lineString.setProperties(properties); + return lineString; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Polygon|undefined} Polygon. + */ +ol.format.KML.readLinearRing_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LinearRing', + 'localName should be LinearRing'); + var properties = ol.xml.pushParseAndPop({}, + ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node, + objectStack); + var flatCoordinates = + ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack); + if (flatCoordinates) { + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates, + [flatCoordinates.length]); + polygon.setProperties(properties); + return polygon; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.KML.readMultiGeometry_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MultiGeometry', + 'localName should be MultiGeometry'); + var geometries = ol.xml.pushParseAndPop([], + ol.format.KML.MULTI_GEOMETRY_PARSERS_, node, objectStack); + if (!geometries) { + return null; + } + if (geometries.length === 0) { + return new ol.geom.GeometryCollection(geometries); + } + /** @type {ol.geom.Geometry} */ + var multiGeometry; + var homogeneous = true; + var type = geometries[0].getType(); + var geometry, i, ii; + for (i = 1, ii = geometries.length; i < ii; ++i) { + geometry = geometries[i]; + if (geometry.getType() != type) { + homogeneous = false; + break; + } + } + if (homogeneous) { + var layout; + var flatCoordinates; + if (type == ol.geom.GeometryType.POINT) { + var point = geometries[0]; + layout = point.getLayout(); + flatCoordinates = point.getFlatCoordinates(); + for (i = 1, ii = geometries.length; i < ii; ++i) { + geometry = geometries[i]; + ol.DEBUG && console.assert(geometry.getLayout() == layout, + 'geometry layout should be consistent'); + ol.array.extend(flatCoordinates, geometry.getFlatCoordinates()); + } + multiGeometry = new ol.geom.MultiPoint(null); + multiGeometry.setFlatCoordinates(layout, flatCoordinates); + ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries); + } else if (type == ol.geom.GeometryType.LINE_STRING) { + multiGeometry = new ol.geom.MultiLineString(null); + multiGeometry.setLineStrings(geometries); + ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries); + } else if (type == ol.geom.GeometryType.POLYGON) { + multiGeometry = new ol.geom.MultiPolygon(null); + multiGeometry.setPolygons(geometries); + ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries); + } else if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) { + multiGeometry = new ol.geom.GeometryCollection(geometries); + } else { + ol.asserts.assert(false, 37); // Unknown geometry type found + } + } else { + multiGeometry = new ol.geom.GeometryCollection(geometries); + } + return /** @type {ol.geom.Geometry} */ (multiGeometry); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Point|undefined} Point. + */ +ol.format.KML.readPoint_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Point', 'localName should be Point'); + var properties = ol.xml.pushParseAndPop({}, + ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node, + objectStack); + var flatCoordinates = + ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack); + if (flatCoordinates) { + var point = new ol.geom.Point(null); + ol.DEBUG && console.assert(flatCoordinates.length == 3, + 'flatCoordinates should have a length of 3'); + point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + point.setProperties(properties); + return point; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Polygon|undefined} Polygon. + */ +ol.format.KML.readPolygon_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Polygon', + 'localName should be Polygon'); + var properties = ol.xml.pushParseAndPop(/** @type {Object<string,*>} */ ({}), + ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node, + objectStack); + var flatLinearRings = ol.xml.pushParseAndPop([null], + ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack); + if (flatLinearRings && flatLinearRings[0]) { + var polygon = new ol.geom.Polygon(null); + var flatCoordinates = flatLinearRings[0]; + var ends = [flatCoordinates.length]; + var i, ii; + for (i = 1, ii = flatLinearRings.length; i < ii; ++i) { + ol.array.extend(flatCoordinates, flatLinearRings[i]); + ends.push(flatCoordinates.length); + } + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XYZ, flatCoordinates, ends); + polygon.setProperties(properties); + return polygon; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<ol.style.Style>} Style. + */ +ol.format.KML.readStyle_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Style', 'localName should be Style'); + var styleObject = ol.xml.pushParseAndPop( + {}, ol.format.KML.STYLE_PARSERS_, node, objectStack); + if (!styleObject) { + return null; + } + var fillStyle = /** @type {ol.style.Fill} */ + ('fillStyle' in styleObject ? + styleObject['fillStyle'] : ol.format.KML.DEFAULT_FILL_STYLE_); + var fill = /** @type {boolean|undefined} */ (styleObject['fill']); + if (fill !== undefined && !fill) { + fillStyle = null; + } + var imageStyle = /** @type {ol.style.Image} */ + ('imageStyle' in styleObject ? + styleObject['imageStyle'] : ol.format.KML.DEFAULT_IMAGE_STYLE_); + if (imageStyle == ol.format.KML.DEFAULT_NO_IMAGE_STYLE_) { + imageStyle = undefined; + } + var textStyle = /** @type {ol.style.Text} */ + ('textStyle' in styleObject ? + styleObject['textStyle'] : ol.format.KML.DEFAULT_TEXT_STYLE_); + var strokeStyle = /** @type {ol.style.Stroke} */ + ('strokeStyle' in styleObject ? + styleObject['strokeStyle'] : ol.format.KML.DEFAULT_STROKE_STYLE_); + var outline = /** @type {boolean|undefined} */ + (styleObject['outline']); + if (outline !== undefined && !outline) { + strokeStyle = null; + } + return [new ol.style.Style({ + fill: fillStyle, + image: imageStyle, + stroke: strokeStyle, + text: textStyle, + zIndex: undefined // FIXME + })]; +}; + + +/** + * Reads an array of geometries and creates arrays for common geometry + * properties. Then sets them to the multi geometry. + * @param {ol.geom.MultiPoint|ol.geom.MultiLineString|ol.geom.MultiPolygon} + * multiGeometry A multi-geometry. + * @param {Array.<ol.geom.Geometry>} geometries List of geometries. + * @private + */ +ol.format.KML.setCommonGeometryProperties_ = function(multiGeometry, + geometries) { + var ii = geometries.length; + var extrudes = new Array(geometries.length); + var altitudeModes = new Array(geometries.length); + var geometry, i, hasExtrude, hasAltitudeMode; + hasExtrude = hasAltitudeMode = false; + for (i = 0; i < ii; ++i) { + geometry = geometries[i]; + extrudes[i] = geometry.get('extrude'); + altitudeModes[i] = geometry.get('altitudeMode'); + hasExtrude = hasExtrude || extrudes[i] !== undefined; + hasAltitudeMode = hasAltitudeMode || altitudeModes[i]; + } + if (hasExtrude) { + multiGeometry.set('extrude', extrudes); + } + if (hasAltitudeMode) { + multiGeometry.set('altitudeMode', altitudeModes); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.DataParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Data', 'localName should be Data'); + var name = node.getAttribute('name'); + ol.xml.parseNode(ol.format.KML.DATA_PARSERS_, node, objectStack); + var featureObject = + /** @type {Object} */ (objectStack[objectStack.length - 1]); + if (name !== null) { + featureObject[name] = featureObject.value; + } else if (featureObject.displayName !== null) { + featureObject[featureObject.displayName] = featureObject.value; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.ExtendedDataParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ExtendedData', + 'localName should be ExtendedData'); + ol.xml.parseNode(ol.format.KML.EXTENDED_DATA_PARSERS_, node, objectStack); +}; + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.RegionParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Region', + 'localName should be Region'); + ol.xml.parseNode(ol.format.KML.REGION_PARSERS_, node, objectStack); +}; + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.PairDataParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Pair', 'localName should be Pair'); + var pairObject = ol.xml.pushParseAndPop( + {}, ol.format.KML.PAIR_PARSERS_, node, objectStack); + if (!pairObject) { + return; + } + var key = /** @type {string|undefined} */ + (pairObject['key']); + if (key && key == 'normal') { + var styleUrl = /** @type {string|undefined} */ + (pairObject['styleUrl']); + if (styleUrl) { + objectStack[objectStack.length - 1] = styleUrl; + } + var Style = /** @type {ol.style.Style} */ + (pairObject['Style']); + if (Style) { + objectStack[objectStack.length - 1] = Style; + } + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.PlacemarkStyleMapParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'StyleMap', + 'localName should be StyleMap'); + var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack); + if (!styleMapValue) { + return; + } + var placemarkObject = objectStack[objectStack.length - 1]; + if (Array.isArray(styleMapValue)) { + placemarkObject['Style'] = styleMapValue; + } else if (typeof styleMapValue === 'string') { + placemarkObject['styleUrl'] = styleMapValue; + } else { + ol.asserts.assert(false, 38); // `styleMapValue` has an unknown type + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.SchemaDataParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'SchemaData', + 'localName should be SchemaData'); + ol.xml.parseNode(ol.format.KML.SCHEMA_DATA_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.SimpleDataParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'SimpleData', + 'localName should be SimpleData'); + var name = node.getAttribute('name'); + if (name !== null) { + var data = ol.format.XSD.readString(node); + var featureObject = + /** @type {Object} */ (objectStack[objectStack.length - 1]); + featureObject[name] = data; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.LatLonAltBoxParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LatLonAltBox', + 'localName should be LatLonAltBox'); + var object = ol.xml.pushParseAndPop({}, ol.format.KML.LAT_LON_ALT_BOX_PARSERS_, node, objectStack); + if (!object) { + return; + } + var regionObject = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var extent = [ + parseFloat(object['west']), + parseFloat(object['south']), + parseFloat(object['east']), + parseFloat(object['north']) + ]; + regionObject['extent'] = extent; + regionObject['altitudeMode'] = object['altitudeMode']; + regionObject['minAltitude'] = parseFloat(object['minAltitude']); + regionObject['maxAltitude'] = parseFloat(object['maxAltitude']); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.LodParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Lod', + 'localName should be Lod'); + var object = ol.xml.pushParseAndPop({}, ol.format.KML.LOD_PARSERS_, node, objectStack); + if (!object) { + return; + } + var lodObject = /** @type {Object} */ (objectStack[objectStack.length - 1]); + lodObject['minLodPixels'] = parseFloat(object['minLodPixels']); + lodObject['maxLodPixels'] = parseFloat(object['maxLodPixels']); + lodObject['minFadeExtent'] = parseFloat(object['minFadeExtent']); + lodObject['maxFadeExtent'] = parseFloat(object['maxFadeExtent']); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.innerBoundaryIsParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'innerBoundaryIs', + 'localName should be innerBoundaryIs'); + /** @type {Array.<number>|undefined} */ + var flatLinearRing = ol.xml.pushParseAndPop(undefined, + ol.format.KML.INNER_BOUNDARY_IS_PARSERS_, node, objectStack); + if (flatLinearRing) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + ol.DEBUG && console.assert(flatLinearRings.length > 0, + 'flatLinearRings array should not be empty'); + flatLinearRings.push(flatLinearRing); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.outerBoundaryIsParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'outerBoundaryIs', + 'localName should be outerBoundaryIs'); + /** @type {Array.<number>|undefined} */ + var flatLinearRing = ol.xml.pushParseAndPop(undefined, + ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_, node, objectStack); + if (flatLinearRing) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + ol.DEBUG && console.assert(Array.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + ol.DEBUG && console.assert(flatLinearRings.length > 0, + 'flatLinearRings array should not be empty'); + flatLinearRings[0] = flatLinearRing; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.LinkParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Link', 'localName should be Link'); + ol.xml.parseNode(ol.format.KML.LINK_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.whenParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'when', 'localName should be when'); + var gxTrackObject = /** @type {ol.KMLGxTrackObject_} */ + (objectStack[objectStack.length - 1]); + var whens = gxTrackObject.whens; + var s = ol.xml.getAllTextContent(node, false); + var when = Date.parse(s); + whens.push(isNaN(when) ? 0 : when); +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.DATA_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'displayName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'value': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Data': ol.format.KML.DataParser_, + 'SchemaData': ol.format.KML.SchemaDataParser_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.REGION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LatLonAltBox': ol.format.KML.LatLonAltBoxParser_, + 'Lod': ol.format.KML.LodParser_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.LAT_LON_ALT_BOX_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'altitudeMode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'minAltitude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'maxAltitude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'north': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'south': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'east': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'west': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.LOD_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'minLodPixels': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'maxLodPixels': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'minFadeExtent': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'maxFadeExtent': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'extrude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), + 'altitudeMode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.FLAT_LINEAR_RING_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'innerBoundaryIs': ol.format.KML.innerBoundaryIsParser_, + 'outerBoundaryIs': ol.format.KML.outerBoundaryIsParser_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.GX_TRACK_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'when': ol.format.KML.whenParser_ + }, ol.xml.makeStructureNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'coord': ol.format.KML.gxCoordParser_ + })); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.ICON_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_) + }, ol.xml.makeStructureNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'x': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'y': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'w': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'h': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + })); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.ICON_STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Icon': ol.xml.makeObjectPropertySetter(ol.format.KML.readIcon_), + 'heading': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'hotSpot': ol.xml.makeObjectPropertySetter(ol.format.KML.readVec2_), + 'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.INNER_BOUNDARY_IS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.LABEL_STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_), + 'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.LINE_STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_), + 'width': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.MULTI_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LineString': ol.xml.makeArrayPusher(ol.format.KML.readLineString_), + 'LinearRing': ol.xml.makeArrayPusher(ol.format.KML.readLinearRing_), + 'MultiGeometry': ol.xml.makeArrayPusher(ol.format.KML.readMultiGeometry_), + 'Point': ol.xml.makeArrayPusher(ol.format.KML.readPoint_), + 'Polygon': ol.xml.makeArrayPusher(ol.format.KML.readPolygon_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'Track': ol.xml.makeArrayPusher(ol.format.KML.readGxTrack_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.NETWORK_LINK_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'ExtendedData': ol.format.KML.ExtendedDataParser_, + 'Region': ol.format.KML.RegionParser_, + 'Link': ol.format.KML.LinkParser_, + 'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'open': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), + 'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'visibility': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.LINK_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.PAIR_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_), + 'key': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'ExtendedData': ol.format.KML.ExtendedDataParser_, + 'Region': ol.format.KML.RegionParser_, + 'MultiGeometry': ol.xml.makeObjectPropertySetter( + ol.format.KML.readMultiGeometry_, 'geometry'), + 'LineString': ol.xml.makeObjectPropertySetter( + ol.format.KML.readLineString_, 'geometry'), + 'LinearRing': ol.xml.makeObjectPropertySetter( + ol.format.KML.readLinearRing_, 'geometry'), + 'Point': ol.xml.makeObjectPropertySetter( + ol.format.KML.readPoint_, 'geometry'), + 'Polygon': ol.xml.makeObjectPropertySetter( + ol.format.KML.readPolygon_, 'geometry'), + 'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_), + 'StyleMap': ol.format.KML.PlacemarkStyleMapParser_, + 'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'open': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), + 'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_), + 'visibility': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean) + }, ol.xml.makeStructureNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'MultiTrack': ol.xml.makeObjectPropertySetter( + ol.format.KML.readGxMultiTrack_, 'geometry'), + 'Track': ol.xml.makeObjectPropertySetter( + ol.format.KML.readGxTrack_, 'geometry') + } + )); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.POLY_STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_), + 'fill': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), + 'outline': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.SCHEMA_DATA_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'SimpleData': ol.format.KML.SimpleDataParser_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'IconStyle': ol.format.KML.IconStyleParser_, + 'LabelStyle': ol.format.KML.LabelStyleParser_, + 'LineStyle': ol.format.KML.LineStyleParser_, + 'PolyStyle': ol.format.KML.PolyStyleParser_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.KML.STYLE_MAP_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Pair': ol.format.KML.PairDataParser_ + }); + + +/** + * @inheritDoc + */ +ol.format.KML.prototype.getExtensions = function() { + return ol.format.KML.EXTENSIONS_; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<ol.Feature>|undefined} Features. + */ +ol.format.KML.prototype.readDocumentOrFolder_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + var localName = node.localName; + ol.DEBUG && console.assert(localName == 'Document' || localName == 'Folder', + 'localName should be Document or Folder'); + // FIXME use scope somehow + var parsersNS = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Document': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this), + 'Folder': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this), + 'Placemark': ol.xml.makeArrayPusher(this.readPlacemark_, this), + 'Style': this.readSharedStyle_.bind(this), + 'StyleMap': this.readSharedStyleMap_.bind(this) + }); + /** @type {Array.<ol.Feature>} */ + var features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack, this); + if (features) { + return features; + } else { + return undefined; + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Feature|undefined} Feature. + */ +ol.format.KML.prototype.readPlacemark_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Placemark', + 'localName should be Placemark'); + var object = ol.xml.pushParseAndPop({'geometry': null}, + ol.format.KML.PLACEMARK_PARSERS_, node, objectStack); + if (!object) { + return undefined; + } + var feature = new ol.Feature(); + var id = node.getAttribute('id'); + if (id !== null) { + feature.setId(id); + } + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + + var geometry = object['geometry']; + if (geometry) { + ol.format.Feature.transformWithOptions(geometry, false, options); + } + feature.setGeometry(geometry); + delete object['geometry']; + + if (this.extractStyles_) { + var style = object['Style']; + var styleUrl = object['styleUrl']; + var styleFunction = ol.format.KML.createFeatureStyleFunction_( + style, styleUrl, this.defaultStyle_, this.sharedStyles_, + this.showPointNames_); + feature.setStyle(styleFunction); + } + delete object['Style']; + // we do not remove the styleUrl property from the object, so it + // gets stored on feature when setProperties is called + + feature.setProperties(object); + + return feature; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.prototype.readSharedStyle_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Style', 'localName should be Style'); + var id = node.getAttribute('id'); + if (id !== null) { + var style = ol.format.KML.readStyle_(node, objectStack); + if (style) { + var styleUri; + if (node.baseURI) { + var url = new URL('#' + id, node.baseURI); + styleUri = url.href; + } else { + styleUri = '#' + id; + } + this.sharedStyles_[styleUri] = style; + } + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.prototype.readSharedStyleMap_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'StyleMap', + 'localName should be StyleMap'); + var id = node.getAttribute('id'); + if (id === null) { + return; + } + var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack); + if (!styleMapValue) { + return; + } + var styleUri; + if (node.baseURI) { + var url = new URL('#' + id, node.baseURI); + styleUri = url.href; + } else { + styleUri = '#' + id; + } + this.sharedStyles_[styleUri] = styleMapValue; +}; + + +/** + * Read the first feature from a KML source. MultiGeometries are converted into + * GeometryCollections if they are a mix of geometry types, and into MultiPoint/ + * MultiLineString/MultiPolygon if they are all of the same type. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable + */ +ol.format.KML.prototype.readFeature; + + +/** + * @inheritDoc + */ +ol.format.KML.prototype.readFeatureFromNode = function(node, opt_options) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + if (!ol.array.includes(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) { + return null; + } + ol.DEBUG && console.assert(node.localName == 'Placemark', + 'localName should be Placemark'); + var feature = this.readPlacemark_( + node, [this.getReadOptions(node, opt_options)]); + if (feature) { + return feature; + } else { + return null; + } +}; + + +/** + * Read all features from a KML source. MultiGeometries are converted into + * GeometryCollections if they are a mix of geometry types, and into MultiPoint/ + * MultiLineString/MultiPolygon if they are all of the same type. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.KML.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.KML.prototype.readFeaturesFromNode = function(node, opt_options) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + if (!ol.array.includes(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) { + return []; + } + var features; + var localName = node.localName; + if (localName == 'Document' || localName == 'Folder') { + features = this.readDocumentOrFolder_( + node, [this.getReadOptions(node, opt_options)]); + if (features) { + return features; + } else { + return []; + } + } else if (localName == 'Placemark') { + var feature = this.readPlacemark_( + node, [this.getReadOptions(node, opt_options)]); + if (feature) { + return [feature]; + } else { + return []; + } + } else if (localName == 'kml') { + features = []; + var n; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var fs = this.readFeaturesFromNode(n, opt_options); + if (fs) { + ol.array.extend(features, fs); + } + } + return features; + } else { + return []; + } +}; + + +/** + * Read the name of the KML. + * + * @param {Document|Node|string} source Souce. + * @return {string|undefined} Name. + * @api stable + */ +ol.format.KML.prototype.readName = function(source) { + if (ol.xml.isDocument(source)) { + return this.readNameFromDocument(/** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readNameFromNode(/** @type {Node} */ (source)); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readNameFromDocument(doc); + } else { + return undefined; + } +}; + + +/** + * @param {Document} doc Document. + * @return {string|undefined} Name. + */ +ol.format.KML.prototype.readNameFromDocument = function(doc) { + var n; + for (n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + var name = this.readNameFromNode(n); + if (name) { + return name; + } + } + } + return undefined; +}; + + +/** + * @param {Node} node Node. + * @return {string|undefined} Name. + */ +ol.format.KML.prototype.readNameFromNode = function(node) { + var n; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + n.localName == 'name') { + return ol.format.XSD.readString(n); + } + } + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var localName = n.localName; + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + (localName == 'Document' || + localName == 'Folder' || + localName == 'Placemark' || + localName == 'kml')) { + var name = this.readNameFromNode(n); + if (name) { + return name; + } + } + } + return undefined; +}; + + +/** + * Read the network links of the KML. + * + * @param {Document|Node|string} source Source. + * @return {Array.<Object>} Network links. + * @api + */ +ol.format.KML.prototype.readNetworkLinks = function(source) { + var networkLinks = []; + if (ol.xml.isDocument(source)) { + ol.array.extend(networkLinks, this.readNetworkLinksFromDocument( + /** @type {Document} */ (source))); + } else if (ol.xml.isNode(source)) { + ol.array.extend(networkLinks, this.readNetworkLinksFromNode( + /** @type {Node} */ (source))); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + ol.array.extend(networkLinks, this.readNetworkLinksFromDocument(doc)); + } + return networkLinks; +}; + + +/** + * @param {Document} doc Document. + * @return {Array.<Object>} Network links. + */ +ol.format.KML.prototype.readNetworkLinksFromDocument = function(doc) { + var n, networkLinks = []; + for (n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + ol.array.extend(networkLinks, this.readNetworkLinksFromNode(n)); + } + } + return networkLinks; +}; + + +/** + * @param {Node} node Node. + * @return {Array.<Object>} Network links. + */ +ol.format.KML.prototype.readNetworkLinksFromNode = function(node) { + var n, networkLinks = []; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + n.localName == 'NetworkLink') { + var obj = ol.xml.pushParseAndPop({}, ol.format.KML.NETWORK_LINK_PARSERS_, + n, []); + networkLinks.push(obj); + } + } + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var localName = n.localName; + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + (localName == 'Document' || + localName == 'Folder' || + localName == 'kml')) { + ol.array.extend(networkLinks, this.readNetworkLinksFromNode(n)); + } + } + return networkLinks; +}; + + +/** + * Read the regions of the KML. + * + * @param {Document|Node|string} source Source. + * @return {Array.<Object>} Regions. + * @api + */ +ol.format.KML.prototype.readRegion = function(source) { + var regions = []; + if (ol.xml.isDocument(source)) { + ol.array.extend(regions, this.readRegionFromDocument( + /** @type {Document} */ (source))); + } else if (ol.xml.isNode(source)) { + ol.array.extend(regions, this.readRegionFromNode( + /** @type {Node} */ (source))); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + ol.array.extend(regions, this.readRegionFromDocument(doc)); + } + return regions; +}; + + +/** + * @param {Document} doc Document. + * @return {Array.<Object>} Region. + */ +ol.format.KML.prototype.readRegionFromDocument = function(doc) { + var n, regions = []; + for (n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + ol.array.extend(regions, this.readRegionFromNode(n)); + } + } + return regions; +}; + + +/** + * @param {Node} node Node. + * @return {Array.<Object>} Region. + * @api + */ +ol.format.KML.prototype.readRegionFromNode = function(node) { + var n, regions = []; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + n.localName == 'Region') { + var obj = ol.xml.pushParseAndPop({}, ol.format.KML.REGION_PARSERS_, + n, []); + regions.push(obj); + } + } + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var localName = n.localName; + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + (localName == 'Document' || + localName == 'Folder' || + localName == 'kml')) { + ol.array.extend(regions, this.readRegionFromNode(n)); + } + } + return regions; +}; + + +/** + * Read the projection from a KML source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.KML.prototype.readProjection; + + +/** + * @param {Node} node Node to append a TextNode with the color to. + * @param {ol.Color|string} color Color. + * @private + */ +ol.format.KML.writeColorTextNode_ = function(node, color) { + var rgba = ol.color.asArray(color); + var opacity = (rgba.length == 4) ? rgba[3] : 1; + var abgr = [opacity * 255, rgba[2], rgba[1], rgba[0]]; + var i; + for (i = 0; i < 4; ++i) { + var hex = parseInt(abgr[i], 10).toString(16); + abgr[i] = (hex.length == 1) ? '0' + hex : hex; + } + ol.format.XSD.writeStringTextNode(node, abgr.join('')); +}; + + +/** + * @param {Node} node Node to append a TextNode with the coordinates to. + * @param {Array.<number>} coordinates Coordinates. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeCoordinatesTextNode_ = function(node, coordinates, objectStack) { + var context = objectStack[objectStack.length - 1]; + + var layout = context['layout']; + var stride = context['stride']; + + var dimension; + if (layout == ol.geom.GeometryLayout.XY || + layout == ol.geom.GeometryLayout.XYM) { + dimension = 2; + } else if (layout == ol.geom.GeometryLayout.XYZ || + layout == ol.geom.GeometryLayout.XYZM) { + dimension = 3; + } else { + ol.asserts.assert(false, 34); // Invalid geometry layout + } + + var d, i; + var ii = coordinates.length; + var text = ''; + if (ii > 0) { + text += coordinates[0]; + for (d = 1; d < dimension; ++d) { + text += ',' + coordinates[d]; + } + for (i = stride; i < ii; i += stride) { + text += ' ' + coordinates[i]; + for (d = 1; d < dimension; ++d) { + text += ',' + coordinates[i + d]; + } + } + } + ol.format.XSD.writeStringTextNode(node, text); +}; + + +/** + * @param {Node} node Node. + * @param {{name: *, value: *}} pair Name value pair. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeDataNode_ = function(node, pair, objectStack) { + node.setAttribute('name', pair.name); + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var value = pair.value; + + if (typeof value == 'object') { + if (value !== null && value.displayName) { + ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value.displayName], objectStack, ['displayName']); + } + + if (value !== null && value.value) { + ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value.value], objectStack, ['value']); + } + } else { + ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value], objectStack, ['value']); + } +}; + + +/** + * @param {Node} node Node to append a TextNode with the name to. + * @param {string} name DisplayName. + * @private + */ +ol.format.KML.writeDataNodeName_ = function(node, name) { + ol.format.XSD.writeCDATASection(node, name); +}; + + +/** + * @param {Node} node Node to append a CDATA Section with the value to. + * @param {string} value Value. + * @private + */ +ol.format.KML.writeDataNodeValue_ = function(node, value) { + ol.format.XSD.writeStringTextNode(node, value); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<ol.Feature>} features Features. + * @param {Array.<*>} objectStack Object stack. + * @this {ol.format.KML} + * @private + */ +ol.format.KML.writeDocument_ = function(node, features, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + ol.xml.pushSerializeAndPop(context, ol.format.KML.DOCUMENT_SERIALIZERS_, + ol.format.KML.DOCUMENT_NODE_FACTORY_, features, objectStack, undefined, + this); +}; + + +/** + * @param {Node} node Node. + * @param {{names: Array<string>, values: (Array<*>)}} namesAndValues Names and values. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeExtendedData_ = function(node, namesAndValues, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var names = namesAndValues.names, values = namesAndValues.values; + var length = names.length; + + for (var i = 0; i < length; i++) { + ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_, + ol.format.KML.DATA_NODE_FACTORY_, [{name: names[i], value: values[i]}], objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {Object} icon Icon object. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeIcon_ = function(node, icon, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.ICON_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(icon, orderedKeys); + ol.xml.pushSerializeAndPop(context, + ol.format.KML.ICON_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); + orderedKeys = + ol.format.KML.ICON_SEQUENCE_[ol.format.KML.GX_NAMESPACE_URIS_[0]]; + values = ol.xml.makeSequence(icon, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_SERIALIZERS_, + ol.format.KML.GX_NODE_FACTORY_, values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.style.Icon} style Icon style. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeIconStyle_ = function(node, style, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var properties = {}; + var src = style.getSrc(); + var size = style.getSize(); + var iconImageSize = style.getImageSize(); + var iconProperties = { + 'href': src + }; + + if (size) { + iconProperties['w'] = size[0]; + iconProperties['h'] = size[1]; + var anchor = style.getAnchor(); // top-left + var origin = style.getOrigin(); // top-left + + if (origin && iconImageSize && origin[0] !== 0 && origin[1] !== size[1]) { + iconProperties['x'] = origin[0]; + iconProperties['y'] = iconImageSize[1] - (origin[1] + size[1]); + } + + if (anchor && anchor[0] !== 0 && anchor[1] !== size[1]) { + var /** @type {ol.KMLVec2_} */ hotSpot = { + x: anchor[0], + xunits: ol.style.Icon.AnchorUnits.PIXELS, + y: size[1] - anchor[1], + yunits: ol.style.Icon.AnchorUnits.PIXELS + }; + properties['hotSpot'] = hotSpot; + } + } + + properties['Icon'] = iconProperties; + + var scale = style.getScale(); + if (scale !== 1) { + properties['scale'] = scale; + } + + var rotation = style.getRotation(); + if (rotation !== 0) { + properties['heading'] = rotation; // 0-360 + } + + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.ICON_STYLE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_STYLE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.style.Text} style style. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeLabelStyle_ = function(node, style, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var properties = {}; + var fill = style.getFill(); + if (fill) { + properties['color'] = fill.getColor(); + } + var scale = style.getScale(); + if (scale && scale !== 1) { + properties['scale'] = scale; + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = + ol.format.KML.LABEL_STYLE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.LABEL_STYLE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.style.Stroke} style style. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeLineStyle_ = function(node, style, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var properties = { + 'color': style.getColor(), + 'width': style.getWidth() + }; + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.LINE_STYLE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.LINE_STYLE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeMultiGeometry_ = function(node, geometry, objectStack) { + /** @type {ol.XmlNodeStackItem} */ + var context = {node: node}; + var type = geometry.getType(); + /** @type {Array.<ol.geom.Geometry>} */ + var geometries; + /** @type {function(*, Array.<*>, string=): (Node|undefined)} */ + var factory; + if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) { + geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries(); + factory = ol.format.KML.GEOMETRY_NODE_FACTORY_; + } else if (type == ol.geom.GeometryType.MULTI_POINT) { + geometries = /** @type {ol.geom.MultiPoint} */ (geometry).getPoints(); + factory = ol.format.KML.POINT_NODE_FACTORY_; + } else if (type == ol.geom.GeometryType.MULTI_LINE_STRING) { + geometries = + (/** @type {ol.geom.MultiLineString} */ (geometry)).getLineStrings(); + factory = ol.format.KML.LINE_STRING_NODE_FACTORY_; + } else if (type == ol.geom.GeometryType.MULTI_POLYGON) { + geometries = + (/** @type {ol.geom.MultiPolygon} */ (geometry)).getPolygons(); + factory = ol.format.KML.POLYGON_NODE_FACTORY_; + } else { + ol.asserts.assert(false, 39); // Unknown geometry type + } + ol.xml.pushSerializeAndPop(context, + ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_, factory, + geometries, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.LinearRing} linearRing Linear ring. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeBoundaryIs_ = function(node, linearRing, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + ol.xml.pushSerializeAndPop(context, + ol.format.KML.BOUNDARY_IS_SERIALIZERS_, + ol.format.KML.LINEAR_RING_NODE_FACTORY_, [linearRing], objectStack); +}; + + +/** + * FIXME currently we do serialize arbitrary/custom feature properties + * (ExtendedData). + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Object stack. + * @this {ol.format.KML} + * @private + */ +ol.format.KML.writePlacemark_ = function(node, feature, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + + // set id + if (feature.getId()) { + node.setAttribute('id', feature.getId()); + } + + // serialize properties (properties unknown to KML are not serialized) + var properties = feature.getProperties(); + + // don't export these to ExtendedData + var filter = {'address': 1, 'description': 1, 'name': 1, 'open': 1, + 'phoneNumber': 1, 'styleUrl': 1, 'visibility': 1}; + filter[feature.getGeometryName()] = 1; + var keys = Object.keys(properties || {}).sort().filter(function(v) { + return !filter[v]; + }); + + if (keys.length > 0) { + var sequence = ol.xml.makeSequence(properties, keys); + var namesAndValues = {names: keys, values: sequence}; + ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_, + ol.format.KML.EXTENDEDDATA_NODE_FACTORY_, [namesAndValues], objectStack); + } + + var styleFunction = feature.getStyleFunction(); + if (styleFunction) { + // FIXME the styles returned by the style function are supposed to be + // resolution-independent here + var styles = styleFunction.call(feature, 0); + if (styles) { + var style = Array.isArray(styles) ? styles[0] : styles; + if (this.writeStyles_) { + properties['Style'] = style; + } + var textStyle = style.getText(); + if (textStyle) { + properties['name'] = textStyle.getText(); + } + } + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.PLACEMARK_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); + + // serialize geometry + var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); + var geometry = feature.getGeometry(); + if (geometry) { + geometry = + ol.format.Feature.transformWithOptions(geometry, true, options); + } + ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_, + ol.format.KML.GEOMETRY_NODE_FACTORY_, [geometry], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.SimpleGeometry} geometry Geometry. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writePrimitiveGeometry_ = function(node, geometry, objectStack) { + ol.DEBUG && console.assert( + (geometry instanceof ol.geom.Point) || + (geometry instanceof ol.geom.LineString) || + (geometry instanceof ol.geom.LinearRing), + 'geometry should be one of ol.geom.Point, ol.geom.LineString ' + + 'or ol.geom.LinearRing'); + var flatCoordinates = geometry.getFlatCoordinates(); + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + context['layout'] = geometry.getLayout(); + context['stride'] = geometry.getStride(); + ol.xml.pushSerializeAndPop(context, + ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_, + ol.format.KML.COORDINATES_NODE_FACTORY_, + [flatCoordinates], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.geom.Polygon} polygon Polygon. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writePolygon_ = function(node, polygon, objectStack) { + var linearRings = polygon.getLinearRings(); + ol.DEBUG && console.assert(linearRings.length > 0, + 'linearRings should not be empty'); + var outerRing = linearRings.shift(); + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + // inner rings + ol.xml.pushSerializeAndPop(context, + ol.format.KML.POLYGON_SERIALIZERS_, + ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_, + linearRings, objectStack); + // outer ring + ol.xml.pushSerializeAndPop(context, + ol.format.KML.POLYGON_SERIALIZERS_, + ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_, + [outerRing], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.style.Fill} style Style. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writePolyStyle_ = function(node, style, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + ol.xml.pushSerializeAndPop(context, ol.format.KML.POLY_STYLE_SERIALIZERS_, + ol.format.KML.COLOR_NODE_FACTORY_, [style.getColor()], objectStack); +}; + + +/** + * @param {Node} node Node to append a TextNode with the scale to. + * @param {number|undefined} scale Scale. + * @private + */ +ol.format.KML.writeScaleTextNode_ = function(node, scale) { + // the Math is to remove any excess decimals created by float arithmetic + ol.format.XSD.writeDecimalTextNode(node, + Math.round(scale * 1e6) / 1e6); +}; + + +/** + * @param {Node} node Node. + * @param {ol.style.Style} style Style. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.writeStyle_ = function(node, style, objectStack) { + var /** @type {ol.XmlNodeStackItem} */ context = {node: node}; + var properties = {}; + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + var imageStyle = style.getImage(); + var textStyle = style.getText(); + if (imageStyle instanceof ol.style.Icon) { + properties['IconStyle'] = imageStyle; + } + if (textStyle) { + properties['LabelStyle'] = textStyle; + } + if (strokeStyle) { + properties['LineStyle'] = strokeStyle; + } + if (fillStyle) { + properties['PolyStyle'] = fillStyle; + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.STYLE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.STYLE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); +}; + + +/** + * @param {Node} node Node to append a TextNode with the Vec2 to. + * @param {ol.KMLVec2_} vec2 Vec2. + * @private + */ +ol.format.KML.writeVec2_ = function(node, vec2) { + node.setAttribute('x', vec2.x); + node.setAttribute('y', vec2.y); + node.setAttribute('xunits', vec2.xunits); + node.setAttribute('yunits', vec2.yunits); +}; + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.KML_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'Document', 'Placemark' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.KML_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Document': ol.xml.makeChildAppender(ol.format.KML.writeDocument_), + 'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Data': ol.xml.makeChildAppender(ol.format.KML.writeDataNode_), + 'value': ol.xml.makeChildAppender(ol.format.KML.writeDataNodeValue_), + 'displayName': ol.xml.makeChildAppender(ol.format.KML.writeDataNodeName_) + }); + + +/** + * @const + * @type {Object.<string, string>} + * @private + */ +ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_ = { + 'Point': 'Point', + 'LineString': 'LineString', + 'LinearRing': 'LinearRing', + 'Polygon': 'Polygon', + 'MultiPoint': 'MultiGeometry', + 'MultiLineString': 'MultiGeometry', + 'MultiPolygon': 'MultiGeometry', + 'GeometryCollection': 'MultiGeometry' +}; + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.ICON_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'href' + ], + ol.xml.makeStructureNS(ol.format.KML.GX_NAMESPACE_URIS_, [ + 'x', 'y', 'w', 'h' + ])); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.ICON_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'href': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) + }, ol.xml.makeStructureNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'x': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'y': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'w': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'h': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode) + })); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.ICON_STYLE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'scale', 'heading', 'Icon', 'hotSpot' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.ICON_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Icon': ol.xml.makeChildAppender(ol.format.KML.writeIcon_), + 'heading': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'hotSpot': ol.xml.makeChildAppender(ol.format.KML.writeVec2_), + 'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.LABEL_STYLE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'color', 'scale' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.LABEL_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_), + 'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.LINE_STYLE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'color', 'width' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.LINE_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_), + 'width': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.BOUNDARY_IS_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LinearRing': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LineString': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'Point': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_), + 'GeometryCollection': ol.xml.makeChildAppender( + ol.format.KML.writeMultiGeometry_) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.PLACEMARK_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'name', 'open', 'visibility', 'address', 'phoneNumber', 'description', + 'styleUrl', 'Style' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'ExtendedData': ol.xml.makeChildAppender( + ol.format.KML.writeExtendedData_), + 'MultiGeometry': ol.xml.makeChildAppender( + ol.format.KML.writeMultiGeometry_), + 'LineString': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'LinearRing': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'Point': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_), + 'Style': ol.xml.makeChildAppender(ol.format.KML.writeStyle_), + 'address': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'description': ol.xml.makeChildAppender( + ol.format.XSD.writeStringTextNode), + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'open': ol.xml.makeChildAppender(ol.format.XSD.writeBooleanTextNode), + 'phoneNumber': ol.xml.makeChildAppender( + ol.format.XSD.writeStringTextNode), + 'styleUrl': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'visibility': ol.xml.makeChildAppender( + ol.format.XSD.writeBooleanTextNode) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'coordinates': ol.xml.makeChildAppender( + ol.format.KML.writeCoordinatesTextNode_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.POLYGON_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'outerBoundaryIs': ol.xml.makeChildAppender( + ol.format.KML.writeBoundaryIs_), + 'innerBoundaryIs': ol.xml.makeChildAppender( + ol.format.KML.writeBoundaryIs_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.POLY_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_) + }); + + +/** + * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.KML.STYLE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'IconStyle', 'LabelStyle', 'LineStyle', 'PolyStyle' + ]); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.KML.STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'IconStyle': ol.xml.makeChildAppender(ol.format.KML.writeIconStyle_), + 'LabelStyle': ol.xml.makeChildAppender(ol.format.KML.writeLabelStyle_), + 'LineStyle': ol.xml.makeChildAppender(ol.format.KML.writeLineStyle_), + 'PolyStyle': ol.xml.makeChildAppender(ol.format.KML.writePolyStyle_) + }); + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private + */ +ol.format.KML.GX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + return ol.xml.createElementNS(ol.format.KML.GX_NAMESPACE_URIS_[0], + 'gx:' + opt_nodeName); +}; + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private + */ +ol.format.KML.DOCUMENT_NODE_FACTORY_ = function(value, objectStack, + opt_nodeName) { + var parentNode = objectStack[objectStack.length - 1].node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode), + 'parentNode should be an XML node'); + return ol.xml.createElementNS(parentNode.namespaceURI, 'Placemark'); +}; + + +/** + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private + */ +ol.format.KML.GEOMETRY_NODE_FACTORY_ = function(value, objectStack, + opt_nodeName) { + if (value) { + var parentNode = objectStack[objectStack.length - 1].node; + ol.DEBUG && console.assert(ol.xml.isNode(parentNode), + 'parentNode should be an XML node'); + return ol.xml.createElementNS(parentNode.namespaceURI, + ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_[/** @type {ol.geom.Geometry} */ (value).getType()]); + } +}; + + +/** + * A factory for creating coordinates nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.COLOR_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('color'); + + +/** + * A factory for creating coordinates nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.COORDINATES_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('coordinates'); + + +/** + * A factory for creating Data nodes. + * @const + * @type {function(*, Array.<*>): (Node|undefined)} + * @private + */ +ol.format.KML.DATA_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('Data'); + + +/** + * A factory for creating ExtendedData nodes. + * @const + * @type {function(*, Array.<*>): (Node|undefined)} + * @private + */ +ol.format.KML.EXTENDEDDATA_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('ExtendedData'); + + +/** + * A factory for creating innerBoundaryIs nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('innerBoundaryIs'); + + +/** + * A factory for creating Point nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.POINT_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('Point'); + + +/** + * A factory for creating LineString nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.LINE_STRING_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('LineString'); + + +/** + * A factory for creating LinearRing nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.LINEAR_RING_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('LinearRing'); + + +/** + * A factory for creating Polygon nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.POLYGON_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('Polygon'); + + +/** + * A factory for creating outerBoundaryIs nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private + */ +ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('outerBoundaryIs'); + + +/** + * Encode an array of features in the KML format. GeometryCollections, MultiPoints, + * MultiLineStrings, and MultiPolygons are output as MultiGeometries. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {string} Result. + * @api stable + */ +ol.format.KML.prototype.writeFeatures; + + +/** + * Encode an array of features in the KML format as an XML node. GeometryCollections, + * MultiPoints, MultiLineStrings, and MultiPolygons are output as MultiGeometries. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api + */ +ol.format.KML.prototype.writeFeaturesNode = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + var kml = ol.xml.createElementNS(ol.format.KML.NAMESPACE_URIS_[4], 'kml'); + var xmlnsUri = 'http://www.w3.org/2000/xmlns/'; + var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance'; + ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:gx', + ol.format.KML.GX_NAMESPACE_URIS_[0]); + ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri); + ol.xml.setAttributeNS(kml, xmlSchemaInstanceUri, 'xsi:schemaLocation', + ol.format.KML.SCHEMA_LOCATION_); + + var /** @type {ol.XmlNodeStackItem} */ context = {node: kml}; + var properties = {}; + if (features.length > 1) { + properties['Document'] = features; + } else if (features.length == 1) { + properties['Placemark'] = features[0]; + } + var orderedKeys = ol.format.KML.KML_SEQUENCE_[kml.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.KML_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, [opt_options], orderedKeys, + this); + return kml; +}; + +goog.provide('ol.ext.pbf'); +/** @typedef {function(*)} */ +ol.ext.pbf; +(function() { +var exports = {}; +var module = {exports: exports}; +var define; +/** + * @fileoverview + * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility} + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pbf = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) +} + +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 +} + +},{}],2:[function(_dereq_,module,exports){ +'use strict'; + +module.exports = Pbf; + +var ieee754 = _dereq_('ieee754'); + +function Pbf(buf) { + this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0); + this.pos = 0; + this.type = 0; + this.length = this.buf.length; +} + +Pbf.Varint = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum +Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64 +Pbf.Bytes = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields +Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32 + +var SHIFT_LEFT_32 = (1 << 16) * (1 << 16), + SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32; + +Pbf.prototype = { + + destroy: function() { + this.buf = null; + }, + + // === READING ================================================================= + + readFields: function(readField, result, end) { + end = end || this.length; + + while (this.pos < end) { + var val = this.readVarint(), + tag = val >> 3, + startPos = this.pos; + + this.type = val & 0x7; + readField(tag, result, this); + + if (this.pos === startPos) this.skip(val); + } + return result; + }, + + readMessage: function(readField, result) { + return this.readFields(readField, result, this.readVarint() + this.pos); + }, + + readFixed32: function() { + var val = readUInt32(this.buf, this.pos); + this.pos += 4; + return val; + }, + + readSFixed32: function() { + var val = readInt32(this.buf, this.pos); + this.pos += 4; + return val; + }, + + // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed) + + readFixed64: function() { + var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32; + this.pos += 8; + return val; + }, + + readSFixed64: function() { + var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32; + this.pos += 8; + return val; + }, + + readFloat: function() { + var val = ieee754.read(this.buf, this.pos, true, 23, 4); + this.pos += 4; + return val; + }, + + readDouble: function() { + var val = ieee754.read(this.buf, this.pos, true, 52, 8); + this.pos += 8; + return val; + }, + + readVarint: function(isSigned) { + var buf = this.buf, + val, b; + + b = buf[this.pos++]; val = b & 0x7f; if (b < 0x80) return val; + b = buf[this.pos++]; val |= (b & 0x7f) << 7; if (b < 0x80) return val; + b = buf[this.pos++]; val |= (b & 0x7f) << 14; if (b < 0x80) return val; + b = buf[this.pos++]; val |= (b & 0x7f) << 21; if (b < 0x80) return val; + b = buf[this.pos]; val |= (b & 0x0f) << 28; + + return readVarintRemainder(val, isSigned, this); + }, + + readVarint64: function() { // for compatibility with v2.0.1 + return this.readVarint(true); + }, + + readSVarint: function() { + var num = this.readVarint(); + return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding + }, + + readBoolean: function() { + return Boolean(this.readVarint()); + }, + + readString: function() { + var end = this.readVarint() + this.pos, + str = readUtf8(this.buf, this.pos, end); + this.pos = end; + return str; + }, + + readBytes: function() { + var end = this.readVarint() + this.pos, + buffer = this.buf.subarray(this.pos, end); + this.pos = end; + return buffer; + }, + + // verbose for performance reasons; doesn't affect gzipped size + + readPackedVarint: function(arr, isSigned) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readVarint(isSigned)); + return arr; + }, + readPackedSVarint: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readSVarint()); + return arr; + }, + readPackedBoolean: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readBoolean()); + return arr; + }, + readPackedFloat: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readFloat()); + return arr; + }, + readPackedDouble: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readDouble()); + return arr; + }, + readPackedFixed32: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readFixed32()); + return arr; + }, + readPackedSFixed32: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readSFixed32()); + return arr; + }, + readPackedFixed64: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readFixed64()); + return arr; + }, + readPackedSFixed64: function(arr) { + var end = readPackedEnd(this); + arr = arr || []; + while (this.pos < end) arr.push(this.readSFixed64()); + return arr; + }, + + skip: function(val) { + var type = val & 0x7; + if (type === Pbf.Varint) while (this.buf[this.pos++] > 0x7f) {} + else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos; + else if (type === Pbf.Fixed32) this.pos += 4; + else if (type === Pbf.Fixed64) this.pos += 8; + else throw new Error('Unimplemented type: ' + type); + }, + + // === WRITING ================================================================= + + writeTag: function(tag, type) { + this.writeVarint((tag << 3) | type); + }, + + realloc: function(min) { + var length = this.length || 16; + + while (length < this.pos + min) length *= 2; + + if (length !== this.length) { + var buf = new Uint8Array(length); + buf.set(this.buf); + this.buf = buf; + this.length = length; + } + }, + + finish: function() { + this.length = this.pos; + this.pos = 0; + return this.buf.subarray(0, this.length); + }, + + writeFixed32: function(val) { + this.realloc(4); + writeInt32(this.buf, val, this.pos); + this.pos += 4; + }, + + writeSFixed32: function(val) { + this.realloc(4); + writeInt32(this.buf, val, this.pos); + this.pos += 4; + }, + + writeFixed64: function(val) { + this.realloc(8); + writeInt32(this.buf, val & -1, this.pos); + writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4); + this.pos += 8; + }, + + writeSFixed64: function(val) { + this.realloc(8); + writeInt32(this.buf, val & -1, this.pos); + writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4); + this.pos += 8; + }, + + writeVarint: function(val) { + val = +val || 0; + + if (val > 0xfffffff || val < 0) { + writeBigVarint(val, this); + return; + } + + this.realloc(4); + + this.buf[this.pos++] = val & 0x7f | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return; + this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return; + this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return; + this.buf[this.pos++] = (val >>> 7) & 0x7f; + }, + + writeSVarint: function(val) { + this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2); + }, + + writeBoolean: function(val) { + this.writeVarint(Boolean(val)); + }, + + writeString: function(str) { + str = String(str); + this.realloc(str.length * 4); + + this.pos++; // reserve 1 byte for short string length + + var startPos = this.pos; + // write the string directly to the buffer and see how much was written + this.pos = writeUtf8(this.buf, str, this.pos); + var len = this.pos - startPos; + + if (len >= 0x80) makeRoomForExtraLength(startPos, len, this); + + // finally, write the message length in the reserved place and restore the position + this.pos = startPos - 1; + this.writeVarint(len); + this.pos += len; + }, + + writeFloat: function(val) { + this.realloc(4); + ieee754.write(this.buf, val, this.pos, true, 23, 4); + this.pos += 4; + }, + + writeDouble: function(val) { + this.realloc(8); + ieee754.write(this.buf, val, this.pos, true, 52, 8); + this.pos += 8; + }, + + writeBytes: function(buffer) { + var len = buffer.length; + this.writeVarint(len); + this.realloc(len); + for (var i = 0; i < len; i++) this.buf[this.pos++] = buffer[i]; + }, + + writeRawMessage: function(fn, obj) { + this.pos++; // reserve 1 byte for short message length + + // write the message directly to the buffer and see how much was written + var startPos = this.pos; + fn(obj, this); + var len = this.pos - startPos; + + if (len >= 0x80) makeRoomForExtraLength(startPos, len, this); + + // finally, write the message length in the reserved place and restore the position + this.pos = startPos - 1; + this.writeVarint(len); + this.pos += len; + }, + + writeMessage: function(tag, fn, obj) { + this.writeTag(tag, Pbf.Bytes); + this.writeRawMessage(fn, obj); + }, + + writePackedVarint: function(tag, arr) { this.writeMessage(tag, writePackedVarint, arr); }, + writePackedSVarint: function(tag, arr) { this.writeMessage(tag, writePackedSVarint, arr); }, + writePackedBoolean: function(tag, arr) { this.writeMessage(tag, writePackedBoolean, arr); }, + writePackedFloat: function(tag, arr) { this.writeMessage(tag, writePackedFloat, arr); }, + writePackedDouble: function(tag, arr) { this.writeMessage(tag, writePackedDouble, arr); }, + writePackedFixed32: function(tag, arr) { this.writeMessage(tag, writePackedFixed32, arr); }, + writePackedSFixed32: function(tag, arr) { this.writeMessage(tag, writePackedSFixed32, arr); }, + writePackedFixed64: function(tag, arr) { this.writeMessage(tag, writePackedFixed64, arr); }, + writePackedSFixed64: function(tag, arr) { this.writeMessage(tag, writePackedSFixed64, arr); }, + + writeBytesField: function(tag, buffer) { + this.writeTag(tag, Pbf.Bytes); + this.writeBytes(buffer); + }, + writeFixed32Field: function(tag, val) { + this.writeTag(tag, Pbf.Fixed32); + this.writeFixed32(val); + }, + writeSFixed32Field: function(tag, val) { + this.writeTag(tag, Pbf.Fixed32); + this.writeSFixed32(val); + }, + writeFixed64Field: function(tag, val) { + this.writeTag(tag, Pbf.Fixed64); + this.writeFixed64(val); + }, + writeSFixed64Field: function(tag, val) { + this.writeTag(tag, Pbf.Fixed64); + this.writeSFixed64(val); + }, + writeVarintField: function(tag, val) { + this.writeTag(tag, Pbf.Varint); + this.writeVarint(val); + }, + writeSVarintField: function(tag, val) { + this.writeTag(tag, Pbf.Varint); + this.writeSVarint(val); + }, + writeStringField: function(tag, str) { + this.writeTag(tag, Pbf.Bytes); + this.writeString(str); + }, + writeFloatField: function(tag, val) { + this.writeTag(tag, Pbf.Fixed32); + this.writeFloat(val); + }, + writeDoubleField: function(tag, val) { + this.writeTag(tag, Pbf.Fixed64); + this.writeDouble(val); + }, + writeBooleanField: function(tag, val) { + this.writeVarintField(tag, Boolean(val)); + } +}; + +function readVarintRemainder(l, s, p) { + var buf = p.buf, + h, b; + + b = buf[p.pos++]; h = (b & 0x70) >> 4; if (b < 0x80) return toNum(l, h, s); + b = buf[p.pos++]; h |= (b & 0x7f) << 3; if (b < 0x80) return toNum(l, h, s); + b = buf[p.pos++]; h |= (b & 0x7f) << 10; if (b < 0x80) return toNum(l, h, s); + b = buf[p.pos++]; h |= (b & 0x7f) << 17; if (b < 0x80) return toNum(l, h, s); + b = buf[p.pos++]; h |= (b & 0x7f) << 24; if (b < 0x80) return toNum(l, h, s); + b = buf[p.pos++]; h |= (b & 0x01) << 31; if (b < 0x80) return toNum(l, h, s); + + throw new Error('Expected varint not more than 10 bytes'); +} + +function readPackedEnd(pbf) { + return pbf.type === Pbf.Bytes ? + pbf.readVarint() + pbf.pos : pbf.pos + 1; +} + +function toNum(low, high, isSigned) { + if (isSigned) { + return high * 0x100000000 + (low >>> 0); + } + + return ((high >>> 0) * 0x100000000) + (low >>> 0); +} + +function writeBigVarint(val, pbf) { + var low, high; + + if (val >= 0) { + low = (val % 0x100000000) | 0; + high = (val / 0x100000000) | 0; + } else { + low = ~(-val % 0x100000000); + high = ~(-val / 0x100000000); + + if (low ^ 0xffffffff) { + low = (low + 1) | 0; + } else { + low = 0; + high = (high + 1) | 0; + } + } + + if (val >= 0x10000000000000000 || val < -0x10000000000000000) { + throw new Error('Given varint doesn\'t fit into 10 bytes'); + } + + pbf.realloc(10); + + writeBigVarintLow(low, high, pbf); + writeBigVarintHigh(high, pbf); +} + +function writeBigVarintLow(low, high, pbf) { + pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; + pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; + pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; + pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; + pbf.buf[pbf.pos] = low & 0x7f; +} + +function writeBigVarintHigh(high, pbf) { + var lsb = (high & 0x07) << 4; + + pbf.buf[pbf.pos++] |= lsb | ((high >>>= 3) ? 0x80 : 0); if (!high) return; + pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; + pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; + pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; + pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; + pbf.buf[pbf.pos++] = high & 0x7f; +} + +function makeRoomForExtraLength(startPos, len, pbf) { + var extraLen = + len <= 0x3fff ? 1 : + len <= 0x1fffff ? 2 : + len <= 0xfffffff ? 3 : Math.ceil(Math.log(len) / (Math.LN2 * 7)); + + // if 1 byte isn't enough for encoding message length, shift the data to the right + pbf.realloc(extraLen); + for (var i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i]; +} + +function writePackedVarint(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeVarint(arr[i]); } +function writePackedSVarint(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSVarint(arr[i]); } +function writePackedFloat(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFloat(arr[i]); } +function writePackedDouble(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeDouble(arr[i]); } +function writePackedBoolean(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeBoolean(arr[i]); } +function writePackedFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFixed32(arr[i]); } +function writePackedSFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed32(arr[i]); } +function writePackedFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFixed64(arr[i]); } +function writePackedSFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed64(arr[i]); } + +// Buffer code below from https://github.com/feross/buffer, MIT-licensed + +function readUInt32(buf, pos) { + return ((buf[pos]) | + (buf[pos + 1] << 8) | + (buf[pos + 2] << 16)) + + (buf[pos + 3] * 0x1000000); +} + +function writeInt32(buf, val, pos) { + buf[pos] = val; + buf[pos + 1] = (val >>> 8); + buf[pos + 2] = (val >>> 16); + buf[pos + 3] = (val >>> 24); +} + +function readInt32(buf, pos) { + return ((buf[pos]) | + (buf[pos + 1] << 8) | + (buf[pos + 2] << 16)) + + (buf[pos + 3] << 24); +} + +function readUtf8(buf, pos, end) { + var str = ''; + var i = pos; + + while (i < end) { + var b0 = buf[i]; + var c = null; // codepoint + var bytesPerSequence = + b0 > 0xEF ? 4 : + b0 > 0xDF ? 3 : + b0 > 0xBF ? 2 : 1; + + if (i + bytesPerSequence > end) break; + + var b1, b2, b3; + + if (bytesPerSequence === 1) { + if (b0 < 0x80) { + c = b0; + } + } else if (bytesPerSequence === 2) { + b1 = buf[i + 1]; + if ((b1 & 0xC0) === 0x80) { + c = (b0 & 0x1F) << 0x6 | (b1 & 0x3F); + if (c <= 0x7F) { + c = null; + } + } + } else if (bytesPerSequence === 3) { + b1 = buf[i + 1]; + b2 = buf[i + 2]; + if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) { + c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | (b2 & 0x3F); + if (c <= 0x7FF || (c >= 0xD800 && c <= 0xDFFF)) { + c = null; + } + } + } else if (bytesPerSequence === 4) { + b1 = buf[i + 1]; + b2 = buf[i + 2]; + b3 = buf[i + 3]; + if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) { + c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | (b3 & 0x3F); + if (c <= 0xFFFF || c >= 0x110000) { + c = null; + } + } + } + + if (c === null) { + c = 0xFFFD; + bytesPerSequence = 1; + + } else if (c > 0xFFFF) { + c -= 0x10000; + str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800); + c = 0xDC00 | c & 0x3FF; + } + + str += String.fromCharCode(c); + i += bytesPerSequence; + } + + return str; +} + +function writeUtf8(buf, str, pos) { + for (var i = 0, c, lead; i < str.length; i++) { + c = str.charCodeAt(i); // code point + + if (c > 0xD7FF && c < 0xE000) { + if (lead) { + if (c < 0xDC00) { + buf[pos++] = 0xEF; + buf[pos++] = 0xBF; + buf[pos++] = 0xBD; + lead = c; + continue; + } else { + c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000; + lead = null; + } + } else { + if (c > 0xDBFF || (i + 1 === str.length)) { + buf[pos++] = 0xEF; + buf[pos++] = 0xBF; + buf[pos++] = 0xBD; + } else { + lead = c; + } + continue; + } + } else if (lead) { + buf[pos++] = 0xEF; + buf[pos++] = 0xBF; + buf[pos++] = 0xBD; + lead = null; + } + + if (c < 0x80) { + buf[pos++] = c; + } else { + if (c < 0x800) { + buf[pos++] = c >> 0x6 | 0xC0; + } else { + if (c < 0x10000) { + buf[pos++] = c >> 0xC | 0xE0; + } else { + buf[pos++] = c >> 0x12 | 0xF0; + buf[pos++] = c >> 0xC & 0x3F | 0x80; + } + buf[pos++] = c >> 0x6 & 0x3F | 0x80; + } + buf[pos++] = c & 0x3F | 0x80; + } + } + return pos; +} + +},{"ieee754":1}]},{},[2])(2) +}); +ol.ext.pbf = module.exports; +})(); + +goog.provide('ol.ext.vectortile'); +/** @typedef {function(*)} */ +ol.ext.vectortile; +(function() { +var exports = {}; +var module = {exports: exports}; +var define; +/** + * @fileoverview + * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility} + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.vectortile = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ +'use strict'; + +module.exports = Point; + +function Point(x, y) { + this.x = x; + this.y = y; +} + +Point.prototype = { + clone: function() { return new Point(this.x, this.y); }, + + add: function(p) { return this.clone()._add(p); }, + sub: function(p) { return this.clone()._sub(p); }, + mult: function(k) { return this.clone()._mult(k); }, + div: function(k) { return this.clone()._div(k); }, + rotate: function(a) { return this.clone()._rotate(a); }, + matMult: function(m) { return this.clone()._matMult(m); }, + unit: function() { return this.clone()._unit(); }, + perp: function() { return this.clone()._perp(); }, + round: function() { return this.clone()._round(); }, + + mag: function() { + return Math.sqrt(this.x * this.x + this.y * this.y); + }, + + equals: function(p) { + return this.x === p.x && + this.y === p.y; + }, + + dist: function(p) { + return Math.sqrt(this.distSqr(p)); + }, + + distSqr: function(p) { + var dx = p.x - this.x, + dy = p.y - this.y; + return dx * dx + dy * dy; + }, + + angle: function() { + return Math.atan2(this.y, this.x); + }, + + angleTo: function(b) { + return Math.atan2(this.y - b.y, this.x - b.x); + }, + + angleWith: function(b) { + return this.angleWithSep(b.x, b.y); + }, + + // Find the angle of the two vectors, solving the formula for the cross product a x b = |a||b|sin(θ) for θ. + angleWithSep: function(x, y) { + return Math.atan2( + this.x * y - this.y * x, + this.x * x + this.y * y); + }, + + _matMult: function(m) { + var x = m[0] * this.x + m[1] * this.y, + y = m[2] * this.x + m[3] * this.y; + this.x = x; + this.y = y; + return this; + }, + + _add: function(p) { + this.x += p.x; + this.y += p.y; + return this; + }, + + _sub: function(p) { + this.x -= p.x; + this.y -= p.y; + return this; + }, + + _mult: function(k) { + this.x *= k; + this.y *= k; + return this; + }, + + _div: function(k) { + this.x /= k; + this.y /= k; + return this; + }, + + _unit: function() { + this._div(this.mag()); + return this; + }, + + _perp: function() { + var y = this.y; + this.y = this.x; + this.x = -y; + return this; + }, + + _rotate: function(angle) { + var cos = Math.cos(angle), + sin = Math.sin(angle), + x = cos * this.x - sin * this.y, + y = sin * this.x + cos * this.y; + this.x = x; + this.y = y; + return this; + }, + + _round: function() { + this.x = Math.round(this.x); + this.y = Math.round(this.y); + return this; + } +}; + +// constructs Point from an array if necessary +Point.convert = function (a) { + if (a instanceof Point) { + return a; + } + if (Array.isArray(a)) { + return new Point(a[0], a[1]); + } + return a; +}; + +},{}],2:[function(_dereq_,module,exports){ +module.exports.VectorTile = _dereq_('./lib/vectortile.js'); +module.exports.VectorTileFeature = _dereq_('./lib/vectortilefeature.js'); +module.exports.VectorTileLayer = _dereq_('./lib/vectortilelayer.js'); + +},{"./lib/vectortile.js":3,"./lib/vectortilefeature.js":4,"./lib/vectortilelayer.js":5}],3:[function(_dereq_,module,exports){ +'use strict'; + +var VectorTileLayer = _dereq_('./vectortilelayer'); + +module.exports = VectorTile; + +function VectorTile(pbf, end) { + this.layers = pbf.readFields(readTile, {}, end); +} + +function readTile(tag, layers, pbf) { + if (tag === 3) { + var layer = new VectorTileLayer(pbf, pbf.readVarint() + pbf.pos); + if (layer.length) layers[layer.name] = layer; + } +} + + +},{"./vectortilelayer":5}],4:[function(_dereq_,module,exports){ +'use strict'; + +var Point = _dereq_('point-geometry'); + +module.exports = VectorTileFeature; + +function VectorTileFeature(pbf, end, extent, keys, values) { + // Public + this.properties = {}; + this.extent = extent; + this.type = 0; + + // Private + this._pbf = pbf; + this._geometry = -1; + this._keys = keys; + this._values = values; + + pbf.readFields(readFeature, this, end); +} + +function readFeature(tag, feature, pbf) { + if (tag == 1) feature.id = pbf.readVarint(); + else if (tag == 2) readTag(pbf, feature); + else if (tag == 3) feature.type = pbf.readVarint(); + else if (tag == 4) feature._geometry = pbf.pos; +} + +function readTag(pbf, feature) { + var end = pbf.readVarint() + pbf.pos; + + while (pbf.pos < end) { + var key = feature._keys[pbf.readVarint()], + value = feature._values[pbf.readVarint()]; + feature.properties[key] = value; + } +} + +VectorTileFeature.types = ['Unknown', 'Point', 'LineString', 'Polygon']; + +VectorTileFeature.prototype.loadGeometry = function() { + var pbf = this._pbf; + pbf.pos = this._geometry; + + var end = pbf.readVarint() + pbf.pos, + cmd = 1, + length = 0, + x = 0, + y = 0, + lines = [], + line; + + while (pbf.pos < end) { + if (!length) { + var cmdLen = pbf.readVarint(); + cmd = cmdLen & 0x7; + length = cmdLen >> 3; + } + + length--; + + if (cmd === 1 || cmd === 2) { + x += pbf.readSVarint(); + y += pbf.readSVarint(); + + if (cmd === 1) { // moveTo + if (line) lines.push(line); + line = []; + } + + line.push(new Point(x, y)); + + } else if (cmd === 7) { + + // Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90 + if (line) { + line.push(line[0].clone()); // closePolygon + } + + } else { + throw new Error('unknown command ' + cmd); + } + } + + if (line) lines.push(line); + + return lines; +}; + +VectorTileFeature.prototype.bbox = function() { + var pbf = this._pbf; + pbf.pos = this._geometry; + + var end = pbf.readVarint() + pbf.pos, + cmd = 1, + length = 0, + x = 0, + y = 0, + x1 = Infinity, + x2 = -Infinity, + y1 = Infinity, + y2 = -Infinity; + + while (pbf.pos < end) { + if (!length) { + var cmdLen = pbf.readVarint(); + cmd = cmdLen & 0x7; + length = cmdLen >> 3; + } + + length--; + + if (cmd === 1 || cmd === 2) { + x += pbf.readSVarint(); + y += pbf.readSVarint(); + if (x < x1) x1 = x; + if (x > x2) x2 = x; + if (y < y1) y1 = y; + if (y > y2) y2 = y; + + } else if (cmd !== 7) { + throw new Error('unknown command ' + cmd); + } + } + + return [x1, y1, x2, y2]; +}; + +VectorTileFeature.prototype.toGeoJSON = function(x, y, z) { + var size = this.extent * Math.pow(2, z), + x0 = this.extent * x, + y0 = this.extent * y, + coords = this.loadGeometry(), + type = VectorTileFeature.types[this.type], + i, j; + + function project(line) { + for (var j = 0; j < line.length; j++) { + var p = line[j], y2 = 180 - (p.y + y0) * 360 / size; + line[j] = [ + (p.x + x0) * 360 / size - 180, + 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90 + ]; + } + } + + switch (this.type) { + case 1: + var points = []; + for (i = 0; i < coords.length; i++) { + points[i] = coords[i][0]; + } + coords = points; + project(coords); + break; + + case 2: + for (i = 0; i < coords.length; i++) { + project(coords[i]); + } + break; + + case 3: + coords = classifyRings(coords); + for (i = 0; i < coords.length; i++) { + for (j = 0; j < coords[i].length; j++) { + project(coords[i][j]); + } + } + break; + } + + if (coords.length === 1) { + coords = coords[0]; + } else { + type = 'Multi' + type; + } + + var result = { + type: "Feature", + geometry: { + type: type, + coordinates: coords + }, + properties: this.properties + }; + + if ('id' in this) { + result.id = this.id; + } + + return result; +}; + +// classifies an array of rings into polygons with outer rings and holes + +function classifyRings(rings) { + var len = rings.length; + + if (len <= 1) return [rings]; + + var polygons = [], + polygon, + ccw; + + for (var i = 0; i < len; i++) { + var area = signedArea(rings[i]); + if (area === 0) continue; + + if (ccw === undefined) ccw = area < 0; + + if (ccw === area < 0) { + if (polygon) polygons.push(polygon); + polygon = [rings[i]]; + + } else { + polygon.push(rings[i]); + } + } + if (polygon) polygons.push(polygon); + + return polygons; +} + +function signedArea(ring) { + var sum = 0; + for (var i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) { + p1 = ring[i]; + p2 = ring[j]; + sum += (p2.x - p1.x) * (p1.y + p2.y); + } + return sum; +} + +},{"point-geometry":1}],5:[function(_dereq_,module,exports){ +'use strict'; + +var VectorTileFeature = _dereq_('./vectortilefeature.js'); + +module.exports = VectorTileLayer; + +function VectorTileLayer(pbf, end) { + // Public + this.version = 1; + this.name = null; + this.extent = 4096; + this.length = 0; + + // Private + this._pbf = pbf; + this._keys = []; + this._values = []; + this._features = []; + + pbf.readFields(readLayer, this, end); + + this.length = this._features.length; +} + +function readLayer(tag, layer, pbf) { + if (tag === 15) layer.version = pbf.readVarint(); + else if (tag === 1) layer.name = pbf.readString(); + else if (tag === 5) layer.extent = pbf.readVarint(); + else if (tag === 2) layer._features.push(pbf.pos); + else if (tag === 3) layer._keys.push(pbf.readString()); + else if (tag === 4) layer._values.push(readValueMessage(pbf)); +} + +function readValueMessage(pbf) { + var value = null, + end = pbf.readVarint() + pbf.pos; + + while (pbf.pos < end) { + var tag = pbf.readVarint() >> 3; + + value = tag === 1 ? pbf.readString() : + tag === 2 ? pbf.readFloat() : + tag === 3 ? pbf.readDouble() : + tag === 4 ? pbf.readVarint64() : + tag === 5 ? pbf.readVarint() : + tag === 6 ? pbf.readSVarint() : + tag === 7 ? pbf.readBoolean() : null; + } + + return value; +} + +// return feature `i` from this layer as a `VectorTileFeature` +VectorTileLayer.prototype.feature = function(i) { + if (i < 0 || i >= this._features.length) throw new Error('feature index out of bounds'); + + this._pbf.pos = this._features[i]; + + var end = this._pbf.readVarint() + this._pbf.pos; + return new VectorTileFeature(this._pbf, end, this.extent, this._keys, this._values); +}; + +},{"./vectortilefeature.js":4}]},{},[2])(2) +}); +ol.ext.vectortile = module.exports; +})(); + +goog.provide('ol.render.Feature'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); + + +/** + * Lightweight, read-only, {@link ol.Feature} and {@link ol.geom.Geometry} like + * structure, optimized for rendering and styling. Geometry access through the + * API is limited to getting the type and extent of the geometry. + * + * @constructor + * @param {ol.geom.GeometryType} type Geometry type. + * @param {Array.<number>} flatCoordinates Flat coordinates. These always need + * to be right-handed for polygons. + * @param {Array.<number>|Array.<Array.<number>>} ends Ends or Endss. + * @param {Object.<string, *>} properties Properties. + */ +ol.render.Feature = function(type, flatCoordinates, ends, properties) { + + /** + * @private + * @type {ol.Extent|undefined} + */ + this.extent_; + + ol.DEBUG && console.assert(type === ol.geom.GeometryType.POINT || + type === ol.geom.GeometryType.MULTI_POINT || + type === ol.geom.GeometryType.LINE_STRING || + type === ol.geom.GeometryType.MULTI_LINE_STRING || + type === ol.geom.GeometryType.POLYGON, + 'Need a Point, MultiPoint, LineString, MultiLineString or Polygon type'); + + /** + * @private + * @type {ol.geom.GeometryType} + */ + this.type_ = type; + + /** + * @private + * @type {Array.<number>} + */ + this.flatCoordinates_ = flatCoordinates; + + /** + * @private + * @type {Array.<number>|Array.<Array.<number>>} + */ + this.ends_ = ends; + + /** + * @private + * @type {Object.<string, *>} + */ + this.properties_ = properties; + +}; + + +/** + * Get a feature property by its key. + * @param {string} key Key + * @return {*} Value for the requested key. + * @api + */ +ol.render.Feature.prototype.get = function(key) { + return this.properties_[key]; +}; + + +/** + * @return {Array.<number>|Array.<Array.<number>>} Ends or endss. + */ +ol.render.Feature.prototype.getEnds = function() { + return this.ends_; +}; + + +/** + * Get the extent of this feature's geometry. + * @return {ol.Extent} Extent. + * @api + */ +ol.render.Feature.prototype.getExtent = function() { + if (!this.extent_) { + this.extent_ = this.type_ === ol.geom.GeometryType.POINT ? + ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates_) : + ol.extent.createOrUpdateFromFlatCoordinates( + this.flatCoordinates_, 0, this.flatCoordinates_.length, 2); + + } + return this.extent_; +}; + + +/** + * @return {Array.<number>} Flat coordinates. + */ +ol.render.Feature.prototype.getOrientedFlatCoordinates = function() { + return this.flatCoordinates_; +}; + + +/** + * @return {Array.<number>} Flat coordinates. + */ +ol.render.Feature.prototype.getFlatCoordinates = + ol.render.Feature.prototype.getOrientedFlatCoordinates; + + +/** + * Get the feature for working with its geometry. + * @return {ol.render.Feature} Feature. + * @api + */ +ol.render.Feature.prototype.getGeometry = function() { + return this; +}; + + +/** + * Get the feature properties. + * @return {Object.<string, *>} Feature properties. + * @api + */ +ol.render.Feature.prototype.getProperties = function() { + return this.properties_; +}; + + +/** + * Get the feature for working with its geometry. + * @return {ol.render.Feature} Feature. + */ +ol.render.Feature.prototype.getSimplifiedGeometry = + ol.render.Feature.prototype.getGeometry; + + +/** + * @return {number} Stride. + */ +ol.render.Feature.prototype.getStride = function() { + return 2; +}; + + +/** + * @return {undefined} + */ +ol.render.Feature.prototype.getStyleFunction = ol.nullFunction; + + +/** + * Get the type of this feature's geometry. + * @return {ol.geom.GeometryType} Geometry type. + * @api + */ +ol.render.Feature.prototype.getType = function() { + return this.type_; +}; + +//FIXME Implement projection handling + +goog.provide('ol.format.MVT'); + +goog.require('ol'); +goog.require('ol.ext.pbf'); +goog.require('ol.ext.vectortile'); +goog.require('ol.format.Feature'); +goog.require('ol.format.FormatType'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); +goog.require('ol.render.Feature'); + + +/** + * @classdesc + * Feature format for reading data in the Mapbox MVT format. + * + * @constructor + * @extends {ol.format.Feature} + * @param {olx.format.MVTOptions=} opt_options Options. + * @api + */ +ol.format.MVT = function(opt_options) { + + ol.format.Feature.call(this); + + var options = opt_options ? opt_options : {}; + + /** + * @type {ol.proj.Projection} + */ + this.defaultDataProjection = new ol.proj.Projection({ + code: '', + units: ol.proj.Units.TILE_PIXELS + }); + + /** + * @private + * @type {function((ol.geom.Geometry|Object.<string, *>)=)| + * function(ol.geom.GeometryType,Array.<number>, + * (Array.<number>|Array.<Array.<number>>),Object.<string, *>)} + */ + this.featureClass_ = options.featureClass ? + options.featureClass : ol.render.Feature; + + /** + * @private + * @type {string} + */ + this.geometryName_ = options.geometryName ? + options.geometryName : 'geometry'; + + /** + * @private + * @type {string} + */ + this.layerName_ = options.layerName ? options.layerName : 'layer'; + + /** + * @private + * @type {Array.<string>} + */ + this.layers_ = options.layers ? options.layers : null; + +}; +ol.inherits(ol.format.MVT, ol.format.Feature); + + +/** + * @inheritDoc + */ +ol.format.MVT.prototype.getType = function() { + return ol.format.FormatType.ARRAY_BUFFER; +}; + + +/** + * @private + * @param {Object} rawFeature Raw Mapbox feature. + * @param {string} layer Layer. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + */ +ol.format.MVT.prototype.readFeature_ = function( + rawFeature, layer, opt_options) { + var feature = new this.featureClass_(); + var id = rawFeature.id; + var values = rawFeature.properties; + values[this.layerName_] = layer; + var geometry = ol.format.Feature.transformWithOptions( + ol.format.MVT.readGeometry_(rawFeature), false, + this.adaptOptions(opt_options)); + if (geometry) { + values[this.geometryName_] = geometry; + } + feature.setId(id); + feature.setProperties(values); + feature.setGeometryName(this.geometryName_); + return feature; +}; + + +/** + * @private + * @param {Object} rawFeature Raw Mapbox feature. + * @param {string} layer Layer. + * @return {ol.render.Feature} Feature. + */ +ol.format.MVT.prototype.readRenderFeature_ = function(rawFeature, layer) { + var coords = rawFeature.loadGeometry(); + var ends = []; + var flatCoordinates = []; + ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends); + + var type = rawFeature.type; + /** @type {ol.geom.GeometryType} */ + var geometryType; + if (type === 1) { + geometryType = coords.length === 1 ? + ol.geom.GeometryType.POINT : ol.geom.GeometryType.MULTI_POINT; + } else if (type === 2) { + if (coords.length === 1) { + geometryType = ol.geom.GeometryType.LINE_STRING; + } else { + geometryType = ol.geom.GeometryType.MULTI_LINE_STRING; + } + } else if (type === 3) { + geometryType = ol.geom.GeometryType.POLYGON; + } + + var values = rawFeature.properties; + values[this.layerName_] = layer; + + return new this.featureClass_(geometryType, flatCoordinates, ends, values); +}; + + +/** + * @inheritDoc + * @api + */ +ol.format.MVT.prototype.readFeatures = function(source, opt_options) { + var layers = this.layers_; + + var pbf = new ol.ext.pbf(/** @type {ArrayBuffer} */ (source)); + var tile = new ol.ext.vectortile.VectorTile(pbf); + var features = []; + var featureClass = this.featureClass_; + var layer, feature; + for (var name in tile.layers) { + if (layers && layers.indexOf(name) == -1) { + continue; + } + layer = tile.layers[name]; + + for (var i = 0, ii = layer.length; i < ii; ++i) { + if (featureClass === ol.render.Feature) { + feature = this.readRenderFeature_(layer.feature(i), name); + } else { + feature = this.readFeature_(layer.feature(i), name, opt_options); + } + features.push(feature); + } + } + + return features; +}; + + +/** + * @inheritDoc + * @api + */ +ol.format.MVT.prototype.readProjection = function(source) { + return this.defaultDataProjection; +}; + + +/** + * Sets the layers that features will be read from. + * @param {Array.<string>} layers Layers. + * @api + */ +ol.format.MVT.prototype.setLayers = function(layers) { + this.layers_ = layers; +}; + + +/** + * @private + * @param {Object} coords Raw feature coordinates. + * @param {Array.<number>} flatCoordinates Flat coordinates to be populated by + * this function. + * @param {Array.<number>} ends Ends to be populated by this function. + */ +ol.format.MVT.calculateFlatCoordinates_ = function( + coords, flatCoordinates, ends) { + var end = 0; + for (var i = 0, ii = coords.length; i < ii; ++i) { + var line = coords[i]; + var j, jj; + for (j = 0, jj = line.length; j < jj; ++j) { + var coord = line[j]; + // Non-tilespace coords can be calculated here when a TileGrid and + // TileCoord are known. + flatCoordinates.push(coord.x, coord.y); + } + end += 2 * j; + ends.push(end); + } +}; + + +/** + * @private + * @param {Object} rawFeature Raw Mapbox feature. + * @return {ol.geom.Geometry} Geometry. + */ +ol.format.MVT.readGeometry_ = function(rawFeature) { + var type = rawFeature.type; + if (type === 0) { + return null; + } + + var coords = rawFeature.loadGeometry(); + var ends = []; + var flatCoordinates = []; + ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends); + + var geom; + if (type === 1) { + geom = coords.length === 1 ? + new ol.geom.Point(null) : new ol.geom.MultiPoint(null); + } else if (type === 2) { + if (coords.length === 1) { + geom = new ol.geom.LineString(null); + } else { + geom = new ol.geom.MultiLineString(null); + } + } else if (type === 3) { + geom = new ol.geom.Polygon(null); + } + + geom.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates, + ends); + + return geom; +}; + +// FIXME add typedef for stack state objects +goog.provide('ol.format.OSMXML'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Feature format for reading data in the + * [OSMXML format](http://wiki.openstreetmap.org/wiki/OSM_XML). + * + * @constructor + * @extends {ol.format.XMLFeature} + * @api stable + */ +ol.format.OSMXML = function() { + ol.format.XMLFeature.call(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); +}; +ol.inherits(ol.format.OSMXML, ol.format.XMLFeature); + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.OSMXML.EXTENSIONS_ = ['.osm']; + + +/** + * @inheritDoc + */ +ol.format.OSMXML.prototype.getExtensions = function() { + return ol.format.OSMXML.EXTENSIONS_; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.OSMXML.readNode_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'node', 'localName should be node'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var state = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var id = node.getAttribute('id'); + /** @type {ol.Coordinate} */ + var coordinates = [ + parseFloat(node.getAttribute('lon')), + parseFloat(node.getAttribute('lat')) + ]; + state.nodes[id] = coordinates; + + var values = ol.xml.pushParseAndPop({ + tags: {} + }, ol.format.OSMXML.NODE_PARSERS_, node, objectStack); + if (!ol.obj.isEmpty(values.tags)) { + var geometry = new ol.geom.Point(coordinates); + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setId(id); + feature.setProperties(values.tags); + state.features.push(feature); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.OSMXML.readWay_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'way', 'localName should be way'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var id = node.getAttribute('id'); + var values = ol.xml.pushParseAndPop({ + ndrefs: [], + tags: {} + }, ol.format.OSMXML.WAY_PARSERS_, node, objectStack); + var state = /** @type {Object} */ (objectStack[objectStack.length - 1]); + /** @type {Array.<number>} */ + var flatCoordinates = []; + for (var i = 0, ii = values.ndrefs.length; i < ii; i++) { + var point = state.nodes[values.ndrefs[i]]; + ol.array.extend(flatCoordinates, point); + } + var geometry; + if (values.ndrefs[0] == values.ndrefs[values.ndrefs.length - 1]) { + // closed way + geometry = new ol.geom.Polygon(null); + geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates, + [flatCoordinates.length]); + } else { + geometry = new ol.geom.LineString(null); + geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates); + } + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setId(id); + feature.setProperties(values.tags); + state.features.push(feature); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.OSMXML.readNd_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'nd', 'localName should be nd'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + values.ndrefs.push(node.getAttribute('ref')); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.OSMXML.readTag_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'tag', 'localName should be tag'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + values.tags[node.getAttribute('k')] = node.getAttribute('v'); +}; + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.format.OSMXML.NAMESPACE_URIS_ = [ + null +]; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OSMXML.NAMESPACE_URIS_, { + 'nd': ol.format.OSMXML.readNd_, + 'tag': ol.format.OSMXML.readTag_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OSMXML.PARSERS_ = ol.xml.makeStructureNS( + ol.format.OSMXML.NAMESPACE_URIS_, { + 'node': ol.format.OSMXML.readNode_, + 'way': ol.format.OSMXML.readWay_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OSMXML.NODE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OSMXML.NAMESPACE_URIS_, { + 'tag': ol.format.OSMXML.readTag_ + }); + + +/** + * Read all features from an OSM source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.OSMXML.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.OSMXML.prototype.readFeaturesFromNode = function(node, opt_options) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + var options = this.getReadOptions(node, opt_options); + if (node.localName == 'osm') { + var state = ol.xml.pushParseAndPop({ + nodes: {}, + features: [] + }, ol.format.OSMXML.PARSERS_, node, [options]); + if (state.features) { + return state.features; + } + } + return []; +}; + + +/** + * Read the projection from an OSM source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.OSMXML.prototype.readProjection; + +goog.provide('ol.format.XLink'); + + +/** + * @const + * @type {string} + */ +ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink'; + + +/** + * @param {Node} node Node. + * @return {boolean|undefined} Boolean. + */ +ol.format.XLink.readHref = function(node) { + return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href'); +}; + +goog.provide('ol.format.XML'); + +goog.require('ol.xml'); + + +/** + * @classdesc + * Generic format for reading non-feature XML data + * + * @constructor + * @struct + */ +ol.format.XML = function() { +}; + + +/** + * @param {Document|Node|string} source Source. + * @return {Object} The parsed result. + */ +ol.format.XML.prototype.read = function(source) { + if (ol.xml.isDocument(source)) { + return this.readFromDocument(/** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readFromNode(/** @type {Node} */ (source)); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readFromDocument(doc); + } else { + return null; + } +}; + + +/** + * @abstract + * @param {Document} doc Document. + * @return {Object} Object + */ +ol.format.XML.prototype.readFromDocument = function(doc) {}; + + +/** + * @abstract + * @param {Node} node Node. + * @return {Object} Object + */ +ol.format.XML.prototype.readFromNode = function(node) {}; + +goog.provide('ol.format.OWS'); + +goog.require('ol'); +goog.require('ol.format.XLink'); +goog.require('ol.format.XML'); +goog.require('ol.format.XSD'); +goog.require('ol.xml'); + + +/** + * @constructor + * @extends {ol.format.XML} + */ +ol.format.OWS = function() { + ol.format.XML.call(this); +}; +ol.inherits(ol.format.OWS, ol.format.XML); + + +/** + * @param {Document} doc Document. + * @return {Object} OWS object. + */ +ol.format.OWS.prototype.readFromDocument = function(doc) { + ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + return this.readFromNode(n); + } + } + return null; +}; + + +/** + * @param {Node} node Node. + * @return {Object} OWS object. + */ +ol.format.OWS.prototype.readFromNode = function(node) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + var owsObject = ol.xml.pushParseAndPop({}, + ol.format.OWS.PARSERS_, node, []); + return owsObject ? owsObject : null; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The address. + */ +ol.format.OWS.readAddress_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Address', + 'localName should be Address'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.ADDRESS_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The values. + */ +ol.format.OWS.readAllowedValues_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'AllowedValues', + 'localName should be AllowedValues'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.ALLOWED_VALUES_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The constraint. + */ +ol.format.OWS.readConstraint_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Constraint', + 'localName should be Constraint'); + var name = node.getAttribute('name'); + if (!name) { + return undefined; + } + return ol.xml.pushParseAndPop({'name': name}, + ol.format.OWS.CONSTRAINT_PARSERS_, node, + objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The contact info. + */ +ol.format.OWS.readContactInfo_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ContactInfo', + 'localName should be ContactInfo'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.CONTACT_INFO_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The DCP. + */ +ol.format.OWS.readDcp_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'DCP', 'localName should be DCP'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.DCP_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The GET object. + */ +ol.format.OWS.readGet_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Get', 'localName should be Get'); + var href = ol.format.XLink.readHref(node); + if (!href) { + return undefined; + } + return ol.xml.pushParseAndPop({'href': href}, + ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The HTTP object. + */ +ol.format.OWS.readHttp_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'HTTP', 'localName should be HTTP'); + return ol.xml.pushParseAndPop({}, ol.format.OWS.HTTP_PARSERS_, + node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The operation. + */ +ol.format.OWS.readOperation_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Operation', + 'localName should be Operation'); + var name = node.getAttribute('name'); + var value = ol.xml.pushParseAndPop({}, + ol.format.OWS.OPERATION_PARSERS_, node, objectStack); + if (!value) { + return undefined; + } + var object = /** @type {Object} */ + (objectStack[objectStack.length - 1]); + object[name] = value; + +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The operations metadata. + */ +ol.format.OWS.readOperationsMetadata_ = function(node, + objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'OperationsMetadata', + 'localName should be OperationsMetadata'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.OPERATIONS_METADATA_PARSERS_, node, + objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The phone. + */ +ol.format.OWS.readPhone_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Phone', 'localName should be Phone'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.PHONE_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The service identification. + */ +ol.format.OWS.readServiceIdentification_ = function(node, + objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ServiceIdentification', + 'localName should be ServiceIdentification'); + return ol.xml.pushParseAndPop( + {}, ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_, node, + objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The service contact. + */ +ol.format.OWS.readServiceContact_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ServiceContact', + 'localName should be ServiceContact'); + return ol.xml.pushParseAndPop( + {}, ol.format.OWS.SERVICE_CONTACT_PARSERS_, node, + objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} The service provider. + */ +ol.format.OWS.readServiceProvider_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ServiceProvider', + 'localName should be ServiceProvider'); + return ol.xml.pushParseAndPop( + {}, ol.format.OWS.SERVICE_PROVIDER_PARSERS_, node, + objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {string|undefined} The value. + */ +ol.format.OWS.readValue_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Value', 'localName should be Value'); + return ol.format.XSD.readString(node); +}; + + +/** + * @const + * @type {Array.<string>} + * @private + */ +ol.format.OWS.NAMESPACE_URIS_ = [ + null, + 'http://www.opengis.net/ows/1.1' +]; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'ServiceIdentification': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readServiceIdentification_), + 'ServiceProvider': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readServiceProvider_), + 'OperationsMetadata': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readOperationsMetadata_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.ADDRESS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'DeliveryPoint': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'AdministrativeArea': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'PostalCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'ElectronicMailAddress': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Value': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readValue_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'AllowedValues': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readAllowedValues_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.CONTACT_INFO_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Phone': ol.xml.makeObjectPropertySetter(ol.format.OWS.readPhone_), + 'Address': ol.xml.makeObjectPropertySetter(ol.format.OWS.readAddress_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.DCP_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'HTTP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readHttp_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Get': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readGet_), + 'Post': undefined // TODO + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'DCP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readDcp_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Operation': ol.format.OWS.readOperation_ + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.PHONE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Voice': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Facsimile': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Constraint': ol.xml.makeObjectPropertyPusher( + ol.format.OWS.readConstraint_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.SERVICE_CONTACT_PARSERS_ = + ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'IndividualName': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'PositionName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'ContactInfo': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readContactInfo_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_ = + ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'ServiceTypeVersion': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ServiceType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.OWS.SERVICE_PROVIDER_PARSERS_ = + ol.xml.makeStructureNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'ProviderName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'ProviderSite': ol.xml.makeObjectPropertySetter(ol.format.XLink.readHref), + 'ServiceContact': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readServiceContact_) + }); + +goog.provide('ol.geom.flat.flip'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {Array.<number>=} opt_dest Destination. + * @param {number=} opt_destOffset Destination offset. + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.flat.flip.flipXY = function(flatCoordinates, offset, end, stride, opt_dest, opt_destOffset) { + var dest, destOffset; + if (opt_dest !== undefined) { + dest = opt_dest; + destOffset = opt_destOffset !== undefined ? opt_destOffset : 0; + } else { + dest = []; + destOffset = 0; + } + var j = offset; + while (j < end) { + var x = flatCoordinates[j++]; + dest[destOffset++] = flatCoordinates[j++]; + dest[destOffset++] = x; + for (var k = 2; k < stride; ++k) { + dest[destOffset++] = flatCoordinates[j++]; + } + } + dest.length = destOffset; + return dest; +}; + +goog.provide('ol.format.Polyline'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.TextFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.flip'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Feature format for reading and writing data in the Encoded + * Polyline Algorithm Format. + * + * @constructor + * @extends {ol.format.TextFeature} + * @param {olx.format.PolylineOptions=} opt_options + * Optional configuration object. + * @api stable + */ +ol.format.Polyline = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.TextFeature.call(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); + + /** + * @private + * @type {number} + */ + this.factor_ = options.factor ? options.factor : 1e5; + + /** + * @private + * @type {ol.geom.GeometryLayout} + */ + this.geometryLayout_ = options.geometryLayout ? + options.geometryLayout : ol.geom.GeometryLayout.XY; +}; +ol.inherits(ol.format.Polyline, ol.format.TextFeature); + + +/** + * Encode a list of n-dimensional points and return an encoded string + * + * Attention: This function will modify the passed array! + * + * @param {Array.<number>} numbers A list of n-dimensional points. + * @param {number} stride The number of dimension of the points in the list. + * @param {number=} opt_factor The factor by which the numbers will be + * multiplied. The remaining decimal places will get rounded away. + * Default is `1e5`. + * @return {string} The encoded string. + * @api + */ +ol.format.Polyline.encodeDeltas = function(numbers, stride, opt_factor) { + var factor = opt_factor ? opt_factor : 1e5; + var d; + + var lastNumbers = new Array(stride); + for (d = 0; d < stride; ++d) { + lastNumbers[d] = 0; + } + + var i, ii; + for (i = 0, ii = numbers.length; i < ii;) { + for (d = 0; d < stride; ++d, ++i) { + var num = numbers[i]; + var delta = num - lastNumbers[d]; + lastNumbers[d] = num; + + numbers[i] = delta; + } + } + + return ol.format.Polyline.encodeFloats(numbers, factor); +}; + + +/** + * Decode a list of n-dimensional points from an encoded string + * + * @param {string} encoded An encoded string. + * @param {number} stride The number of dimension of the points in the + * encoded string. + * @param {number=} opt_factor The factor by which the resulting numbers will + * be divided. Default is `1e5`. + * @return {Array.<number>} A list of n-dimensional points. + * @api + */ +ol.format.Polyline.decodeDeltas = function(encoded, stride, opt_factor) { + var factor = opt_factor ? opt_factor : 1e5; + var d; + + /** @type {Array.<number>} */ + var lastNumbers = new Array(stride); + for (d = 0; d < stride; ++d) { + lastNumbers[d] = 0; + } + + var numbers = ol.format.Polyline.decodeFloats(encoded, factor); + + var i, ii; + for (i = 0, ii = numbers.length; i < ii;) { + for (d = 0; d < stride; ++d, ++i) { + lastNumbers[d] += numbers[i]; + + numbers[i] = lastNumbers[d]; + } + } + + return numbers; +}; + + +/** + * Encode a list of floating point numbers and return an encoded string + * + * Attention: This function will modify the passed array! + * + * @param {Array.<number>} numbers A list of floating point numbers. + * @param {number=} opt_factor The factor by which the numbers will be + * multiplied. The remaining decimal places will get rounded away. + * Default is `1e5`. + * @return {string} The encoded string. + * @api + */ +ol.format.Polyline.encodeFloats = function(numbers, opt_factor) { + var factor = opt_factor ? opt_factor : 1e5; + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + numbers[i] = Math.round(numbers[i] * factor); + } + + return ol.format.Polyline.encodeSignedIntegers(numbers); +}; + + +/** + * Decode a list of floating point numbers from an encoded string + * + * @param {string} encoded An encoded string. + * @param {number=} opt_factor The factor by which the result will be divided. + * Default is `1e5`. + * @return {Array.<number>} A list of floating point numbers. + * @api + */ +ol.format.Polyline.decodeFloats = function(encoded, opt_factor) { + var factor = opt_factor ? opt_factor : 1e5; + var numbers = ol.format.Polyline.decodeSignedIntegers(encoded); + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + numbers[i] /= factor; + } + return numbers; +}; + + +/** + * Encode a list of signed integers and return an encoded string + * + * Attention: This function will modify the passed array! + * + * @param {Array.<number>} numbers A list of signed integers. + * @return {string} The encoded string. + */ +ol.format.Polyline.encodeSignedIntegers = function(numbers) { + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + var num = numbers[i]; + numbers[i] = (num < 0) ? ~(num << 1) : (num << 1); + } + return ol.format.Polyline.encodeUnsignedIntegers(numbers); +}; + + +/** + * Decode a list of signed integers from an encoded string + * + * @param {string} encoded An encoded string. + * @return {Array.<number>} A list of signed integers. + */ +ol.format.Polyline.decodeSignedIntegers = function(encoded) { + var numbers = ol.format.Polyline.decodeUnsignedIntegers(encoded); + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + var num = numbers[i]; + numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1); + } + return numbers; +}; + + +/** + * Encode a list of unsigned integers and return an encoded string + * + * @param {Array.<number>} numbers A list of unsigned integers. + * @return {string} The encoded string. + */ +ol.format.Polyline.encodeUnsignedIntegers = function(numbers) { + var encoded = ''; + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + encoded += ol.format.Polyline.encodeUnsignedInteger(numbers[i]); + } + return encoded; +}; + + +/** + * Decode a list of unsigned integers from an encoded string + * + * @param {string} encoded An encoded string. + * @return {Array.<number>} A list of unsigned integers. + */ +ol.format.Polyline.decodeUnsignedIntegers = function(encoded) { + var numbers = []; + var current = 0; + var shift = 0; + var i, ii; + for (i = 0, ii = encoded.length; i < ii; ++i) { + var b = encoded.charCodeAt(i) - 63; + current |= (b & 0x1f) << shift; + if (b < 0x20) { + numbers.push(current); + current = 0; + shift = 0; + } else { + shift += 5; + } + } + return numbers; +}; + + +/** + * Encode one single unsigned integer and return an encoded string + * + * @param {number} num Unsigned integer that should be encoded. + * @return {string} The encoded string. + */ +ol.format.Polyline.encodeUnsignedInteger = function(num) { + var value, encoded = ''; + while (num >= 0x20) { + value = (0x20 | (num & 0x1f)) + 63; + encoded += String.fromCharCode(value); + num >>= 5; + } + value = num + 63; + encoded += String.fromCharCode(value); + return encoded; +}; + + +/** + * Read the feature from the Polyline source. The coordinates are assumed to be + * in two dimensions and in latitude, longitude order. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable + */ +ol.format.Polyline.prototype.readFeature; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.readFeatureFromText = function(text, opt_options) { + var geometry = this.readGeometryFromText(text, opt_options); + return new ol.Feature(geometry); +}; + + +/** + * Read the feature from the source. As Polyline sources contain a single + * feature, this will return the feature in an array. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.Polyline.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.readFeaturesFromText = function(text, opt_options) { + var feature = this.readFeatureFromText(text, opt_options); + return [feature]; +}; + + +/** + * Read the geometry from the source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. + * @api stable + */ +ol.format.Polyline.prototype.readGeometry; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.readGeometryFromText = function(text, opt_options) { + var stride = ol.geom.SimpleGeometry.getStrideForLayout(this.geometryLayout_); + var flatCoordinates = ol.format.Polyline.decodeDeltas( + text, stride, this.factor_); + ol.geom.flat.flip.flipXY( + flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates); + var coordinates = ol.geom.flat.inflate.coordinates( + flatCoordinates, 0, flatCoordinates.length, stride); + + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions( + new ol.geom.LineString(coordinates, this.geometryLayout_), false, + this.adaptOptions(opt_options))); +}; + + +/** + * Read the projection from a Polyline source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.Polyline.prototype.readProjection; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.writeFeatureText = function(feature, opt_options) { + var geometry = feature.getGeometry(); + if (geometry) { + return this.writeGeometryText(geometry, opt_options); + } else { + ol.asserts.assert(false, 40); // Expected `feature` to have a geometry + return ''; + } +}; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.writeFeaturesText = function(features, opt_options) { + ol.DEBUG && console.assert(features.length == 1, + 'features array should have 1 item'); + return this.writeFeatureText(features[0], opt_options); +}; + + +/** + * Write a single geometry in Polyline format. + * + * @function + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Geometry. + * @api stable + */ +ol.format.Polyline.prototype.writeGeometry; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.writeGeometryText = function(geometry, opt_options) { + geometry = /** @type {ol.geom.LineString} */ + (ol.format.Feature.transformWithOptions( + geometry, true, this.adaptOptions(opt_options))); + var flatCoordinates = geometry.getFlatCoordinates(); + var stride = geometry.getStride(); + ol.geom.flat.flip.flipXY( + flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates); + return ol.format.Polyline.encodeDeltas(flatCoordinates, stride, this.factor_); +}; + +goog.provide('ol.format.TopoJSON'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.JSONFeature'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.obj'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Feature format for reading data in the TopoJSON format. + * + * @constructor + * @extends {ol.format.JSONFeature} + * @param {olx.format.TopoJSONOptions=} opt_options Options. + * @api stable + */ +ol.format.TopoJSON = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.JSONFeature.call(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get( + options.defaultDataProjection ? + options.defaultDataProjection : 'EPSG:4326'); + +}; +ol.inherits(ol.format.TopoJSON, ol.format.JSONFeature); + + +/** + * @const {Array.<string>} + * @private + */ +ol.format.TopoJSON.EXTENSIONS_ = ['.topojson']; + + +/** + * Concatenate arcs into a coordinate array. + * @param {Array.<number>} indices Indices of arcs to concatenate. Negative + * values indicate arcs need to be reversed. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs (already + * transformed). + * @return {Array.<ol.Coordinate>} Coordinates array. + * @private + */ +ol.format.TopoJSON.concatenateArcs_ = function(indices, arcs) { + /** @type {Array.<ol.Coordinate>} */ + var coordinates = []; + var index, arc; + var i, ii; + var j, jj; + for (i = 0, ii = indices.length; i < ii; ++i) { + index = indices[i]; + if (i > 0) { + // splicing together arcs, discard last point + coordinates.pop(); + } + if (index >= 0) { + // forward arc + arc = arcs[index]; + } else { + // reverse arc + arc = arcs[~index].slice().reverse(); + } + coordinates.push.apply(coordinates, arc); + } + // provide fresh copies of coordinate arrays + for (j = 0, jj = coordinates.length; j < jj; ++j) { + coordinates[j] = coordinates[j].slice(); + } + return coordinates; +}; + + +/** + * Create a point from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @return {ol.geom.Point} Geometry. + * @private + */ +ol.format.TopoJSON.readPointGeometry_ = function(object, scale, translate) { + var coordinates = object.coordinates; + if (scale && translate) { + ol.format.TopoJSON.transformVertex_(coordinates, scale, translate); + } + return new ol.geom.Point(coordinates); +}; + + +/** + * Create a multi-point from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @return {ol.geom.MultiPoint} Geometry. + * @private + */ +ol.format.TopoJSON.readMultiPointGeometry_ = function(object, scale, + translate) { + var coordinates = object.coordinates; + var i, ii; + if (scale && translate) { + for (i = 0, ii = coordinates.length; i < ii; ++i) { + ol.format.TopoJSON.transformVertex_(coordinates[i], scale, translate); + } + } + return new ol.geom.MultiPoint(coordinates); +}; + + +/** + * Create a linestring from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @return {ol.geom.LineString} Geometry. + * @private + */ +ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) { + var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs); + return new ol.geom.LineString(coordinates); +}; + + +/** + * Create a multi-linestring from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @return {ol.geom.MultiLineString} Geometry. + * @private + */ +ol.format.TopoJSON.readMultiLineStringGeometry_ = function(object, arcs) { + var coordinates = []; + var i, ii; + for (i = 0, ii = object.arcs.length; i < ii; ++i) { + coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs); + } + return new ol.geom.MultiLineString(coordinates); +}; + + +/** + * Create a polygon from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @return {ol.geom.Polygon} Geometry. + * @private + */ +ol.format.TopoJSON.readPolygonGeometry_ = function(object, arcs) { + var coordinates = []; + var i, ii; + for (i = 0, ii = object.arcs.length; i < ii; ++i) { + coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs); + } + return new ol.geom.Polygon(coordinates); +}; + + +/** + * Create a multi-polygon from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @return {ol.geom.MultiPolygon} Geometry. + * @private + */ +ol.format.TopoJSON.readMultiPolygonGeometry_ = function(object, arcs) { + var coordinates = []; + var polyArray, ringCoords, j, jj; + var i, ii; + for (i = 0, ii = object.arcs.length; i < ii; ++i) { + // for each polygon + polyArray = object.arcs[i]; + ringCoords = []; + for (j = 0, jj = polyArray.length; j < jj; ++j) { + // for each ring + ringCoords[j] = ol.format.TopoJSON.concatenateArcs_(polyArray[j], arcs); + } + coordinates[i] = ringCoords; + } + return new ol.geom.MultiPolygon(coordinates); +}; + + +/** + * @inheritDoc + */ +ol.format.TopoJSON.prototype.getExtensions = function() { + return ol.format.TopoJSON.EXTENSIONS_; +}; + + +/** + * Create features from a TopoJSON GeometryCollection object. + * + * @param {TopoJSONGeometryCollection} collection TopoJSON Geometry + * object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Array of features. + * @private + */ +ol.format.TopoJSON.readFeaturesFromGeometryCollection_ = function( + collection, arcs, scale, translate, opt_options) { + var geometries = collection.geometries; + var features = []; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + features[i] = ol.format.TopoJSON.readFeatureFromGeometry_( + geometries[i], arcs, scale, translate, opt_options); + } + return features; +}; + + +/** + * Create a feature from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON geometry object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @private + */ +ol.format.TopoJSON.readFeatureFromGeometry_ = function(object, arcs, + scale, translate, opt_options) { + var geometry; + var type = object.type; + var geometryReader = ol.format.TopoJSON.GEOMETRY_READERS_[type]; + if ((type === 'Point') || (type === 'MultiPoint')) { + geometry = geometryReader(object, scale, translate); + } else { + geometry = geometryReader(object, arcs); + } + var feature = new ol.Feature(); + feature.setGeometry(/** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, false, opt_options))); + if (object.id !== undefined) { + feature.setId(object.id); + } + if (object.properties) { + feature.setProperties(object.properties); + } + return feature; +}; + + +/** + * Read all features from a TopoJSON source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.TopoJSON.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.TopoJSON.prototype.readFeaturesFromObject = function( + object, opt_options) { + if (object.type == 'Topology') { + var topoJSONTopology = /** @type {TopoJSONTopology} */ (object); + var transform, scale = null, translate = null; + if (topoJSONTopology.transform) { + transform = topoJSONTopology.transform; + scale = transform.scale; + translate = transform.translate; + } + var arcs = topoJSONTopology.arcs; + if (transform) { + ol.format.TopoJSON.transformArcs_(arcs, scale, translate); + } + /** @type {Array.<ol.Feature>} */ + var features = []; + var topoJSONFeatures = ol.obj.getValues(topoJSONTopology.objects); + var i, ii; + var feature; + for (i = 0, ii = topoJSONFeatures.length; i < ii; ++i) { + if (topoJSONFeatures[i].type === 'GeometryCollection') { + feature = /** @type {TopoJSONGeometryCollection} */ + (topoJSONFeatures[i]); + features.push.apply(features, + ol.format.TopoJSON.readFeaturesFromGeometryCollection_( + feature, arcs, scale, translate, opt_options)); + } else { + feature = /** @type {TopoJSONGeometry} */ + (topoJSONFeatures[i]); + features.push(ol.format.TopoJSON.readFeatureFromGeometry_( + feature, arcs, scale, translate, opt_options)); + } + } + return features; + } else { + return []; + } +}; + + +/** + * Apply a linear transform to array of arcs. The provided array of arcs is + * modified in place. + * + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @private + */ +ol.format.TopoJSON.transformArcs_ = function(arcs, scale, translate) { + var i, ii; + for (i = 0, ii = arcs.length; i < ii; ++i) { + ol.format.TopoJSON.transformArc_(arcs[i], scale, translate); + } +}; + + +/** + * Apply a linear transform to an arc. The provided arc is modified in place. + * + * @param {Array.<ol.Coordinate>} arc Arc. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @private + */ +ol.format.TopoJSON.transformArc_ = function(arc, scale, translate) { + var x = 0; + var y = 0; + var vertex; + var i, ii; + for (i = 0, ii = arc.length; i < ii; ++i) { + vertex = arc[i]; + x += vertex[0]; + y += vertex[1]; + vertex[0] = x; + vertex[1] = y; + ol.format.TopoJSON.transformVertex_(vertex, scale, translate); + } +}; + + +/** + * Apply a linear transform to a vertex. The provided vertex is modified in + * place. + * + * @param {ol.Coordinate} vertex Vertex. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @private + */ +ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) { + vertex[0] = vertex[0] * scale[0] + translate[0]; + vertex[1] = vertex[1] * scale[1] + translate[1]; +}; + + +/** + * Read the projection from a TopoJSON source. + * + * @function + * @param {Document|Node|Object|string} object Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.TopoJSON.prototype.readProjection = function(object) { + return this.defaultDataProjection; +}; + + +/** + * @const + * @private + * @type {Object.<string, function(TopoJSONGeometry, Array, ...Array): ol.geom.Geometry>} + */ +ol.format.TopoJSON.GEOMETRY_READERS_ = { + 'Point': ol.format.TopoJSON.readPointGeometry_, + 'LineString': ol.format.TopoJSON.readLineStringGeometry_, + 'Polygon': ol.format.TopoJSON.readPolygonGeometry_, + 'MultiPoint': ol.format.TopoJSON.readMultiPointGeometry_, + 'MultiLineString': ol.format.TopoJSON.readMultiLineStringGeometry_, + 'MultiPolygon': ol.format.TopoJSON.readMultiPolygonGeometry_ +}; + +goog.provide('ol.format.WFS'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.format.GML3'); +goog.require('ol.format.GMLBase'); +goog.require('ol.format.filter'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.format.XSD'); +goog.require('ol.geom.Geometry'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Feature format for reading and writing data in the WFS format. + * By default, supports WFS version 1.1.0. You can pass a GML format + * as option if you want to read a WFS that contains GML2 (WFS 1.0.0). + * Also see {@link ol.format.GMLBase} which is used by this format. + * + * @constructor + * @param {olx.format.WFSOptions=} opt_options + * Optional configuration object. + * @extends {ol.format.XMLFeature} + * @api stable + */ +ol.format.WFS = function(opt_options) { + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {Array.<string>|string|undefined} + */ + this.featureType_ = options.featureType; + + /** + * @private + * @type {Object.<string, string>|string|undefined} + */ + this.featureNS_ = options.featureNS; + + /** + * @private + * @type {ol.format.GMLBase} + */ + this.gmlFormat_ = options.gmlFormat ? + options.gmlFormat : new ol.format.GML3(); + + /** + * @private + * @type {string} + */ + this.schemaLocation_ = options.schemaLocation ? + options.schemaLocation : ol.format.WFS.SCHEMA_LOCATION; + + ol.format.XMLFeature.call(this); +}; +ol.inherits(ol.format.WFS, ol.format.XMLFeature); + + +/** + * @const + * @type {string} + */ +ol.format.WFS.FEATURE_PREFIX = 'feature'; + + +/** + * @const + * @type {string} + */ +ol.format.WFS.XMLNS = 'http://www.w3.org/2000/xmlns/'; + + +/** + * @const + * @type {string} + */ +ol.format.WFS.OGCNS = 'http://www.opengis.net/ogc'; + + +/** + * @const + * @type {string} + */ +ol.format.WFS.WFSNS = 'http://www.opengis.net/wfs'; + + +/** + * @const + * @type {string} + */ +ol.format.WFS.SCHEMA_LOCATION = 'http://www.opengis.net/wfs ' + + 'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd'; + + +/** + * Read all features from a WFS FeatureCollection. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.WFS.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.WFS.prototype.readFeaturesFromNode = function(node, opt_options) { + var context = /** @type {ol.XmlNodeStackItem} */ ({ + 'featureType': this.featureType_, + 'featureNS': this.featureNS_ + }); + ol.obj.assign(context, this.getReadOptions(node, + opt_options ? opt_options : {})); + var objectStack = [context]; + this.gmlFormat_.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS][ + 'featureMember'] = + ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readFeaturesInternal); + var features = ol.xml.pushParseAndPop([], + this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node, + objectStack, this.gmlFormat_); + if (!features) { + features = []; + } + return features; +}; + + +/** + * Read transaction response of the source. + * + * @param {Document|Node|Object|string} source Source. + * @return {ol.WFSTransactionResponse|undefined} Transaction response. + * @api stable + */ +ol.format.WFS.prototype.readTransactionResponse = function(source) { + if (ol.xml.isDocument(source)) { + return this.readTransactionResponseFromDocument( + /** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readTransactionResponseFromNode(/** @type {Node} */ (source)); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readTransactionResponseFromDocument(doc); + } else { + return undefined; + } +}; + + +/** + * Read feature collection metadata of the source. + * + * @param {Document|Node|Object|string} source Source. + * @return {ol.WFSFeatureCollectionMetadata|undefined} + * FeatureCollection metadata. + * @api stable + */ +ol.format.WFS.prototype.readFeatureCollectionMetadata = function(source) { + if (ol.xml.isDocument(source)) { + return this.readFeatureCollectionMetadataFromDocument( + /** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readFeatureCollectionMetadataFromNode( + /** @type {Node} */ (source)); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + return this.readFeatureCollectionMetadataFromDocument(doc); + } else { + return undefined; + } +}; + + +/** + * @param {Document} doc Document. + * @return {ol.WFSFeatureCollectionMetadata|undefined} + * FeatureCollection metadata. + */ +ol.format.WFS.prototype.readFeatureCollectionMetadataFromDocument = function(doc) { + ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + return this.readFeatureCollectionMetadataFromNode(n); + } + } + return undefined; +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WFS.FEATURE_COLLECTION_PARSERS_ = { + 'http://www.opengis.net/gml': { + 'boundedBy': ol.xml.makeObjectPropertySetter( + ol.format.GMLBase.prototype.readGeometryElement, 'bounds') + } +}; + + +/** + * @param {Node} node Node. + * @return {ol.WFSFeatureCollectionMetadata|undefined} + * FeatureCollection metadata. + */ +ol.format.WFS.prototype.readFeatureCollectionMetadataFromNode = function(node) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'FeatureCollection', + 'localName should be FeatureCollection'); + var result = {}; + var value = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('numberOfFeatures')); + result['numberOfFeatures'] = value; + return ol.xml.pushParseAndPop( + /** @type {ol.WFSFeatureCollectionMetadata} */ (result), + ol.format.WFS.FEATURE_COLLECTION_PARSERS_, node, [], this.gmlFormat_); +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_ = { + 'http://www.opengis.net/wfs': { + 'totalInserted': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'totalUpdated': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'totalDeleted': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger) + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Transaction Summary. + * @private + */ +ol.format.WFS.readTransactionSummary_ = function(node, objectStack) { + return ol.xml.pushParseAndPop( + {}, ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_, node, objectStack); +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WFS.OGC_FID_PARSERS_ = { + 'http://www.opengis.net/ogc': { + 'FeatureId': ol.xml.makeArrayPusher(function(node, objectStack) { + return node.getAttribute('fid'); + }) + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.WFS.fidParser_ = function(node, objectStack) { + ol.xml.parseNode(ol.format.WFS.OGC_FID_PARSERS_, node, objectStack); +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WFS.INSERT_RESULTS_PARSERS_ = { + 'http://www.opengis.net/wfs': { + 'Feature': ol.format.WFS.fidParser_ + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Array.<string>|undefined} Insert results. + * @private + */ +ol.format.WFS.readInsertResults_ = function(node, objectStack) { + return ol.xml.pushParseAndPop( + [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack); +}; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_ = { + 'http://www.opengis.net/wfs': { + 'TransactionSummary': ol.xml.makeObjectPropertySetter( + ol.format.WFS.readTransactionSummary_, 'transactionSummary'), + 'InsertResults': ol.xml.makeObjectPropertySetter( + ol.format.WFS.readInsertResults_, 'insertIds') + } +}; + + +/** + * @param {Document} doc Document. + * @return {ol.WFSTransactionResponse|undefined} Transaction response. + */ +ol.format.WFS.prototype.readTransactionResponseFromDocument = function(doc) { + ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + return this.readTransactionResponseFromNode(n); + } + } + return undefined; +}; + + +/** + * @param {Node} node Node. + * @return {ol.WFSTransactionResponse|undefined} Transaction response. + */ +ol.format.WFS.prototype.readTransactionResponseFromNode = function(node) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'TransactionResponse', + 'localName should be TransactionResponse'); + return ol.xml.pushParseAndPop( + /** @type {ol.WFSTransactionResponse} */({}), + ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_, node, []); +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.WFS.QUERY_SERIALIZERS_ = { + 'http://www.opengis.net/wfs': { + 'PropertyName': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeFeature_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + var featureType = context['featureType']; + var featureNS = context['featureNS']; + var child = ol.xml.createElementNS(featureNS, featureType); + node.appendChild(child); + ol.format.GML3.prototype.writeFeatureElement(child, feature, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {number|string} fid Feature identifier. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeOgcFidFilter_ = function(node, fid, objectStack) { + var filter = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'Filter'); + var child = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'FeatureId'); + filter.appendChild(child); + child.setAttribute('fid', fid); + node.appendChild(filter); +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeDelete_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + ol.asserts.assert(feature.getId() !== undefined, 26); // Features must have an id set + var featureType = context['featureType']; + var featurePrefix = context['featurePrefix']; + featurePrefix = featurePrefix ? featurePrefix : + ol.format.WFS.FEATURE_PREFIX; + var featureNS = context['featureNS']; + node.setAttribute('typeName', featurePrefix + ':' + featureType); + ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix, + featureNS); + var fid = feature.getId(); + if (fid !== undefined) { + ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + ol.asserts.assert(feature.getId() !== undefined, 27); // Features must have an id set + var featureType = context['featureType']; + var featurePrefix = context['featurePrefix']; + featurePrefix = featurePrefix ? featurePrefix : + ol.format.WFS.FEATURE_PREFIX; + var featureNS = context['featureNS']; + node.setAttribute('typeName', featurePrefix + ':' + featureType); + ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix, + featureNS); + var fid = feature.getId(); + if (fid !== undefined) { + var keys = feature.getKeys(); + var values = []; + for (var i = 0, ii = keys.length; i < ii; i++) { + var value = feature.get(keys[i]); + if (value !== undefined) { + values.push({name: keys[i], value: value}); + } + } + ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ ( + {node: node, 'srsName': context['srsName']}), + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Property'), values, + objectStack); + ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {Object} pair Property name and value. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeProperty_ = function(node, pair, objectStack) { + var name = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'Name'); + node.appendChild(name); + ol.format.XSD.writeStringTextNode(name, pair.name); + if (pair.value !== undefined && pair.value !== null) { + var value = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'Value'); + node.appendChild(value); + if (pair.value instanceof ol.geom.Geometry) { + ol.format.GML3.prototype.writeGeometryElement(value, + pair.value, objectStack); + } else { + ol.format.XSD.writeStringTextNode(value, pair.value); + } + } +}; + + +/** + * @param {Node} node Node. + * @param {{vendorId: string, safeToIgnore: boolean, value: string}} + * nativeElement The native element. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeNative_ = function(node, nativeElement, objectStack) { + if (nativeElement.vendorId) { + node.setAttribute('vendorId', nativeElement.vendorId); + } + if (nativeElement.safeToIgnore !== undefined) { + node.setAttribute('safeToIgnore', nativeElement.safeToIgnore); + } + if (nativeElement.value !== undefined) { + ol.format.XSD.writeStringTextNode(node, nativeElement.value); + } +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.WFS.TRANSACTION_SERIALIZERS_ = { + 'http://www.opengis.net/wfs': { + 'Insert': ol.xml.makeChildAppender(ol.format.WFS.writeFeature_), + 'Update': ol.xml.makeChildAppender(ol.format.WFS.writeUpdate_), + 'Delete': ol.xml.makeChildAppender(ol.format.WFS.writeDelete_), + 'Property': ol.xml.makeChildAppender(ol.format.WFS.writeProperty_), + 'Native': ol.xml.makeChildAppender(ol.format.WFS.writeNative_) + } +}; + + +/** + * @param {Node} node Node. + * @param {string} featureType Feature type. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeQuery_ = function(node, featureType, objectStack) { + var context = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var featurePrefix = context['featurePrefix']; + var featureNS = context['featureNS']; + var propertyNames = context['propertyNames']; + var srsName = context['srsName']; + var prefix = featurePrefix ? featurePrefix + ':' : ''; + node.setAttribute('typeName', prefix + featureType); + if (srsName) { + node.setAttribute('srsName', srsName); + } + if (featureNS) { + ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix, + featureNS); + } + var item = /** @type {ol.XmlNodeStackItem} */ (ol.obj.assign({}, context)); + item.node = node; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.QUERY_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('PropertyName'), propertyNames, + objectStack); + var filter = context['filter']; + if (filter) { + var child = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'Filter'); + node.appendChild(child); + ol.format.WFS.writeFilterCondition_(child, filter, objectStack); + } +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.Filter} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeFilterCondition_ = function(node, filter, objectStack) { + /** @type {ol.XmlNodeStackItem} */ + var item = {node: node}; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory(filter.getTagName()), + [filter], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.Bbox} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeBboxFilter_ = function(node, filter, objectStack) { + var context = objectStack[objectStack.length - 1]; + context['srsName'] = filter.srsName; + + ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName); + ol.format.GML3.prototype.writeGeometryElement(node, filter.extent, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.Intersects} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeIntersectsFilter_ = function(node, filter, objectStack) { + var context = objectStack[objectStack.length - 1]; + context['srsName'] = filter.srsName; + + ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName); + ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.Within} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeWithinFilter_ = function(node, filter, objectStack) { + var context = objectStack[objectStack.length - 1]; + context['srsName'] = filter.srsName; + + ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName); + ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.LogicalBinary} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeLogicalFilter_ = function(node, filter, objectStack) { + /** @type {ol.XmlNodeStackItem} */ + var item = {node: node}; + var conditionA = filter.conditionA; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory(conditionA.getTagName()), + [conditionA], objectStack); + var conditionB = filter.conditionB; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory(conditionB.getTagName()), + [conditionB], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.Not} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeNotFilter_ = function(node, filter, objectStack) { + /** @type {ol.XmlNodeStackItem} */ + var item = {node: node}; + var condition = filter.condition; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory(condition.getTagName()), + [condition], objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.ComparisonBinary} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeComparisonFilter_ = function(node, filter, objectStack) { + if (filter.matchCase !== undefined) { + node.setAttribute('matchCase', filter.matchCase.toString()); + } + ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName); + ol.format.WFS.writeOgcLiteral_(node, '' + filter.expression); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.IsNull} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeIsNullFilter_ = function(node, filter, objectStack) { + ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.IsBetween} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeIsBetweenFilter_ = function(node, filter, objectStack) { + ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName); + + var lowerBoundary = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'LowerBoundary'); + node.appendChild(lowerBoundary); + ol.format.WFS.writeOgcLiteral_(lowerBoundary, '' + filter.lowerBoundary); + + var upperBoundary = ol.xml.createElementNS(ol.format.WFS.OGCNS, 'UpperBoundary'); + node.appendChild(upperBoundary); + ol.format.WFS.writeOgcLiteral_(upperBoundary, '' + filter.upperBoundary); +}; + + +/** + * @param {Node} node Node. + * @param {ol.format.filter.IsLike} filter Filter. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeIsLikeFilter_ = function(node, filter, objectStack) { + node.setAttribute('wildCard', filter.wildCard); + node.setAttribute('singleChar', filter.singleChar); + node.setAttribute('escapeChar', filter.escapeChar); + if (filter.matchCase !== undefined) { + node.setAttribute('matchCase', filter.matchCase.toString()); + } + ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName); + ol.format.WFS.writeOgcLiteral_(node, '' + filter.pattern); +}; + + +/** + * @param {string} tagName Tag name. + * @param {Node} node Node. + * @param {string} value Value. + * @private + */ +ol.format.WFS.writeOgcExpression_ = function(tagName, node, value) { + var property = ol.xml.createElementNS(ol.format.WFS.OGCNS, tagName); + ol.format.XSD.writeStringTextNode(property, value); + node.appendChild(property); +}; + + +/** + * @param {Node} node Node. + * @param {string} value PropertyName value. + * @private + */ +ol.format.WFS.writeOgcPropertyName_ = function(node, value) { + ol.format.WFS.writeOgcExpression_('PropertyName', node, value); +}; + + +/** + * @param {Node} node Node. + * @param {string} value PropertyName value. + * @private + */ +ol.format.WFS.writeOgcLiteral_ = function(node, value) { + ol.format.WFS.writeOgcExpression_('Literal', node, value); +}; + + +/** + * @type {Object.<string, Object.<string, ol.XmlSerializer>>} + * @private + */ +ol.format.WFS.GETFEATURE_SERIALIZERS_ = { + 'http://www.opengis.net/wfs': { + 'Query': ol.xml.makeChildAppender(ol.format.WFS.writeQuery_) + }, + 'http://www.opengis.net/ogc': { + 'And': ol.xml.makeChildAppender(ol.format.WFS.writeLogicalFilter_), + 'Or': ol.xml.makeChildAppender(ol.format.WFS.writeLogicalFilter_), + 'Not': ol.xml.makeChildAppender(ol.format.WFS.writeNotFilter_), + 'BBOX': ol.xml.makeChildAppender(ol.format.WFS.writeBboxFilter_), + 'Intersects': ol.xml.makeChildAppender(ol.format.WFS.writeIntersectsFilter_), + 'Within': ol.xml.makeChildAppender(ol.format.WFS.writeWithinFilter_), + 'PropertyIsEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsNotEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsLessThan': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsLessThanOrEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsGreaterThan': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsGreaterThanOrEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_), + 'PropertyIsNull': ol.xml.makeChildAppender(ol.format.WFS.writeIsNullFilter_), + 'PropertyIsBetween': ol.xml.makeChildAppender(ol.format.WFS.writeIsBetweenFilter_), + 'PropertyIsLike': ol.xml.makeChildAppender(ol.format.WFS.writeIsLikeFilter_) + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<string>} featureTypes Feature types. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeGetFeature_ = function(node, featureTypes, objectStack) { + var context = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var item = /** @type {ol.XmlNodeStackItem} */ (ol.obj.assign({}, context)); + item.node = node; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Query'), featureTypes, + objectStack); +}; + + +/** + * Encode format as WFS `GetFeature` and return the Node. + * + * @param {olx.format.WFSWriteGetFeatureOptions} options Options. + * @return {Node} Result. + * @api stable + */ +ol.format.WFS.prototype.writeGetFeature = function(options) { + var node = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'GetFeature'); + node.setAttribute('service', 'WFS'); + node.setAttribute('version', '1.1.0'); + var filter; + if (options) { + if (options.handle) { + node.setAttribute('handle', options.handle); + } + if (options.outputFormat) { + node.setAttribute('outputFormat', options.outputFormat); + } + if (options.maxFeatures !== undefined) { + node.setAttribute('maxFeatures', options.maxFeatures); + } + if (options.resultType) { + node.setAttribute('resultType', options.resultType); + } + if (options.startIndex !== undefined) { + node.setAttribute('startIndex', options.startIndex); + } + if (options.count !== undefined) { + node.setAttribute('count', options.count); + } + filter = options.filter; + if (options.bbox) { + ol.asserts.assert(options.geometryName, + 12); // `options.geometryName` must also be provided when `options.bbox` is set + var bbox = ol.format.filter.bbox( + /** @type {string} */ (options.geometryName), options.bbox, options.srsName); + if (filter) { + // if bbox and filter are both set, combine the two into a single filter + filter = ol.format.filter.and(filter, bbox); + } else { + filter = bbox; + } + } + } + ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation_); + /** @type {ol.XmlNodeStackItem} */ + var context = { + node: node, + 'srsName': options.srsName, + 'featureNS': options.featureNS ? options.featureNS : this.featureNS_, + 'featurePrefix': options.featurePrefix, + 'geometryName': options.geometryName, + 'filter': filter, + 'propertyNames': options.propertyNames ? options.propertyNames : [] + }; + ol.asserts.assert(Array.isArray(options.featureTypes), + 11); // `options.featureTypes` should be an Array + ol.format.WFS.writeGetFeature_(node, /** @type {!Array.<string>} */ (options.featureTypes), [context]); + return node; +}; + + +/** + * Encode format as WFS `Transaction` and return the Node. + * + * @param {Array.<ol.Feature>} inserts The features to insert. + * @param {Array.<ol.Feature>} updates The features to update. + * @param {Array.<ol.Feature>} deletes The features to delete. + * @param {olx.format.WFSWriteTransactionOptions} options Write options. + * @return {Node} Result. + * @api stable + */ +ol.format.WFS.prototype.writeTransaction = function(inserts, updates, deletes, + options) { + var objectStack = []; + var node = ol.xml.createElementNS(ol.format.WFS.WFSNS, 'Transaction'); + node.setAttribute('service', 'WFS'); + node.setAttribute('version', '1.1.0'); + var baseObj; + /** @type {ol.XmlNodeStackItem} */ + var obj; + if (options) { + baseObj = options.gmlOptions ? options.gmlOptions : {}; + if (options.handle) { + node.setAttribute('handle', options.handle); + } + } + ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation_); + if (inserts) { + obj = {node: node, 'featureNS': options.featureNS, + 'featureType': options.featureType, 'featurePrefix': options.featurePrefix, + 'srsName': options.srsName}; + ol.obj.assign(obj, baseObj); + ol.xml.pushSerializeAndPop(obj, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Insert'), inserts, + objectStack); + } + if (updates) { + obj = {node: node, 'featureNS': options.featureNS, + 'featureType': options.featureType, 'featurePrefix': options.featurePrefix, + 'srsName': options.srsName}; + ol.obj.assign(obj, baseObj); + ol.xml.pushSerializeAndPop(obj, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Update'), updates, + objectStack); + } + if (deletes) { + ol.xml.pushSerializeAndPop({node: node, 'featureNS': options.featureNS, + 'featureType': options.featureType, 'featurePrefix': options.featurePrefix, + 'srsName': options.srsName}, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Delete'), deletes, + objectStack); + } + if (options.nativeElements) { + ol.xml.pushSerializeAndPop({node: node, 'featureNS': options.featureNS, + 'featureType': options.featureType, 'featurePrefix': options.featurePrefix, + 'srsName': options.srsName}, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Native'), options.nativeElements, + objectStack); + } + return node; +}; + + +/** + * Read the projection from a WFS source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {?ol.proj.Projection} Projection. + * @api stable + */ +ol.format.WFS.prototype.readProjection; + + +/** + * @inheritDoc + */ +ol.format.WFS.prototype.readProjectionFromDocument = function(doc) { + ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE, + 'doc.nodeType should be a DOCUMENT'); + for (var n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + return this.readProjectionFromNode(n); + } + } + return null; +}; + + +/** + * @inheritDoc + */ +ol.format.WFS.prototype.readProjectionFromNode = function(node) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'FeatureCollection', + 'localName should be FeatureCollection'); + + if (node.firstElementChild && + node.firstElementChild.firstElementChild) { + node = node.firstElementChild.firstElementChild; + for (var n = node.firstElementChild; n; n = n.nextElementSibling) { + if (!(n.childNodes.length === 0 || + (n.childNodes.length === 1 && + n.firstChild.nodeType === 3))) { + var objectStack = [{}]; + this.gmlFormat_.readGeometryElement(n, objectStack); + return ol.proj.get(objectStack.pop().srsName); + } + } + } + + return null; +}; + +goog.provide('ol.format.WKT'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.TextFeature'); +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.SimpleGeometry'); + + +/** + * @classdesc + * Geometry format for reading and writing data in the `WellKnownText` (WKT) + * format. + * + * @constructor + * @extends {ol.format.TextFeature} + * @param {olx.format.WKTOptions=} opt_options Options. + * @api stable + */ +ol.format.WKT = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.format.TextFeature.call(this); + + /** + * Split GeometryCollection into multiple features. + * @type {boolean} + * @private + */ + this.splitCollection_ = options.splitCollection !== undefined ? + options.splitCollection : false; + +}; +ol.inherits(ol.format.WKT, ol.format.TextFeature); + + +/** + * @const + * @type {string} + */ +ol.format.WKT.EMPTY = 'EMPTY'; + + +/** + * @const + * @type {string} + */ +ol.format.WKT.Z = 'Z'; + + +/** + * @const + * @type {string} + */ +ol.format.WKT.M = 'M'; + + +/** + * @const + * @type {string} + */ +ol.format.WKT.ZM = 'ZM'; + + +/** + * @param {ol.geom.Point} geom Point geometry. + * @return {string} Coordinates part of Point as WKT. + * @private + */ +ol.format.WKT.encodePointGeometry_ = function(geom) { + var coordinates = geom.getCoordinates(); + if (coordinates.length === 0) { + return ''; + } + return coordinates.join(' '); +}; + + +/** + * @param {ol.geom.MultiPoint} geom MultiPoint geometry. + * @return {string} Coordinates part of MultiPoint as WKT. + * @private + */ +ol.format.WKT.encodeMultiPointGeometry_ = function(geom) { + var array = []; + var components = geom.getPoints(); + for (var i = 0, ii = components.length; i < ii; ++i) { + array.push('(' + ol.format.WKT.encodePointGeometry_(components[i]) + ')'); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry. + * @return {string} Coordinates part of GeometryCollection as WKT. + * @private + */ +ol.format.WKT.encodeGeometryCollectionGeometry_ = function(geom) { + var array = []; + var geoms = geom.getGeometries(); + for (var i = 0, ii = geoms.length; i < ii; ++i) { + array.push(ol.format.WKT.encode_(geoms[i])); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry. + * @return {string} Coordinates part of LineString as WKT. + * @private + */ +ol.format.WKT.encodeLineStringGeometry_ = function(geom) { + var coordinates = geom.getCoordinates(); + var array = []; + for (var i = 0, ii = coordinates.length; i < ii; ++i) { + array.push(coordinates[i].join(' ')); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.MultiLineString} geom MultiLineString geometry. + * @return {string} Coordinates part of MultiLineString as WKT. + * @private + */ +ol.format.WKT.encodeMultiLineStringGeometry_ = function(geom) { + var array = []; + var components = geom.getLineStrings(); + for (var i = 0, ii = components.length; i < ii; ++i) { + array.push('(' + ol.format.WKT.encodeLineStringGeometry_( + components[i]) + ')'); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.Polygon} geom Polygon geometry. + * @return {string} Coordinates part of Polygon as WKT. + * @private + */ +ol.format.WKT.encodePolygonGeometry_ = function(geom) { + var array = []; + var rings = geom.getLinearRings(); + for (var i = 0, ii = rings.length; i < ii; ++i) { + array.push('(' + ol.format.WKT.encodeLineStringGeometry_( + rings[i]) + ')'); + } + return array.join(','); +}; + + +/** + * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry. + * @return {string} Coordinates part of MultiPolygon as WKT. + * @private + */ +ol.format.WKT.encodeMultiPolygonGeometry_ = function(geom) { + var array = []; + var components = geom.getPolygons(); + for (var i = 0, ii = components.length; i < ii; ++i) { + array.push('(' + ol.format.WKT.encodePolygonGeometry_( + components[i]) + ')'); + } + return array.join(','); +}; + +/** + * @param {ol.geom.SimpleGeometry} geom SimpleGeometry geometry. + * @return {string} Potential dimensional information for WKT type. + * @private + */ +ol.format.WKT.encodeGeometryLayout_ = function(geom) { + var layout = geom.getLayout(); + var dimInfo = ''; + if (layout === ol.geom.GeometryLayout.XYZ || layout === ol.geom.GeometryLayout.XYZM) { + dimInfo += ol.format.WKT.Z; + } + if (layout === ol.geom.GeometryLayout.XYM || layout === ol.geom.GeometryLayout.XYZM) { + dimInfo += ol.format.WKT.M; + } + return dimInfo; +}; + + +/** + * Encode a geometry as WKT. + * @param {ol.geom.Geometry} geom The geometry to encode. + * @return {string} WKT string for the geometry. + * @private + */ +ol.format.WKT.encode_ = function(geom) { + var type = geom.getType(); + var geometryEncoder = ol.format.WKT.GeometryEncoder_[type]; + ol.DEBUG && console.assert(geometryEncoder, 'geometryEncoder should be defined'); + var enc = geometryEncoder(geom); + type = type.toUpperCase(); + if (geom instanceof ol.geom.SimpleGeometry) { + var dimInfo = ol.format.WKT.encodeGeometryLayout_(geom); + if (dimInfo.length > 0) { + type += ' ' + dimInfo; + } + } + if (enc.length === 0) { + return type + ' ' + ol.format.WKT.EMPTY; + } + return type + '(' + enc + ')'; +}; + + +/** + * @const + * @type {Object.<string, function(ol.geom.Geometry): string>} + * @private + */ +ol.format.WKT.GeometryEncoder_ = { + 'Point': ol.format.WKT.encodePointGeometry_, + 'LineString': ol.format.WKT.encodeLineStringGeometry_, + 'Polygon': ol.format.WKT.encodePolygonGeometry_, + 'MultiPoint': ol.format.WKT.encodeMultiPointGeometry_, + 'MultiLineString': ol.format.WKT.encodeMultiLineStringGeometry_, + 'MultiPolygon': ol.format.WKT.encodeMultiPolygonGeometry_, + 'GeometryCollection': ol.format.WKT.encodeGeometryCollectionGeometry_ +}; + + +/** + * Parse a WKT string. + * @param {string} wkt WKT string. + * @return {ol.geom.Geometry|undefined} + * The geometry created. + * @private + */ +ol.format.WKT.prototype.parse_ = function(wkt) { + var lexer = new ol.format.WKT.Lexer(wkt); + var parser = new ol.format.WKT.Parser(lexer); + return parser.parse(); +}; + + +/** + * Read a feature from a WKT source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable + */ +ol.format.WKT.prototype.readFeature; + + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.readFeatureFromText = function(text, opt_options) { + var geom = this.readGeometryFromText(text, opt_options); + if (geom) { + var feature = new ol.Feature(); + feature.setGeometry(geom); + return feature; + } + return null; +}; + + +/** + * Read all features from a WKT source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.WKT.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.readFeaturesFromText = function(text, opt_options) { + var geometries = []; + var geometry = this.readGeometryFromText(text, opt_options); + if (this.splitCollection_ && + geometry.getType() == ol.geom.GeometryType.GEOMETRY_COLLECTION) { + geometries = (/** @type {ol.geom.GeometryCollection} */ (geometry)) + .getGeometriesArray(); + } else { + geometries = [geometry]; + } + var feature, features = []; + for (var i = 0, ii = geometries.length; i < ii; ++i) { + feature = new ol.Feature(); + feature.setGeometry(geometries[i]); + features.push(feature); + } + return features; +}; + + +/** + * Read a single geometry from a WKT source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. + * @api stable + */ +ol.format.WKT.prototype.readGeometry; + + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.readGeometryFromText = function(text, opt_options) { + var geometry = this.parse_(text); + if (geometry) { + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, false, opt_options)); + } else { + return null; + } +}; + + +/** + * Encode a feature as a WKT string. + * + * @function + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} WKT string. + * @api stable + */ +ol.format.WKT.prototype.writeFeature; + + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.writeFeatureText = function(feature, opt_options) { + var geometry = feature.getGeometry(); + if (geometry) { + return this.writeGeometryText(geometry, opt_options); + } + return ''; +}; + + +/** + * Encode an array of features as a WKT string. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} WKT string. + * @api stable + */ +ol.format.WKT.prototype.writeFeatures; + + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.writeFeaturesText = function(features, opt_options) { + if (features.length == 1) { + return this.writeFeatureText(features[0], opt_options); + } + var geometries = []; + for (var i = 0, ii = features.length; i < ii; ++i) { + geometries.push(features[i].getGeometry()); + } + var collection = new ol.geom.GeometryCollection(geometries); + return this.writeGeometryText(collection, opt_options); +}; + + +/** + * Write a single geometry as a WKT string. + * + * @function + * @param {ol.geom.Geometry} geometry Geometry. + * @return {string} WKT string. + * @api stable + */ +ol.format.WKT.prototype.writeGeometry; + + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.writeGeometryText = function(geometry, opt_options) { + return ol.format.WKT.encode_(/** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, true, opt_options))); +}; + + +/** + * @const + * @enum {number} + */ +ol.format.WKT.TokenType = { + TEXT: 1, + LEFT_PAREN: 2, + RIGHT_PAREN: 3, + NUMBER: 4, + COMMA: 5, + EOF: 6 +}; + + +/** + * Class to tokenize a WKT string. + * @param {string} wkt WKT string. + * @constructor + * @protected + */ +ol.format.WKT.Lexer = function(wkt) { + + /** + * @type {string} + */ + this.wkt = wkt; + + /** + * @type {number} + * @private + */ + this.index_ = -1; +}; + + +/** + * @param {string} c Character. + * @return {boolean} Whether the character is alphabetic. + * @private + */ +ol.format.WKT.Lexer.prototype.isAlpha_ = function(c) { + return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; +}; + + +/** + * @param {string} c Character. + * @param {boolean=} opt_decimal Whether the string number + * contains a dot, i.e. is a decimal number. + * @return {boolean} Whether the character is numeric. + * @private + */ +ol.format.WKT.Lexer.prototype.isNumeric_ = function(c, opt_decimal) { + var decimal = opt_decimal !== undefined ? opt_decimal : false; + return c >= '0' && c <= '9' || c == '.' && !decimal; +}; + + +/** + * @param {string} c Character. + * @return {boolean} Whether the character is whitespace. + * @private + */ +ol.format.WKT.Lexer.prototype.isWhiteSpace_ = function(c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +}; + + +/** + * @return {string} Next string character. + * @private + */ +ol.format.WKT.Lexer.prototype.nextChar_ = function() { + return this.wkt.charAt(++this.index_); +}; + + +/** + * Fetch and return the next token. + * @return {!ol.WKTToken} Next string token. + */ +ol.format.WKT.Lexer.prototype.nextToken = function() { + var c = this.nextChar_(); + var token = {position: this.index_, value: c}; + + if (c == '(') { + token.type = ol.format.WKT.TokenType.LEFT_PAREN; + } else if (c == ',') { + token.type = ol.format.WKT.TokenType.COMMA; + } else if (c == ')') { + token.type = ol.format.WKT.TokenType.RIGHT_PAREN; + } else if (this.isNumeric_(c) || c == '-') { + token.type = ol.format.WKT.TokenType.NUMBER; + token.value = this.readNumber_(); + } else if (this.isAlpha_(c)) { + token.type = ol.format.WKT.TokenType.TEXT; + token.value = this.readText_(); + } else if (this.isWhiteSpace_(c)) { + return this.nextToken(); + } else if (c === '') { + token.type = ol.format.WKT.TokenType.EOF; + } else { + throw new Error('Unexpected character: ' + c); + } + + return token; +}; + + +/** + * @return {number} Numeric token value. + * @private + */ +ol.format.WKT.Lexer.prototype.readNumber_ = function() { + var c, index = this.index_; + var decimal = false; + var scientificNotation = false; + do { + if (c == '.') { + decimal = true; + } else if (c == 'e' || c == 'E') { + scientificNotation = true; + } + c = this.nextChar_(); + } while ( + this.isNumeric_(c, decimal) || + // if we haven't detected a scientific number before, 'e' or 'E' + // hint that we should continue to read + !scientificNotation && (c == 'e' || c == 'E') || + // once we know that we have a scientific number, both '-' and '+' + // are allowed + scientificNotation && (c == '-' || c == '+') + ); + return parseFloat(this.wkt.substring(index, this.index_--)); +}; + + +/** + * @return {string} String token value. + * @private + */ +ol.format.WKT.Lexer.prototype.readText_ = function() { + var c, index = this.index_; + do { + c = this.nextChar_(); + } while (this.isAlpha_(c)); + return this.wkt.substring(index, this.index_--).toUpperCase(); +}; + + +/** + * Class to parse the tokens from the WKT string. + * @param {ol.format.WKT.Lexer} lexer The lexer. + * @constructor + * @protected + */ +ol.format.WKT.Parser = function(lexer) { + + /** + * @type {ol.format.WKT.Lexer} + * @private + */ + this.lexer_ = lexer; + + /** + * @type {ol.WKTToken} + * @private + */ + this.token_; + + /** + * @type {ol.geom.GeometryLayout} + * @private + */ + this.layout_ = ol.geom.GeometryLayout.XY; +}; + + +/** + * Fetch the next token form the lexer and replace the active token. + * @private + */ +ol.format.WKT.Parser.prototype.consume_ = function() { + this.token_ = this.lexer_.nextToken(); +}; + +/** + * Tests if the given type matches the type of the current token. + * @param {ol.format.WKT.TokenType} type Token type. + * @return {boolean} Whether the token matches the given type. + */ +ol.format.WKT.Parser.prototype.isTokenType = function(type) { + var isMatch = this.token_.type == type; + return isMatch; +}; + + +/** + * If the given type matches the current token, consume it. + * @param {ol.format.WKT.TokenType} type Token type. + * @return {boolean} Whether the token matches the given type. + */ +ol.format.WKT.Parser.prototype.match = function(type) { + var isMatch = this.isTokenType(type); + if (isMatch) { + this.consume_(); + } + return isMatch; +}; + + +/** + * Try to parse the tokens provided by the lexer. + * @return {ol.geom.Geometry} The geometry. + */ +ol.format.WKT.Parser.prototype.parse = function() { + this.consume_(); + var geometry = this.parseGeometry_(); + ol.DEBUG && console.assert(this.token_.type == ol.format.WKT.TokenType.EOF, + 'token type should be end of file'); + return geometry; +}; + + +/** + * Try to parse the dimensional info. + * @return {ol.geom.GeometryLayout} The layout. + * @private + */ +ol.format.WKT.Parser.prototype.parseGeometryLayout_ = function() { + var layout = ol.geom.GeometryLayout.XY; + var dimToken = this.token_; + if (this.isTokenType(ol.format.WKT.TokenType.TEXT)) { + var dimInfo = dimToken.value; + if (dimInfo === ol.format.WKT.Z) { + layout = ol.geom.GeometryLayout.XYZ; + } else if (dimInfo === ol.format.WKT.M) { + layout = ol.geom.GeometryLayout.XYM; + } else if (dimInfo === ol.format.WKT.ZM) { + layout = ol.geom.GeometryLayout.XYZM; + } + if (layout !== ol.geom.GeometryLayout.XY) { + this.consume_(); + } + } + return layout; +}; + + +/** + * @return {!ol.geom.Geometry} The geometry. + * @private + */ +ol.format.WKT.Parser.prototype.parseGeometry_ = function() { + var token = this.token_; + if (this.match(ol.format.WKT.TokenType.TEXT)) { + var geomType = token.value; + this.layout_ = this.parseGeometryLayout_(); + if (geomType == ol.geom.GeometryType.GEOMETRY_COLLECTION.toUpperCase()) { + var geometries = this.parseGeometryCollectionText_(); + return new ol.geom.GeometryCollection(geometries); + } else { + var parser = ol.format.WKT.Parser.GeometryParser_[geomType]; + var ctor = ol.format.WKT.Parser.GeometryConstructor_[geomType]; + if (!parser || !ctor) { + throw new Error('Invalid geometry type: ' + geomType); + } + var coordinates = parser.call(this); + return new ctor(coordinates, this.layout_); + } + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<ol.geom.Geometry>} A collection of geometries. + * @private + */ +ol.format.WKT.Parser.prototype.parseGeometryCollectionText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var geometries = []; + do { + geometries.push(this.parseGeometry_()); + } while (this.match(ol.format.WKT.TokenType.COMMA)); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return geometries; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {Array.<number>} All values in a point. + * @private + */ +ol.format.WKT.Parser.prototype.parsePointText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parsePoint_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return null; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<!Array.<number>>} All points in a linestring. + * @private + */ +ol.format.WKT.Parser.prototype.parseLineStringText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parsePointList_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<!Array.<number>>} All points in a polygon. + * @private + */ +ol.format.WKT.Parser.prototype.parsePolygonText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parseLineStringTextList_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<!Array.<number>>} All points in a multipoint. + * @private + */ +ol.format.WKT.Parser.prototype.parseMultiPointText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates; + if (this.token_.type == ol.format.WKT.TokenType.LEFT_PAREN) { + coordinates = this.parsePointTextList_(); + } else { + coordinates = this.parsePointList_(); + } + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<!Array.<number>>} All linestring points + * in a multilinestring. + * @private + */ +ol.format.WKT.Parser.prototype.parseMultiLineStringText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parseLineStringTextList_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<!Array.<number>>} All polygon points in a multipolygon. + * @private + */ +ol.format.WKT.Parser.prototype.parseMultiPolygonText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parsePolygonTextList_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<number>} A point. + * @private + */ +ol.format.WKT.Parser.prototype.parsePoint_ = function() { + var coordinates = []; + var dimensions = this.layout_.length; + for (var i = 0; i < dimensions; ++i) { + var token = this.token_; + if (this.match(ol.format.WKT.TokenType.NUMBER)) { + coordinates.push(token.value); + } else { + break; + } + } + if (coordinates.length == dimensions) { + return coordinates; + } + throw new Error(this.formatErrorMessage_()); +}; + + +/** + * @return {!Array.<!Array.<number>>} An array of points. + * @private + */ +ol.format.WKT.Parser.prototype.parsePointList_ = function() { + var coordinates = [this.parsePoint_()]; + while (this.match(ol.format.WKT.TokenType.COMMA)) { + coordinates.push(this.parsePoint_()); + } + return coordinates; +}; + + +/** + * @return {!Array.<!Array.<number>>} An array of points. + * @private + */ +ol.format.WKT.Parser.prototype.parsePointTextList_ = function() { + var coordinates = [this.parsePointText_()]; + while (this.match(ol.format.WKT.TokenType.COMMA)) { + coordinates.push(this.parsePointText_()); + } + return coordinates; +}; + + +/** + * @return {!Array.<!Array.<number>>} An array of points. + * @private + */ +ol.format.WKT.Parser.prototype.parseLineStringTextList_ = function() { + var coordinates = [this.parseLineStringText_()]; + while (this.match(ol.format.WKT.TokenType.COMMA)) { + coordinates.push(this.parseLineStringText_()); + } + return coordinates; +}; + + +/** + * @return {!Array.<!Array.<number>>} An array of points. + * @private + */ +ol.format.WKT.Parser.prototype.parsePolygonTextList_ = function() { + var coordinates = [this.parsePolygonText_()]; + while (this.match(ol.format.WKT.TokenType.COMMA)) { + coordinates.push(this.parsePolygonText_()); + } + return coordinates; +}; + + +/** + * @return {boolean} Whether the token implies an empty geometry. + * @private + */ +ol.format.WKT.Parser.prototype.isEmptyGeometry_ = function() { + var isEmpty = this.isTokenType(ol.format.WKT.TokenType.TEXT) && + this.token_.value == ol.format.WKT.EMPTY; + if (isEmpty) { + this.consume_(); + } + return isEmpty; +}; + + +/** + * Create an error message for an unexpected token error. + * @return {string} Error message. + * @private + */ +ol.format.WKT.Parser.prototype.formatErrorMessage_ = function() { + return 'Unexpected `' + this.token_.value + '` at position ' + + this.token_.position + ' in `' + this.lexer_.wkt + '`'; +}; + + +/** + * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout)} + * @private + */ +ol.format.WKT.Parser.GeometryConstructor_ = { + 'POINT': ol.geom.Point, + 'LINESTRING': ol.geom.LineString, + 'POLYGON': ol.geom.Polygon, + 'MULTIPOINT': ol.geom.MultiPoint, + 'MULTILINESTRING': ol.geom.MultiLineString, + 'MULTIPOLYGON': ol.geom.MultiPolygon +}; + + +/** + * @enum {(function(): Array)} + * @private + */ +ol.format.WKT.Parser.GeometryParser_ = { + 'POINT': ol.format.WKT.Parser.prototype.parsePointText_, + 'LINESTRING': ol.format.WKT.Parser.prototype.parseLineStringText_, + 'POLYGON': ol.format.WKT.Parser.prototype.parsePolygonText_, + 'MULTIPOINT': ol.format.WKT.Parser.prototype.parseMultiPointText_, + 'MULTILINESTRING': ol.format.WKT.Parser.prototype.parseMultiLineStringText_, + 'MULTIPOLYGON': ol.format.WKT.Parser.prototype.parseMultiPolygonText_ +}; + +goog.provide('ol.format.WMSCapabilities'); + +goog.require('ol'); +goog.require('ol.format.XLink'); +goog.require('ol.format.XML'); +goog.require('ol.format.XSD'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Format for reading WMS capabilities data + * + * @constructor + * @extends {ol.format.XML} + * @api + */ +ol.format.WMSCapabilities = function() { + + ol.format.XML.call(this); + + /** + * @type {string|undefined} + */ + this.version = undefined; +}; +ol.inherits(ol.format.WMSCapabilities, ol.format.XML); + + +/** + * Read a WMS capabilities document. + * + * @function + * @param {Document|Node|string} source The XML source. + * @return {Object} An object representing the WMS capabilities. + * @api + */ +ol.format.WMSCapabilities.prototype.read; + + +/** + * @param {Document} doc Document. + * @return {Object} WMS Capability object. + */ +ol.format.WMSCapabilities.prototype.readFromDocument = function(doc) { + ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + return this.readFromNode(n); + } + } + return null; +}; + + +/** + * @param {Node} node Node. + * @return {Object} WMS Capability object. + */ +ol.format.WMSCapabilities.prototype.readFromNode = function(node) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'WMS_Capabilities' || + node.localName == 'WMT_MS_Capabilities', + 'localName should be WMS_Capabilities or WMT_MS_Capabilities'); + this.version = node.getAttribute('version').trim(); + var wmsCapabilityObject = ol.xml.pushParseAndPop({ + 'version': this.version + }, ol.format.WMSCapabilities.PARSERS_, node, []); + return wmsCapabilityObject ? wmsCapabilityObject : null; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Attribution object. + */ +ol.format.WMSCapabilities.readAttribution_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Attribution', + 'localName should be Attribution'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object} Bounding box object. + */ +ol.format.WMSCapabilities.readBoundingBox_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'BoundingBox', + 'localName should be BoundingBox'); + + var extent = [ + ol.format.XSD.readDecimalString(node.getAttribute('minx')), + ol.format.XSD.readDecimalString(node.getAttribute('miny')), + ol.format.XSD.readDecimalString(node.getAttribute('maxx')), + ol.format.XSD.readDecimalString(node.getAttribute('maxy')) + ]; + + var resolutions = [ + ol.format.XSD.readDecimalString(node.getAttribute('resx')), + ol.format.XSD.readDecimalString(node.getAttribute('resy')) + ]; + + return { + 'crs': node.getAttribute('CRS'), + 'extent': extent, + 'res': resolutions + }; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.Extent|undefined} Bounding box object. + */ +ol.format.WMSCapabilities.readEXGeographicBoundingBox_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'EX_GeographicBoundingBox', + 'localName should be EX_GeographicBoundingBox'); + var geographicBoundingBox = ol.xml.pushParseAndPop( + {}, + ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_, + node, objectStack); + if (!geographicBoundingBox) { + return undefined; + } + var westBoundLongitude = /** @type {number|undefined} */ + (geographicBoundingBox['westBoundLongitude']); + var southBoundLatitude = /** @type {number|undefined} */ + (geographicBoundingBox['southBoundLatitude']); + var eastBoundLongitude = /** @type {number|undefined} */ + (geographicBoundingBox['eastBoundLongitude']); + var northBoundLatitude = /** @type {number|undefined} */ + (geographicBoundingBox['northBoundLatitude']); + if (westBoundLongitude === undefined || southBoundLatitude === undefined || + eastBoundLongitude === undefined || northBoundLatitude === undefined) { + return undefined; + } + return [ + westBoundLongitude, southBoundLatitude, + eastBoundLongitude, northBoundLatitude + ]; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Capability object. + */ +ol.format.WMSCapabilities.readCapability_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Capability', + 'localName should be Capability'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.CAPABILITY_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Service object. + */ +ol.format.WMSCapabilities.readService_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Service', + 'localName should be Service'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.SERVICE_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Contact information object. + */ +ol.format.WMSCapabilities.readContactInformation_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType shpuld be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ContactInformation', + 'localName should be ContactInformation'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_, + node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Contact person object. + */ +ol.format.WMSCapabilities.readContactPersonPrimary_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ContactPersonPrimary', + 'localName should be ContactPersonPrimary'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_, + node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Contact address object. + */ +ol.format.WMSCapabilities.readContactAddress_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'ContactAddress', + 'localName should be ContactAddress'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_, + node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<string>|undefined} Format array. + */ +ol.format.WMSCapabilities.readException_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Exception', + 'localName should be Exception'); + return ol.xml.pushParseAndPop( + [], ol.format.WMSCapabilities.EXCEPTION_PARSERS_, node, objectStack); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Layer object. + */ +ol.format.WMSCapabilities.readCapabilityLayer_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Layer', 'localName should be Layer'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Layer object. + */ +ol.format.WMSCapabilities.readLayer_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Layer', 'localName should be Layer'); + var parentLayerObject = /** @type {Object.<string,*>} */ + (objectStack[objectStack.length - 1]); + + var layerObject = ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack); + + if (!layerObject) { + return undefined; + } + var queryable = + ol.format.XSD.readBooleanString(node.getAttribute('queryable')); + if (queryable === undefined) { + queryable = parentLayerObject['queryable']; + } + layerObject['queryable'] = queryable !== undefined ? queryable : false; + + var cascaded = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('cascaded')); + if (cascaded === undefined) { + cascaded = parentLayerObject['cascaded']; + } + layerObject['cascaded'] = cascaded; + + var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque')); + if (opaque === undefined) { + opaque = parentLayerObject['opaque']; + } + layerObject['opaque'] = opaque !== undefined ? opaque : false; + + var noSubsets = + ol.format.XSD.readBooleanString(node.getAttribute('noSubsets')); + if (noSubsets === undefined) { + noSubsets = parentLayerObject['noSubsets']; + } + layerObject['noSubsets'] = noSubsets !== undefined ? noSubsets : false; + + var fixedWidth = + ol.format.XSD.readDecimalString(node.getAttribute('fixedWidth')); + if (!fixedWidth) { + fixedWidth = parentLayerObject['fixedWidth']; + } + layerObject['fixedWidth'] = fixedWidth; + + var fixedHeight = + ol.format.XSD.readDecimalString(node.getAttribute('fixedHeight')); + if (!fixedHeight) { + fixedHeight = parentLayerObject['fixedHeight']; + } + layerObject['fixedHeight'] = fixedHeight; + + // See 7.2.4.8 + var addKeys = ['Style', 'CRS', 'AuthorityURL']; + addKeys.forEach(function(key) { + if (key in parentLayerObject) { + var childValue = layerObject[key] || []; + layerObject[key] = childValue.concat(parentLayerObject[key]); + } + }); + + var replaceKeys = ['EX_GeographicBoundingBox', 'BoundingBox', 'Dimension', + 'Attribution', 'MinScaleDenominator', 'MaxScaleDenominator']; + replaceKeys.forEach(function(key) { + if (!(key in layerObject)) { + var parentValue = parentLayerObject[key]; + layerObject[key] = parentValue; + } + }); + + return layerObject; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object} Dimension object. + */ +ol.format.WMSCapabilities.readDimension_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Dimension', + 'localName should be Dimension'); + var dimensionObject = { + 'name': node.getAttribute('name'), + 'units': node.getAttribute('units'), + 'unitSymbol': node.getAttribute('unitSymbol'), + 'default': node.getAttribute('default'), + 'multipleValues': ol.format.XSD.readBooleanString( + node.getAttribute('multipleValues')), + 'nearestValue': ol.format.XSD.readBooleanString( + node.getAttribute('nearestValue')), + 'current': ol.format.XSD.readBooleanString(node.getAttribute('current')), + 'values': ol.format.XSD.readString(node) + }; + return dimensionObject; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Online resource object. + */ +ol.format.WMSCapabilities.readFormatOnlineresource_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_, + node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Request object. + */ +ol.format.WMSCapabilities.readRequest_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Request', + 'localName should be Request'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.REQUEST_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} DCP type object. + */ +ol.format.WMSCapabilities.readDCPType_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'DCPType', + 'localName should be DCPType'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.DCPTYPE_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} HTTP object. + */ +ol.format.WMSCapabilities.readHTTP_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'HTTP', 'localName should be HTTP'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.HTTP_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Operation type object. + */ +ol.format.WMSCapabilities.readOperationType_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Online resource object. + */ +ol.format.WMSCapabilities.readSizedFormatOnlineresource_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + var formatOnlineresource = + ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack); + if (formatOnlineresource) { + var size = [ + ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('width')), + ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('height')) + ]; + formatOnlineresource['size'] = size; + return formatOnlineresource; + } + return undefined; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Authority URL object. + */ +ol.format.WMSCapabilities.readAuthorityURL_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'AuthorityURL', + 'localName should be AuthorityURL'); + var authorityObject = + ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack); + if (authorityObject) { + authorityObject['name'] = node.getAttribute('name'); + return authorityObject; + } + return undefined; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Metadata URL object. + */ +ol.format.WMSCapabilities.readMetadataURL_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'MetadataURL', + 'localName should be MetadataURL'); + var metadataObject = + ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack); + if (metadataObject) { + metadataObject['type'] = node.getAttribute('type'); + return metadataObject; + } + return undefined; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Style object. + */ +ol.format.WMSCapabilities.readStyle_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Style', 'localName should be Style'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.STYLE_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Array.<string>|undefined} Keyword list. + */ +ol.format.WMSCapabilities.readKeywordList_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'KeywordList', + 'localName should be KeywordList'); + return ol.xml.pushParseAndPop( + [], ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_, node, objectStack); +}; + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.format.WMSCapabilities.NAMESPACE_URIS_ = [ + null, + 'http://www.opengis.net/wms' +]; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Service': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readService_), + 'Capability': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readCapability_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.CAPABILITY_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Request': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readRequest_), + 'Exception': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readException_), + 'Layer': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readCapabilityLayer_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.SERVICE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'KeywordList': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readKeywordList_), + 'OnlineResource': ol.xml.makeObjectPropertySetter( + ol.format.XLink.readHref), + 'ContactInformation': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readContactInformation_), + 'Fees': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'AccessConstraints': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'LayerLimit': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MaxWidth': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MaxHeight': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'ContactPersonPrimary': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readContactPersonPrimary_), + 'ContactPosition': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ContactAddress': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readContactAddress_), + 'ContactVoiceTelephone': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ContactFacsimileTelephone': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ContactElectronicMailAddress': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'ContactPerson': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ContactOrganization': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'AddressType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'StateOrProvince': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'PostCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.EXCEPTION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Format': ol.xml.makeArrayPusher(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.LAYER_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'KeywordList': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readKeywordList_), + 'CRS': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString), + 'EX_GeographicBoundingBox': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readEXGeographicBoundingBox_), + 'BoundingBox': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readBoundingBox_), + 'Dimension': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readDimension_), + 'Attribution': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readAttribution_), + 'AuthorityURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readAuthorityURL_), + 'Identifier': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString), + 'MetadataURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readMetadataURL_), + 'DataURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readFormatOnlineresource_), + 'FeatureListURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readFormatOnlineresource_), + 'Style': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readStyle_), + 'MinScaleDenominator': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'MaxScaleDenominator': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'Layer': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readLayer_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'OnlineResource': ol.xml.makeObjectPropertySetter( + ol.format.XLink.readHref), + 'LogoURL': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readSizedFormatOnlineresource_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_ = + ol.xml.makeStructureNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'westBoundLongitude': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'eastBoundLongitude': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'southBoundLatitude': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'northBoundLatitude': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.REQUEST_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'GetCapabilities': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readOperationType_), + 'GetMap': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readOperationType_), + 'GetFeatureInfo': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readOperationType_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Format': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString), + 'DCPType': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readDCPType_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'HTTP': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readHTTP_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.HTTP_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Get': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readFormatOnlineresource_), + 'Post': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readFormatOnlineresource_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'LegendURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readSizedFormatOnlineresource_), + 'StyleSheetURL': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readFormatOnlineresource_), + 'StyleURL': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readFormatOnlineresource_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_ = + ol.xml.makeStructureNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Format': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'OnlineResource': ol.xml.makeObjectPropertySetter( + ol.format.XLink.readHref) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Keyword': ol.xml.makeArrayPusher(ol.format.XSD.readString) + }); + +goog.provide('ol.format.WMSGetFeatureInfo'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.format.GML2'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.obj'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Format for reading WMSGetFeatureInfo format. It uses + * {@link ol.format.GML2} to read features. + * + * @constructor + * @extends {ol.format.XMLFeature} + * @param {olx.format.WMSGetFeatureInfoOptions=} opt_options Options. + * @api + */ +ol.format.WMSGetFeatureInfo = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {string} + */ + this.featureNS_ = 'http://mapserver.gis.umn.edu/mapserver'; + + + /** + * @private + * @type {ol.format.GML2} + */ + this.gmlFormat_ = new ol.format.GML2(); + + + /** + * @private + * @type {Array.<string>} + */ + this.layers_ = options.layers ? options.layers : null; + + ol.format.XMLFeature.call(this); +}; +ol.inherits(ol.format.WMSGetFeatureInfo, ol.format.XMLFeature); + + +/** + * @const + * @type {string} + * @private + */ +ol.format.WMSGetFeatureInfo.featureIdentifier_ = '_feature'; + + +/** + * @const + * @type {string} + * @private + */ +ol.format.WMSGetFeatureInfo.layerIdentifier_ = '_layer'; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Array.<ol.Feature>} Features. + * @private + */ +ol.format.WMSGetFeatureInfo.prototype.readFeatures_ = function(node, objectStack) { + + node.setAttribute('namespaceURI', this.featureNS_); + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + var localName = node.localName; + /** @type {Array.<ol.Feature>} */ + var features = []; + if (node.childNodes.length === 0) { + return features; + } + if (localName == 'msGMLOutput') { + for (var i = 0, ii = node.childNodes.length; i < ii; i++) { + var layer = node.childNodes[i]; + if (layer.nodeType !== Node.ELEMENT_NODE) { + continue; + } + var context = objectStack[0]; + + ol.DEBUG && console.assert(layer.localName.indexOf( + ol.format.WMSGetFeatureInfo.layerIdentifier_) >= 0, + 'localName of layer node should match layerIdentifier'); + + var toRemove = ol.format.WMSGetFeatureInfo.layerIdentifier_; + var layerName = layer.localName.replace(toRemove, ''); + + if (this.layers_ && !ol.array.includes(this.layers_, layerName)) { + continue; + } + + var featureType = layerName + + ol.format.WMSGetFeatureInfo.featureIdentifier_; + + context['featureType'] = featureType; + context['featureNS'] = this.featureNS_; + + var parsers = {}; + parsers[featureType] = ol.xml.makeArrayPusher( + this.gmlFormat_.readFeatureElement, this.gmlFormat_); + var parsersNS = ol.xml.makeStructureNS( + [context['featureNS'], null], parsers); + layer.setAttribute('namespaceURI', this.featureNS_); + var layerFeatures = ol.xml.pushParseAndPop( + [], parsersNS, layer, objectStack, this.gmlFormat_); + if (layerFeatures) { + ol.array.extend(features, layerFeatures); + } + } + } + if (localName == 'FeatureCollection') { + var gmlFeatures = ol.xml.pushParseAndPop([], + this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node, + [{}], this.gmlFormat_); + if (gmlFeatures) { + features = gmlFeatures; + } + } + return features; +}; + + +/** + * Read all features from a WMSGetFeatureInfo response. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.WMSGetFeatureInfo.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.WMSGetFeatureInfo.prototype.readFeaturesFromNode = function(node, opt_options) { + var options = {}; + if (opt_options) { + ol.obj.assign(options, this.getReadOptions(node, opt_options)); + } + return this.readFeatures_(node, [options]); +}; + +goog.provide('ol.format.WMTSCapabilities'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.format.OWS'); +goog.require('ol.format.XLink'); +goog.require('ol.format.XML'); +goog.require('ol.format.XSD'); +goog.require('ol.xml'); + + +/** + * @classdesc + * Format for reading WMTS capabilities data. + * + * @constructor + * @extends {ol.format.XML} + * @api + */ +ol.format.WMTSCapabilities = function() { + ol.format.XML.call(this); + + /** + * @type {ol.format.OWS} + * @private + */ + this.owsParser_ = new ol.format.OWS(); +}; +ol.inherits(ol.format.WMTSCapabilities, ol.format.XML); + + +/** + * Read a WMTS capabilities document. + * + * @function + * @param {Document|Node|string} source The XML source. + * @return {Object} An object representing the WMTS capabilities. + * @api + */ +ol.format.WMTSCapabilities.prototype.read; + + +/** + * @param {Document} doc Document. + * @return {Object} WMTS Capability object. + */ +ol.format.WMTSCapabilities.prototype.readFromDocument = function(doc) { + ol.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + return this.readFromNode(n); + } + } + return null; +}; + + +/** + * @param {Node} node Node. + * @return {Object} WMTS Capability object. + */ +ol.format.WMTSCapabilities.prototype.readFromNode = function(node) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Capabilities', + 'localName should be Capabilities'); + var version = node.getAttribute('version').trim(); + var WMTSCapabilityObject = this.owsParser_.readFromNode(node); + if (!WMTSCapabilityObject) { + return null; + } + WMTSCapabilityObject['version'] = version; + WMTSCapabilityObject = ol.xml.pushParseAndPop(WMTSCapabilityObject, + ol.format.WMTSCapabilities.PARSERS_, node, []); + return WMTSCapabilityObject ? WMTSCapabilityObject : null; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Attribution object. + */ +ol.format.WMTSCapabilities.readContents_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Contents', + 'localName should be Contents'); + + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.CONTENTS_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Layers object. + */ +ol.format.WMTSCapabilities.readLayer_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Layer', 'localName should be Layer'); + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.LAYER_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Tile Matrix Set object. + */ +ol.format.WMTSCapabilities.readTileMatrixSet_ = function(node, objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.TMS_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Style object. + */ +ol.format.WMTSCapabilities.readStyle_ = function(node, objectStack) { + var style = ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.STYLE_PARSERS_, node, objectStack); + if (!style) { + return undefined; + } + var isDefault = node.getAttribute('isDefault') === 'true'; + style['isDefault'] = isDefault; + return style; + +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Tile Matrix Set Link object. + */ +ol.format.WMTSCapabilities.readTileMatrixSetLink_ = function(node, + objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Dimension object. + */ +ol.format.WMTSCapabilities.readDimensions_ = function(node, objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.DIMENSION_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Resource URL object. + */ +ol.format.WMTSCapabilities.readResourceUrl_ = function(node, objectStack) { + var format = node.getAttribute('format'); + var template = node.getAttribute('template'); + var resourceType = node.getAttribute('resourceType'); + var resource = {}; + if (format) { + resource['format'] = format; + } + if (template) { + resource['template'] = template; + } + if (resourceType) { + resource['resourceType'] = resourceType; + } + return resource; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} WGS84 BBox object. + */ +ol.format.WMTSCapabilities.readWgs84BoundingBox_ = function(node, objectStack) { + var coordinates = ol.xml.pushParseAndPop([], + ol.format.WMTSCapabilities.WGS84_BBOX_READERS_, node, objectStack); + if (coordinates.length != 2) { + return undefined; + } + return ol.extent.boundingExtent(coordinates); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Legend object. + */ +ol.format.WMTSCapabilities.readLegendUrl_ = function(node, objectStack) { + var legend = {}; + legend['format'] = node.getAttribute('format'); + legend['href'] = ol.format.XLink.readHref(node); + return legend; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Coordinates object. + */ +ol.format.WMTSCapabilities.readCoordinates_ = function(node, objectStack) { + var coordinates = ol.format.XSD.readString(node).split(' '); + if (!coordinates || coordinates.length != 2) { + return undefined; + } + var x = +coordinates[0]; + var y = +coordinates[1]; + if (isNaN(x) || isNaN(y)) { + return undefined; + } + return [x, y]; +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} TileMatrix object. + */ +ol.format.WMTSCapabilities.readTileMatrix_ = function(node, objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.TM_PARSERS_, node, objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} TileMatrixSetLimits Object. + */ +ol.format.WMTSCapabilities.readTileMatrixLimitsList_ = function(node, + objectStack) { + return ol.xml.pushParseAndPop([], + ol.format.WMTSCapabilities.TMS_LIMITS_LIST_PARSERS_, node, + objectStack); +}; + + +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} TileMatrixLimits Array. + */ +ol.format.WMTSCapabilities.readTileMatrixLimits_ = function(node, objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.TMS_LIMITS_PARSERS_, node, objectStack); +}; + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.format.WMTSCapabilities.NAMESPACE_URIS_ = [ + null, + 'http://www.opengis.net/wmts/1.0' +]; + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_ = [ + null, + 'http://www.opengis.net/ows/1.1' +]; + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'Contents': ol.xml.makeObjectPropertySetter( + ol.format.WMTSCapabilities.readContents_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.CONTENTS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'Layer': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readLayer_), + 'TileMatrixSet': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readTileMatrixSet_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.LAYER_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'Style': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readStyle_), + 'Format': ol.xml.makeObjectPropertyPusher( + ol.format.XSD.readString), + 'TileMatrixSetLink': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readTileMatrixSetLink_), + 'Dimension': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readDimensions_), + 'ResourceURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readResourceUrl_) + }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'Title': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'Abstract': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'WGS84BoundingBox': ol.xml.makeObjectPropertySetter( + ol.format.WMTSCapabilities.readWgs84BoundingBox_), + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.STYLE_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'LegendURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readLegendUrl_) + }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'Title': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'TileMatrixSet': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'TileMatrixSetLimits': ol.xml.makeObjectPropertySetter( + ol.format.WMTSCapabilities.readTileMatrixLimitsList_) + }); + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.TMS_LIMITS_LIST_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'TileMatrixLimits': ol.xml.makeArrayPusher( + ol.format.WMTSCapabilities.readTileMatrixLimits_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.TMS_LIMITS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'TileMatrix': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'MinTileRow': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MaxTileRow': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MinTileCol': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MaxTileCol': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.DIMENSION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'Default': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'Value': ol.xml.makeObjectPropertyPusher( + ol.format.XSD.readString) + }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.WGS84_BBOX_READERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'LowerCorner': ol.xml.makeArrayPusher( + ol.format.WMTSCapabilities.readCoordinates_), + 'UpperCorner': ol.xml.makeArrayPusher( + ol.format.WMTSCapabilities.readCoordinates_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.TMS_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'WellKnownScaleSet': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'TileMatrix': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readTileMatrix_) + }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'SupportedCRS': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.XmlParser>>} + * @private + */ +ol.format.WMTSCapabilities.TM_PARSERS_ = ol.xml.makeStructureNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'TopLeftCorner': ol.xml.makeObjectPropertySetter( + ol.format.WMTSCapabilities.readCoordinates_), + 'ScaleDenominator': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'TileWidth': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'TileHeight': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MatrixWidth': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MatrixHeight': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger) + }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); + +// FIXME handle geolocation not supported + +goog.provide('ol.Geolocation'); + +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.geom.Polygon'); +goog.require('ol.has'); +goog.require('ol.math'); +goog.require('ol.proj'); +goog.require('ol.sphere.WGS84'); + + +/** + * @classdesc + * Helper class for providing HTML5 Geolocation capabilities. + * The [Geolocation API](http://www.w3.org/TR/geolocation-API/) + * is used to locate a user's position. + * + * To get notified of position changes, register a listener for the generic + * `change` event on your instance of `ol.Geolocation`. + * + * Example: + * + * var geolocation = new ol.Geolocation({ + * // take the projection to use from the map's view + * projection: view.getProjection() + * }); + * // listen to changes in position + * geolocation.on('change', function(evt) { + * window.console.log(geolocation.getPosition()); + * }); + * + * @fires error + * @constructor + * @extends {ol.Object} + * @param {olx.GeolocationOptions=} opt_options Options. + * @api stable + */ +ol.Geolocation = function(opt_options) { + + ol.Object.call(this); + + var options = opt_options || {}; + + /** + * The unprojected (EPSG:4326) device position. + * @private + * @type {ol.Coordinate} + */ + this.position_ = null; + + /** + * @private + * @type {ol.TransformFunction} + */ + this.transform_ = ol.proj.identityTransform; + + /** + * @private + * @type {number|undefined} + */ + this.watchId_ = undefined; + + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Geolocation.Property.PROJECTION), + this.handleProjectionChanged_, this); + ol.events.listen( + this, ol.Object.getChangeEventType(ol.Geolocation.Property.TRACKING), + this.handleTrackingChanged_, this); + + if (options.projection !== undefined) { + this.setProjection(ol.proj.get(options.projection)); + } + if (options.trackingOptions !== undefined) { + this.setTrackingOptions(options.trackingOptions); + } + + this.setTracking(options.tracking !== undefined ? options.tracking : false); + +}; +ol.inherits(ol.Geolocation, ol.Object); + + +/** + * @inheritDoc + */ +ol.Geolocation.prototype.disposeInternal = function() { + this.setTracking(false); + ol.Object.prototype.disposeInternal.call(this); +}; + + +/** + * @private + */ +ol.Geolocation.prototype.handleProjectionChanged_ = function() { + var projection = this.getProjection(); + if (projection) { + this.transform_ = ol.proj.getTransformFromProjections( + ol.proj.get('EPSG:4326'), projection); + if (this.position_) { + this.set( + ol.Geolocation.Property.POSITION, this.transform_(this.position_)); + } + } +}; + + +/** + * @private + */ +ol.Geolocation.prototype.handleTrackingChanged_ = function() { + if (ol.has.GEOLOCATION) { + var tracking = this.getTracking(); + if (tracking && this.watchId_ === undefined) { + this.watchId_ = navigator.geolocation.watchPosition( + this.positionChange_.bind(this), + this.positionError_.bind(this), + this.getTrackingOptions()); + } else if (!tracking && this.watchId_ !== undefined) { + navigator.geolocation.clearWatch(this.watchId_); + this.watchId_ = undefined; + } + } +}; + + +/** + * @private + * @param {GeolocationPosition} position position event. + */ +ol.Geolocation.prototype.positionChange_ = function(position) { + var coords = position.coords; + this.set(ol.Geolocation.Property.ACCURACY, coords.accuracy); + this.set(ol.Geolocation.Property.ALTITUDE, + coords.altitude === null ? undefined : coords.altitude); + this.set(ol.Geolocation.Property.ALTITUDE_ACCURACY, + coords.altitudeAccuracy === null ? + undefined : coords.altitudeAccuracy); + this.set(ol.Geolocation.Property.HEADING, coords.heading === null ? + undefined : ol.math.toRadians(coords.heading)); + if (!this.position_) { + this.position_ = [coords.longitude, coords.latitude]; + } else { + this.position_[0] = coords.longitude; + this.position_[1] = coords.latitude; + } + var projectedPosition = this.transform_(this.position_); + this.set(ol.Geolocation.Property.POSITION, projectedPosition); + this.set(ol.Geolocation.Property.SPEED, + coords.speed === null ? undefined : coords.speed); + var geometry = ol.geom.Polygon.circular( + ol.sphere.WGS84, this.position_, coords.accuracy); + geometry.applyTransform(this.transform_); + this.set(ol.Geolocation.Property.ACCURACY_GEOMETRY, geometry); + this.changed(); +}; + +/** + * Triggered when the Geolocation returns an error. + * @event error + * @api + */ + +/** + * @private + * @param {GeolocationPositionError} error error object. + */ +ol.Geolocation.prototype.positionError_ = function(error) { + error.type = ol.events.EventType.ERROR; + this.setTracking(false); + this.dispatchEvent(/** @type {{type: string, target: undefined}} */ (error)); +}; + + +/** + * Get the accuracy of the position in meters. + * @return {number|undefined} The accuracy of the position measurement in + * meters. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getAccuracy = function() { + return /** @type {number|undefined} */ ( + this.get(ol.Geolocation.Property.ACCURACY)); +}; + + +/** + * Get a geometry of the position accuracy. + * @return {?ol.geom.Geometry} A geometry of the position accuracy. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getAccuracyGeometry = function() { + return /** @type {?ol.geom.Geometry} */ ( + this.get(ol.Geolocation.Property.ACCURACY_GEOMETRY) || null); +}; + + +/** + * Get the altitude associated with the position. + * @return {number|undefined} The altitude of the position in meters above mean + * sea level. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getAltitude = function() { + return /** @type {number|undefined} */ ( + this.get(ol.Geolocation.Property.ALTITUDE)); +}; + + +/** + * Get the altitude accuracy of the position. + * @return {number|undefined} The accuracy of the altitude measurement in + * meters. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getAltitudeAccuracy = function() { + return /** @type {number|undefined} */ ( + this.get(ol.Geolocation.Property.ALTITUDE_ACCURACY)); +}; + + +/** + * Get the heading as radians clockwise from North. + * @return {number|undefined} The heading of the device in radians from north. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getHeading = function() { + return /** @type {number|undefined} */ ( + this.get(ol.Geolocation.Property.HEADING)); +}; + + +/** + * Get the position of the device. + * @return {ol.Coordinate|undefined} The current position of the device reported + * in the current projection. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getPosition = function() { + return /** @type {ol.Coordinate|undefined} */ ( + this.get(ol.Geolocation.Property.POSITION)); +}; + + +/** + * Get the projection associated with the position. + * @return {ol.proj.Projection|undefined} The projection the position is + * reported in. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getProjection = function() { + return /** @type {ol.proj.Projection|undefined} */ ( + this.get(ol.Geolocation.Property.PROJECTION)); +}; + + +/** + * Get the speed in meters per second. + * @return {number|undefined} The instantaneous speed of the device in meters + * per second. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getSpeed = function() { + return /** @type {number|undefined} */ ( + this.get(ol.Geolocation.Property.SPEED)); +}; + + +/** + * Determine if the device location is being tracked. + * @return {boolean} The device location is being tracked. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getTracking = function() { + return /** @type {boolean} */ ( + this.get(ol.Geolocation.Property.TRACKING)); +}; + + +/** + * Get the tracking options. + * @see http://www.w3.org/TR/geolocation-API/#position-options + * @return {GeolocationPositionOptions|undefined} PositionOptions as defined by + * the [HTML5 Geolocation spec + * ](http://www.w3.org/TR/geolocation-API/#position_options_interface). + * @observable + * @api stable + */ +ol.Geolocation.prototype.getTrackingOptions = function() { + return /** @type {GeolocationPositionOptions|undefined} */ ( + this.get(ol.Geolocation.Property.TRACKING_OPTIONS)); +}; + + +/** + * Set the projection to use for transforming the coordinates. + * @param {ol.proj.Projection} projection The projection the position is + * reported in. + * @observable + * @api stable + */ +ol.Geolocation.prototype.setProjection = function(projection) { + this.set(ol.Geolocation.Property.PROJECTION, projection); +}; + + +/** + * Enable or disable tracking. + * @param {boolean} tracking Enable tracking. + * @observable + * @api stable + */ +ol.Geolocation.prototype.setTracking = function(tracking) { + this.set(ol.Geolocation.Property.TRACKING, tracking); +}; + + +/** + * Set the tracking options. + * @see http://www.w3.org/TR/geolocation-API/#position-options + * @param {GeolocationPositionOptions} options PositionOptions as defined by the + * [HTML5 Geolocation spec + * ](http://www.w3.org/TR/geolocation-API/#position_options_interface). + * @observable + * @api stable + */ +ol.Geolocation.prototype.setTrackingOptions = function(options) { + this.set(ol.Geolocation.Property.TRACKING_OPTIONS, options); +}; + + +/** + * @enum {string} + */ +ol.Geolocation.Property = { + ACCURACY: 'accuracy', + ACCURACY_GEOMETRY: 'accuracyGeometry', + ALTITUDE: 'altitude', + ALTITUDE_ACCURACY: 'altitudeAccuracy', + HEADING: 'heading', + POSITION: 'position', + PROJECTION: 'projection', + SPEED: 'speed', + TRACKING: 'tracking', + TRACKING_OPTIONS: 'trackingOptions' +}; + +goog.provide('ol.geom.Circle'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.deflate'); + + +/** + * @classdesc + * Circle geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {ol.Coordinate} center Center. + * @param {number=} opt_radius Radius. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api + */ +ol.geom.Circle = function(center, opt_radius, opt_layout) { + ol.geom.SimpleGeometry.call(this); + var radius = opt_radius ? opt_radius : 0; + this.setCenterAndRadius(center, radius, opt_layout); +}; +ol.inherits(ol.geom.Circle, ol.geom.SimpleGeometry); + + +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.Circle} Clone. + * @api + */ +ol.geom.Circle.prototype.clone = function() { + var circle = new ol.geom.Circle(null); + circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return circle; +}; + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) { + var flatCoordinates = this.flatCoordinates; + var dx = x - flatCoordinates[0]; + var dy = y - flatCoordinates[1]; + var squaredDistance = dx * dx + dy * dy; + if (squaredDistance < minSquaredDistance) { + var i; + if (squaredDistance === 0) { + for (i = 0; i < this.stride; ++i) { + closestPoint[i] = flatCoordinates[i]; + } + } else { + var delta = this.getRadius() / Math.sqrt(squaredDistance); + closestPoint[0] = flatCoordinates[0] + delta * dx; + closestPoint[1] = flatCoordinates[1] + delta * dy; + for (i = 2; i < this.stride; ++i) { + closestPoint[i] = flatCoordinates[i]; + } + } + closestPoint.length = this.stride; + return squaredDistance; + } else { + return minSquaredDistance; + } +}; + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.containsXY = function(x, y) { + var flatCoordinates = this.flatCoordinates; + var dx = x - flatCoordinates[0]; + var dy = y - flatCoordinates[1]; + return dx * dx + dy * dy <= this.getRadiusSquared_(); +}; + + +/** + * Return the center of the circle as {@link ol.Coordinate coordinate}. + * @return {ol.Coordinate} Center. + * @api + */ +ol.geom.Circle.prototype.getCenter = function() { + return this.flatCoordinates.slice(0, this.stride); +}; + + +/** + * @inheritDoc + */ +ol.geom.Circle.prototype.computeExtent = function(extent) { + var flatCoordinates = this.flatCoordinates; + var radius = flatCoordinates[this.stride] - flatCoordinates[0]; + return ol.extent.createOrUpdate( + flatCoordinates[0] - radius, flatCoordinates[1] - radius, + flatCoordinates[0] + radius, flatCoordinates[1] + radius, + extent); +}; + + +/** + * Return the radius of the circle. + * @return {number} Radius. + * @api + */ +ol.geom.Circle.prototype.getRadius = function() { + return Math.sqrt(this.getRadiusSquared_()); +}; + + +/** + * @private + * @return {number} Radius squared. + */ +ol.geom.Circle.prototype.getRadiusSquared_ = function() { + var dx = this.flatCoordinates[this.stride] - this.flatCoordinates[0]; + var dy = this.flatCoordinates[this.stride + 1] - this.flatCoordinates[1]; + return dx * dx + dy * dy; +}; + + +/** + * @inheritDoc + * @api + */ +ol.geom.Circle.prototype.getType = function() { + return ol.geom.GeometryType.CIRCLE; +}; + + +/** + * @inheritDoc + * @api stable + */ +ol.geom.Circle.prototype.intersectsExtent = function(extent) { + var circleExtent = this.getExtent(); + if (ol.extent.intersects(extent, circleExtent)) { + var center = this.getCenter(); + + if (extent[0] <= center[0] && extent[2] >= center[0]) { + return true; + } + if (extent[1] <= center[1] && extent[3] >= center[1]) { + return true; + } + + return ol.extent.forEachCorner(extent, this.intersectsCoordinate, this); + } + return false; + +}; + + +/** + * Set the center of the circle as {@link ol.Coordinate coordinate}. + * @param {ol.Coordinate} center Center. + * @api + */ +ol.geom.Circle.prototype.setCenter = function(center) { + var stride = this.stride; + ol.DEBUG && console.assert(center.length == stride, + 'center array length should match stride'); + var radius = this.flatCoordinates[stride] - this.flatCoordinates[0]; + var flatCoordinates = center.slice(); + flatCoordinates[stride] = flatCoordinates[0] + radius; + var i; + for (i = 1; i < stride; ++i) { + flatCoordinates[stride + i] = center[i]; + } + this.setFlatCoordinates(this.layout, flatCoordinates); +}; + + +/** + * Set the center (as {@link ol.Coordinate coordinate}) and the radius (as + * number) of the circle. + * @param {ol.Coordinate} center Center. + * @param {number} radius Radius. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api + */ +ol.geom.Circle.prototype.setCenterAndRadius = function(center, radius, opt_layout) { + if (!center) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, center, 0); + if (!this.flatCoordinates) { + this.flatCoordinates = []; + } + /** @type {Array.<number>} */ + var flatCoordinates = this.flatCoordinates; + var offset = ol.geom.flat.deflate.coordinate( + flatCoordinates, 0, center, this.stride); + flatCoordinates[offset++] = flatCoordinates[0] + radius; + var i, ii; + for (i = 1, ii = this.stride; i < ii; ++i) { + flatCoordinates[offset++] = flatCoordinates[i]; + } + flatCoordinates.length = offset; + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + */ +ol.geom.Circle.prototype.setFlatCoordinates = function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; + + +/** + * Set the radius of the circle. The radius is in the units of the projection. + * @param {number} radius Radius. + * @api + */ +ol.geom.Circle.prototype.setRadius = function(radius) { + this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius; + this.changed(); +}; + + +/** + * Transform each coordinate of the circle from one coordinate reference system + * to another. The geometry is modified in place. + * If you do not want the geometry modified in place, first clone() it and + * then use this function on the clone. + * + * Internally a circle is currently represented by two points: the center of + * the circle `[cx, cy]`, and the point to the right of the circle + * `[cx + r, cy]`. This `transform` function just transforms these two points. + * So the resulting geometry is also a circle, and that circle does not + * correspond to the shape that would be obtained by transforming every point + * of the original circle. + * + * @param {ol.ProjectionLike} source The current projection. Can be a + * string identifier or a {@link ol.proj.Projection} object. + * @param {ol.ProjectionLike} destination The desired projection. Can be a + * string identifier or a {@link ol.proj.Projection} object. + * @return {ol.geom.Circle} This geometry. Note that original geometry is + * modified in place. + * @function + * @api stable + */ +ol.geom.Circle.prototype.transform; + +goog.provide('ol.geom.flat.geodesic'); + +goog.require('ol'); +goog.require('ol.math'); +goog.require('ol.proj'); + + +/** + * @private + * @param {function(number): ol.Coordinate} interpolate Interpolate function. + * @param {ol.TransformFunction} transform Transform from longitude/latitude to + * projected coordinates. + * @param {number} squaredTolerance Squared tolerance. + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.flat.geodesic.line_ = function(interpolate, transform, squaredTolerance) { + // FIXME reduce garbage generation + // FIXME optimize stack operations + + /** @type {Array.<number>} */ + var flatCoordinates = []; + + var geoA = interpolate(0); + var geoB = interpolate(1); + + var a = transform(geoA); + var b = transform(geoB); + + /** @type {Array.<ol.Coordinate>} */ + var geoStack = [geoB, geoA]; + /** @type {Array.<ol.Coordinate>} */ + var stack = [b, a]; + /** @type {Array.<number>} */ + var fractionStack = [1, 0]; + + /** @type {Object.<string, boolean>} */ + var fractions = {}; + + var maxIterations = 1e5; + var geoM, m, fracA, fracB, fracM, key; + + while (--maxIterations > 0 && fractionStack.length > 0) { + // Pop the a coordinate off the stack + fracA = fractionStack.pop(); + geoA = geoStack.pop(); + a = stack.pop(); + // Add the a coordinate if it has not been added yet + key = fracA.toString(); + if (!(key in fractions)) { + flatCoordinates.push(a[0], a[1]); + fractions[key] = true; + } + // Pop the b coordinate off the stack + fracB = fractionStack.pop(); + geoB = geoStack.pop(); + b = stack.pop(); + // Find the m point between the a and b coordinates + fracM = (fracA + fracB) / 2; + geoM = interpolate(fracM); + m = transform(geoM); + if (ol.math.squaredSegmentDistance(m[0], m[1], a[0], a[1], + b[0], b[1]) < squaredTolerance) { + // If the m point is sufficiently close to the straight line, then we + // discard it. Just use the b coordinate and move on to the next line + // segment. + flatCoordinates.push(b[0], b[1]); + key = fracB.toString(); + ol.DEBUG && console.assert(!(key in fractions), + 'fractions object should contain key : ' + key); + fractions[key] = true; + } else { + // Otherwise, we need to subdivide the current line segment. Split it + // into two and push the two line segments onto the stack. + fractionStack.push(fracB, fracM, fracM, fracA); + stack.push(b, m, m, a); + geoStack.push(geoB, geoM, geoM, geoA); + } + } + ol.DEBUG && console.assert(maxIterations > 0, + 'maxIterations should be more than 0'); + + return flatCoordinates; +}; + + +/** +* Generate a great-circle arcs between two lat/lon points. +* @param {number} lon1 Longitude 1 in degrees. +* @param {number} lat1 Latitude 1 in degrees. +* @param {number} lon2 Longitude 2 in degrees. +* @param {number} lat2 Latitude 2 in degrees. + * @param {ol.proj.Projection} projection Projection. +* @param {number} squaredTolerance Squared tolerance. +* @return {Array.<number>} Flat coordinates. +*/ +ol.geom.flat.geodesic.greatCircleArc = function( + lon1, lat1, lon2, lat2, projection, squaredTolerance) { + + var geoProjection = ol.proj.get('EPSG:4326'); + + var cosLat1 = Math.cos(ol.math.toRadians(lat1)); + var sinLat1 = Math.sin(ol.math.toRadians(lat1)); + var cosLat2 = Math.cos(ol.math.toRadians(lat2)); + var sinLat2 = Math.sin(ol.math.toRadians(lat2)); + var cosDeltaLon = Math.cos(ol.math.toRadians(lon2 - lon1)); + var sinDeltaLon = Math.sin(ol.math.toRadians(lon2 - lon1)); + var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon; + + return ol.geom.flat.geodesic.line_( + /** + * @param {number} frac Fraction. + * @return {ol.Coordinate} Coordinate. + */ + function(frac) { + if (1 <= d) { + return [lon2, lat2]; + } + var D = frac * Math.acos(d); + var cosD = Math.cos(D); + var sinD = Math.sin(D); + var y = sinDeltaLon * cosLat2; + var x = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosDeltaLon; + var theta = Math.atan2(y, x); + var lat = Math.asin(sinLat1 * cosD + cosLat1 * sinD * Math.cos(theta)); + var lon = ol.math.toRadians(lon1) + + Math.atan2(Math.sin(theta) * sinD * cosLat1, + cosD - sinLat1 * Math.sin(lat)); + return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)]; + }, ol.proj.getTransform(geoProjection, projection), squaredTolerance); +}; + + +/** + * Generate a meridian (line at constant longitude). + * @param {number} lon Longitude. + * @param {number} lat1 Latitude 1. + * @param {number} lat2 Latitude 2. + * @param {ol.proj.Projection} projection Projection. + * @param {number} squaredTolerance Squared tolerance. + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.flat.geodesic.meridian = function(lon, lat1, lat2, projection, squaredTolerance) { + var epsg4326Projection = ol.proj.get('EPSG:4326'); + return ol.geom.flat.geodesic.line_( + /** + * @param {number} frac Fraction. + * @return {ol.Coordinate} Coordinate. + */ + function(frac) { + return [lon, lat1 + ((lat2 - lat1) * frac)]; + }, + ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance); +}; + + +/** + * Generate a parallel (line at constant latitude). + * @param {number} lat Latitude. + * @param {number} lon1 Longitude 1. + * @param {number} lon2 Longitude 2. + * @param {ol.proj.Projection} projection Projection. + * @param {number} squaredTolerance Squared tolerance. + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.flat.geodesic.parallel = function(lat, lon1, lon2, projection, squaredTolerance) { + var epsg4326Projection = ol.proj.get('EPSG:4326'); + return ol.geom.flat.geodesic.line_( + /** + * @param {number} frac Fraction. + * @return {ol.Coordinate} Coordinate. + */ + function(frac) { + return [lon1 + ((lon2 - lon1) * frac), lat]; + }, + ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance); +}; + +goog.provide('ol.Graticule'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.flat.geodesic'); +goog.require('ol.math'); +goog.require('ol.proj'); +goog.require('ol.render.Event'); +goog.require('ol.style.Stroke'); + + +/** + * Render a grid for a coordinate system on a map. + * @constructor + * @param {olx.GraticuleOptions=} opt_options Options. + * @api + */ +ol.Graticule = function(opt_options) { + + var options = opt_options || {}; + + /** + * @type {ol.Map} + * @private + */ + this.map_ = null; + + /** + * @type {ol.proj.Projection} + * @private + */ + this.projection_ = null; + + /** + * @type {number} + * @private + */ + this.maxLat_ = Infinity; + + /** + * @type {number} + * @private + */ + this.maxLon_ = Infinity; + + /** + * @type {number} + * @private + */ + this.minLat_ = -Infinity; + + /** + * @type {number} + * @private + */ + this.minLon_ = -Infinity; + + /** + * @type {number} + * @private + */ + this.maxLatP_ = Infinity; + + /** + * @type {number} + * @private + */ + this.maxLonP_ = Infinity; + + /** + * @type {number} + * @private + */ + this.minLatP_ = -Infinity; + + /** + * @type {number} + * @private + */ + this.minLonP_ = -Infinity; + + /** + * @type {number} + * @private + */ + this.targetSize_ = options.targetSize !== undefined ? + options.targetSize : 100; + + /** + * @type {number} + * @private + */ + this.maxLines_ = options.maxLines !== undefined ? options.maxLines : 100; + ol.DEBUG && console.assert(this.maxLines_ > 0, + 'this.maxLines_ should be more than 0'); + + /** + * @type {Array.<ol.geom.LineString>} + * @private + */ + this.meridians_ = []; + + /** + * @type {Array.<ol.geom.LineString>} + * @private + */ + this.parallels_ = []; + + /** + * @type {ol.style.Stroke} + * @private + */ + this.strokeStyle_ = options.strokeStyle !== undefined ? + options.strokeStyle : ol.Graticule.DEFAULT_STROKE_STYLE_; + + /** + * @type {ol.TransformFunction|undefined} + * @private + */ + this.fromLonLatTransform_ = undefined; + + /** + * @type {ol.TransformFunction|undefined} + * @private + */ + this.toLonLatTransform_ = undefined; + + /** + * @type {ol.Coordinate} + * @private + */ + this.projectionCenterLonLat_ = null; + + this.setMap(options.map !== undefined ? options.map : null); +}; + + +/** + * @type {ol.style.Stroke} + * @private + * @const + */ +ol.Graticule.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({ + color: 'rgba(0,0,0,0.2)' +}); + + +/** + * TODO can be configurable + * @type {Array.<number>} + * @private + */ +ol.Graticule.intervals_ = [90, 45, 30, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, + 0.01, 0.005, 0.002, 0.001]; + + +/** + * @param {number} lon Longitude. + * @param {number} minLat Minimal latitude. + * @param {number} maxLat Maximal latitude. + * @param {number} squaredTolerance Squared tolerance. + * @param {ol.Extent} extent Extent. + * @param {number} index Index. + * @return {number} Index. + * @private + */ +ol.Graticule.prototype.addMeridian_ = function(lon, minLat, maxLat, squaredTolerance, extent, index) { + var lineString = this.getMeridian_(lon, minLat, maxLat, + squaredTolerance, index); + if (ol.extent.intersects(lineString.getExtent(), extent)) { + this.meridians_[index++] = lineString; + } + return index; +}; + + +/** + * @param {number} lat Latitude. + * @param {number} minLon Minimal longitude. + * @param {number} maxLon Maximal longitude. + * @param {number} squaredTolerance Squared tolerance. + * @param {ol.Extent} extent Extent. + * @param {number} index Index. + * @return {number} Index. + * @private + */ +ol.Graticule.prototype.addParallel_ = function(lat, minLon, maxLon, squaredTolerance, extent, index) { + var lineString = this.getParallel_(lat, minLon, maxLon, squaredTolerance, + index); + if (ol.extent.intersects(lineString.getExtent(), extent)) { + this.parallels_[index++] = lineString; + } + return index; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} squaredTolerance Squared tolerance. + * @private + */ +ol.Graticule.prototype.createGraticule_ = function(extent, center, resolution, squaredTolerance) { + + var interval = this.getInterval_(resolution); + if (interval == -1) { + this.meridians_.length = this.parallels_.length = 0; + return; + } + + var centerLonLat = this.toLonLatTransform_(center); + var centerLon = centerLonLat[0]; + var centerLat = centerLonLat[1]; + var maxLines = this.maxLines_; + var cnt, idx, lat, lon; + + var validExtent = [ + Math.max(extent[0], this.minLonP_), + Math.max(extent[1], this.minLatP_), + Math.min(extent[2], this.maxLonP_), + Math.min(extent[3], this.maxLatP_) + ]; + + validExtent = ol.proj.transformExtent(validExtent, this.projection_, + 'EPSG:4326'); + var maxLat = validExtent[3]; + var maxLon = validExtent[2]; + var minLat = validExtent[1]; + var minLon = validExtent[0]; + + // Create meridians + + centerLon = Math.floor(centerLon / interval) * interval; + lon = ol.math.clamp(centerLon, this.minLon_, this.maxLon_); + + idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, 0); + + cnt = 0; + while (lon != this.minLon_ && cnt++ < maxLines) { + lon = Math.max(lon - interval, this.minLon_); + idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx); + } + + lon = ol.math.clamp(centerLon, this.minLon_, this.maxLon_); + + cnt = 0; + while (lon != this.maxLon_ && cnt++ < maxLines) { + lon = Math.min(lon + interval, this.maxLon_); + idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx); + } + + this.meridians_.length = idx; + + // Create parallels + + centerLat = Math.floor(centerLat / interval) * interval; + lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_); + + idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, 0); + + cnt = 0; + while (lat != this.minLat_ && cnt++ < maxLines) { + lat = Math.max(lat - interval, this.minLat_); + idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx); + } + + lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_); + + cnt = 0; + while (lat != this.maxLat_ && cnt++ < maxLines) { + lat = Math.min(lat + interval, this.maxLat_); + idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx); + } + + this.parallels_.length = idx; + +}; + + +/** + * @param {number} resolution Resolution. + * @return {number} The interval in degrees. + * @private + */ +ol.Graticule.prototype.getInterval_ = function(resolution) { + var centerLon = this.projectionCenterLonLat_[0]; + var centerLat = this.projectionCenterLonLat_[1]; + var interval = -1; + var i, ii, delta, dist; + var target = Math.pow(this.targetSize_ * resolution, 2); + /** @type {Array.<number>} **/ + var p1 = []; + /** @type {Array.<number>} **/ + var p2 = []; + for (i = 0, ii = ol.Graticule.intervals_.length; i < ii; ++i) { + delta = ol.Graticule.intervals_[i] / 2; + p1[0] = centerLon - delta; + p1[1] = centerLat - delta; + p2[0] = centerLon + delta; + p2[1] = centerLat + delta; + this.fromLonLatTransform_(p1, p1); + this.fromLonLatTransform_(p2, p2); + dist = Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2); + if (dist <= target) { + break; + } + interval = ol.Graticule.intervals_[i]; + } + return interval; +}; + + +/** + * Get the map associated with this graticule. + * @return {ol.Map} The map. + * @api + */ +ol.Graticule.prototype.getMap = function() { + return this.map_; +}; + + +/** + * @param {number} lon Longitude. + * @param {number} minLat Minimal latitude. + * @param {number} maxLat Maximal latitude. + * @param {number} squaredTolerance Squared tolerance. + * @return {ol.geom.LineString} The meridian line string. + * @param {number} index Index. + * @private + */ +ol.Graticule.prototype.getMeridian_ = function(lon, minLat, maxLat, + squaredTolerance, index) { + ol.DEBUG && console.assert(lon >= this.minLon_, + 'lon should be larger than or equal to this.minLon_'); + ol.DEBUG && console.assert(lon <= this.maxLon_, + 'lon should be smaller than or equal to this.maxLon_'); + var flatCoordinates = ol.geom.flat.geodesic.meridian(lon, + minLat, maxLat, this.projection_, squaredTolerance); + ol.DEBUG && console.assert(flatCoordinates.length > 0, + 'flatCoordinates cannot be empty'); + var lineString = this.meridians_[index] !== undefined ? + this.meridians_[index] : new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates); + return lineString; +}; + + +/** + * Get the list of meridians. Meridians are lines of equal longitude. + * @return {Array.<ol.geom.LineString>} The meridians. + * @api + */ +ol.Graticule.prototype.getMeridians = function() { + return this.meridians_; +}; + + +/** + * @param {number} lat Latitude. + * @param {number} minLon Minimal longitude. + * @param {number} maxLon Maximal longitude. + * @param {number} squaredTolerance Squared tolerance. + * @return {ol.geom.LineString} The parallel line string. + * @param {number} index Index. + * @private + */ +ol.Graticule.prototype.getParallel_ = function(lat, minLon, maxLon, + squaredTolerance, index) { + ol.DEBUG && console.assert(lat >= this.minLat_, + 'lat should be larger than or equal to this.minLat_'); + ol.DEBUG && console.assert(lat <= this.maxLat_, + 'lat should be smaller than or equal to this.maxLat_'); + var flatCoordinates = ol.geom.flat.geodesic.parallel(lat, + this.minLon_, this.maxLon_, this.projection_, squaredTolerance); + ol.DEBUG && console.assert(flatCoordinates.length > 0, + 'flatCoordinates cannot be empty'); + var lineString = this.parallels_[index] !== undefined ? + this.parallels_[index] : new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates); + return lineString; +}; + + +/** + * Get the list of parallels. Pallels are lines of equal latitude. + * @return {Array.<ol.geom.LineString>} The parallels. + * @api + */ +ol.Graticule.prototype.getParallels = function() { + return this.parallels_; +}; + + +/** + * @param {ol.render.Event} e Event. + * @private + */ +ol.Graticule.prototype.handlePostCompose_ = function(e) { + var vectorContext = e.vectorContext; + var frameState = e.frameState; + var extent = frameState.extent; + var viewState = frameState.viewState; + var center = viewState.center; + var projection = viewState.projection; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var squaredTolerance = + resolution * resolution / (4 * pixelRatio * pixelRatio); + + var updateProjectionInfo = !this.projection_ || + !ol.proj.equivalent(this.projection_, projection); + + if (updateProjectionInfo) { + this.updateProjectionInfo_(projection); + } + + //Fix the extent if wrapped. + //(note: this is the same extent as vectorContext.extent_) + var offsetX = 0; + if (projection.canWrapX()) { + var projectionExtent = projection.getExtent(); + var worldWidth = ol.extent.getWidth(projectionExtent); + var x = frameState.focus[0]; + if (x < projectionExtent[0] || x > projectionExtent[2]) { + var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth); + offsetX = worldWidth * worldsAway; + extent = [ + extent[0] + offsetX, extent[1], + extent[2] + offsetX, extent[3] + ]; + } + } + + this.createGraticule_(extent, center, resolution, squaredTolerance); + + // Draw the lines + vectorContext.setFillStrokeStyle(null, this.strokeStyle_); + var i, l, line; + for (i = 0, l = this.meridians_.length; i < l; ++i) { + line = this.meridians_[i]; + vectorContext.drawLineString(line, null); + } + for (i = 0, l = this.parallels_.length; i < l; ++i) { + line = this.parallels_[i]; + vectorContext.drawLineString(line, null); + } +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @private + */ +ol.Graticule.prototype.updateProjectionInfo_ = function(projection) { + var epsg4326Projection = ol.proj.get('EPSG:4326'); + + var extent = projection.getExtent(); + var worldExtent = projection.getWorldExtent(); + var worldExtentP = ol.proj.transformExtent(worldExtent, + epsg4326Projection, projection); + + var maxLat = worldExtent[3]; + var maxLon = worldExtent[2]; + var minLat = worldExtent[1]; + var minLon = worldExtent[0]; + + var maxLatP = worldExtentP[3]; + var maxLonP = worldExtentP[2]; + var minLatP = worldExtentP[1]; + var minLonP = worldExtentP[0]; + + ol.DEBUG && console.assert(maxLat !== undefined, 'maxLat should be defined'); + ol.DEBUG && console.assert(maxLon !== undefined, 'maxLon should be defined'); + ol.DEBUG && console.assert(minLat !== undefined, 'minLat should be defined'); + ol.DEBUG && console.assert(minLon !== undefined, 'minLon should be defined'); + + ol.DEBUG && console.assert(maxLatP !== undefined, + 'projected maxLat should be defined'); + ol.DEBUG && console.assert(maxLonP !== undefined, + 'projected maxLon should be defined'); + ol.DEBUG && console.assert(minLatP !== undefined, + 'projected minLat should be defined'); + ol.DEBUG && console.assert(minLonP !== undefined, + 'projected minLon should be defined'); + + this.maxLat_ = maxLat; + this.maxLon_ = maxLon; + this.minLat_ = minLat; + this.minLon_ = minLon; + + this.maxLatP_ = maxLatP; + this.maxLonP_ = maxLonP; + this.minLatP_ = minLatP; + this.minLonP_ = minLonP; + + + this.fromLonLatTransform_ = ol.proj.getTransform( + epsg4326Projection, projection); + + this.toLonLatTransform_ = ol.proj.getTransform( + projection, epsg4326Projection); + + this.projectionCenterLonLat_ = this.toLonLatTransform_( + ol.extent.getCenter(extent)); + + this.projection_ = projection; +}; + + +/** + * Set the map for this graticule. The graticule will be rendered on the + * provided map. + * @param {ol.Map} map Map. + * @api + */ +ol.Graticule.prototype.setMap = function(map) { + if (this.map_) { + this.map_.un(ol.render.Event.Type.POSTCOMPOSE, + this.handlePostCompose_, this); + this.map_.render(); + } + if (map) { + map.on(ol.render.Event.Type.POSTCOMPOSE, + this.handlePostCompose_, this); + map.render(); + } + this.map_ = map; +}; + +goog.provide('ol.ImageTile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Tile.State} state State. + * @param {string} src Image source URI. + * @param {?string} crossOrigin Cross origin. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. + */ +ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction) { + + ol.Tile.call(this, tileCoord, state); + + /** + * Image URI + * + * @private + * @type {string} + */ + this.src_ = src; + + /** + * @private + * @type {Image} + */ + this.image_ = new Image(); + if (crossOrigin !== null) { + this.image_.crossOrigin = crossOrigin; + } + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.imageListenerKeys_ = null; + + /** + * @private + * @type {ol.TileLoadFunctionType} + */ + this.tileLoadFunction_ = tileLoadFunction; + +}; +ol.inherits(ol.ImageTile, ol.Tile); + + +/** + * @inheritDoc + */ +ol.ImageTile.prototype.disposeInternal = function() { + if (this.state == ol.Tile.State.LOADING) { + this.unlistenImage_(); + } + if (this.interimTile) { + this.interimTile.dispose(); + } + this.state = ol.Tile.State.ABORT; + this.changed(); + ol.Tile.prototype.disposeInternal.call(this); +}; + + +/** + * Get the image element for this tile. + * @inheritDoc + * @api + */ +ol.ImageTile.prototype.getImage = function() { + return this.image_; +}; + + +/** + * @inheritDoc + */ +ol.ImageTile.prototype.getKey = function() { + return this.src_; +}; + + +/** + * Tracks loading or read errors. + * + * @private + */ +ol.ImageTile.prototype.handleImageError_ = function() { + this.state = ol.Tile.State.ERROR; + this.unlistenImage_(); + this.changed(); +}; + + +/** + * Tracks successful image load. + * + * @private + */ +ol.ImageTile.prototype.handleImageLoad_ = function() { + if (this.image_.naturalWidth && this.image_.naturalHeight) { + this.state = ol.Tile.State.LOADED; + } else { + this.state = ol.Tile.State.EMPTY; + } + this.unlistenImage_(); + this.changed(); +}; + + +/** + * Load the image or retry if loading previously failed. + * Loading is taken care of by the tile queue, and calling this method is + * only needed for preloading or for reloading in case of an error. + * @api + */ +ol.ImageTile.prototype.load = function() { + if (this.state == ol.Tile.State.IDLE || this.state == ol.Tile.State.ERROR) { + this.state = ol.Tile.State.LOADING; + this.changed(); + ol.DEBUG && console.assert(!this.imageListenerKeys_, + 'this.imageListenerKeys_ should be null'); + this.imageListenerKeys_ = [ + ol.events.listenOnce(this.image_, ol.events.EventType.ERROR, + this.handleImageError_, this), + ol.events.listenOnce(this.image_, ol.events.EventType.LOAD, + this.handleImageLoad_, this) + ]; + this.tileLoadFunction_(this, this.src_); + } +}; + + +/** + * Discards event handlers which listen for load completion or errors. + * + * @private + */ +ol.ImageTile.prototype.unlistenImage_ = function() { + this.imageListenerKeys_.forEach(ol.events.unlistenByKey); + this.imageListenerKeys_ = null; +}; + +// FIXME should handle all geo-referenced data, not just vector data + +goog.provide('ol.interaction.DragAndDrop'); + +goog.require('ol'); +goog.require('ol.functions'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.EventType'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.proj'); + + +/** + * @classdesc + * Handles input of vector data by drag and drop. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @fires ol.interaction.DragAndDrop.Event + * @param {olx.interaction.DragAndDropOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragAndDrop = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.DragAndDrop.handleEvent + }); + + /** + * @private + * @type {Array.<function(new: ol.format.Feature)>} + */ + this.formatConstructors_ = options.formatConstructors ? + options.formatConstructors : []; + + /** + * @private + * @type {ol.proj.Projection} + */ + this.projection_ = options.projection ? + ol.proj.get(options.projection) : null; + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.dropListenKeys_ = null; + + /** + * @private + * @type {Element} + */ + this.target = options.target ? options.target : null; + +}; +ol.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction); + + +/** + * @param {Event} event Event. + * @this {ol.interaction.DragAndDrop} + * @private + */ +ol.interaction.DragAndDrop.handleDrop_ = function(event) { + var files = event.dataTransfer.files; + var i, ii, file; + for (i = 0, ii = files.length; i < ii; ++i) { + file = files.item(i); + var reader = new FileReader(); + reader.addEventListener(ol.events.EventType.LOAD, + this.handleResult_.bind(this, file)); + reader.readAsText(file); + } +}; + + +/** + * @param {Event} event Event. + * @private + */ +ol.interaction.DragAndDrop.handleStop_ = function(event) { + event.stopPropagation(); + event.preventDefault(); + event.dataTransfer.dropEffect = 'copy'; +}; + + +/** + * @param {File} file File. + * @param {Event} event Load event. + * @private + */ +ol.interaction.DragAndDrop.prototype.handleResult_ = function(file, event) { + var result = event.target.result; + var map = this.getMap(); + var projection = this.projection_; + if (!projection) { + var view = map.getView(); + projection = view.getProjection(); + ol.DEBUG && console.assert(projection !== undefined, + 'projection should be defined'); + } + var formatConstructors = this.formatConstructors_; + var features = []; + var i, ii; + for (i = 0, ii = formatConstructors.length; i < ii; ++i) { + var formatConstructor = formatConstructors[i]; + var format = new formatConstructor(); + features = this.tryReadFeatures_(format, result, { + featureProjection: projection + }); + if (features && features.length > 0) { + break; + } + } + this.dispatchEvent( + new ol.interaction.DragAndDrop.Event( + ol.interaction.DragAndDrop.EventType.ADD_FEATURES, file, + features, projection)); +}; + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} unconditionally and + * neither prevents the browser default nor stops event propagation. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.DragAndDrop} + * @api + */ +ol.interaction.DragAndDrop.handleEvent = ol.functions.TRUE; + + +/** + * @inheritDoc + */ +ol.interaction.DragAndDrop.prototype.setMap = function(map) { + if (this.dropListenKeys_) { + this.dropListenKeys_.forEach(ol.events.unlistenByKey); + this.dropListenKeys_ = null; + } + ol.interaction.Interaction.prototype.setMap.call(this, map); + if (map) { + var dropArea = this.target ? this.target : map.getViewport(); + this.dropListenKeys_ = [ + ol.events.listen(dropArea, ol.events.EventType.DROP, + ol.interaction.DragAndDrop.handleDrop_, this), + ol.events.listen(dropArea, ol.events.EventType.DRAGENTER, + ol.interaction.DragAndDrop.handleStop_, this), + ol.events.listen(dropArea, ol.events.EventType.DRAGOVER, + ol.interaction.DragAndDrop.handleStop_, this), + ol.events.listen(dropArea, ol.events.EventType.DROP, + ol.interaction.DragAndDrop.handleStop_, this) + ]; + } +}; + + +/** + * @param {ol.format.Feature} format Format. + * @param {string} text Text. + * @param {olx.format.ReadOptions} options Read options. + * @private + * @return {Array.<ol.Feature>} Features. + */ +ol.interaction.DragAndDrop.prototype.tryReadFeatures_ = function(format, text, options) { + try { + return format.readFeatures(text, options); + } catch (e) { + return null; + } +}; + + +/** + * @enum {string} + */ +ol.interaction.DragAndDrop.EventType = { + /** + * Triggered when features are added + * @event ol.interaction.DragAndDrop.Event#addfeatures + * @api stable + */ + ADD_FEATURES: 'addfeatures' +}; + + +/** + * @classdesc + * Events emitted by {@link ol.interaction.DragAndDrop} instances are instances + * of this type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.interaction.DragAndDropEvent} + * @param {ol.interaction.DragAndDrop.EventType} type Type. + * @param {File} file File. + * @param {Array.<ol.Feature>=} opt_features Features. + * @param {ol.proj.Projection=} opt_projection Projection. + */ +ol.interaction.DragAndDrop.Event = function(type, file, opt_features, opt_projection) { + + ol.events.Event.call(this, type); + + /** + * The features parsed from dropped data. + * @type {Array.<ol.Feature>|undefined} + * @api stable + */ + this.features = opt_features; + + /** + * The dropped file. + * @type {File} + * @api stable + */ + this.file = file; + + /** + * The feature projection. + * @type {ol.proj.Projection|undefined} + * @api + */ + this.projection = opt_projection; + +}; +ol.inherits(ol.interaction.DragAndDrop.Event, ol.events.Event); + +goog.provide('ol.interaction.DragRotateAndZoom'); + +goog.require('ol'); +goog.require('ol.View'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); + + +/** + * @classdesc + * Allows the user to zoom and rotate the map by clicking and dragging + * on the map. By default, this interaction is limited to when the shift + * key is held down. + * + * This interaction is only supported for mouse devices. + * + * And this interaction is not included in the default interactions. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.DragRotateAndZoomOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragRotateAndZoom = function(opt_options) { + + var options = opt_options ? opt_options : {}; + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.DragRotateAndZoom.handleDownEvent_, + handleDragEvent: ol.interaction.DragRotateAndZoom.handleDragEvent_, + handleUpEvent: ol.interaction.DragRotateAndZoom.handleUpEvent_ + }); + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.shiftKeyOnly; + + /** + * @private + * @type {number|undefined} + */ + this.lastAngle_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.lastMagnitude_ = undefined; + + /** + * @private + * @type {number} + */ + this.lastScaleDelta_ = 0; + + /** + * @private + * @type {number} + */ + this.duration_ = options.duration !== undefined ? options.duration : 400; + +}; +ol.inherits(ol.interaction.DragRotateAndZoom, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.DragRotateAndZoom} + * @private + */ +ol.interaction.DragRotateAndZoom.handleDragEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return; + } + + var map = mapBrowserEvent.map; + var size = map.getSize(); + var offset = mapBrowserEvent.pixel; + var deltaX = offset[0] - size[0] / 2; + var deltaY = size[1] / 2 - offset[1]; + var theta = Math.atan2(deltaY, deltaX); + var magnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + var view = map.getView(); + if (this.lastAngle_ !== undefined) { + var angleDelta = theta - this.lastAngle_; + ol.interaction.Interaction.rotateWithoutConstraints( + map, view, view.getRotation() - angleDelta); + } + this.lastAngle_ = theta; + if (this.lastMagnitude_ !== undefined) { + var resolution = this.lastMagnitude_ * (view.getResolution() / magnitude); + ol.interaction.Interaction.zoomWithoutConstraints(map, view, resolution); + } + if (this.lastMagnitude_ !== undefined) { + this.lastScaleDelta_ = this.lastMagnitude_ / magnitude; + } + this.lastMagnitude_ = magnitude; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.DragRotateAndZoom} + * @private + */ +ol.interaction.DragRotateAndZoom.handleUpEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return true; + } + + var map = mapBrowserEvent.map; + var view = map.getView(); + view.setHint(ol.View.Hint.INTERACTING, -1); + var direction = this.lastScaleDelta_ - 1; + ol.interaction.Interaction.rotate(map, view, view.getRotation()); + ol.interaction.Interaction.zoom(map, view, view.getResolution(), + undefined, this.duration_, direction); + this.lastScaleDelta_ = 0; + return false; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.DragRotateAndZoom} + * @private + */ +ol.interaction.DragRotateAndZoom.handleDownEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return false; + } + + if (this.condition_(mapBrowserEvent)) { + mapBrowserEvent.map.getView().setHint(ol.View.Hint.INTERACTING, 1); + this.lastAngle_ = undefined; + this.lastMagnitude_ = undefined; + return true; + } else { + return false; + } +}; + +goog.provide('ol.loadingstrategy'); + + +/** + * Strategy function for loading all features with a single request. + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @return {Array.<ol.Extent>} Extents. + * @api + */ +ol.loadingstrategy.all = function(extent, resolution) { + return [[-Infinity, -Infinity, Infinity, Infinity]]; +}; + + +/** + * Strategy function for loading features based on the view's extent and + * resolution. + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @return {Array.<ol.Extent>} Extents. + * @api + */ +ol.loadingstrategy.bbox = function(extent, resolution) { + return [extent]; +}; + + +/** + * Creates a strategy function for loading features based on a tile grid. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @return {function(ol.Extent, number): Array.<ol.Extent>} Loading strategy. + * @api + */ +ol.loadingstrategy.tile = function(tileGrid) { + return ( + /** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @return {Array.<ol.Extent>} Extents. + */ + function(extent, resolution) { + var z = tileGrid.getZForResolution(resolution); + var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + /** @type {Array.<ol.Extent>} */ + var extents = []; + /** @type {ol.TileCoord} */ + var tileCoord = [z, 0, 0]; + for (tileCoord[1] = tileRange.minX; tileCoord[1] <= tileRange.maxX; + ++tileCoord[1]) { + for (tileCoord[2] = tileRange.minY; tileCoord[2] <= tileRange.maxY; + ++tileCoord[2]) { + extents.push(tileGrid.getTileCoordExtent(tileCoord)); + } + } + return extents; + }); +}; + +// FIXME bulk feature upload - suppress events +// FIXME make change-detection more refined (notably, geometry hint) + +goog.provide('ol.source.Vector'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.Object'); +goog.require('ol.array'); +goog.require('ol.asserts'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.featureloader'); +goog.require('ol.functions'); +goog.require('ol.loadingstrategy'); +goog.require('ol.obj'); +goog.require('ol.source.Source'); +goog.require('ol.source.State'); +goog.require('ol.structs.RBush'); + + +/** + * @classdesc + * Provides a source of features for vector layers. Vector features provided + * by this source are suitable for editing. See {@link ol.source.VectorTile} for + * vector data that is optimized for rendering. + * + * @constructor + * @extends {ol.source.Source} + * @fires ol.source.Vector.Event + * @param {olx.source.VectorOptions=} opt_options Vector source options. + * @api stable + */ +ol.source.Vector = function(opt_options) { + + var options = opt_options || {}; + + ol.source.Source.call(this, { + attributions: options.attributions, + logo: options.logo, + projection: undefined, + state: ol.source.State.READY, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + + /** + * @private + * @type {ol.FeatureLoader} + */ + this.loader_ = ol.nullFunction; + + /** + * @private + * @type {ol.format.Feature|undefined} + */ + this.format_ = options.format; + + /** + * @private + * @type {boolean} + */ + this.overlaps_ = options.overlaps == undefined ? true : options.overlaps; + + /** + * @private + * @type {string|ol.FeatureUrlFunction|undefined} + */ + this.url_ = options.url; + + if (options.loader !== undefined) { + this.loader_ = options.loader; + } else if (this.url_ !== undefined) { + ol.asserts.assert(this.format_, 7); // `format` must be set when `url` is set + // create a XHR feature loader for "url" and "format" + this.loader_ = ol.featureloader.xhr(this.url_, /** @type {ol.format.Feature} */ (this.format_)); + } + + /** + * @private + * @type {ol.LoadingStrategy} + */ + this.strategy_ = options.strategy !== undefined ? options.strategy : + ol.loadingstrategy.all; + + var useSpatialIndex = + options.useSpatialIndex !== undefined ? options.useSpatialIndex : true; + + /** + * @private + * @type {ol.structs.RBush.<ol.Feature>} + */ + this.featuresRtree_ = useSpatialIndex ? new ol.structs.RBush() : null; + + /** + * @private + * @type {ol.structs.RBush.<{extent: ol.Extent}>} + */ + this.loadedExtentsRtree_ = new ol.structs.RBush(); + + /** + * @private + * @type {Object.<string, ol.Feature>} + */ + this.nullGeometryFeatures_ = {}; + + /** + * A lookup of features by id (the return from feature.getId()). + * @private + * @type {Object.<string, ol.Feature>} + */ + this.idIndex_ = {}; + + /** + * A lookup of features without id (keyed by ol.getUid(feature)). + * @private + * @type {Object.<string, ol.Feature>} + */ + this.undefIdIndex_ = {}; + + /** + * @private + * @type {Object.<string, Array.<ol.EventsKey>>} + */ + this.featureChangeKeys_ = {}; + + /** + * @private + * @type {ol.Collection.<ol.Feature>} + */ + this.featuresCollection_ = null; + + var collection, features; + if (options.features instanceof ol.Collection) { + collection = options.features; + features = collection.getArray(); + } else if (Array.isArray(options.features)) { + features = options.features; + } + if (!useSpatialIndex && collection === undefined) { + collection = new ol.Collection(features); + } + if (features !== undefined) { + this.addFeaturesInternal(features); + } + if (collection !== undefined) { + this.bindFeaturesCollection_(collection); + } + +}; +ol.inherits(ol.source.Vector, ol.source.Source); + + +/** + * Add a single feature to the source. If you want to add a batch of features + * at once, call {@link ol.source.Vector#addFeatures source.addFeatures()} + * instead. + * @param {ol.Feature} feature Feature to add. + * @api stable + */ +ol.source.Vector.prototype.addFeature = function(feature) { + this.addFeatureInternal(feature); + this.changed(); +}; + + +/** + * Add a feature without firing a `change` event. + * @param {ol.Feature} feature Feature. + * @protected + */ +ol.source.Vector.prototype.addFeatureInternal = function(feature) { + var featureKey = ol.getUid(feature).toString(); + + if (!this.addToIndex_(featureKey, feature)) { + return; + } + + this.setupChangeEvents_(featureKey, feature); + + var geometry = feature.getGeometry(); + if (geometry) { + var extent = geometry.getExtent(); + if (this.featuresRtree_) { + this.featuresRtree_.insert(extent, feature); + } + } else { + this.nullGeometryFeatures_[featureKey] = feature; + } + + this.dispatchEvent( + new ol.source.Vector.Event(ol.source.Vector.EventType.ADDFEATURE, feature)); +}; + + +/** + * @param {string} featureKey Unique identifier for the feature. + * @param {ol.Feature} feature The feature. + * @private + */ +ol.source.Vector.prototype.setupChangeEvents_ = function(featureKey, feature) { + ol.DEBUG && console.assert(!(featureKey in this.featureChangeKeys_), + 'key (%s) not yet registered in featureChangeKey', featureKey); + this.featureChangeKeys_[featureKey] = [ + ol.events.listen(feature, ol.events.EventType.CHANGE, + this.handleFeatureChange_, this), + ol.events.listen(feature, ol.Object.EventType.PROPERTYCHANGE, + this.handleFeatureChange_, this) + ]; +}; + + +/** + * @param {string} featureKey Unique identifier for the feature. + * @param {ol.Feature} feature The feature. + * @return {boolean} The feature is "valid", in the sense that it is also a + * candidate for insertion into the Rtree. + * @private + */ +ol.source.Vector.prototype.addToIndex_ = function(featureKey, feature) { + var valid = true; + var id = feature.getId(); + if (id !== undefined) { + if (!(id.toString() in this.idIndex_)) { + this.idIndex_[id.toString()] = feature; + } else { + valid = false; + } + } else { + ol.asserts.assert(!(featureKey in this.undefIdIndex_), + 30); // The passed `feature` was already added to the source + this.undefIdIndex_[featureKey] = feature; + } + return valid; +}; + + +/** + * Add a batch of features to the source. + * @param {Array.<ol.Feature>} features Features to add. + * @api stable + */ +ol.source.Vector.prototype.addFeatures = function(features) { + this.addFeaturesInternal(features); + this.changed(); +}; + + +/** + * Add features without firing a `change` event. + * @param {Array.<ol.Feature>} features Features. + * @protected + */ +ol.source.Vector.prototype.addFeaturesInternal = function(features) { + var featureKey, i, length, feature; + + var extents = []; + var newFeatures = []; + var geometryFeatures = []; + + for (i = 0, length = features.length; i < length; i++) { + feature = features[i]; + featureKey = ol.getUid(feature).toString(); + if (this.addToIndex_(featureKey, feature)) { + newFeatures.push(feature); + } + } + + for (i = 0, length = newFeatures.length; i < length; i++) { + feature = newFeatures[i]; + featureKey = ol.getUid(feature).toString(); + this.setupChangeEvents_(featureKey, feature); + + var geometry = feature.getGeometry(); + if (geometry) { + var extent = geometry.getExtent(); + extents.push(extent); + geometryFeatures.push(feature); + } else { + this.nullGeometryFeatures_[featureKey] = feature; + } + } + if (this.featuresRtree_) { + this.featuresRtree_.load(extents, geometryFeatures); + } + + for (i = 0, length = newFeatures.length; i < length; i++) { + this.dispatchEvent(new ol.source.Vector.Event( + ol.source.Vector.EventType.ADDFEATURE, newFeatures[i])); + } +}; + + +/** + * @param {!ol.Collection.<ol.Feature>} collection Collection. + * @private + */ +ol.source.Vector.prototype.bindFeaturesCollection_ = function(collection) { + ol.DEBUG && console.assert(!this.featuresCollection_, + 'bindFeaturesCollection can only be called once'); + var modifyingCollection = false; + ol.events.listen(this, ol.source.Vector.EventType.ADDFEATURE, + function(evt) { + if (!modifyingCollection) { + modifyingCollection = true; + collection.push(evt.feature); + modifyingCollection = false; + } + }); + ol.events.listen(this, ol.source.Vector.EventType.REMOVEFEATURE, + function(evt) { + if (!modifyingCollection) { + modifyingCollection = true; + collection.remove(evt.feature); + modifyingCollection = false; + } + }); + ol.events.listen(collection, ol.Collection.EventType.ADD, + function(evt) { + if (!modifyingCollection) { + modifyingCollection = true; + this.addFeature(/** @type {ol.Feature} */ (evt.element)); + modifyingCollection = false; + } + }, this); + ol.events.listen(collection, ol.Collection.EventType.REMOVE, + function(evt) { + if (!modifyingCollection) { + modifyingCollection = true; + this.removeFeature(/** @type {ol.Feature} */ (evt.element)); + modifyingCollection = false; + } + }, this); + this.featuresCollection_ = collection; +}; + + +/** + * Remove all features from the source. + * @param {boolean=} opt_fast Skip dispatching of {@link removefeature} events. + * @api stable + */ +ol.source.Vector.prototype.clear = function(opt_fast) { + if (opt_fast) { + for (var featureId in this.featureChangeKeys_) { + var keys = this.featureChangeKeys_[featureId]; + keys.forEach(ol.events.unlistenByKey); + } + if (!this.featuresCollection_) { + this.featureChangeKeys_ = {}; + this.idIndex_ = {}; + this.undefIdIndex_ = {}; + } + } else { + if (this.featuresRtree_) { + this.featuresRtree_.forEach(this.removeFeatureInternal, this); + for (var id in this.nullGeometryFeatures_) { + this.removeFeatureInternal(this.nullGeometryFeatures_[id]); + } + } + } + if (this.featuresCollection_) { + this.featuresCollection_.clear(); + } + ol.DEBUG && console.assert(ol.obj.isEmpty(this.featureChangeKeys_), + 'featureChangeKeys is an empty object now'); + ol.DEBUG && console.assert(ol.obj.isEmpty(this.idIndex_), + 'idIndex is an empty object now'); + ol.DEBUG && console.assert(ol.obj.isEmpty(this.undefIdIndex_), + 'undefIdIndex is an empty object now'); + + if (this.featuresRtree_) { + this.featuresRtree_.clear(); + } + this.loadedExtentsRtree_.clear(); + this.nullGeometryFeatures_ = {}; + + var clearEvent = new ol.source.Vector.Event(ol.source.Vector.EventType.CLEAR); + this.dispatchEvent(clearEvent); + this.changed(); +}; + + +/** + * Iterate through all features on the source, calling the provided callback + * with each one. If the callback returns any "truthy" value, iteration will + * stop and the function will return the same value. + * + * @param {function(this: T, ol.Feature): S} callback Called with each feature + * on the source. Return a truthy value to stop iteration. + * @param {T=} opt_this The object to use as `this` in the callback. + * @return {S|undefined} The return value from the last call to the callback. + * @template T,S + * @api stable + */ +ol.source.Vector.prototype.forEachFeature = function(callback, opt_this) { + if (this.featuresRtree_) { + return this.featuresRtree_.forEach(callback, opt_this); + } else if (this.featuresCollection_) { + return this.featuresCollection_.forEach(callback, opt_this); + } +}; + + +/** + * Iterate through all features whose geometries contain the provided + * coordinate, calling the callback with each feature. If the callback returns + * a "truthy" value, iteration will stop and the function will return the same + * value. + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {function(this: T, ol.Feature): S} callback Called with each feature + * whose goemetry contains the provided coordinate. + * @param {T=} opt_this The object to use as `this` in the callback. + * @return {S|undefined} The return value from the last call to the callback. + * @template T,S + */ +ol.source.Vector.prototype.forEachFeatureAtCoordinateDirect = function(coordinate, callback, opt_this) { + var extent = [coordinate[0], coordinate[1], coordinate[0], coordinate[1]]; + return this.forEachFeatureInExtent(extent, function(feature) { + var geometry = feature.getGeometry(); + ol.DEBUG && console.assert(geometry, 'feature geometry is defined and not null'); + if (geometry.intersectsCoordinate(coordinate)) { + return callback.call(opt_this, feature); + } else { + return undefined; + } + }); +}; + + +/** + * Iterate through all features whose bounding box intersects the provided + * extent (note that the feature's geometry may not intersect the extent), + * calling the callback with each feature. If the callback returns a "truthy" + * value, iteration will stop and the function will return the same value. + * + * If you are interested in features whose geometry intersects an extent, call + * the {@link ol.source.Vector#forEachFeatureIntersectingExtent + * source.forEachFeatureIntersectingExtent()} method instead. + * + * When `useSpatialIndex` is set to false, this method will loop through all + * features, equivalent to {@link ol.source.Vector#forEachFeature}. + * + * @param {ol.Extent} extent Extent. + * @param {function(this: T, ol.Feature): S} callback Called with each feature + * whose bounding box intersects the provided extent. + * @param {T=} opt_this The object to use as `this` in the callback. + * @return {S|undefined} The return value from the last call to the callback. + * @template T,S + * @api + */ +ol.source.Vector.prototype.forEachFeatureInExtent = function(extent, callback, opt_this) { + if (this.featuresRtree_) { + return this.featuresRtree_.forEachInExtent(extent, callback, opt_this); + } else if (this.featuresCollection_) { + return this.featuresCollection_.forEach(callback, opt_this); + } +}; + + +/** + * Iterate through all features whose geometry intersects the provided extent, + * calling the callback with each feature. If the callback returns a "truthy" + * value, iteration will stop and the function will return the same value. + * + * If you only want to test for bounding box intersection, call the + * {@link ol.source.Vector#forEachFeatureInExtent + * source.forEachFeatureInExtent()} method instead. + * + * @param {ol.Extent} extent Extent. + * @param {function(this: T, ol.Feature): S} callback Called with each feature + * whose geometry intersects the provided extent. + * @param {T=} opt_this The object to use as `this` in the callback. + * @return {S|undefined} The return value from the last call to the callback. + * @template T,S + * @api + */ +ol.source.Vector.prototype.forEachFeatureIntersectingExtent = function(extent, callback, opt_this) { + return this.forEachFeatureInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + * @return {S|undefined} The return value from the last call to the callback. + * @template S + */ + function(feature) { + var geometry = feature.getGeometry(); + ol.DEBUG && console.assert(geometry, + 'feature geometry is defined and not null'); + if (geometry.intersectsExtent(extent)) { + var result = callback.call(opt_this, feature); + if (result) { + return result; + } + } + }); +}; + + +/** + * Get the features collection associated with this source. Will be `null` + * unless the source was configured with `useSpatialIndex` set to `false`, or + * with an {@link ol.Collection} as `features`. + * @return {ol.Collection.<ol.Feature>} The collection of features. + * @api + */ +ol.source.Vector.prototype.getFeaturesCollection = function() { + return this.featuresCollection_; +}; + + +/** + * Get all features on the source in random order. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.source.Vector.prototype.getFeatures = function() { + var features; + if (this.featuresCollection_) { + features = this.featuresCollection_.getArray(); + } else if (this.featuresRtree_) { + features = this.featuresRtree_.getAll(); + if (!ol.obj.isEmpty(this.nullGeometryFeatures_)) { + ol.array.extend( + features, ol.obj.getValues(this.nullGeometryFeatures_)); + } + } + return /** @type {Array.<ol.Feature>} */ (features); +}; + + +/** + * Get all features whose geometry intersects the provided coordinate. + * @param {ol.Coordinate} coordinate Coordinate. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) { + var features = []; + this.forEachFeatureAtCoordinateDirect(coordinate, function(feature) { + features.push(feature); + }); + return features; +}; + + +/** + * Get all features in the provided extent. Note that this returns an array of + * all features intersecting the given extent in random order (so it may include + * features whose geometries do not intersect the extent). + * + * This method is not available when the source is configured with + * `useSpatialIndex` set to `false`. + * @param {ol.Extent} extent Extent. + * @return {Array.<ol.Feature>} Features. + * @api + */ +ol.source.Vector.prototype.getFeaturesInExtent = function(extent) { + ol.DEBUG && console.assert(this.featuresRtree_, + 'getFeaturesInExtent does not work when useSpatialIndex is set to false'); + return this.featuresRtree_.getInExtent(extent); +}; + + +/** + * Get the closest feature to the provided coordinate. + * + * This method is not available when the source is configured with + * `useSpatialIndex` set to `false`. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {function(ol.Feature):boolean=} opt_filter Feature filter function. + * The filter function will receive one argument, the {@link ol.Feature feature} + * and it should return a boolean value. By default, no filtering is made. + * @return {ol.Feature} Closest feature. + * @api stable + */ +ol.source.Vector.prototype.getClosestFeatureToCoordinate = function(coordinate, opt_filter) { + // Find the closest feature using branch and bound. We start searching an + // infinite extent, and find the distance from the first feature found. This + // becomes the closest feature. We then compute a smaller extent which any + // closer feature must intersect. We continue searching with this smaller + // extent, trying to find a closer feature. Every time we find a closer + // feature, we update the extent being searched so that any even closer + // feature must intersect it. We continue until we run out of features. + var x = coordinate[0]; + var y = coordinate[1]; + var closestFeature = null; + var closestPoint = [NaN, NaN]; + var minSquaredDistance = Infinity; + var extent = [-Infinity, -Infinity, Infinity, Infinity]; + ol.DEBUG && console.assert(this.featuresRtree_, + 'getClosestFeatureToCoordinate does not work with useSpatialIndex set ' + + 'to false'); + var filter = opt_filter ? opt_filter : ol.functions.TRUE; + this.featuresRtree_.forEachInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + if (filter(feature)) { + var geometry = feature.getGeometry(); + ol.DEBUG && console.assert(geometry, + 'feature geometry is defined and not null'); + var previousMinSquaredDistance = minSquaredDistance; + minSquaredDistance = geometry.closestPointXY( + x, y, closestPoint, minSquaredDistance); + if (minSquaredDistance < previousMinSquaredDistance) { + closestFeature = feature; + // This is sneaky. Reduce the extent that it is currently being + // searched while the R-Tree traversal using this same extent object + // is still in progress. This is safe because the new extent is + // strictly contained by the old extent. + var minDistance = Math.sqrt(minSquaredDistance); + extent[0] = x - minDistance; + extent[1] = y - minDistance; + extent[2] = x + minDistance; + extent[3] = y + minDistance; + } + } + }); + return closestFeature; +}; + + +/** + * Get the extent of the features currently in the source. + * + * This method is not available when the source is configured with + * `useSpatialIndex` set to `false`. + * @return {!ol.Extent} Extent. + * @api stable + */ +ol.source.Vector.prototype.getExtent = function() { + ol.DEBUG && console.assert(this.featuresRtree_, + 'getExtent does not work when useSpatialIndex is set to false'); + return this.featuresRtree_.getExtent(); +}; + + +/** + * Get a feature by its identifier (the value returned by feature.getId()). + * Note that the index treats string and numeric identifiers as the same. So + * `source.getFeatureById(2)` will return a feature with id `'2'` or `2`. + * + * @param {string|number} id Feature identifier. + * @return {ol.Feature} The feature (or `null` if not found). + * @api stable + */ +ol.source.Vector.prototype.getFeatureById = function(id) { + var feature = this.idIndex_[id.toString()]; + return feature !== undefined ? feature : null; +}; + + +/** + * Get the format associated with this source. + * + * @return {ol.format.Feature|undefined} The feature format. + * @api + */ +ol.source.Vector.prototype.getFormat = function() { + return this.format_; +}; + + +/** + * @return {boolean} The source can have overlapping geometries. + */ +ol.source.Vector.prototype.getOverlaps = function() { + return this.overlaps_; +}; + + +/** + * Get the url associated with this source. + * + * @return {string|ol.FeatureUrlFunction|undefined} The url. + * @api + */ +ol.source.Vector.prototype.getUrl = function() { + return this.url_; +}; + + +/** + * @param {ol.events.Event} event Event. + * @private + */ +ol.source.Vector.prototype.handleFeatureChange_ = function(event) { + var feature = /** @type {ol.Feature} */ (event.target); + var featureKey = ol.getUid(feature).toString(); + var geometry = feature.getGeometry(); + if (!geometry) { + if (!(featureKey in this.nullGeometryFeatures_)) { + if (this.featuresRtree_) { + this.featuresRtree_.remove(feature); + } + this.nullGeometryFeatures_[featureKey] = feature; + } + } else { + var extent = geometry.getExtent(); + if (featureKey in this.nullGeometryFeatures_) { + delete this.nullGeometryFeatures_[featureKey]; + if (this.featuresRtree_) { + this.featuresRtree_.insert(extent, feature); + } + } else { + if (this.featuresRtree_) { + this.featuresRtree_.update(extent, feature); + } + } + } + var id = feature.getId(); + var removed; + if (id !== undefined) { + var sid = id.toString(); + if (featureKey in this.undefIdIndex_) { + delete this.undefIdIndex_[featureKey]; + this.idIndex_[sid] = feature; + } else { + if (this.idIndex_[sid] !== feature) { + removed = this.removeFromIdIndex_(feature); + ol.DEBUG && console.assert(removed, + 'Expected feature to be removed from index'); + this.idIndex_[sid] = feature; + } + } + } else { + if (!(featureKey in this.undefIdIndex_)) { + removed = this.removeFromIdIndex_(feature); + ol.DEBUG && console.assert(removed, + 'Expected feature to be removed from index'); + this.undefIdIndex_[featureKey] = feature; + } else { + ol.DEBUG && console.assert(this.undefIdIndex_[featureKey] === feature, + 'feature keyed under %s in undefIdKeys', featureKey); + } + } + this.changed(); + this.dispatchEvent(new ol.source.Vector.Event( + ol.source.Vector.EventType.CHANGEFEATURE, feature)); +}; + + +/** + * @return {boolean} Is empty. + */ +ol.source.Vector.prototype.isEmpty = function() { + return this.featuresRtree_.isEmpty() && + ol.obj.isEmpty(this.nullGeometryFeatures_); +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {ol.proj.Projection} projection Projection. + */ +ol.source.Vector.prototype.loadFeatures = function( + extent, resolution, projection) { + var loadedExtentsRtree = this.loadedExtentsRtree_; + var extentsToLoad = this.strategy_(extent, resolution); + var i, ii; + for (i = 0, ii = extentsToLoad.length; i < ii; ++i) { + var extentToLoad = extentsToLoad[i]; + var alreadyLoaded = loadedExtentsRtree.forEachInExtent(extentToLoad, + /** + * @param {{extent: ol.Extent}} object Object. + * @return {boolean} Contains. + */ + function(object) { + return ol.extent.containsExtent(object.extent, extentToLoad); + }); + if (!alreadyLoaded) { + this.loader_.call(this, extentToLoad, resolution, projection); + loadedExtentsRtree.insert(extentToLoad, {extent: extentToLoad.slice()}); + } + } +}; + + +/** + * Remove a single feature from the source. If you want to remove all features + * at once, use the {@link ol.source.Vector#clear source.clear()} method + * instead. + * @param {ol.Feature} feature Feature to remove. + * @api stable + */ +ol.source.Vector.prototype.removeFeature = function(feature) { + var featureKey = ol.getUid(feature).toString(); + if (featureKey in this.nullGeometryFeatures_) { + delete this.nullGeometryFeatures_[featureKey]; + } else { + if (this.featuresRtree_) { + this.featuresRtree_.remove(feature); + } + } + this.removeFeatureInternal(feature); + this.changed(); +}; + + +/** + * Remove feature without firing a `change` event. + * @param {ol.Feature} feature Feature. + * @protected + */ +ol.source.Vector.prototype.removeFeatureInternal = function(feature) { + var featureKey = ol.getUid(feature).toString(); + ol.DEBUG && console.assert(featureKey in this.featureChangeKeys_, + 'featureKey exists in featureChangeKeys'); + this.featureChangeKeys_[featureKey].forEach(ol.events.unlistenByKey); + delete this.featureChangeKeys_[featureKey]; + var id = feature.getId(); + if (id !== undefined) { + delete this.idIndex_[id.toString()]; + } else { + delete this.undefIdIndex_[featureKey]; + } + this.dispatchEvent(new ol.source.Vector.Event( + ol.source.Vector.EventType.REMOVEFEATURE, feature)); +}; + + +/** + * Remove a feature from the id index. Called internally when the feature id + * may have changed. + * @param {ol.Feature} feature The feature. + * @return {boolean} Removed the feature from the index. + * @private + */ +ol.source.Vector.prototype.removeFromIdIndex_ = function(feature) { + var removed = false; + for (var id in this.idIndex_) { + if (this.idIndex_[id] === feature) { + delete this.idIndex_[id]; + removed = true; + break; + } + } + return removed; +}; + + +/** + * @classdesc + * Events emitted by {@link ol.source.Vector} instances are instances of this + * type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.source.Vector.Event} + * @param {string} type Type. + * @param {ol.Feature=} opt_feature Feature. + */ +ol.source.Vector.Event = function(type, opt_feature) { + + ol.events.Event.call(this, type); + + /** + * The feature being added or removed. + * @type {ol.Feature|undefined} + * @api stable + */ + this.feature = opt_feature; + +}; +ol.inherits(ol.source.Vector.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.source.Vector.EventType = { + /** + * Triggered when a feature is added to the source. + * @event ol.source.Vector.Event#addfeature + * @api stable + */ + ADDFEATURE: 'addfeature', + + /** + * Triggered when a feature is updated. + * @event ol.source.Vector.Event#changefeature + * @api + */ + CHANGEFEATURE: 'changefeature', + + /** + * Triggered when the clear method is called on the source. + * @event ol.source.Vector.Event#clear + * @api + */ + CLEAR: 'clear', + + /** + * Triggered when a feature is removed from the source. + * See {@link ol.source.Vector#clear source.clear()} for exceptions. + * @event ol.source.Vector.Event#removefeature + * @api stable + */ + REMOVEFEATURE: 'removefeature' +}; + +goog.provide('ol.interaction.Draw'); + +goog.require('ol'); +goog.require('ol.events'); +goog.require('ol.extent'); +goog.require('ol.events.Event'); +goog.require('ol.Feature'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.Object'); +goog.require('ol.coordinate'); +goog.require('ol.functions'); +goog.require('ol.events.condition'); +goog.require('ol.geom.Circle'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * Interaction for drawing feature geometries. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @fires ol.interaction.Draw.Event + * @param {olx.interaction.DrawOptions} options Options. + * @api stable + */ +ol.interaction.Draw = function(options) { + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.Draw.handleDownEvent_, + handleEvent: ol.interaction.Draw.handleEvent, + handleUpEvent: ol.interaction.Draw.handleUpEvent_ + }); + + /** + * @type {ol.Pixel} + * @private + */ + this.downPx_ = null; + + /** + * @type {boolean} + * @private + */ + this.freehand_ = false; + + /** + * Target source for drawn features. + * @type {ol.source.Vector} + * @private + */ + this.source_ = options.source ? options.source : null; + + /** + * Target collection for drawn features. + * @type {ol.Collection.<ol.Feature>} + * @private + */ + this.features_ = options.features ? options.features : null; + + /** + * Pixel distance for snapping. + * @type {number} + * @private + */ + this.snapTolerance_ = options.snapTolerance ? options.snapTolerance : 12; + + /** + * Geometry type. + * @type {ol.geom.GeometryType} + * @private + */ + this.type_ = options.type; + + /** + * Drawing mode (derived from geometry type. + * @type {ol.interaction.Draw.Mode} + * @private + */ + this.mode_ = ol.interaction.Draw.getMode_(this.type_); + + /** + * The number of points that must be drawn before a polygon ring or line + * string can be finished. The default is 3 for polygon rings and 2 for + * line strings. + * @type {number} + * @private + */ + this.minPoints_ = options.minPoints ? + options.minPoints : + (this.mode_ === ol.interaction.Draw.Mode.POLYGON ? 3 : 2); + + /** + * The number of points that can be drawn before a polygon ring or line string + * is finished. The default is no restriction. + * @type {number} + * @private + */ + this.maxPoints_ = options.maxPoints ? options.maxPoints : Infinity; + + /** + * A function to decide if a potential finish coordinate is permissable + * @private + * @type {ol.EventsConditionType} + */ + this.finishCondition_ = options.finishCondition ? options.finishCondition : ol.functions.TRUE; + + var geometryFunction = options.geometryFunction; + if (!geometryFunction) { + if (this.type_ === ol.geom.GeometryType.CIRCLE) { + /** + * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates + * The coordinates. + * @param {ol.geom.SimpleGeometry=} opt_geometry Optional geometry. + * @return {ol.geom.SimpleGeometry} A geometry. + */ + geometryFunction = function(coordinates, opt_geometry) { + var circle = opt_geometry ? /** @type {ol.geom.Circle} */ (opt_geometry) : + new ol.geom.Circle([NaN, NaN]); + var squaredLength = ol.coordinate.squaredDistance( + coordinates[0], coordinates[1]); + circle.setCenterAndRadius(coordinates[0], Math.sqrt(squaredLength)); + return circle; + }; + } else { + var Constructor; + var mode = this.mode_; + if (mode === ol.interaction.Draw.Mode.POINT) { + Constructor = ol.geom.Point; + } else if (mode === ol.interaction.Draw.Mode.LINE_STRING) { + Constructor = ol.geom.LineString; + } else if (mode === ol.interaction.Draw.Mode.POLYGON) { + Constructor = ol.geom.Polygon; + } + /** + * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates + * The coordinates. + * @param {ol.geom.SimpleGeometry=} opt_geometry Optional geometry. + * @return {ol.geom.SimpleGeometry} A geometry. + */ + geometryFunction = function(coordinates, opt_geometry) { + var geometry = opt_geometry; + if (geometry) { + if (mode === ol.interaction.Draw.Mode.POLYGON) { + geometry.setCoordinates([coordinates[0].concat([coordinates[0][0]])]); + } else { + geometry.setCoordinates(coordinates); + } + } else { + geometry = new Constructor(coordinates); + } + return geometry; + }; + } + } + + /** + * @type {ol.DrawGeometryFunctionType} + * @private + */ + this.geometryFunction_ = geometryFunction; + + /** + * Finish coordinate for the feature (first point for polygons, last point for + * linestrings). + * @type {ol.Coordinate} + * @private + */ + this.finishCoordinate_ = null; + + /** + * Sketch feature. + * @type {ol.Feature} + * @private + */ + this.sketchFeature_ = null; + + /** + * Sketch point. + * @type {ol.Feature} + * @private + */ + this.sketchPoint_ = null; + + /** + * Sketch coordinates. Used when drawing a line or polygon. + * @type {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} + * @private + */ + this.sketchCoords_ = null; + + /** + * Sketch line. Used when drawing polygon. + * @type {ol.Feature} + * @private + */ + this.sketchLine_ = null; + + /** + * Sketch line coordinates. Used when drawing a polygon or circle. + * @type {Array.<ol.Coordinate>} + * @private + */ + this.sketchLineCoords_ = null; + + /** + * Squared tolerance for handling up events. If the squared distance + * between a down and up event is greater than this tolerance, up events + * will not be handled. + * @type {number} + * @private + */ + this.squaredClickTolerance_ = options.clickTolerance ? + options.clickTolerance * options.clickTolerance : 36; + + /** + * Draw overlay where our sketch features are drawn. + * @type {ol.layer.Vector} + * @private + */ + this.overlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: options.wrapX ? options.wrapX : false + }), + style: options.style ? options.style : + ol.interaction.Draw.getDefaultStyleFunction() + }); + + /** + * Name of the geometry attribute for newly created features. + * @type {string|undefined} + * @private + */ + this.geometryName_ = options.geometryName; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.noModifierKeys; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.freehandCondition_; + if (options.freehand) { + this.freehandCondition_ = ol.events.condition.always; + } else { + this.freehandCondition_ = options.freehandCondition ? + options.freehandCondition : ol.events.condition.shiftKeyOnly; + } + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.interaction.Interaction.Property.ACTIVE), + this.updateState_, this); + +}; +ol.inherits(ol.interaction.Draw, ol.interaction.Pointer); + + +/** + * @return {ol.StyleFunction} Styles. + */ +ol.interaction.Draw.getDefaultStyleFunction = function() { + var styles = ol.style.Style.createDefaultEditing(); + return function(feature, resolution) { + return styles[feature.getGeometry().getType()]; + }; +}; + + +/** + * @inheritDoc + */ +ol.interaction.Draw.prototype.setMap = function(map) { + ol.interaction.Pointer.prototype.setMap.call(this, map); + this.updateState_(); +}; + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} and may actually + * draw or finish the drawing. + * @param {ol.MapBrowserEvent} event Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.Draw} + * @api + */ +ol.interaction.Draw.handleEvent = function(event) { + this.freehand_ = this.mode_ !== ol.interaction.Draw.Mode.POINT && this.freehandCondition_(event); + var pass = !this.freehand_; + if (this.freehand_ && + event.type === ol.MapBrowserEvent.EventType.POINTERDRAG && this.sketchFeature_ !== null) { + this.addToDrawing_(event); + pass = false; + } else if (event.type === + ol.MapBrowserEvent.EventType.POINTERMOVE) { + pass = this.handlePointerMove_(event); + } else if (event.type === ol.MapBrowserEvent.EventType.DBLCLICK) { + pass = false; + } + return ol.interaction.Pointer.handleEvent.call(this, event) && pass; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} event Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.Draw} + * @private + */ +ol.interaction.Draw.handleDownEvent_ = function(event) { + if (this.freehand_) { + this.downPx_ = event.pixel; + if (!this.finishCoordinate_) { + this.startDrawing_(event); + } + return true; + } else if (this.condition_(event)) { + this.downPx_ = event.pixel; + return true; + } else { + return false; + } +}; + + +/** + * @param {ol.MapBrowserPointerEvent} event Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Draw} + * @private + */ +ol.interaction.Draw.handleUpEvent_ = function(event) { + var downPx = this.downPx_; + var clickPx = event.pixel; + var dx = downPx[0] - clickPx[0]; + var dy = downPx[1] - clickPx[1]; + var squaredDistance = dx * dx + dy * dy; + var pass = true; + var shouldHandle = this.freehand_ ? + squaredDistance > this.squaredClickTolerance_ : + squaredDistance <= this.squaredClickTolerance_; + var circleMode = this.mode_ === ol.interaction.Draw.Mode.CIRCLE; + if (shouldHandle) { + this.handlePointerMove_(event); + if (!this.finishCoordinate_) { + this.startDrawing_(event); + if (this.mode_ === ol.interaction.Draw.Mode.POINT) { + this.finishDrawing(); + } + } else if (this.freehand_ || circleMode) { + this.finishDrawing(); + } else if (this.atFinish_(event)) { + if (this.finishCondition_(event)) { + this.finishDrawing(); + } + } else { + this.addToDrawing_(event); + } + pass = false; + } else if (circleMode) { + this.finishCoordinate_ = null; + } + return pass; +}; + + +/** + * Handle move events. + * @param {ol.MapBrowserEvent} event A move event. + * @return {boolean} Pass the event to other interactions. + * @private + */ +ol.interaction.Draw.prototype.handlePointerMove_ = function(event) { + if (this.finishCoordinate_) { + this.modifyDrawing_(event); + } else { + this.createOrUpdateSketchPoint_(event); + } + return true; +}; + + +/** + * Determine if an event is within the snapping tolerance of the start coord. + * @param {ol.MapBrowserEvent} event Event. + * @return {boolean} The event is within the snapping tolerance of the start. + * @private + */ +ol.interaction.Draw.prototype.atFinish_ = function(event) { + var at = false; + if (this.sketchFeature_) { + var potentiallyDone = false; + var potentiallyFinishCoordinates = [this.finishCoordinate_]; + if (this.mode_ === ol.interaction.Draw.Mode.LINE_STRING) { + potentiallyDone = this.sketchCoords_.length > this.minPoints_; + } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) { + potentiallyDone = this.sketchCoords_[0].length > + this.minPoints_; + potentiallyFinishCoordinates = [this.sketchCoords_[0][0], + this.sketchCoords_[0][this.sketchCoords_[0].length - 2]]; + } + if (potentiallyDone) { + var map = event.map; + for (var i = 0, ii = potentiallyFinishCoordinates.length; i < ii; i++) { + var finishCoordinate = potentiallyFinishCoordinates[i]; + var finishPixel = map.getPixelFromCoordinate(finishCoordinate); + var pixel = event.pixel; + var dx = pixel[0] - finishPixel[0]; + var dy = pixel[1] - finishPixel[1]; + var snapTolerance = this.freehand_ ? 1 : this.snapTolerance_; + at = Math.sqrt(dx * dx + dy * dy) <= snapTolerance; + if (at) { + this.finishCoordinate_ = finishCoordinate; + break; + } + } + } + } + return at; +}; + + +/** + * @param {ol.MapBrowserEvent} event Event. + * @private + */ +ol.interaction.Draw.prototype.createOrUpdateSketchPoint_ = function(event) { + var coordinates = event.coordinate.slice(); + if (!this.sketchPoint_) { + this.sketchPoint_ = new ol.Feature(new ol.geom.Point(coordinates)); + this.updateSketchFeatures_(); + } else { + var sketchPointGeom = /** @type {ol.geom.Point} */ (this.sketchPoint_.getGeometry()); + sketchPointGeom.setCoordinates(coordinates); + } +}; + + +/** + * Start the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private + */ +ol.interaction.Draw.prototype.startDrawing_ = function(event) { + var start = event.coordinate; + this.finishCoordinate_ = start; + if (this.mode_ === ol.interaction.Draw.Mode.POINT) { + this.sketchCoords_ = start.slice(); + } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) { + this.sketchCoords_ = [[start.slice(), start.slice()]]; + this.sketchLineCoords_ = this.sketchCoords_[0]; + } else { + this.sketchCoords_ = [start.slice(), start.slice()]; + if (this.mode_ === ol.interaction.Draw.Mode.CIRCLE) { + this.sketchLineCoords_ = this.sketchCoords_; + } + } + if (this.sketchLineCoords_) { + this.sketchLine_ = new ol.Feature( + new ol.geom.LineString(this.sketchLineCoords_)); + } + var geometry = this.geometryFunction_(this.sketchCoords_); + ol.DEBUG && console.assert(geometry !== undefined, 'geometry should be defined'); + this.sketchFeature_ = new ol.Feature(); + if (this.geometryName_) { + this.sketchFeature_.setGeometryName(this.geometryName_); + } + this.sketchFeature_.setGeometry(geometry); + this.updateSketchFeatures_(); + this.dispatchEvent(new ol.interaction.Draw.Event( + ol.interaction.Draw.EventType.DRAWSTART, this.sketchFeature_)); +}; + + +/** + * Modify the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private + */ +ol.interaction.Draw.prototype.modifyDrawing_ = function(event) { + var coordinate = event.coordinate; + var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry()); + var coordinates, last; + if (this.mode_ === ol.interaction.Draw.Mode.POINT) { + last = this.sketchCoords_; + } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) { + coordinates = this.sketchCoords_[0]; + last = coordinates[coordinates.length - 1]; + if (this.atFinish_(event)) { + // snap to finish + coordinate = this.finishCoordinate_.slice(); + } + } else { + coordinates = this.sketchCoords_; + last = coordinates[coordinates.length - 1]; + } + last[0] = coordinate[0]; + last[1] = coordinate[1]; + ol.DEBUG && console.assert(this.sketchCoords_, 'sketchCoords_ expected'); + this.geometryFunction_( + /** @type {!ol.Coordinate|!Array.<ol.Coordinate>|!Array.<Array.<ol.Coordinate>>} */ (this.sketchCoords_), + geometry); + if (this.sketchPoint_) { + var sketchPointGeom = /** @type {ol.geom.Point} */ (this.sketchPoint_.getGeometry()); + sketchPointGeom.setCoordinates(coordinate); + } + var sketchLineGeom; + if (geometry instanceof ol.geom.Polygon && + this.mode_ !== ol.interaction.Draw.Mode.POLYGON) { + if (!this.sketchLine_) { + this.sketchLine_ = new ol.Feature(new ol.geom.LineString(null)); + } + var ring = geometry.getLinearRing(0); + sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry()); + sketchLineGeom.setFlatCoordinates( + ring.getLayout(), ring.getFlatCoordinates()); + } else if (this.sketchLineCoords_) { + sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry()); + sketchLineGeom.setCoordinates(this.sketchLineCoords_); + } + this.updateSketchFeatures_(); +}; + + +/** + * Add a new coordinate to the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private + */ +ol.interaction.Draw.prototype.addToDrawing_ = function(event) { + var coordinate = event.coordinate; + var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry()); + var done; + var coordinates; + if (this.mode_ === ol.interaction.Draw.Mode.LINE_STRING) { + this.finishCoordinate_ = coordinate.slice(); + coordinates = this.sketchCoords_; + if (coordinates.length >= this.maxPoints_) { + if (this.freehand_) { + coordinates.pop(); + } else { + done = true; + } + } + coordinates.push(coordinate.slice()); + this.geometryFunction_(coordinates, geometry); + } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) { + coordinates = this.sketchCoords_[0]; + if (coordinates.length >= this.maxPoints_) { + if (this.freehand_) { + coordinates.pop(); + } else { + done = true; + } + } + coordinates.push(coordinate.slice()); + if (done) { + this.finishCoordinate_ = coordinates[0]; + } + this.geometryFunction_(this.sketchCoords_, geometry); + } + this.updateSketchFeatures_(); + if (done) { + this.finishDrawing(); + } +}; + + +/** + * Remove last point of the feature currently being drawn. + * @api + */ +ol.interaction.Draw.prototype.removeLastPoint = function() { + var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry()); + var coordinates, sketchLineGeom; + if (this.mode_ === ol.interaction.Draw.Mode.LINE_STRING) { + coordinates = this.sketchCoords_; + coordinates.splice(-2, 1); + this.geometryFunction_(coordinates, geometry); + } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) { + coordinates = this.sketchCoords_[0]; + coordinates.splice(-2, 1); + sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry()); + sketchLineGeom.setCoordinates(coordinates); + this.geometryFunction_(this.sketchCoords_, geometry); + } + + if (coordinates.length === 0) { + this.finishCoordinate_ = null; + } + + this.updateSketchFeatures_(); +}; + + +/** + * Stop drawing and add the sketch feature to the target layer. + * The {@link ol.interaction.Draw.EventType.DRAWEND} event is dispatched before + * inserting the feature. + * @api + */ +ol.interaction.Draw.prototype.finishDrawing = function() { + var sketchFeature = this.abortDrawing_(); + var coordinates = this.sketchCoords_; + var geometry = /** @type {ol.geom.SimpleGeometry} */ (sketchFeature.getGeometry()); + if (this.mode_ === ol.interaction.Draw.Mode.LINE_STRING) { + // remove the redundant last point + coordinates.pop(); + this.geometryFunction_(coordinates, geometry); + } else if (this.mode_ === ol.interaction.Draw.Mode.POLYGON) { + // remove the redundant last point in ring + coordinates[0].pop(); + this.geometryFunction_(coordinates, geometry); + coordinates = geometry.getCoordinates(); + } + + // cast multi-part geometries + if (this.type_ === ol.geom.GeometryType.MULTI_POINT) { + sketchFeature.setGeometry(new ol.geom.MultiPoint([coordinates])); + } else if (this.type_ === ol.geom.GeometryType.MULTI_LINE_STRING) { + sketchFeature.setGeometry(new ol.geom.MultiLineString([coordinates])); + } else if (this.type_ === ol.geom.GeometryType.MULTI_POLYGON) { + sketchFeature.setGeometry(new ol.geom.MultiPolygon([coordinates])); + } + + // First dispatch event to allow full set up of feature + this.dispatchEvent(new ol.interaction.Draw.Event( + ol.interaction.Draw.EventType.DRAWEND, sketchFeature)); + + // Then insert feature + if (this.features_) { + this.features_.push(sketchFeature); + } + if (this.source_) { + this.source_.addFeature(sketchFeature); + } +}; + + +/** + * Stop drawing without adding the sketch feature to the target layer. + * @return {ol.Feature} The sketch feature (or null if none). + * @private + */ +ol.interaction.Draw.prototype.abortDrawing_ = function() { + this.finishCoordinate_ = null; + var sketchFeature = this.sketchFeature_; + if (sketchFeature) { + this.sketchFeature_ = null; + this.sketchPoint_ = null; + this.sketchLine_ = null; + this.overlay_.getSource().clear(true); + } + return sketchFeature; +}; + + +/** + * Extend an existing geometry by adding additional points. This only works + * on features with `LineString` geometries, where the interaction will + * extend lines by adding points to the end of the coordinates array. + * @param {!ol.Feature} feature Feature to be extended. + * @api + */ +ol.interaction.Draw.prototype.extend = function(feature) { + var geometry = feature.getGeometry(); + ol.DEBUG && console.assert(this.mode_ == ol.interaction.Draw.Mode.LINE_STRING, + 'interaction mode must be "line"'); + ol.DEBUG && console.assert(geometry.getType() == ol.geom.GeometryType.LINE_STRING, + 'feature geometry must be a line string'); + var lineString = /** @type {ol.geom.LineString} */ (geometry); + this.sketchFeature_ = feature; + this.sketchCoords_ = lineString.getCoordinates(); + var last = this.sketchCoords_[this.sketchCoords_.length - 1]; + this.finishCoordinate_ = last.slice(); + this.sketchCoords_.push(last.slice()); + this.updateSketchFeatures_(); + this.dispatchEvent(new ol.interaction.Draw.Event( + ol.interaction.Draw.EventType.DRAWSTART, this.sketchFeature_)); +}; + + +/** + * @inheritDoc + */ +ol.interaction.Draw.prototype.shouldStopEvent = ol.functions.FALSE; + + +/** + * Redraw the sketch features. + * @private + */ +ol.interaction.Draw.prototype.updateSketchFeatures_ = function() { + var sketchFeatures = []; + if (this.sketchFeature_) { + sketchFeatures.push(this.sketchFeature_); + } + if (this.sketchLine_) { + sketchFeatures.push(this.sketchLine_); + } + if (this.sketchPoint_) { + sketchFeatures.push(this.sketchPoint_); + } + var overlaySource = this.overlay_.getSource(); + overlaySource.clear(true); + overlaySource.addFeatures(sketchFeatures); +}; + + +/** + * @private + */ +ol.interaction.Draw.prototype.updateState_ = function() { + var map = this.getMap(); + var active = this.getActive(); + if (!map || !active) { + this.abortDrawing_(); + } + this.overlay_.setMap(active ? map : null); +}; + + +/** + * Create a `geometryFunction` for `type: 'Circle'` that will create a regular + * polygon with a user specified number of sides and start angle instead of an + * `ol.geom.Circle` geometry. + * @param {number=} opt_sides Number of sides of the regular polygon. Default is + * 32. + * @param {number=} opt_angle Angle of the first point in radians. 0 means East. + * Default is the angle defined by the heading from the center of the + * regular polygon to the current pointer position. + * @return {ol.DrawGeometryFunctionType} Function that draws a + * polygon. + * @api + */ +ol.interaction.Draw.createRegularPolygon = function(opt_sides, opt_angle) { + return ( + /** + * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates + * @param {ol.geom.SimpleGeometry=} opt_geometry + * @return {ol.geom.SimpleGeometry} + */ + function(coordinates, opt_geometry) { + var center = coordinates[0]; + var end = coordinates[1]; + var radius = Math.sqrt( + ol.coordinate.squaredDistance(center, end)); + var geometry = opt_geometry ? /** @type {ol.geom.Polygon} */ (opt_geometry) : + ol.geom.Polygon.fromCircle(new ol.geom.Circle(center), opt_sides); + var angle = opt_angle ? opt_angle : + Math.atan((end[1] - center[1]) / (end[0] - center[0])); + ol.geom.Polygon.makeRegular(geometry, center, radius, angle); + return geometry; + } + ); +}; + + +/** + * Create a `geometryFunction` that will create a box-shaped polygon (aligned + * with the coordinate system axes). Use this with the draw interaction and + * `type: 'Circle'` to return a box instead of a circle geometry. + * @return {ol.DrawGeometryFunctionType} Function that draws a box-shaped polygon. + * @api + */ +ol.interaction.Draw.createBox = function() { + return ( + /** + * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates + * @param {ol.geom.SimpleGeometry=} opt_geometry + * @return {ol.geom.SimpleGeometry} + */ + function(coordinates, opt_geometry) { + var extent = ol.extent.boundingExtent(coordinates); + var geometry = opt_geometry || new ol.geom.Polygon(null); + geometry.setCoordinates([[ + ol.extent.getBottomLeft(extent), + ol.extent.getBottomRight(extent), + ol.extent.getTopRight(extent), + ol.extent.getTopLeft(extent), + ol.extent.getBottomLeft(extent) + ]]); + return geometry; + } + ); +}; + + +/** + * Get the drawing mode. The mode for mult-part geometries is the same as for + * their single-part cousins. + * @param {ol.geom.GeometryType} type Geometry type. + * @return {ol.interaction.Draw.Mode} Drawing mode. + * @private + */ +ol.interaction.Draw.getMode_ = function(type) { + var mode; + if (type === ol.geom.GeometryType.POINT || + type === ol.geom.GeometryType.MULTI_POINT) { + mode = ol.interaction.Draw.Mode.POINT; + } else if (type === ol.geom.GeometryType.LINE_STRING || + type === ol.geom.GeometryType.MULTI_LINE_STRING) { + mode = ol.interaction.Draw.Mode.LINE_STRING; + } else if (type === ol.geom.GeometryType.POLYGON || + type === ol.geom.GeometryType.MULTI_POLYGON) { + mode = ol.interaction.Draw.Mode.POLYGON; + } else if (type === ol.geom.GeometryType.CIRCLE) { + mode = ol.interaction.Draw.Mode.CIRCLE; + } + return /** @type {!ol.interaction.Draw.Mode} */ (mode); +}; + + +/** + * Draw mode. This collapses multi-part geometry types with their single-part + * cousins. + * @enum {string} + */ +ol.interaction.Draw.Mode = { + POINT: 'Point', + LINE_STRING: 'LineString', + POLYGON: 'Polygon', + CIRCLE: 'Circle' +}; + +/** + * @classdesc + * Events emitted by {@link ol.interaction.Draw} instances are instances of + * this type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.DrawEvent} + * @param {ol.interaction.Draw.EventType} type Type. + * @param {ol.Feature} feature The feature drawn. + */ +ol.interaction.Draw.Event = function(type, feature) { + + ol.events.Event.call(this, type); + + /** + * The feature being drawn. + * @type {ol.Feature} + * @api stable + */ + this.feature = feature; + +}; +ol.inherits(ol.interaction.Draw.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.interaction.Draw.EventType = { + /** + * Triggered upon feature draw start + * @event ol.interaction.Draw.Event#drawstart + * @api stable + */ + DRAWSTART: 'drawstart', + /** + * Triggered upon feature draw end + * @event ol.interaction.Draw.Event#drawend + * @api stable + */ + DRAWEND: 'drawend' +}; + +goog.provide('ol.interaction.Extent'); + +goog.require('ol'); +goog.require('ol.Feature'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserPointerEvent'); +goog.require('ol.coordinate'); +goog.require('ol.events.Event'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * Allows the user to draw a vector box by clicking and dragging on the map. + * Once drawn, the vector box can be modified by dragging its vertices or edges. + * This interaction is only supported for mouse devices. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @fires ol.interaction.Extent.Event + * @param {olx.interaction.ExtentOptions=} opt_options Options. + * @api + */ +ol.interaction.Extent = function(opt_options) { + + /** + * Extent of the drawn box + * @type {ol.Extent} + * @private + */ + this.extent_ = null; + + /** + * Handler for pointer move events + * @type {function (ol.Coordinate): ol.Extent|null} + * @private + */ + this.pointerHandler_ = null; + + /** + * Pixel threshold to snap to extent + * @type {number} + * @private + */ + this.pixelTolerance_ = 10; + + /** + * Is the pointer snapped to an extent vertex + * @type {boolean} + * @private + */ + this.snappedToVertex_ = false; + + /** + * Feature for displaying the visible extent + * @type {ol.Feature} + * @private + */ + this.extentFeature_ = null; + + /** + * Feature for displaying the visible pointer + * @type {ol.Feature} + * @private + */ + this.vertexFeature_ = null; + + if (!opt_options) { + opt_options = {}; + } + + if (opt_options.extent) { + this.setExtent(opt_options.extent); + } + + /* Inherit ol.interaction.Pointer */ + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.Extent.handleDownEvent_, + handleDragEvent: ol.interaction.Extent.handleDragEvent_, + handleEvent: ol.interaction.Extent.handleEvent_, + handleUpEvent: ol.interaction.Extent.handleUpEvent_ + }); + + /** + * Layer for the extentFeature + * @type {ol.layer.Vector} + * @private + */ + this.extentOverlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: !!opt_options.wrapX + }), + style: opt_options.boxStyle ? opt_options.boxStyle : ol.interaction.Extent.getDefaultExtentStyleFunction_(), + updateWhileAnimating: true, + updateWhileInteracting: true + }); + + /** + * Layer for the vertexFeature + * @type {ol.layer.Vector} + * @private + */ + this.vertexOverlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: !!opt_options.wrapX + }), + style: opt_options.pointerStyle ? opt_options.pointerStyle : ol.interaction.Extent.getDefaultPointerStyleFunction_(), + updateWhileAnimating: true, + updateWhileInteracting: true + }); +}; + +ol.inherits(ol.interaction.Extent, ol.interaction.Pointer); + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent Event. + * @return {boolean} Propagate event? + * @this {ol.interaction.Extent} + * @private + */ +ol.interaction.Extent.handleEvent_ = function(mapBrowserEvent) { + if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) { + return true; + } + //display pointer (if not dragging) + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE && !this.handlingDownUpSequence) { + this.handlePointerMove_(mapBrowserEvent); + } + //call pointer to determine up/down/drag + ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent); + //return false to stop propagation + return false; +}; + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Event handled? + * @this {ol.interaction.Extent} + * @private + */ +ol.interaction.Extent.handleDownEvent_ = function(mapBrowserEvent) { + var pixel = mapBrowserEvent.pixel; + var map = mapBrowserEvent.map; + + var extent = this.getExtent(); + var vertex = this.snapToVertex_(pixel, map); + + //find the extent corner opposite the passed corner + var getOpposingPoint = function(point) { + var x_ = null; + var y_ = null; + if (point[0] == extent[0]) { + x_ = extent[2]; + } else if (point[0] == extent[2]) { + x_ = extent[0]; + } + if (point[1] == extent[1]) { + y_ = extent[3]; + } else if (point[1] == extent[3]) { + y_ = extent[1]; + } + if (x_ !== null && y_ !== null) { + return [x_, y_]; + } + return null; + }; + if (vertex && extent) { + var x = (vertex[0] == extent[0] || vertex[0] == extent[2]) ? vertex[0] : null; + var y = (vertex[1] == extent[1] || vertex[1] == extent[3]) ? vertex[1] : null; + + //snap to point + if (x !== null && y !== null) { + this.pointerHandler_ = ol.interaction.Extent.getPointHandler_(getOpposingPoint(vertex)); + //snap to edge + } else if (x !== null) { + this.pointerHandler_ = ol.interaction.Extent.getEdgeHandler_( + getOpposingPoint([x, extent[1]]), + getOpposingPoint([x, extent[3]]) + ); + } else if (y !== null) { + this.pointerHandler_ = ol.interaction.Extent.getEdgeHandler_( + getOpposingPoint([extent[0], y]), + getOpposingPoint([extent[2], y]) + ); + } + //no snap - new bbox + } else { + vertex = map.getCoordinateFromPixel(pixel); + this.setExtent([vertex[0], vertex[1], vertex[0], vertex[1]]); + this.pointerHandler_ = ol.interaction.Extent.getPointHandler_(vertex); + } + return true; //event handled; start downup sequence +}; + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Event handled? + * @this {ol.interaction.Extent} + * @private + */ +ol.interaction.Extent.handleDragEvent_ = function(mapBrowserEvent) { + if (this.pointerHandler_) { + var pixelCoordinate = mapBrowserEvent.coordinate; + this.setExtent(this.pointerHandler_(pixelCoordinate)); + this.createOrUpdatePointerFeature_(pixelCoordinate); + } + return true; +}; + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Extent} + * @private + */ +ol.interaction.Extent.handleUpEvent_ = function(mapBrowserEvent) { + this.pointerHandler_ = null; + //If bbox is zero area, set to null; + var extent = this.getExtent(); + if (!extent || ol.extent.getArea(extent) === 0) { + this.setExtent(null); + } + return false; //Stop handling downup sequence +}; + +/** + * Returns the default style for the drawn bbox + * + * @return {ol.StyleFunction} Default Extent style + * @private + */ +ol.interaction.Extent.getDefaultExtentStyleFunction_ = function() { + var style = ol.style.Style.createDefaultEditing(); + return function(feature, resolution) { + return style[ol.geom.GeometryType.POLYGON]; + }; +}; + +/** + * Returns the default style for the pointer + * + * @return {ol.StyleFunction} Default pointer style + * @private + */ +ol.interaction.Extent.getDefaultPointerStyleFunction_ = function() { + var style = ol.style.Style.createDefaultEditing(); + return function(feature, resolution) { + return style[ol.geom.GeometryType.POINT]; + }; +}; + +/** + * @param {ol.Coordinate} fixedPoint corner that will be unchanged in the new extent + * @returns {function (ol.Coordinate): ol.Extent} event handler + * @private + */ +ol.interaction.Extent.getPointHandler_ = function(fixedPoint) { + return function(point) { + return ol.extent.boundingExtent([fixedPoint, point]); + }; +}; + +/** + * @param {ol.Coordinate} fixedP1 first corner that will be unchanged in the new extent + * @param {ol.Coordinate} fixedP2 second corner that will be unchanged in the new extent + * @returns {function (ol.Coordinate): ol.Extent|null} event handler + * @private + */ +ol.interaction.Extent.getEdgeHandler_ = function(fixedP1, fixedP2) { + if (fixedP1[0] == fixedP2[0]) { + return function(point) { + return ol.extent.boundingExtent([fixedP1, [point[0], fixedP2[1]]]); + }; + } else if (fixedP1[1] == fixedP2[1]) { + return function(point) { + return ol.extent.boundingExtent([fixedP1, [fixedP2[0], point[1]]]); + }; + } else { + return null; + } +}; + +/** + * @param {ol.Extent} extent extent + * @returns {Array<Array<ol.Coordinate>>} extent line segments + * @private + */ +ol.interaction.Extent.getSegments_ = function(extent) { + return [ + [[extent[0], extent[1]], [extent[0], extent[3]]], + [[extent[0], extent[3]], [extent[2], extent[3]]], + [[extent[2], extent[3]], [extent[2], extent[1]]], + [[extent[2], extent[1]], [extent[0], extent[1]]] + ]; +}; + +/** + * @param {ol.Pixel} pixel cursor location + * @param {ol.Map} map map + * @returns {ol.Coordinate|null} snapped vertex on extent + * @private + */ +ol.interaction.Extent.prototype.snapToVertex_ = function(pixel, map) { + var pixelCoordinate = map.getCoordinateFromPixel(pixel); + var sortByDistance = function(a, b) { + return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a) - + ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b); + }; + var extent = this.getExtent(); + if (extent) { + //convert extents to line segments and find the segment closest to pixelCoordinate + var segments = ol.interaction.Extent.getSegments_(extent); + segments.sort(sortByDistance); + var closestSegment = segments[0]; + + var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate, + closestSegment)); + var vertexPixel = map.getPixelFromCoordinate(vertex); + + //if the distance is within tolerance, snap to the segment + if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <= + this.pixelTolerance_) { + + //test if we should further snap to a vertex + var pixel1 = map.getPixelFromCoordinate(closestSegment[0]); + var pixel2 = map.getPixelFromCoordinate(closestSegment[1]); + var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1); + var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2); + var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); + this.snappedToVertex_ = dist <= this.pixelTolerance_; + if (this.snappedToVertex_) { + vertex = squaredDist1 > squaredDist2 ? + closestSegment[1] : closestSegment[0]; + } + return vertex; + } + } + return null; +}; + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent pointer move event + * @private + */ +ol.interaction.Extent.prototype.handlePointerMove_ = function(mapBrowserEvent) { + var pixel = mapBrowserEvent.pixel; + var map = mapBrowserEvent.map; + + var vertex = this.snapToVertex_(pixel, map); + if (!vertex) { + vertex = map.getCoordinateFromPixel(pixel); + } + this.createOrUpdatePointerFeature_(vertex); +}; + +/** + * @param {ol.Extent} extent extent + * @returns {ol.Feature} extent as featrue + * @private + */ +ol.interaction.Extent.prototype.createOrUpdateExtentFeature_ = function(extent) { + var extentFeature = this.extentFeature_; + + if (!extentFeature) { + if (!extent) { + extentFeature = new ol.Feature({}); + } else { + extentFeature = new ol.Feature(ol.geom.Polygon.fromExtent(extent)); + } + this.extentFeature_ = extentFeature; + this.extentOverlay_.getSource().addFeature(extentFeature); + } else { + if (!extent) { + extentFeature.setGeometry(undefined); + } else { + extentFeature.setGeometry(ol.geom.Polygon.fromExtent(extent)); + } + } + return extentFeature; +}; + + +/** + * @param {ol.Coordinate} vertex location of feature + * @returns {ol.Feature} vertex as feature + * @private + */ +ol.interaction.Extent.prototype.createOrUpdatePointerFeature_ = function(vertex) { + var vertexFeature = this.vertexFeature_; + if (!vertexFeature) { + vertexFeature = new ol.Feature(new ol.geom.Point(vertex)); + this.vertexFeature_ = vertexFeature; + this.vertexOverlay_.getSource().addFeature(vertexFeature); + } else { + var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry()); + geometry.setCoordinates(vertex); + } + return vertexFeature; +}; + + +/** + * @inheritDoc + */ +ol.interaction.Extent.prototype.setMap = function(map) { + this.extentOverlay_.setMap(map); + this.vertexOverlay_.setMap(map); + ol.interaction.Pointer.prototype.setMap.call(this, map); +}; + +/** + * Returns the current drawn extent in the view projection + * + * @return {ol.Extent} Drawn extent in the view projection. + * @api + */ +ol.interaction.Extent.prototype.getExtent = function() { + return this.extent_; +}; + +/** + * Manually sets the drawn extent, using the view projection. + * + * @param {ol.Extent} extent Extent + * @api + */ +ol.interaction.Extent.prototype.setExtent = function(extent) { + //Null extent means no bbox + this.extent_ = extent ? extent : null; + this.createOrUpdateExtentFeature_(extent); + this.dispatchEvent(new ol.interaction.Extent.Event(this.extent_)); +}; + + +/** + * @classdesc + * Events emitted by {@link ol.interaction.Extent} instances are instances of + * this type. + * + * @constructor + * @param {ol.Extent} extent the new extent + * @extends {ol.events.Event} + */ +ol.interaction.Extent.Event = function(extent) { + ol.events.Event.call(this, ol.interaction.Extent.EventType.EXTENTCHANGED); + + /** + * The current extent. + * @type {ol.Extent} + * @api + */ + this.extent_ = extent; +}; +ol.inherits(ol.interaction.Extent.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.interaction.Extent.EventType = { + /** + * Triggered after the extent is changed + * @event ol.interaction.Extent.Event + * @api + */ + EXTENTCHANGED: 'extentchanged' +}; + +goog.provide('ol.interaction.Modify'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.Feature'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserPointerEvent'); +goog.require('ol.View'); +goog.require('ol.array'); +goog.require('ol.coordinate'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.EventType'); +goog.require('ol.events.condition'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.Point'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); +goog.require('ol.structs.RBush'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * Interaction for modifying feature geometries. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.ModifyOptions} options Options. + * @fires ol.interaction.Modify.Event + * @api + */ +ol.interaction.Modify = function(options) { + + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.Modify.handleDownEvent_, + handleDragEvent: ol.interaction.Modify.handleDragEvent_, + handleEvent: ol.interaction.Modify.handleEvent, + handleUpEvent: ol.interaction.Modify.handleUpEvent_ + }); + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.primaryAction; + + + /** + * @private + * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event. + * @return {boolean} Combined condition result. + */ + this.defaultDeleteCondition_ = function(mapBrowserEvent) { + return ol.events.condition.noModifierKeys(mapBrowserEvent) && + ol.events.condition.singleClick(mapBrowserEvent); + }; + + /** + * @type {ol.EventsConditionType} + * @private + */ + this.deleteCondition_ = options.deleteCondition ? + options.deleteCondition : this.defaultDeleteCondition_; + + /** + * Editing vertex. + * @type {ol.Feature} + * @private + */ + this.vertexFeature_ = null; + + /** + * Segments intersecting {@link this.vertexFeature_} by segment uid. + * @type {Object.<string, boolean>} + * @private + */ + this.vertexSegments_ = null; + + /** + * @type {ol.Pixel} + * @private + */ + this.lastPixel_ = [0, 0]; + + /** + * Tracks if the next `singleclick` event should be ignored to prevent + * accidental deletion right after vertex creation. + * @type {boolean} + * @private + */ + this.ignoreNextSingleClick_ = false; + + /** + * @type {boolean} + * @private + */ + this.modified_ = false; + + /** + * Segment RTree for each layer + * @type {ol.structs.RBush.<ol.ModifySegmentDataType>} + * @private + */ + this.rBush_ = new ol.structs.RBush(); + + /** + * @type {number} + * @private + */ + this.pixelTolerance_ = options.pixelTolerance !== undefined ? + options.pixelTolerance : 10; + + /** + * @type {boolean} + * @private + */ + this.snappedToVertex_ = false; + + /** + * Indicate whether the interaction is currently changing a feature's + * coordinates. + * @type {boolean} + * @private + */ + this.changingFeature_ = false; + + /** + * @type {Array} + * @private + */ + this.dragSegments_ = []; + + /** + * Draw overlay where sketch features are drawn. + * @type {ol.layer.Vector} + * @private + */ + this.overlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: !!options.wrapX + }), + style: options.style ? options.style : + ol.interaction.Modify.getDefaultStyleFunction(), + updateWhileAnimating: true, + updateWhileInteracting: true + }); + + /** + * @const + * @private + * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)>} + */ + this.SEGMENT_WRITERS_ = { + 'Point': this.writePointGeometry_, + 'LineString': this.writeLineStringGeometry_, + 'LinearRing': this.writeLineStringGeometry_, + 'Polygon': this.writePolygonGeometry_, + 'MultiPoint': this.writeMultiPointGeometry_, + 'MultiLineString': this.writeMultiLineStringGeometry_, + 'MultiPolygon': this.writeMultiPolygonGeometry_, + 'GeometryCollection': this.writeGeometryCollectionGeometry_ + }; + + /** + * @type {ol.Collection.<ol.Feature>} + * @private + */ + this.features_ = options.features; + + this.features_.forEach(this.addFeature_, this); + ol.events.listen(this.features_, ol.Collection.EventType.ADD, + this.handleFeatureAdd_, this); + ol.events.listen(this.features_, ol.Collection.EventType.REMOVE, + this.handleFeatureRemove_, this); + + /** + * @type {ol.MapBrowserPointerEvent} + * @private + */ + this.lastPointerEvent_ = null; + +}; +ol.inherits(ol.interaction.Modify, ol.interaction.Pointer); + + +/** + * @param {ol.Feature} feature Feature. + * @private + */ +ol.interaction.Modify.prototype.addFeature_ = function(feature) { + var geometry = feature.getGeometry(); + if (geometry && geometry.getType() in this.SEGMENT_WRITERS_) { + this.SEGMENT_WRITERS_[geometry.getType()].call(this, feature, geometry); + } + var map = this.getMap(); + if (map && map.isRendered()) { + this.handlePointerAtPixel_(this.lastPixel_, map); + } + ol.events.listen(feature, ol.events.EventType.CHANGE, + this.handleFeatureChange_, this); +}; + + +/** + * @param {ol.MapBrowserPointerEvent} evt Map browser event + * @private + */ +ol.interaction.Modify.prototype.willModifyFeatures_ = function(evt) { + if (!this.modified_) { + this.modified_ = true; + this.dispatchEvent(new ol.interaction.Modify.Event( + ol.interaction.Modify.EventType.MODIFYSTART, this.features_, evt)); + } +}; + + +/** + * @param {ol.Feature} feature Feature. + * @private + */ +ol.interaction.Modify.prototype.removeFeature_ = function(feature) { + this.removeFeatureSegmentData_(feature); + // Remove the vertex feature if the collection of canditate features + // is empty. + if (this.vertexFeature_ && this.features_.getLength() === 0) { + this.overlay_.getSource().removeFeature(this.vertexFeature_); + this.vertexFeature_ = null; + } + ol.events.unlisten(feature, ol.events.EventType.CHANGE, + this.handleFeatureChange_, this); +}; + + +/** + * @param {ol.Feature} feature Feature. + * @private + */ +ol.interaction.Modify.prototype.removeFeatureSegmentData_ = function(feature) { + var rBush = this.rBush_; + var /** @type {Array.<ol.ModifySegmentDataType>} */ nodesToRemove = []; + rBush.forEach( + /** + * @param {ol.ModifySegmentDataType} node RTree node. + */ + function(node) { + if (feature === node.feature) { + nodesToRemove.push(node); + } + }); + for (var i = nodesToRemove.length - 1; i >= 0; --i) { + rBush.remove(nodesToRemove[i]); + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.Modify.prototype.setActive = function(active) { + if (this.vertexFeature_ && !active) { + this.overlay_.getSource().removeFeature(this.vertexFeature_); + this.vertexFeature_ = null; + } + ol.interaction.Pointer.prototype.setActive.call(this, active); +}; + + +/** + * @inheritDoc + */ +ol.interaction.Modify.prototype.setMap = function(map) { + this.overlay_.setMap(map); + ol.interaction.Pointer.prototype.setMap.call(this, map); +}; + + +/** + * @param {ol.Collection.Event} evt Event. + * @private + */ +ol.interaction.Modify.prototype.handleFeatureAdd_ = function(evt) { + this.addFeature_(/** @type {ol.Feature} */ (evt.element)); +}; + + +/** + * @param {ol.events.Event} evt Event. + * @private + */ +ol.interaction.Modify.prototype.handleFeatureChange_ = function(evt) { + if (!this.changingFeature_) { + var feature = /** @type {ol.Feature} */ (evt.target); + this.removeFeature_(feature); + this.addFeature_(feature); + } +}; + + +/** + * @param {ol.Collection.Event} evt Event. + * @private + */ +ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) { + var feature = /** @type {ol.Feature} */ (evt.element); + this.removeFeature_(feature); +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.Point} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writePointGeometry_ = function(feature, geometry) { + var coordinates = geometry.getCoordinates(); + var segmentData = /** @type {ol.ModifySegmentDataType} */ ({ + feature: feature, + geometry: geometry, + segment: [coordinates, coordinates] + }); + this.rBush_.insert(geometry.getExtent(), segmentData); +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiPoint} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writeMultiPointGeometry_ = function(feature, geometry) { + var points = geometry.getCoordinates(); + var coordinates, i, ii, segmentData; + for (i = 0, ii = points.length; i < ii; ++i) { + coordinates = points[i]; + segmentData = /** @type {ol.ModifySegmentDataType} */ ({ + feature: feature, + geometry: geometry, + depth: [i], + index: i, + segment: [coordinates, coordinates] + }); + this.rBush_.insert(geometry.getExtent(), segmentData); + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.LineString} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writeLineStringGeometry_ = function(feature, geometry) { + var coordinates = geometry.getCoordinates(); + var i, ii, segment, segmentData; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.ModifySegmentDataType} */ ({ + feature: feature, + geometry: geometry, + index: i, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiLineString} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writeMultiLineStringGeometry_ = function(feature, geometry) { + var lines = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, segment, segmentData; + for (j = 0, jj = lines.length; j < jj; ++j) { + coordinates = lines[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.ModifySegmentDataType} */ ({ + feature: feature, + geometry: geometry, + depth: [j], + index: i, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.Polygon} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writePolygonGeometry_ = function(feature, geometry) { + var rings = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, segment, segmentData; + for (j = 0, jj = rings.length; j < jj; ++j) { + coordinates = rings[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.ModifySegmentDataType} */ ({ + feature: feature, + geometry: geometry, + depth: [j], + index: i, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiPolygon} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writeMultiPolygonGeometry_ = function(feature, geometry) { + var polygons = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData; + for (k = 0, kk = polygons.length; k < kk; ++k) { + rings = polygons[k]; + for (j = 0, jj = rings.length; j < jj; ++j) { + coordinates = rings[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.ModifySegmentDataType} */ ({ + feature: feature, + geometry: geometry, + depth: [j, k], + index: i, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.GeometryCollection} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writeGeometryCollectionGeometry_ = function(feature, geometry) { + var i, geometries = geometry.getGeometriesArray(); + for (i = 0; i < geometries.length; ++i) { + this.SEGMENT_WRITERS_[geometries[i].getType()].call( + this, feature, geometries[i]); + } +}; + + +/** + * @param {ol.Coordinate} coordinates Coordinates. + * @return {ol.Feature} Vertex feature. + * @private + */ +ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ = function(coordinates) { + var vertexFeature = this.vertexFeature_; + if (!vertexFeature) { + vertexFeature = new ol.Feature(new ol.geom.Point(coordinates)); + this.vertexFeature_ = vertexFeature; + this.overlay_.getSource().addFeature(vertexFeature); + } else { + var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry()); + geometry.setCoordinates(coordinates); + } + return vertexFeature; +}; + + +/** + * @param {ol.ModifySegmentDataType} a The first segment data. + * @param {ol.ModifySegmentDataType} b The second segment data. + * @return {number} The difference in indexes. + * @private + */ +ol.interaction.Modify.compareIndexes_ = function(a, b) { + return a.index - b.index; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} evt Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.Modify} + * @private + */ +ol.interaction.Modify.handleDownEvent_ = function(evt) { + if (!this.condition_(evt)) { + return false; + } + this.handlePointerAtPixel_(evt.pixel, evt.map); + this.dragSegments_.length = 0; + this.modified_ = false; + var vertexFeature = this.vertexFeature_; + if (vertexFeature) { + var insertVertices = []; + var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry()); + var vertex = geometry.getCoordinates(); + var vertexExtent = ol.extent.boundingExtent([vertex]); + var segmentDataMatches = this.rBush_.getInExtent(vertexExtent); + var componentSegments = {}; + segmentDataMatches.sort(ol.interaction.Modify.compareIndexes_); + for (var i = 0, ii = segmentDataMatches.length; i < ii; ++i) { + var segmentDataMatch = segmentDataMatches[i]; + var segment = segmentDataMatch.segment; + var uid = ol.getUid(segmentDataMatch.feature); + var depth = segmentDataMatch.depth; + if (depth) { + uid += '-' + depth.join('-'); // separate feature components + } + if (!componentSegments[uid]) { + componentSegments[uid] = new Array(2); + } + if (ol.coordinate.equals(segment[0], vertex) && + !componentSegments[uid][0]) { + this.dragSegments_.push([segmentDataMatch, 0]); + componentSegments[uid][0] = segmentDataMatch; + } else if (ol.coordinate.equals(segment[1], vertex) && + !componentSegments[uid][1]) { + + // prevent dragging closed linestrings by the connecting node + if ((segmentDataMatch.geometry.getType() === + ol.geom.GeometryType.LINE_STRING || + segmentDataMatch.geometry.getType() === + ol.geom.GeometryType.MULTI_LINE_STRING) && + componentSegments[uid][0] && + componentSegments[uid][0].index === 0) { + continue; + } + + this.dragSegments_.push([segmentDataMatch, 1]); + componentSegments[uid][1] = segmentDataMatch; + } else if (ol.getUid(segment) in this.vertexSegments_ && + (!componentSegments[uid][0] && !componentSegments[uid][1])) { + insertVertices.push([segmentDataMatch, vertex]); + } + } + if (insertVertices.length) { + this.willModifyFeatures_(evt); + } + for (var j = insertVertices.length - 1; j >= 0; --j) { + this.insertVertex_.apply(this, insertVertices[j]); + } + } + return !!this.vertexFeature_; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} evt Event. + * @this {ol.interaction.Modify} + * @private + */ +ol.interaction.Modify.handleDragEvent_ = function(evt) { + this.ignoreNextSingleClick_ = false; + this.willModifyFeatures_(evt); + + var vertex = evt.coordinate; + for (var i = 0, ii = this.dragSegments_.length; i < ii; ++i) { + var dragSegment = this.dragSegments_[i]; + var segmentData = dragSegment[0]; + var depth = segmentData.depth; + var geometry = segmentData.geometry; + var coordinates = geometry.getCoordinates(); + var segment = segmentData.segment; + var index = dragSegment[1]; + + while (vertex.length < geometry.getStride()) { + vertex.push(segment[index][vertex.length]); + } + + switch (geometry.getType()) { + case ol.geom.GeometryType.POINT: + coordinates = vertex; + segment[0] = segment[1] = vertex; + break; + case ol.geom.GeometryType.MULTI_POINT: + coordinates[segmentData.index] = vertex; + segment[0] = segment[1] = vertex; + break; + case ol.geom.GeometryType.LINE_STRING: + coordinates[segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case ol.geom.GeometryType.MULTI_LINE_STRING: + coordinates[depth[0]][segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case ol.geom.GeometryType.POLYGON: + coordinates[depth[0]][segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case ol.geom.GeometryType.MULTI_POLYGON: + coordinates[depth[1]][depth[0]][segmentData.index + index] = vertex; + segment[index] = vertex; + break; + default: + // pass + } + + this.setGeometryCoordinates_(geometry, coordinates); + } + this.createOrUpdateVertexFeature_(vertex); +}; + + +/** + * @param {ol.MapBrowserPointerEvent} evt Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Modify} + * @private + */ +ol.interaction.Modify.handleUpEvent_ = function(evt) { + var segmentData; + for (var i = this.dragSegments_.length - 1; i >= 0; --i) { + segmentData = this.dragSegments_[i][0]; + this.rBush_.update(ol.extent.boundingExtent(segmentData.segment), + segmentData); + } + if (this.modified_) { + this.dispatchEvent(new ol.interaction.Modify.Event( + ol.interaction.Modify.EventType.MODIFYEND, this.features_, evt)); + this.modified_ = false; + } + return false; +}; + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} and may modify the + * geometry. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.Modify} + * @api + */ +ol.interaction.Modify.handleEvent = function(mapBrowserEvent) { + if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) { + return true; + } + this.lastPointerEvent_ = mapBrowserEvent; + + var handled; + if (!mapBrowserEvent.map.getView().getHints()[ol.View.Hint.INTERACTING] && + mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE && + !this.handlingDownUpSequence) { + this.handlePointerMove_(mapBrowserEvent); + } + if (this.vertexFeature_ && this.deleteCondition_(mapBrowserEvent)) { + if (mapBrowserEvent.type != ol.MapBrowserEvent.EventType.SINGLECLICK || + !this.ignoreNextSingleClick_) { + handled = this.removePoint(); + } else { + handled = true; + } + } + + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK) { + this.ignoreNextSingleClick_ = false; + } + + return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) && + !handled; +}; + + +/** + * @param {ol.MapBrowserEvent} evt Event. + * @private + */ +ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) { + this.lastPixel_ = evt.pixel; + this.handlePointerAtPixel_(evt.pixel, evt.map); +}; + + +/** + * @param {ol.Pixel} pixel Pixel + * @param {ol.Map} map Map. + * @private + */ +ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) { + var pixelCoordinate = map.getCoordinateFromPixel(pixel); + var sortByDistance = function(a, b) { + return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a.segment) - + ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b.segment); + }; + + var box = ol.extent.buffer( + ol.extent.createOrUpdateFromCoordinate(pixelCoordinate), + map.getView().getResolution() * this.pixelTolerance_); + + var rBush = this.rBush_; + var nodes = rBush.getInExtent(box); + if (nodes.length > 0) { + nodes.sort(sortByDistance); + var node = nodes[0]; + var closestSegment = node.segment; + var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate, + closestSegment)); + var vertexPixel = map.getPixelFromCoordinate(vertex); + if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <= + this.pixelTolerance_) { + var pixel1 = map.getPixelFromCoordinate(closestSegment[0]); + var pixel2 = map.getPixelFromCoordinate(closestSegment[1]); + var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1); + var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2); + var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); + this.snappedToVertex_ = dist <= this.pixelTolerance_; + if (this.snappedToVertex_) { + vertex = squaredDist1 > squaredDist2 ? + closestSegment[1] : closestSegment[0]; + } + this.createOrUpdateVertexFeature_(vertex); + var vertexSegments = {}; + vertexSegments[ol.getUid(closestSegment)] = true; + var segment; + for (var i = 1, ii = nodes.length; i < ii; ++i) { + segment = nodes[i].segment; + if ((ol.coordinate.equals(closestSegment[0], segment[0]) && + ol.coordinate.equals(closestSegment[1], segment[1]) || + (ol.coordinate.equals(closestSegment[0], segment[1]) && + ol.coordinate.equals(closestSegment[1], segment[0])))) { + vertexSegments[ol.getUid(segment)] = true; + } else { + break; + } + } + this.vertexSegments_ = vertexSegments; + return; + } + } + if (this.vertexFeature_) { + this.overlay_.getSource().removeFeature(this.vertexFeature_); + this.vertexFeature_ = null; + } +}; + + +/** + * @param {ol.ModifySegmentDataType} segmentData Segment data. + * @param {ol.Coordinate} vertex Vertex. + * @private + */ +ol.interaction.Modify.prototype.insertVertex_ = function(segmentData, vertex) { + var segment = segmentData.segment; + var feature = segmentData.feature; + var geometry = segmentData.geometry; + var depth = segmentData.depth; + var index = /** @type {number} */ (segmentData.index); + var coordinates; + + while (vertex.length < geometry.getStride()) { + vertex.push(0); + } + + switch (geometry.getType()) { + case ol.geom.GeometryType.MULTI_LINE_STRING: + coordinates = geometry.getCoordinates(); + coordinates[depth[0]].splice(index + 1, 0, vertex); + break; + case ol.geom.GeometryType.POLYGON: + coordinates = geometry.getCoordinates(); + coordinates[depth[0]].splice(index + 1, 0, vertex); + break; + case ol.geom.GeometryType.MULTI_POLYGON: + coordinates = geometry.getCoordinates(); + coordinates[depth[1]][depth[0]].splice(index + 1, 0, vertex); + break; + case ol.geom.GeometryType.LINE_STRING: + coordinates = geometry.getCoordinates(); + coordinates.splice(index + 1, 0, vertex); + break; + default: + return; + } + + this.setGeometryCoordinates_(geometry, coordinates); + var rTree = this.rBush_; + rTree.remove(segmentData); + this.updateSegmentIndices_(geometry, index, depth, 1); + var newSegmentData = /** @type {ol.ModifySegmentDataType} */ ({ + segment: [segment[0], vertex], + feature: feature, + geometry: geometry, + depth: depth, + index: index + }); + rTree.insert(ol.extent.boundingExtent(newSegmentData.segment), + newSegmentData); + this.dragSegments_.push([newSegmentData, 1]); + + var newSegmentData2 = /** @type {ol.ModifySegmentDataType} */ ({ + segment: [vertex, segment[1]], + feature: feature, + geometry: geometry, + depth: depth, + index: index + 1 + }); + rTree.insert(ol.extent.boundingExtent(newSegmentData2.segment), + newSegmentData2); + this.dragSegments_.push([newSegmentData2, 0]); + this.ignoreNextSingleClick_ = true; +}; + +/** + * Removes the vertex currently being pointed. + * @return {boolean} True when a vertex was removed. + * @api + */ +ol.interaction.Modify.prototype.removePoint = function() { + if (this.lastPointerEvent_ && this.lastPointerEvent_.type != ol.MapBrowserEvent.EventType.POINTERDRAG) { + var evt = this.lastPointerEvent_; + this.willModifyFeatures_(evt); + this.removeVertex_(); + this.dispatchEvent(new ol.interaction.Modify.Event( + ol.interaction.Modify.EventType.MODIFYEND, this.features_, evt)); + this.modified_ = false; + return true; + } + return false; +}; + +/** + * Removes a vertex from all matching features. + * @return {boolean} True when a vertex was removed. + * @private + */ +ol.interaction.Modify.prototype.removeVertex_ = function() { + var dragSegments = this.dragSegments_; + var segmentsByFeature = {}; + var deleted = false; + var component, coordinates, dragSegment, geometry, i, index, left; + var newIndex, right, segmentData, uid; + for (i = dragSegments.length - 1; i >= 0; --i) { + dragSegment = dragSegments[i]; + segmentData = dragSegment[0]; + uid = ol.getUid(segmentData.feature); + if (segmentData.depth) { + // separate feature components + uid += '-' + segmentData.depth.join('-'); + } + if (!(uid in segmentsByFeature)) { + segmentsByFeature[uid] = {}; + } + if (dragSegment[1] === 0) { + segmentsByFeature[uid].right = segmentData; + segmentsByFeature[uid].index = segmentData.index; + } else if (dragSegment[1] == 1) { + segmentsByFeature[uid].left = segmentData; + segmentsByFeature[uid].index = segmentData.index + 1; + } + + } + for (uid in segmentsByFeature) { + right = segmentsByFeature[uid].right; + left = segmentsByFeature[uid].left; + index = segmentsByFeature[uid].index; + newIndex = index - 1; + if (left !== undefined) { + segmentData = left; + } else { + segmentData = right; + } + if (newIndex < 0) { + newIndex = 0; + } + geometry = segmentData.geometry; + coordinates = geometry.getCoordinates(); + component = coordinates; + deleted = false; + switch (geometry.getType()) { + case ol.geom.GeometryType.MULTI_LINE_STRING: + if (coordinates[segmentData.depth[0]].length > 2) { + coordinates[segmentData.depth[0]].splice(index, 1); + deleted = true; + } + break; + case ol.geom.GeometryType.LINE_STRING: + if (coordinates.length > 2) { + coordinates.splice(index, 1); + deleted = true; + } + break; + case ol.geom.GeometryType.MULTI_POLYGON: + component = component[segmentData.depth[1]]; + /* falls through */ + case ol.geom.GeometryType.POLYGON: + component = component[segmentData.depth[0]]; + if (component.length > 4) { + if (index == component.length - 1) { + index = 0; + } + component.splice(index, 1); + deleted = true; + if (index === 0) { + // close the ring again + component.pop(); + component.push(component[0]); + newIndex = component.length - 1; + } + } + break; + default: + // pass + } + + if (deleted) { + this.setGeometryCoordinates_(geometry, coordinates); + var segments = []; + if (left !== undefined) { + this.rBush_.remove(left); + segments.push(left.segment[0]); + } + if (right !== undefined) { + this.rBush_.remove(right); + segments.push(right.segment[1]); + } + if (left !== undefined && right !== undefined) { + ol.DEBUG && console.assert(newIndex >= 0, 'newIndex should be larger than 0'); + + var newSegmentData = /** @type {ol.ModifySegmentDataType} */ ({ + depth: segmentData.depth, + feature: segmentData.feature, + geometry: segmentData.geometry, + index: newIndex, + segment: segments + }); + this.rBush_.insert(ol.extent.boundingExtent(newSegmentData.segment), + newSegmentData); + } + this.updateSegmentIndices_(geometry, index, segmentData.depth, -1); + if (this.vertexFeature_) { + this.overlay_.getSource().removeFeature(this.vertexFeature_); + this.vertexFeature_ = null; + } + } + + } + return deleted; +}; + + +/** + * @param {ol.geom.SimpleGeometry} geometry Geometry. + * @param {Array} coordinates Coordinates. + * @private + */ +ol.interaction.Modify.prototype.setGeometryCoordinates_ = function(geometry, coordinates) { + this.changingFeature_ = true; + geometry.setCoordinates(coordinates); + this.changingFeature_ = false; +}; + + +/** + * @param {ol.geom.SimpleGeometry} geometry Geometry. + * @param {number} index Index. + * @param {Array.<number>|undefined} depth Depth. + * @param {number} delta Delta (1 or -1). + * @private + */ +ol.interaction.Modify.prototype.updateSegmentIndices_ = function( + geometry, index, depth, delta) { + this.rBush_.forEachInExtent(geometry.getExtent(), function(segmentDataMatch) { + if (segmentDataMatch.geometry === geometry && + (depth === undefined || segmentDataMatch.depth === undefined || + ol.array.equals(segmentDataMatch.depth, depth)) && + segmentDataMatch.index > index) { + segmentDataMatch.index += delta; + } + }); +}; + + +/** + * @return {ol.StyleFunction} Styles. + */ +ol.interaction.Modify.getDefaultStyleFunction = function() { + var style = ol.style.Style.createDefaultEditing(); + return function(feature, resolution) { + return style[ol.geom.GeometryType.POINT]; + }; +}; + + +/** + * @classdesc + * Events emitted by {@link ol.interaction.Modify} instances are instances of + * this type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.ModifyEvent} + * @param {ol.interaction.Modify.EventType} type Type. + * @param {ol.Collection.<ol.Feature>} features The features modified. + * @param {ol.MapBrowserPointerEvent} mapBrowserPointerEvent Associated + * {@link ol.MapBrowserPointerEvent}. + */ +ol.interaction.Modify.Event = function(type, features, mapBrowserPointerEvent) { + + ol.events.Event.call(this, type); + + /** + * The features being modified. + * @type {ol.Collection.<ol.Feature>} + * @api + */ + this.features = features; + + /** + * Associated {@link ol.MapBrowserEvent}. + * @type {ol.MapBrowserEvent} + * @api + */ + this.mapBrowserEvent = mapBrowserPointerEvent; +}; +ol.inherits(ol.interaction.Modify.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.interaction.Modify.EventType = { + /** + * Triggered upon feature modification start + * @event ol.interaction.Modify.Event#modifystart + * @api + */ + MODIFYSTART: 'modifystart', + /** + * Triggered upon feature modification end + * @event ol.interaction.Modify.Event#modifyend + * @api + */ + MODIFYEND: 'modifyend' +}; + +goog.provide('ol.interaction.Select'); + +goog.require('ol'); +goog.require('ol.functions'); +goog.require('ol.Collection'); +goog.require('ol.array'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.condition'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.layer.Vector'); +goog.require('ol.obj'); +goog.require('ol.source.Vector'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * Interaction for selecting vector features. By default, selected features are + * styled differently, so this interaction can be used for visual highlighting, + * as well as selecting features for other actions, such as modification or + * output. There are three ways of controlling which features are selected: + * using the browser event as defined by the `condition` and optionally the + * `toggle`, `add`/`remove`, and `multi` options; a `layers` filter; and a + * further feature filter using the `filter` option. + * + * Selected features are added to an internal unmanaged layer. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.SelectOptions=} opt_options Options. + * @fires ol.interaction.Select.Event + * @api stable + */ +ol.interaction.Select = function(opt_options) { + + ol.interaction.Interaction.call(this, { + handleEvent: ol.interaction.Select.handleEvent + }); + + var options = opt_options ? opt_options : {}; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.condition_ = options.condition ? + options.condition : ol.events.condition.singleClick; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.addCondition_ = options.addCondition ? + options.addCondition : ol.events.condition.never; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.removeCondition_ = options.removeCondition ? + options.removeCondition : ol.events.condition.never; + + /** + * @private + * @type {ol.EventsConditionType} + */ + this.toggleCondition_ = options.toggleCondition ? + options.toggleCondition : ol.events.condition.shiftKeyOnly; + + /** + * @private + * @type {boolean} + */ + this.multi_ = options.multi ? options.multi : false; + + /** + * @private + * @type {ol.SelectFilterFunction} + */ + this.filter_ = options.filter ? options.filter : + ol.functions.TRUE; + + /** + * @private + * @type {number} + */ + this.hitTolerance_ = options.hitTolerance ? options.hitTolerance : 0; + + var featureOverlay = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + features: options.features, + wrapX: options.wrapX + }), + style: options.style ? options.style : + ol.interaction.Select.getDefaultStyleFunction(), + updateWhileAnimating: true, + updateWhileInteracting: true + }); + + /** + * @private + * @type {ol.layer.Vector} + */ + this.featureOverlay_ = featureOverlay; + + /** @type {function(ol.layer.Layer): boolean} */ + var layerFilter; + if (options.layers) { + if (typeof options.layers === 'function') { + layerFilter = options.layers; + } else { + var layers = options.layers; + layerFilter = function(layer) { + return ol.array.includes(layers, layer); + }; + } + } else { + layerFilter = ol.functions.TRUE; + } + + /** + * @private + * @type {function(ol.layer.Layer): boolean} + */ + this.layerFilter_ = layerFilter; + + /** + * An association between selected feature (key) + * and layer (value) + * @private + * @type {Object.<number, ol.layer.Layer>} + */ + this.featureLayerAssociation_ = {}; + + var features = this.featureOverlay_.getSource().getFeaturesCollection(); + ol.events.listen(features, ol.Collection.EventType.ADD, + this.addFeature_, this); + ol.events.listen(features, ol.Collection.EventType.REMOVE, + this.removeFeature_, this); + +}; +ol.inherits(ol.interaction.Select, ol.interaction.Interaction); + + +/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.layer.Layer} layer Layer. + * @private + */ +ol.interaction.Select.prototype.addFeatureLayerAssociation_ = function(feature, layer) { + var key = ol.getUid(feature); + this.featureLayerAssociation_[key] = layer; +}; + + +/** + * Get the selected features. + * @return {ol.Collection.<ol.Feature>} Features collection. + * @api stable + */ +ol.interaction.Select.prototype.getFeatures = function() { + return this.featureOverlay_.getSource().getFeaturesCollection(); +}; + + +/** + * Returns the Hit-detection tolerance. + * @returns {number} Hit tolerance in pixels. + * @api + */ +ol.interaction.Select.prototype.getHitTolerance = function() { + return this.hitTolerance_; +}; + + +/** + * Returns the associated {@link ol.layer.Vector vectorlayer} of + * the (last) selected feature. Note that this will not work with any + * programmatic method like pushing features to + * {@link ol.interaction.Select#getFeatures collection}. + * @param {ol.Feature|ol.render.Feature} feature Feature + * @return {ol.layer.Vector} Layer. + * @api + */ +ol.interaction.Select.prototype.getLayer = function(feature) { + var key = ol.getUid(feature); + return /** @type {ol.layer.Vector} */ (this.featureLayerAssociation_[key]); +}; + + +/** + * Handles the {@link ol.MapBrowserEvent map browser event} and may change the + * selected state of features. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.Select} + * @api + */ +ol.interaction.Select.handleEvent = function(mapBrowserEvent) { + if (!this.condition_(mapBrowserEvent)) { + return true; + } + var add = this.addCondition_(mapBrowserEvent); + var remove = this.removeCondition_(mapBrowserEvent); + var toggle = this.toggleCondition_(mapBrowserEvent); + var set = !add && !remove && !toggle; + var map = mapBrowserEvent.map; + var features = this.featureOverlay_.getSource().getFeaturesCollection(); + var deselected = []; + var selected = []; + if (set) { + // Replace the currently selected feature(s) with the feature(s) at the + // pixel, or clear the selected feature(s) if there is no feature at + // the pixel. + ol.obj.clear(this.featureLayerAssociation_); + map.forEachFeatureAtPixel(mapBrowserEvent.pixel, + (/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.layer.Layer} layer Layer. + * @return {boolean|undefined} Continue to iterate over the features. + */ + function(feature, layer) { + if (this.filter_(feature, layer)) { + selected.push(feature); + this.addFeatureLayerAssociation_(feature, layer); + return !this.multi_; + } + }).bind(this), { + layerFilter: this.layerFilter_, + hitTolerance: this.hitTolerance_ + }); + var i; + for (i = features.getLength() - 1; i >= 0; --i) { + var feature = features.item(i); + var index = selected.indexOf(feature); + if (index > -1) { + // feature is already selected + selected.splice(index, 1); + } else { + features.remove(feature); + deselected.push(feature); + } + } + if (selected.length !== 0) { + features.extend(selected); + } + } else { + // Modify the currently selected feature(s). + map.forEachFeatureAtPixel(mapBrowserEvent.pixel, + (/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {ol.layer.Layer} layer Layer. + * @return {boolean|undefined} Continue to iterate over the features. + */ + function(feature, layer) { + if (this.filter_(feature, layer)) { + if ((add || toggle) && + !ol.array.includes(features.getArray(), feature)) { + selected.push(feature); + this.addFeatureLayerAssociation_(feature, layer); + } else if ((remove || toggle) && + ol.array.includes(features.getArray(), feature)) { + deselected.push(feature); + this.removeFeatureLayerAssociation_(feature); + } + return !this.multi_; + } + }).bind(this), { + layerFilter: this.layerFilter_, + hitTolerance: this.hitTolerance_ + }); + var j; + for (j = deselected.length - 1; j >= 0; --j) { + features.remove(deselected[j]); + } + features.extend(selected); + } + if (selected.length > 0 || deselected.length > 0) { + this.dispatchEvent( + new ol.interaction.Select.Event(ol.interaction.Select.EventType.SELECT, + selected, deselected, mapBrowserEvent)); + } + return ol.events.condition.pointerMove(mapBrowserEvent); +}; + + +/** + * Hit-detection tolerance. Pixels inside the radius around the given position + * will be checked for features. This only works for the canvas renderer and + * not for WebGL. + * @param {number} hitTolerance Hit tolerance in pixels. + * @api + */ +ol.interaction.Select.prototype.setHitTolerance = function(hitTolerance) { + this.hitTolerance_ = hitTolerance; +}; + + +/** + * Remove the interaction from its current map, if any, and attach it to a new + * map, if any. Pass `null` to just remove the interaction from the current map. + * @param {ol.Map} map Map. + * @api stable + */ +ol.interaction.Select.prototype.setMap = function(map) { + var currentMap = this.getMap(); + var selectedFeatures = + this.featureOverlay_.getSource().getFeaturesCollection(); + if (currentMap) { + selectedFeatures.forEach(currentMap.unskipFeature, currentMap); + } + ol.interaction.Interaction.prototype.setMap.call(this, map); + this.featureOverlay_.setMap(map); + if (map) { + selectedFeatures.forEach(map.skipFeature, map); + } +}; + + +/** + * @return {ol.StyleFunction} Styles. + */ +ol.interaction.Select.getDefaultStyleFunction = function() { + var styles = ol.style.Style.createDefaultEditing(); + ol.array.extend(styles[ol.geom.GeometryType.POLYGON], + styles[ol.geom.GeometryType.LINE_STRING]); + ol.array.extend(styles[ol.geom.GeometryType.GEOMETRY_COLLECTION], + styles[ol.geom.GeometryType.LINE_STRING]); + + return function(feature, resolution) { + if (!feature.getGeometry()) { + return null; + } + return styles[feature.getGeometry().getType()]; + }; +}; + + +/** + * @param {ol.Collection.Event} evt Event. + * @private + */ +ol.interaction.Select.prototype.addFeature_ = function(evt) { + var map = this.getMap(); + if (map) { + map.skipFeature(/** @type {ol.Feature} */ (evt.element)); + } +}; + + +/** + * @param {ol.Collection.Event} evt Event. + * @private + */ +ol.interaction.Select.prototype.removeFeature_ = function(evt) { + var map = this.getMap(); + if (map) { + map.unskipFeature(/** @type {ol.Feature} */ (evt.element)); + } +}; + + +/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @private + */ +ol.interaction.Select.prototype.removeFeatureLayerAssociation_ = function(feature) { + var key = ol.getUid(feature); + delete this.featureLayerAssociation_[key]; +}; + + +/** + * @classdesc + * Events emitted by {@link ol.interaction.Select} instances are instances of + * this type. + * + * @param {ol.interaction.Select.EventType} type The event type. + * @param {Array.<ol.Feature>} selected Selected features. + * @param {Array.<ol.Feature>} deselected Deselected features. + * @param {ol.MapBrowserEvent} mapBrowserEvent Associated + * {@link ol.MapBrowserEvent}. + * @implements {oli.SelectEvent} + * @extends {ol.events.Event} + * @constructor + */ +ol.interaction.Select.Event = function(type, selected, deselected, mapBrowserEvent) { + ol.events.Event.call(this, type); + + /** + * Selected features array. + * @type {Array.<ol.Feature>} + * @api + */ + this.selected = selected; + + /** + * Deselected features array. + * @type {Array.<ol.Feature>} + * @api + */ + this.deselected = deselected; + + /** + * Associated {@link ol.MapBrowserEvent}. + * @type {ol.MapBrowserEvent} + * @api + */ + this.mapBrowserEvent = mapBrowserEvent; +}; +ol.inherits(ol.interaction.Select.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.interaction.Select.EventType = { + /** + * Triggered when feature(s) has been (de)selected. + * @event ol.interaction.Select.Event#select + * @api + */ + SELECT: 'select' +}; + +goog.provide('ol.interaction.Snap'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.Object'); +goog.require('ol.Observable'); +goog.require('ol.coordinate'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.functions'); +goog.require('ol.obj'); +goog.require('ol.source.Vector'); +goog.require('ol.structs.RBush'); + + +/** + * @classdesc + * Handles snapping of vector features while modifying or drawing them. The + * features can come from a {@link ol.source.Vector} or {@link ol.Collection} + * Any interaction object that allows the user to interact + * with the features using the mouse can benefit from the snapping, as long + * as it is added before. + * + * The snap interaction modifies map browser event `coordinate` and `pixel` + * properties to force the snap to occur to any interaction that them. + * + * Example: + * + * var snap = new ol.interaction.Snap({ + * source: source + * }); + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.SnapOptions=} opt_options Options. + * @api + */ +ol.interaction.Snap = function(opt_options) { + + ol.interaction.Pointer.call(this, { + handleEvent: ol.interaction.Snap.handleEvent_, + handleDownEvent: ol.functions.TRUE, + handleUpEvent: ol.interaction.Snap.handleUpEvent_ + }); + + var options = opt_options ? opt_options : {}; + + /** + * @type {ol.source.Vector} + * @private + */ + this.source_ = options.source ? options.source : null; + + /** + * @private + * @type {boolean} + */ + this.vertex_ = options.vertex !== undefined ? options.vertex : true; + + /** + * @private + * @type {boolean} + */ + this.edge_ = options.edge !== undefined ? options.edge : true; + + /** + * @type {ol.Collection.<ol.Feature>} + * @private + */ + this.features_ = options.features ? options.features : null; + + /** + * @type {Array.<ol.EventsKey>} + * @private + */ + this.featuresListenerKeys_ = []; + + /** + * @type {Object.<number, ol.EventsKey>} + * @private + */ + this.geometryChangeListenerKeys_ = {}; + + /** + * @type {Object.<number, ol.EventsKey>} + * @private + */ + this.geometryModifyListenerKeys_ = {}; + + /** + * Extents are preserved so indexed segment can be quickly removed + * when its feature geometry changes + * @type {Object.<number, ol.Extent>} + * @private + */ + this.indexedFeaturesExtents_ = {}; + + /** + * If a feature geometry changes while a pointer drag|move event occurs, the + * feature doesn't get updated right away. It will be at the next 'pointerup' + * event fired. + * @type {Object.<number, ol.Feature>} + * @private + */ + this.pendingFeatures_ = {}; + + /** + * Used for distance sorting in sortByDistance_ + * @type {ol.Coordinate} + * @private + */ + this.pixelCoordinate_ = null; + + /** + * @type {number} + * @private + */ + this.pixelTolerance_ = options.pixelTolerance !== undefined ? + options.pixelTolerance : 10; + + /** + * @type {function(ol.SnapSegmentDataType, ol.SnapSegmentDataType): number} + * @private + */ + this.sortByDistance_ = ol.interaction.Snap.sortByDistance.bind(this); + + + /** + * Segment RTree for each layer + * @type {ol.structs.RBush.<ol.SnapSegmentDataType>} + * @private + */ + this.rBush_ = new ol.structs.RBush(); + + + /** + * @const + * @private + * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)>} + */ + this.SEGMENT_WRITERS_ = { + 'Point': this.writePointGeometry_, + 'LineString': this.writeLineStringGeometry_, + 'LinearRing': this.writeLineStringGeometry_, + 'Polygon': this.writePolygonGeometry_, + 'MultiPoint': this.writeMultiPointGeometry_, + 'MultiLineString': this.writeMultiLineStringGeometry_, + 'MultiPolygon': this.writeMultiPolygonGeometry_, + 'GeometryCollection': this.writeGeometryCollectionGeometry_ + }; +}; +ol.inherits(ol.interaction.Snap, ol.interaction.Pointer); + + +/** + * Add a feature to the collection of features that we may snap to. + * @param {ol.Feature} feature Feature. + * @param {boolean=} opt_listen Whether to listen to the geometry change or not + * Defaults to `true`. + * @api + */ +ol.interaction.Snap.prototype.addFeature = function(feature, opt_listen) { + var listen = opt_listen !== undefined ? opt_listen : true; + var feature_uid = ol.getUid(feature); + var geometry = feature.getGeometry(); + if (geometry) { + var segmentWriter = this.SEGMENT_WRITERS_[geometry.getType()]; + if (segmentWriter) { + this.indexedFeaturesExtents_[feature_uid] = geometry.getExtent( + ol.extent.createEmpty()); + segmentWriter.call(this, feature, geometry); + + if (listen) { + this.geometryModifyListenerKeys_[feature_uid] = ol.events.listen( + geometry, + ol.events.EventType.CHANGE, + this.handleGeometryModify_.bind(this, feature), + this); + } + } + } + + if (listen) { + this.geometryChangeListenerKeys_[feature_uid] = ol.events.listen( + feature, + ol.Object.getChangeEventType(feature.getGeometryName()), + this.handleGeometryChange_, this); + } +}; + + +/** + * @param {ol.Feature} feature Feature. + * @private + */ +ol.interaction.Snap.prototype.forEachFeatureAdd_ = function(feature) { + this.addFeature(feature); +}; + + +/** + * @param {ol.Feature} feature Feature. + * @private + */ +ol.interaction.Snap.prototype.forEachFeatureRemove_ = function(feature) { + this.removeFeature(feature); +}; + + +/** + * @return {ol.Collection.<ol.Feature>|Array.<ol.Feature>} Features. + * @private + */ +ol.interaction.Snap.prototype.getFeatures_ = function() { + var features; + if (this.features_) { + features = this.features_; + } else if (this.source_) { + features = this.source_.getFeatures(); + } + return /** @type {!Array.<ol.Feature>|!ol.Collection.<ol.Feature>} */ (features); +}; + + +/** + * @param {ol.source.Vector.Event|ol.Collection.Event} evt Event. + * @private + */ +ol.interaction.Snap.prototype.handleFeatureAdd_ = function(evt) { + var feature; + if (evt instanceof ol.source.Vector.Event) { + feature = evt.feature; + } else if (evt instanceof ol.Collection.Event) { + feature = evt.element; + } + this.addFeature(/** @type {ol.Feature} */ (feature)); +}; + + +/** + * @param {ol.source.Vector.Event|ol.Collection.Event} evt Event. + * @private + */ +ol.interaction.Snap.prototype.handleFeatureRemove_ = function(evt) { + var feature; + if (evt instanceof ol.source.Vector.Event) { + feature = evt.feature; + } else if (evt instanceof ol.Collection.Event) { + feature = evt.element; + } + this.removeFeature(/** @type {ol.Feature} */ (feature)); +}; + + +/** + * @param {ol.events.Event} evt Event. + * @private + */ +ol.interaction.Snap.prototype.handleGeometryChange_ = function(evt) { + var feature = /** @type {ol.Feature} */ (evt.target); + this.removeFeature(feature, true); + this.addFeature(feature, true); +}; + + +/** + * @param {ol.Feature} feature Feature which geometry was modified. + * @param {ol.events.Event} evt Event. + * @private + */ +ol.interaction.Snap.prototype.handleGeometryModify_ = function(feature, evt) { + if (this.handlingDownUpSequence) { + var uid = ol.getUid(feature); + if (!(uid in this.pendingFeatures_)) { + this.pendingFeatures_[uid] = feature; + } + } else { + this.updateFeature_(feature); + } +}; + + +/** + * Remove a feature from the collection of features that we may snap to. + * @param {ol.Feature} feature Feature + * @param {boolean=} opt_unlisten Whether to unlisten to the geometry change + * or not. Defaults to `true`. + * @api + */ +ol.interaction.Snap.prototype.removeFeature = function(feature, opt_unlisten) { + var unlisten = opt_unlisten !== undefined ? opt_unlisten : true; + var feature_uid = ol.getUid(feature); + var extent = this.indexedFeaturesExtents_[feature_uid]; + if (extent) { + var rBush = this.rBush_; + var i, nodesToRemove = []; + rBush.forEachInExtent(extent, function(node) { + if (feature === node.feature) { + nodesToRemove.push(node); + } + }); + for (i = nodesToRemove.length - 1; i >= 0; --i) { + rBush.remove(nodesToRemove[i]); + } + if (unlisten) { + ol.Observable.unByKey(this.geometryModifyListenerKeys_[feature_uid]); + delete this.geometryModifyListenerKeys_[feature_uid]; + } + } + + if (unlisten) { + ol.Observable.unByKey(this.geometryChangeListenerKeys_[feature_uid]); + delete this.geometryChangeListenerKeys_[feature_uid]; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.Snap.prototype.setMap = function(map) { + var currentMap = this.getMap(); + var keys = this.featuresListenerKeys_; + var features = this.getFeatures_(); + + if (currentMap) { + keys.forEach(ol.Observable.unByKey); + keys.length = 0; + features.forEach(this.forEachFeatureRemove_, this); + } + ol.interaction.Pointer.prototype.setMap.call(this, map); + + if (map) { + if (this.features_) { + keys.push( + ol.events.listen(this.features_, ol.Collection.EventType.ADD, + this.handleFeatureAdd_, this), + ol.events.listen(this.features_, ol.Collection.EventType.REMOVE, + this.handleFeatureRemove_, this) + ); + } else if (this.source_) { + keys.push( + ol.events.listen(this.source_, ol.source.Vector.EventType.ADDFEATURE, + this.handleFeatureAdd_, this), + ol.events.listen(this.source_, ol.source.Vector.EventType.REMOVEFEATURE, + this.handleFeatureRemove_, this) + ); + } + features.forEach(this.forEachFeatureAdd_, this); + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.Snap.prototype.shouldStopEvent = ol.functions.FALSE; + + +/** + * @param {ol.Pixel} pixel Pixel + * @param {ol.Coordinate} pixelCoordinate Coordinate + * @param {ol.Map} map Map. + * @return {ol.SnapResultType} Snap result + */ +ol.interaction.Snap.prototype.snapTo = function(pixel, pixelCoordinate, map) { + + var lowerLeft = map.getCoordinateFromPixel( + [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]); + var upperRight = map.getCoordinateFromPixel( + [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]); + var box = ol.extent.boundingExtent([lowerLeft, upperRight]); + + var segments = this.rBush_.getInExtent(box); + var snappedToVertex = false; + var snapped = false; + var vertex = null; + var vertexPixel = null; + var dist, pixel1, pixel2, squaredDist1, squaredDist2; + if (segments.length > 0) { + this.pixelCoordinate_ = pixelCoordinate; + segments.sort(this.sortByDistance_); + var closestSegment = segments[0].segment; + if (this.vertex_ && !this.edge_) { + pixel1 = map.getPixelFromCoordinate(closestSegment[0]); + pixel2 = map.getPixelFromCoordinate(closestSegment[1]); + squaredDist1 = ol.coordinate.squaredDistance(pixel, pixel1); + squaredDist2 = ol.coordinate.squaredDistance(pixel, pixel2); + dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); + snappedToVertex = dist <= this.pixelTolerance_; + if (snappedToVertex) { + snapped = true; + vertex = squaredDist1 > squaredDist2 ? + closestSegment[1] : closestSegment[0]; + vertexPixel = map.getPixelFromCoordinate(vertex); + } + } else if (this.edge_) { + vertex = (ol.coordinate.closestOnSegment(pixelCoordinate, + closestSegment)); + vertexPixel = map.getPixelFromCoordinate(vertex); + if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <= + this.pixelTolerance_) { + snapped = true; + if (this.vertex_) { + pixel1 = map.getPixelFromCoordinate(closestSegment[0]); + pixel2 = map.getPixelFromCoordinate(closestSegment[1]); + squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1); + squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2); + dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); + snappedToVertex = dist <= this.pixelTolerance_; + if (snappedToVertex) { + vertex = squaredDist1 > squaredDist2 ? + closestSegment[1] : closestSegment[0]; + vertexPixel = map.getPixelFromCoordinate(vertex); + } + } + } + } + if (snapped) { + vertexPixel = [Math.round(vertexPixel[0]), Math.round(vertexPixel[1])]; + } + } + return /** @type {ol.SnapResultType} */ ({ + snapped: snapped, + vertex: vertex, + vertexPixel: vertexPixel + }); +}; + + +/** + * @param {ol.Feature} feature Feature + * @private + */ +ol.interaction.Snap.prototype.updateFeature_ = function(feature) { + this.removeFeature(feature, false); + this.addFeature(feature, false); +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.GeometryCollection} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeGeometryCollectionGeometry_ = function(feature, geometry) { + var i, geometries = geometry.getGeometriesArray(); + for (i = 0; i < geometries.length; ++i) { + this.SEGMENT_WRITERS_[geometries[i].getType()].call( + this, feature, geometries[i]); + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.LineString} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeLineStringGeometry_ = function(feature, geometry) { + var coordinates = geometry.getCoordinates(); + var i, ii, segment, segmentData; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiLineString} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeMultiLineStringGeometry_ = function(feature, geometry) { + var lines = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, segment, segmentData; + for (j = 0, jj = lines.length; j < jj; ++j) { + coordinates = lines[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiPoint} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeMultiPointGeometry_ = function(feature, geometry) { + var points = geometry.getCoordinates(); + var coordinates, i, ii, segmentData; + for (i = 0, ii = points.length; i < ii; ++i) { + coordinates = points[i]; + segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: [coordinates, coordinates] + }); + this.rBush_.insert(geometry.getExtent(), segmentData); + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiPolygon} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeMultiPolygonGeometry_ = function(feature, geometry) { + var polygons = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData; + for (k = 0, kk = polygons.length; k < kk; ++k) { + rings = polygons[k]; + for (j = 0, jj = rings.length; j < jj; ++j) { + coordinates = rings[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } + } +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.Point} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writePointGeometry_ = function(feature, geometry) { + var coordinates = geometry.getCoordinates(); + var segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: [coordinates, coordinates] + }); + this.rBush_.insert(geometry.getExtent(), segmentData); +}; + + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.Polygon} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writePolygonGeometry_ = function(feature, geometry) { + var rings = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, segment, segmentData; + for (j = 0, jj = rings.length; j < jj; ++j) { + coordinates = rings[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.SnapSegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } +}; + + +/** + * Handle all pointer events events. + * @param {ol.MapBrowserEvent} evt A move event. + * @return {boolean} Pass the event to other interactions. + * @this {ol.interaction.Snap} + * @private + */ +ol.interaction.Snap.handleEvent_ = function(evt) { + var result = this.snapTo(evt.pixel, evt.coordinate, evt.map); + if (result.snapped) { + evt.coordinate = result.vertex.slice(0, 2); + evt.pixel = result.vertexPixel; + } + return ol.interaction.Pointer.handleEvent.call(this, evt); +}; + + +/** + * @param {ol.MapBrowserPointerEvent} evt Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Snap} + * @private + */ +ol.interaction.Snap.handleUpEvent_ = function(evt) { + var featuresToUpdate = ol.obj.getValues(this.pendingFeatures_); + if (featuresToUpdate.length) { + featuresToUpdate.forEach(this.updateFeature_, this); + this.pendingFeatures_ = {}; + } + return false; +}; + + +/** + * Sort segments by distance, helper function + * @param {ol.SnapSegmentDataType} a The first segment data. + * @param {ol.SnapSegmentDataType} b The second segment data. + * @return {number} The difference in distance. + * @this {ol.interaction.Snap} + */ +ol.interaction.Snap.sortByDistance = function(a, b) { + return ol.coordinate.squaredDistanceToSegment( + this.pixelCoordinate_, a.segment) - + ol.coordinate.squaredDistanceToSegment( + this.pixelCoordinate_, b.segment); +}; + +goog.provide('ol.interaction.Translate'); + +goog.require('ol'); +goog.require('ol.Collection'); +goog.require('ol.events.Event'); +goog.require('ol.functions'); +goog.require('ol.array'); +goog.require('ol.interaction.Pointer'); + + +/** + * @classdesc + * Interaction for translating (moving) features. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @fires ol.interaction.Translate.Event + * @param {olx.interaction.TranslateOptions=} opt_options Options. + * @api + */ +ol.interaction.Translate = function(opt_options) { + ol.interaction.Pointer.call(this, { + handleDownEvent: ol.interaction.Translate.handleDownEvent_, + handleDragEvent: ol.interaction.Translate.handleDragEvent_, + handleMoveEvent: ol.interaction.Translate.handleMoveEvent_, + handleUpEvent: ol.interaction.Translate.handleUpEvent_ + }); + + var options = opt_options ? opt_options : {}; + + /** + * @type {string|undefined} + * @private + */ + this.previousCursor_ = undefined; + + + /** + * The last position we translated to. + * @type {ol.Coordinate} + * @private + */ + this.lastCoordinate_ = null; + + + /** + * @type {ol.Collection.<ol.Feature>} + * @private + */ + this.features_ = options.features !== undefined ? options.features : null; + + /** @type {function(ol.layer.Layer): boolean} */ + var layerFilter; + if (options.layers) { + if (typeof options.layers === 'function') { + layerFilter = options.layers; + } else { + var layers = options.layers; + layerFilter = function(layer) { + return ol.array.includes(layers, layer); + }; + } + } else { + layerFilter = ol.functions.TRUE; + } + + /** + * @private + * @type {function(ol.layer.Layer): boolean} + */ + this.layerFilter_ = layerFilter; + + /** + * @private + * @type {number} + */ + this.hitTolerance_ = options.hitTolerance ? options.hitTolerance : 0; + + /** + * @type {ol.Feature} + * @private + */ + this.lastFeature_ = null; +}; +ol.inherits(ol.interaction.Translate, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} event Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.Translate} + * @private + */ +ol.interaction.Translate.handleDownEvent_ = function(event) { + this.lastFeature_ = this.featuresAtPixel_(event.pixel, event.map); + if (!this.lastCoordinate_ && this.lastFeature_) { + this.lastCoordinate_ = event.coordinate; + ol.interaction.Translate.handleMoveEvent_.call(this, event); + + var features = this.features_ || new ol.Collection([this.lastFeature_]); + + this.dispatchEvent( + new ol.interaction.Translate.Event( + ol.interaction.Translate.EventType.TRANSLATESTART, features, + event.coordinate)); + return true; + } + return false; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} event Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Translate} + * @private + */ +ol.interaction.Translate.handleUpEvent_ = function(event) { + if (this.lastCoordinate_) { + this.lastCoordinate_ = null; + ol.interaction.Translate.handleMoveEvent_.call(this, event); + + var features = this.features_ || new ol.Collection([this.lastFeature_]); + + this.dispatchEvent( + new ol.interaction.Translate.Event( + ol.interaction.Translate.EventType.TRANSLATEEND, features, + event.coordinate)); + return true; + } + return false; +}; + + +/** + * @param {ol.MapBrowserPointerEvent} event Event. + * @this {ol.interaction.Translate} + * @private + */ +ol.interaction.Translate.handleDragEvent_ = function(event) { + if (this.lastCoordinate_) { + var newCoordinate = event.coordinate; + var deltaX = newCoordinate[0] - this.lastCoordinate_[0]; + var deltaY = newCoordinate[1] - this.lastCoordinate_[1]; + + var features = this.features_ || new ol.Collection([this.lastFeature_]); + + features.forEach(function(feature) { + var geom = feature.getGeometry(); + geom.translate(deltaX, deltaY); + feature.setGeometry(geom); + }); + + this.lastCoordinate_ = newCoordinate; + this.dispatchEvent( + new ol.interaction.Translate.Event( + ol.interaction.Translate.EventType.TRANSLATING, features, + newCoordinate)); + } +}; + + +/** + * @param {ol.MapBrowserEvent} event Event. + * @this {ol.interaction.Translate} + * @private + */ +ol.interaction.Translate.handleMoveEvent_ = function(event) { + var elem = event.map.getTargetElement(); + + // Change the cursor to grab/grabbing if hovering any of the features managed + // by the interaction + if (this.featuresAtPixel_(event.pixel, event.map)) { + this.previousCursor_ = elem.style.cursor; + // WebKit browsers don't support the grab icons without a prefix + elem.style.cursor = this.lastCoordinate_ ? + '-webkit-grabbing' : '-webkit-grab'; + + // Thankfully, attempting to set the standard ones will silently fail, + // keeping the prefixed icons + elem.style.cursor = this.lastCoordinate_ ? 'grabbing' : 'grab'; + } else { + elem.style.cursor = this.previousCursor_ !== undefined ? + this.previousCursor_ : ''; + this.previousCursor_ = undefined; + } +}; + + +/** + * Tests to see if the given coordinates intersects any of our selected + * features. + * @param {ol.Pixel} pixel Pixel coordinate to test for intersection. + * @param {ol.Map} map Map to test the intersection on. + * @return {ol.Feature} Returns the feature found at the specified pixel + * coordinates. + * @private + */ +ol.interaction.Translate.prototype.featuresAtPixel_ = function(pixel, map) { + return map.forEachFeatureAtPixel(pixel, + function(feature) { + if (!this.features_ || + ol.array.includes(this.features_.getArray(), feature)) { + return feature; + } + }.bind(this), { + layerFilter: this.layerFilter_, + hitTolerance: this.hitTolerance_ + }); +}; + + +/** + * Returns the Hit-detection tolerance. + * @returns {number} Hit tolerance in pixels. + * @api + */ +ol.interaction.Translate.prototype.getHitTolerance = function() { + return this.hitTolerance_; +}; + + +/** + * Hit-detection tolerance. Pixels inside the radius around the given position + * will be checked for features. This only works for the canvas renderer and + * not for WebGL. + * @param {number} hitTolerance Hit tolerance in pixels. + * @api + */ +ol.interaction.Translate.prototype.setHitTolerance = function(hitTolerance) { + this.hitTolerance_ = hitTolerance; +}; + + +/** + * @classdesc + * Events emitted by {@link ol.interaction.Translate} instances are instances of + * this type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.interaction.TranslateEvent} + * @param {ol.interaction.Translate.EventType} type Type. + * @param {ol.Collection.<ol.Feature>} features The features translated. + * @param {ol.Coordinate} coordinate The event coordinate. + */ +ol.interaction.Translate.Event = function(type, features, coordinate) { + + ol.events.Event.call(this, type); + + /** + * The features being translated. + * @type {ol.Collection.<ol.Feature>} + * @api + */ + this.features = features; + + /** + * The coordinate of the drag event. + * @const + * @type {ol.Coordinate} + * @api + */ + this.coordinate = coordinate; +}; +ol.inherits(ol.interaction.Translate.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.interaction.Translate.EventType = { + /** + * Triggered upon feature translation start. + * @event ol.interaction.Translate.Event#translatestart + * @api + */ + TRANSLATESTART: 'translatestart', + /** + * Triggered upon feature translation. + * @event ol.interaction.Translate.Event#translating + * @api + */ + TRANSLATING: 'translating', + /** + * Triggered upon feature translation end. + * @event ol.interaction.Translate.Event#translateend + * @api + */ + TRANSLATEEND: 'translateend' +}; + +goog.provide('ol.layer.Heatmap'); + +goog.require('ol.events'); +goog.require('ol'); +goog.require('ol.Object'); +goog.require('ol.dom'); +goog.require('ol.layer.Vector'); +goog.require('ol.math'); +goog.require('ol.obj'); +goog.require('ol.render.Event'); +goog.require('ol.style.Icon'); +goog.require('ol.style.Style'); + + +/** + * @classdesc + * Layer for rendering vector data as a heatmap. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Vector} + * @fires ol.render.Event + * @param {olx.layer.HeatmapOptions=} opt_options Options. + * @api + */ +ol.layer.Heatmap = function(opt_options) { + var options = opt_options ? opt_options : {}; + + var baseOptions = ol.obj.assign({}, options); + + delete baseOptions.gradient; + delete baseOptions.radius; + delete baseOptions.blur; + delete baseOptions.shadow; + delete baseOptions.weight; + ol.layer.Vector.call(this, /** @type {olx.layer.VectorOptions} */ (baseOptions)); + + /** + * @private + * @type {Uint8ClampedArray} + */ + this.gradient_ = null; + + /** + * @private + * @type {number} + */ + this.shadow_ = options.shadow !== undefined ? options.shadow : 250; + + /** + * @private + * @type {string|undefined} + */ + this.circleImage_ = undefined; + + /** + * @private + * @type {Array.<Array.<ol.style.Style>>} + */ + this.styleCache_ = null; + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.Heatmap.Property.GRADIENT), + this.handleGradientChanged_, this); + + this.setGradient(options.gradient ? + options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT); + + this.setBlur(options.blur !== undefined ? options.blur : 15); + + this.setRadius(options.radius !== undefined ? options.radius : 8); + + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.Heatmap.Property.BLUR), + this.handleStyleChanged_, this); + ol.events.listen(this, + ol.Object.getChangeEventType(ol.layer.Heatmap.Property.RADIUS), + this.handleStyleChanged_, this); + + this.handleStyleChanged_(); + + var weight = options.weight ? options.weight : 'weight'; + var weightFunction; + if (typeof weight === 'string') { + weightFunction = function(feature) { + return feature.get(weight); + }; + } else { + weightFunction = weight; + } + ol.DEBUG && console.assert(typeof weightFunction === 'function', + 'weightFunction should be a function'); + + this.setStyle(function(feature, resolution) { + ol.DEBUG && console.assert(this.styleCache_, 'this.styleCache_ expected'); + ol.DEBUG && console.assert(this.circleImage_ !== undefined, + 'this.circleImage_ should be defined'); + var weight = weightFunction(feature); + var opacity = weight !== undefined ? ol.math.clamp(weight, 0, 1) : 1; + // cast to 8 bits + var index = (255 * opacity) | 0; + var style = this.styleCache_[index]; + if (!style) { + style = [ + new ol.style.Style({ + image: new ol.style.Icon({ + opacity: opacity, + src: this.circleImage_ + }) + }) + ]; + this.styleCache_[index] = style; + } + return style; + }.bind(this)); + + // For performance reasons, don't sort the features before rendering. + // The render order is not relevant for a heatmap representation. + this.setRenderOrder(null); + + ol.events.listen(this, ol.render.Event.Type.RENDER, this.handleRender_, this); + +}; +ol.inherits(ol.layer.Heatmap, ol.layer.Vector); + + +/** + * @const + * @type {Array.<string>} + */ +ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00']; + + +/** + * @param {Array.<string>} colors A list of colored. + * @return {Uint8ClampedArray} An array. + * @private + */ +ol.layer.Heatmap.createGradient_ = function(colors) { + var width = 1; + var height = 256; + var context = ol.dom.createCanvasContext2D(width, height); + + var gradient = context.createLinearGradient(0, 0, width, height); + var step = 1 / (colors.length - 1); + for (var i = 0, ii = colors.length; i < ii; ++i) { + gradient.addColorStop(i * step, colors[i]); + } + + context.fillStyle = gradient; + context.fillRect(0, 0, width, height); + + return context.getImageData(0, 0, width, height).data; +}; + + +/** + * @return {string} Data URL for a circle. + * @private + */ +ol.layer.Heatmap.prototype.createCircle_ = function() { + var radius = this.getRadius(); + var blur = this.getBlur(); + ol.DEBUG && console.assert(radius !== undefined && blur !== undefined, + 'radius and blur should be defined'); + var halfSize = radius + blur + 1; + var size = 2 * halfSize; + var context = ol.dom.createCanvasContext2D(size, size); + context.shadowOffsetX = context.shadowOffsetY = this.shadow_; + context.shadowBlur = blur; + context.shadowColor = '#000'; + context.beginPath(); + var center = halfSize - this.shadow_; + context.arc(center, center, radius, 0, Math.PI * 2, true); + context.fill(); + return context.canvas.toDataURL(); +}; + + +/** + * Return the blur size in pixels. + * @return {number} Blur size in pixels. + * @api + * @observable + */ +ol.layer.Heatmap.prototype.getBlur = function() { + return /** @type {number} */ (this.get(ol.layer.Heatmap.Property.BLUR)); +}; + + +/** + * Return the gradient colors as array of strings. + * @return {Array.<string>} Colors. + * @api + * @observable + */ +ol.layer.Heatmap.prototype.getGradient = function() { + return /** @type {Array.<string>} */ ( + this.get(ol.layer.Heatmap.Property.GRADIENT)); +}; + + +/** + * Return the size of the radius in pixels. + * @return {number} Radius size in pixel. + * @api + * @observable + */ +ol.layer.Heatmap.prototype.getRadius = function() { + return /** @type {number} */ (this.get(ol.layer.Heatmap.Property.RADIUS)); +}; + + +/** + * @private + */ +ol.layer.Heatmap.prototype.handleGradientChanged_ = function() { + this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient()); +}; + + +/** + * @private + */ +ol.layer.Heatmap.prototype.handleStyleChanged_ = function() { + this.circleImage_ = this.createCircle_(); + this.styleCache_ = new Array(256); + this.changed(); +}; + + +/** + * @param {ol.render.Event} event Post compose event + * @private + */ +ol.layer.Heatmap.prototype.handleRender_ = function(event) { + ol.DEBUG && console.assert(event.type == ol.render.Event.Type.RENDER, + 'event.type should be RENDER'); + ol.DEBUG && console.assert(this.gradient_, 'this.gradient_ expected'); + var context = event.context; + var canvas = context.canvas; + var image = context.getImageData(0, 0, canvas.width, canvas.height); + var view8 = image.data; + var i, ii, alpha; + for (i = 0, ii = view8.length; i < ii; i += 4) { + alpha = view8[i + 3] * 4; + if (alpha) { + view8[i] = this.gradient_[alpha]; + view8[i + 1] = this.gradient_[alpha + 1]; + view8[i + 2] = this.gradient_[alpha + 2]; + } + } + context.putImageData(image, 0, 0); +}; + + +/** + * Set the blur size in pixels. + * @param {number} blur Blur size in pixels. + * @api + * @observable + */ +ol.layer.Heatmap.prototype.setBlur = function(blur) { + this.set(ol.layer.Heatmap.Property.BLUR, blur); +}; + + +/** + * Set the gradient colors as array of strings. + * @param {Array.<string>} colors Gradient. + * @api + * @observable + */ +ol.layer.Heatmap.prototype.setGradient = function(colors) { + this.set(ol.layer.Heatmap.Property.GRADIENT, colors); +}; + + +/** + * Set the size of the radius in pixels. + * @param {number} radius Radius size in pixel. + * @api + * @observable + */ +ol.layer.Heatmap.prototype.setRadius = function(radius) { + this.set(ol.layer.Heatmap.Property.RADIUS, radius); +}; + + +/** + * @enum {string} + */ +ol.layer.Heatmap.Property = { + BLUR: 'blur', + GRADIENT: 'gradient', + RADIUS: 'radius' +}; + +goog.provide('ol.net'); + +goog.require('ol'); + + +/** + * Simple JSONP helper. Supports error callbacks and a custom callback param. + * The error callback will be called when no JSONP is executed after 10 seconds. + * + * @param {string} url Request url. A 'callback' query parameter will be + * appended. + * @param {Function} callback Callback on success. + * @param {function()=} opt_errback Callback on error. + * @param {string=} opt_callbackParam Custom query parameter for the JSONP + * callback. Default is 'callback'. + */ +ol.net.jsonp = function(url, callback, opt_errback, opt_callbackParam) { + var script = document.createElement('script'); + var key = 'olc_' + ol.getUid(callback); + function cleanup() { + delete window[key]; + script.parentNode.removeChild(script); + } + script.async = true; + script.src = url + (url.indexOf('?') == -1 ? '?' : '&') + + (opt_callbackParam || 'callback') + '=' + key; + var timer = setTimeout(function() { + cleanup(); + if (opt_errback) { + opt_errback(); + } + }, 10000); + window[key] = function(data) { + clearTimeout(timer); + cleanup(); + callback(data); + }; + document.getElementsByTagName('head')[0].appendChild(script); +}; + +goog.provide('ol.render'); + +goog.require('ol.has'); +goog.require('ol.transform'); +goog.require('ol.render.canvas.Immediate'); + + +/** + * Binds a Canvas Immediate API to a canvas context, to allow drawing geometries + * to the context's canvas. + * + * The units for geometry coordinates are css pixels relative to the top left + * corner of the canvas element. + * ```js + * var canvas = document.createElement('canvas'); + * var render = ol.render.toContext(canvas.getContext('2d'), + * { size: [100, 100] }); + * render.setFillStrokeStyle(new ol.style.Fill({ color: blue })); + * render.drawPolygon( + * new ol.geom.Polygon([[[0, 0], [100, 100], [100, 0], [0, 0]]])); + * ``` + * + * @param {CanvasRenderingContext2D} context Canvas context. + * @param {olx.render.ToContextOptions=} opt_options Options. + * @return {ol.render.canvas.Immediate} Canvas Immediate. + * @api + */ +ol.render.toContext = function(context, opt_options) { + var canvas = context.canvas; + var options = opt_options ? opt_options : {}; + var pixelRatio = options.pixelRatio || ol.has.DEVICE_PIXEL_RATIO; + var size = options.size; + if (size) { + canvas.width = size[0] * pixelRatio; + canvas.height = size[1] * pixelRatio; + canvas.style.width = size[0] + 'px'; + canvas.style.height = size[1] + 'px'; + } + var extent = [0, 0, canvas.width, canvas.height]; + var transform = ol.transform.scale(ol.transform.create(), pixelRatio, pixelRatio); + return new ol.render.canvas.Immediate(context, pixelRatio, extent, transform, + 0); +}; + +goog.provide('ol.reproj.Tile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.math'); +goog.require('ol.reproj'); +goog.require('ol.reproj.Triangulation'); + + +/** + * @classdesc + * Class encapsulating single reprojected tile. + * See {@link ol.source.TileImage}. + * + * @constructor + * @extends {ol.Tile} + * @param {ol.proj.Projection} sourceProj Source projection. + * @param {ol.tilegrid.TileGrid} sourceTileGrid Source tile grid. + * @param {ol.proj.Projection} targetProj Target projection. + * @param {ol.tilegrid.TileGrid} targetTileGrid Target tile grid. + * @param {ol.TileCoord} tileCoord Coordinate of the tile. + * @param {ol.TileCoord} wrappedTileCoord Coordinate of the tile wrapped in X. + * @param {number} pixelRatio Pixel ratio. + * @param {number} gutter Gutter of the source tiles. + * @param {ol.ReprojTileFunctionType} getTileFunction + * Function returning source tiles (z, x, y, pixelRatio). + * @param {number=} opt_errorThreshold Acceptable reprojection error (in px). + * @param {boolean=} opt_renderEdges Render reprojection edges. + */ +ol.reproj.Tile = function(sourceProj, sourceTileGrid, + targetProj, targetTileGrid, tileCoord, wrappedTileCoord, + pixelRatio, gutter, getTileFunction, + opt_errorThreshold, + opt_renderEdges) { + ol.Tile.call(this, tileCoord, ol.Tile.State.IDLE); + + /** + * @private + * @type {boolean} + */ + this.renderEdges_ = opt_renderEdges !== undefined ? opt_renderEdges : false; + + /** + * @private + * @type {number} + */ + this.pixelRatio_ = pixelRatio; + + /** + * @private + * @type {number} + */ + this.gutter_ = gutter; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = null; + + /** + * @private + * @type {ol.tilegrid.TileGrid} + */ + this.sourceTileGrid_ = sourceTileGrid; + + /** + * @private + * @type {ol.tilegrid.TileGrid} + */ + this.targetTileGrid_ = targetTileGrid; + + /** + * @private + * @type {ol.TileCoord} + */ + this.wrappedTileCoord_ = wrappedTileCoord ? wrappedTileCoord : tileCoord; + + /** + * @private + * @type {!Array.<ol.Tile>} + */ + this.sourceTiles_ = []; + + /** + * @private + * @type {Array.<ol.EventsKey>} + */ + this.sourcesListenerKeys_ = null; + + /** + * @private + * @type {number} + */ + this.sourceZ_ = 0; + + var targetExtent = targetTileGrid.getTileCoordExtent(this.wrappedTileCoord_); + var maxTargetExtent = this.targetTileGrid_.getExtent(); + var maxSourceExtent = this.sourceTileGrid_.getExtent(); + + var limitedTargetExtent = maxTargetExtent ? + ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent; + + if (ol.extent.getArea(limitedTargetExtent) === 0) { + // Tile is completely outside range -> EMPTY + // TODO: is it actually correct that the source even creates the tile ? + this.state = ol.Tile.State.EMPTY; + return; + } + + var sourceProjExtent = sourceProj.getExtent(); + if (sourceProjExtent) { + if (!maxSourceExtent) { + maxSourceExtent = sourceProjExtent; + } else { + maxSourceExtent = ol.extent.getIntersection( + maxSourceExtent, sourceProjExtent); + } + } + + var targetResolution = targetTileGrid.getResolution( + this.wrappedTileCoord_[0]); + + var targetCenter = ol.extent.getCenter(limitedTargetExtent); + var sourceResolution = ol.reproj.calculateSourceResolution( + sourceProj, targetProj, targetCenter, targetResolution); + + if (!isFinite(sourceResolution) || sourceResolution <= 0) { + // invalid sourceResolution -> EMPTY + // probably edges of the projections when no extent is defined + this.state = ol.Tile.State.EMPTY; + return; + } + + var errorThresholdInPixels = opt_errorThreshold !== undefined ? + opt_errorThreshold : ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD; + + /** + * @private + * @type {!ol.reproj.Triangulation} + */ + this.triangulation_ = new ol.reproj.Triangulation( + sourceProj, targetProj, limitedTargetExtent, maxSourceExtent, + sourceResolution * errorThresholdInPixels); + + if (this.triangulation_.getTriangles().length === 0) { + // no valid triangles -> EMPTY + this.state = ol.Tile.State.EMPTY; + return; + } + + this.sourceZ_ = sourceTileGrid.getZForResolution(sourceResolution); + var sourceExtent = this.triangulation_.calculateSourceExtent(); + + if (maxSourceExtent) { + if (sourceProj.canWrapX()) { + sourceExtent[1] = ol.math.clamp( + sourceExtent[1], maxSourceExtent[1], maxSourceExtent[3]); + sourceExtent[3] = ol.math.clamp( + sourceExtent[3], maxSourceExtent[1], maxSourceExtent[3]); + } else { + sourceExtent = ol.extent.getIntersection(sourceExtent, maxSourceExtent); + } + } + + if (!ol.extent.getArea(sourceExtent)) { + this.state = ol.Tile.State.EMPTY; + } else { + var sourceRange = sourceTileGrid.getTileRangeForExtentAndZ( + sourceExtent, this.sourceZ_); + + var tilesRequired = sourceRange.getWidth() * sourceRange.getHeight(); + if (ol.DEBUG && !(tilesRequired < ol.RASTER_REPROJECTION_MAX_SOURCE_TILES)) { + console.assert(false, 'reasonable number of tiles is required'); + this.state = ol.Tile.State.ERROR; + return; + } + for (var srcX = sourceRange.minX; srcX <= sourceRange.maxX; srcX++) { + for (var srcY = sourceRange.minY; srcY <= sourceRange.maxY; srcY++) { + var tile = getTileFunction(this.sourceZ_, srcX, srcY, pixelRatio); + if (tile) { + this.sourceTiles_.push(tile); + } + } + } + + if (this.sourceTiles_.length === 0) { + this.state = ol.Tile.State.EMPTY; + } + } +}; +ol.inherits(ol.reproj.Tile, ol.Tile); + + +/** + * @inheritDoc + */ +ol.reproj.Tile.prototype.disposeInternal = function() { + if (this.state == ol.Tile.State.LOADING) { + this.unlistenSources_(); + } + ol.Tile.prototype.disposeInternal.call(this); +}; + + +/** + * @inheritDoc + */ +ol.reproj.Tile.prototype.getImage = function() { + return this.canvas_; +}; + + +/** + * @private + */ +ol.reproj.Tile.prototype.reproject_ = function() { + var sources = []; + this.sourceTiles_.forEach(function(tile, i, arr) { + if (tile && tile.getState() == ol.Tile.State.LOADED) { + sources.push({ + extent: this.sourceTileGrid_.getTileCoordExtent(tile.tileCoord), + image: tile.getImage() + }); + } + }, this); + this.sourceTiles_.length = 0; + + if (sources.length === 0) { + this.state = ol.Tile.State.ERROR; + } else { + var z = this.wrappedTileCoord_[0]; + var size = this.targetTileGrid_.getTileSize(z); + var width = typeof size === 'number' ? size : size[0]; + var height = typeof size === 'number' ? size : size[1]; + var targetResolution = this.targetTileGrid_.getResolution(z); + var sourceResolution = this.sourceTileGrid_.getResolution(this.sourceZ_); + + var targetExtent = this.targetTileGrid_.getTileCoordExtent( + this.wrappedTileCoord_); + this.canvas_ = ol.reproj.render(width, height, this.pixelRatio_, + sourceResolution, this.sourceTileGrid_.getExtent(), + targetResolution, targetExtent, this.triangulation_, sources, + this.gutter_, this.renderEdges_); + + this.state = ol.Tile.State.LOADED; + } + this.changed(); +}; + + +/** + * @inheritDoc + */ +ol.reproj.Tile.prototype.load = function() { + if (this.state == ol.Tile.State.IDLE) { + this.state = ol.Tile.State.LOADING; + this.changed(); + + var leftToLoad = 0; + + ol.DEBUG && console.assert(!this.sourcesListenerKeys_, + 'this.sourcesListenerKeys_ should be null'); + + this.sourcesListenerKeys_ = []; + this.sourceTiles_.forEach(function(tile, i, arr) { + var state = tile.getState(); + if (state == ol.Tile.State.IDLE || state == ol.Tile.State.LOADING) { + leftToLoad++; + + var sourceListenKey; + sourceListenKey = ol.events.listen(tile, ol.events.EventType.CHANGE, + function(e) { + var state = tile.getState(); + if (state == ol.Tile.State.LOADED || + state == ol.Tile.State.ERROR || + state == ol.Tile.State.EMPTY) { + ol.events.unlistenByKey(sourceListenKey); + leftToLoad--; + ol.DEBUG && console.assert(leftToLoad >= 0, + 'leftToLoad should not be negative'); + if (leftToLoad === 0) { + this.unlistenSources_(); + this.reproject_(); + } + } + }, this); + this.sourcesListenerKeys_.push(sourceListenKey); + } + }, this); + + this.sourceTiles_.forEach(function(tile, i, arr) { + var state = tile.getState(); + if (state == ol.Tile.State.IDLE) { + tile.load(); + } + }); + + if (leftToLoad === 0) { + setTimeout(this.reproject_.bind(this), 0); + } + } +}; + + +/** + * @private + */ +ol.reproj.Tile.prototype.unlistenSources_ = function() { + this.sourcesListenerKeys_.forEach(ol.events.unlistenByKey); + this.sourcesListenerKeys_ = null; +}; + +goog.provide('ol.TileUrlFunction'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.math'); +goog.require('ol.tilecoord'); + + +/** + * @param {string} template Template. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @return {ol.TileUrlFunctionType} Tile URL function. + */ +ol.TileUrlFunction.createFromTemplate = function(template, tileGrid) { + var zRegEx = /\{z\}/g; + var xRegEx = /\{x\}/g; + var yRegEx = /\{y\}/g; + var dashYRegEx = /\{-y\}/g; + return ( + /** + * @param {ol.TileCoord} tileCoord Tile Coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function(tileCoord, pixelRatio, projection) { + if (!tileCoord) { + return undefined; + } else { + return template.replace(zRegEx, tileCoord[0].toString()) + .replace(xRegEx, tileCoord[1].toString()) + .replace(yRegEx, function() { + var y = -tileCoord[2] - 1; + return y.toString(); + }) + .replace(dashYRegEx, function() { + var z = tileCoord[0]; + var range = tileGrid.getFullTileRange(z); + ol.asserts.assert(range, 55); // The {-y} placeholder requires a tile grid with extent + var y = range.getHeight() + tileCoord[2]; + return y.toString(); + }); + } + }); +}; + + +/** + * @param {Array.<string>} templates Templates. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @return {ol.TileUrlFunctionType} Tile URL function. + */ +ol.TileUrlFunction.createFromTemplates = function(templates, tileGrid) { + var len = templates.length; + var tileUrlFunctions = new Array(len); + for (var i = 0; i < len; ++i) { + tileUrlFunctions[i] = ol.TileUrlFunction.createFromTemplate( + templates[i], tileGrid); + } + return ol.TileUrlFunction.createFromTileUrlFunctions(tileUrlFunctions); +}; + + +/** + * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions. + * @return {ol.TileUrlFunctionType} Tile URL function. + */ +ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) { + ol.DEBUG && console.assert(tileUrlFunctions.length > 0, + 'Length of tile url functions should be greater than 0'); + if (tileUrlFunctions.length === 1) { + return tileUrlFunctions[0]; + } + return ( + /** + * @param {ol.TileCoord} tileCoord Tile Coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function(tileCoord, pixelRatio, projection) { + if (!tileCoord) { + return undefined; + } else { + var h = ol.tilecoord.hash(tileCoord); + var index = ol.math.modulo(h, tileUrlFunctions.length); + return tileUrlFunctions[index](tileCoord, pixelRatio, projection); + } + }); +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ +ol.TileUrlFunction.nullTileUrlFunction = function(tileCoord, pixelRatio, projection) { + return undefined; +}; + + +/** + * @param {string} url URL. + * @return {Array.<string>} Array of urls. + */ +ol.TileUrlFunction.expandUrl = function(url) { + var urls = []; + var match = /\{([a-z])-([a-z])\}/.exec(url); + if (match) { + // char range + var startCharCode = match[1].charCodeAt(0); + var stopCharCode = match[2].charCodeAt(0); + var charCode; + for (charCode = startCharCode; charCode <= stopCharCode; ++charCode) { + urls.push(url.replace(match[0], String.fromCharCode(charCode))); + } + return urls; + } + match = match = /\{(\d+)-(\d+)\}/.exec(url); + if (match) { + // number range + var stop = parseInt(match[2], 10); + for (var i = parseInt(match[1], 10); i <= stop; i++) { + urls.push(url.replace(match[0], i.toString())); + } + return urls; + } + urls.push(url); + return urls; +}; + +goog.provide('ol.TileCache'); + +goog.require('ol'); +goog.require('ol.structs.LRUCache'); + + +/** + * @constructor + * @extends {ol.structs.LRUCache.<ol.Tile>} + * @param {number=} opt_highWaterMark High water mark. + * @struct + */ +ol.TileCache = function(opt_highWaterMark) { + + ol.structs.LRUCache.call(this); + + /** + * @private + * @type {number} + */ + this.highWaterMark_ = opt_highWaterMark !== undefined ? opt_highWaterMark : 2048; + +}; +ol.inherits(ol.TileCache, ol.structs.LRUCache); + + +/** + * @return {boolean} Can expire cache. + */ +ol.TileCache.prototype.canExpireCache = function() { + return this.getCount() > this.highWaterMark_; +}; + + +/** + * @param {Object.<string, ol.TileRange>} usedTiles Used tiles. + */ +ol.TileCache.prototype.expireCache = function(usedTiles) { + var tile, zKey; + while (this.canExpireCache()) { + tile = this.peekLast(); + zKey = tile.tileCoord[0].toString(); + if (zKey in usedTiles && usedTiles[zKey].contains(tile.tileCoord)) { + break; + } else { + this.pop().dispose(); + } + } +}; + +goog.provide('ol.source.Tile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.TileCache'); +goog.require('ol.events.Event'); +goog.require('ol.proj'); +goog.require('ol.size'); +goog.require('ol.source.Source'); +goog.require('ol.tilecoord'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for sources providing images divided into a tile grid. + * + * @constructor + * @extends {ol.source.Source} + * @param {ol.SourceTileOptions} options Tile source options. + * @api + */ +ol.source.Tile = function(options) { + + ol.source.Source.call(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + projection: options.projection, + state: options.state, + wrapX: options.wrapX + }); + + /** + * @private + * @type {boolean} + */ + this.opaque_ = options.opaque !== undefined ? options.opaque : false; + + /** + * @private + * @type {number} + */ + this.tilePixelRatio_ = options.tilePixelRatio !== undefined ? + options.tilePixelRatio : 1; + + /** + * @protected + * @type {ol.tilegrid.TileGrid} + */ + this.tileGrid = options.tileGrid !== undefined ? options.tileGrid : null; + + /** + * @protected + * @type {ol.TileCache} + */ + this.tileCache = new ol.TileCache(options.cacheSize); + + /** + * @protected + * @type {ol.Size} + */ + this.tmpSize = [0, 0]; + + /** + * @private + * @type {string} + */ + this.key_ = ''; + +}; +ol.inherits(ol.source.Tile, ol.source.Source); + + +/** + * @return {boolean} Can expire cache. + */ +ol.source.Tile.prototype.canExpireCache = function() { + return this.tileCache.canExpireCache(); +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @param {Object.<string, ol.TileRange>} usedTiles Used tiles. + */ +ol.source.Tile.prototype.expireCache = function(projection, usedTiles) { + var tileCache = this.getTileCacheForProjection(projection); + if (tileCache) { + tileCache.expireCache(usedTiles); + } +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @param {number} z Zoom level. + * @param {ol.TileRange} tileRange Tile range. + * @param {function(ol.Tile):(boolean|undefined)} callback Called with each + * loaded tile. If the callback returns `false`, the tile will not be + * considered loaded. + * @return {boolean} The tile range is fully covered with loaded tiles. + */ +ol.source.Tile.prototype.forEachLoadedTile = function(projection, z, tileRange, callback) { + var tileCache = this.getTileCacheForProjection(projection); + if (!tileCache) { + return false; + } + + var covered = true; + var tile, tileCoordKey, loaded; + for (var x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (var y = tileRange.minY; y <= tileRange.maxY; ++y) { + tileCoordKey = this.getKeyZXY(z, x, y); + loaded = false; + if (tileCache.containsKey(tileCoordKey)) { + tile = /** @type {!ol.Tile} */ (tileCache.get(tileCoordKey)); + loaded = tile.getState() === ol.Tile.State.LOADED; + if (loaded) { + loaded = (callback(tile) !== false); + } + } + if (!loaded) { + covered = false; + } + } + } + return covered; +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @return {number} Gutter. + */ +ol.source.Tile.prototype.getGutter = function(projection) { + return 0; +}; + + +/** + * Return the key to be used for all tiles in the source. + * @return {string} The key for all tiles. + * @protected + */ +ol.source.Tile.prototype.getKey = function() { + return this.key_; +}; + + +/** + * Set the value to be used as the key for all tiles in the source. + * @param {string} key The key for tiles. + * @protected + */ +ol.source.Tile.prototype.setKey = function(key) { + if (this.key_ !== key) { + this.key_ = key; + this.changed(); + } +}; + + +/** + * @param {number} z Z. + * @param {number} x X. + * @param {number} y Y. + * @return {string} Key. + * @protected + */ +ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @return {boolean} Opaque. + */ +ol.source.Tile.prototype.getOpaque = function(projection) { + return this.opaque_; +}; + + +/** + * @inheritDoc + */ +ol.source.Tile.prototype.getResolutions = function() { + return this.tileGrid.getResolutions(); +}; + + +/** + * @abstract + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {!ol.Tile} Tile. + */ +ol.source.Tile.prototype.getTile = function(z, x, y, pixelRatio, projection) {}; + + +/** + * Return the tile grid of the tile source. + * @return {ol.tilegrid.TileGrid} Tile grid. + * @api stable + */ +ol.source.Tile.prototype.getTileGrid = function() { + return this.tileGrid; +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @return {!ol.tilegrid.TileGrid} Tile grid. + */ +ol.source.Tile.prototype.getTileGridForProjection = function(projection) { + if (!this.tileGrid) { + return ol.tilegrid.getForProjection(projection); + } else { + return this.tileGrid; + } +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + * @return {ol.TileCache} Tile cache. + * @protected + */ +ol.source.Tile.prototype.getTileCacheForProjection = function(projection) { + var thisProj = this.getProjection(); + if (thisProj && !ol.proj.equivalent(thisProj, projection)) { + return null; + } else { + return this.tileCache; + } +}; + + +/** + * Get the tile pixel ratio for this source. Subclasses may override this + * method, which is meant to return a supported pixel ratio that matches the + * provided `opt_pixelRatio` as close as possible. When no `opt_pixelRatio` is + * provided, it is meant to return `this.tilePixelRatio_`. + * @param {number=} opt_pixelRatio Pixel ratio. + * @return {number} Tile pixel ratio. + */ +ol.source.Tile.prototype.getTilePixelRatio = function(opt_pixelRatio) { + return this.tilePixelRatio_; +}; + + +/** + * @param {number} z Z. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.Size} Tile size. + */ +ol.source.Tile.prototype.getTilePixelSize = function(z, pixelRatio, projection) { + var tileGrid = this.getTileGridForProjection(projection); + var tilePixelRatio = this.getTilePixelRatio(pixelRatio); + var tileSize = ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize); + if (tilePixelRatio == 1) { + return tileSize; + } else { + return ol.size.scale(tileSize, tilePixelRatio, this.tmpSize); + } +}; + + +/** + * Returns a tile coordinate wrapped around the x-axis. When the tile coordinate + * is outside the resolution and extent range of the tile grid, `null` will be + * returned. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.proj.Projection=} opt_projection Projection. + * @return {ol.TileCoord} Tile coordinate to be passed to the tileUrlFunction or + * null if no tile URL should be created for the passed `tileCoord`. + */ +ol.source.Tile.prototype.getTileCoordForTileUrlFunction = function(tileCoord, opt_projection) { + var projection = opt_projection !== undefined ? + opt_projection : this.getProjection(); + var tileGrid = this.getTileGridForProjection(projection); + if (this.getWrapX() && projection.isGlobal()) { + tileCoord = ol.tilegrid.wrapX(tileGrid, tileCoord, projection); + } + return ol.tilecoord.withinExtentAndZ(tileCoord, tileGrid) ? tileCoord : null; +}; + + +/** + * @inheritDoc + */ +ol.source.Tile.prototype.refresh = function() { + this.tileCache.clear(); + this.changed(); +}; + + +/** + * Marks a tile coord as being used, without triggering a load. + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @param {ol.proj.Projection} projection Projection. + */ +ol.source.Tile.prototype.useTile = ol.nullFunction; + + +/** + * @classdesc + * Events emitted by {@link ol.source.Tile} instances are instances of this + * type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.source.Tile.Event} + * @param {string} type Type. + * @param {ol.Tile} tile The tile. + */ +ol.source.Tile.Event = function(type, tile) { + + ol.events.Event.call(this, type); + + /** + * The tile related to the event. + * @type {ol.Tile} + * @api + */ + this.tile = tile; + +}; +ol.inherits(ol.source.Tile.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.source.Tile.EventType = { + + /** + * Triggered when a tile starts loading. + * @event ol.source.Tile.Event#tileloadstart + * @api stable + */ + TILELOADSTART: 'tileloadstart', + + /** + * Triggered when a tile finishes loading. + * @event ol.source.Tile.Event#tileloadend + * @api stable + */ + TILELOADEND: 'tileloadend', + + /** + * Triggered if tile loading results in an error. + * @event ol.source.Tile.Event#tileloaderror + * @api stable + */ + TILELOADERROR: 'tileloaderror' + +}; + +goog.provide('ol.source.UrlTile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.source.Tile'); + + +/** + * @classdesc + * Base class for sources providing tiles divided into a tile grid over http. + * + * @constructor + * @fires ol.source.Tile.Event + * @extends {ol.source.Tile} + * @param {ol.SourceUrlTileOptions} options Image tile options. + */ +ol.source.UrlTile = function(options) { + + ol.source.Tile.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + extent: options.extent, + logo: options.logo, + opaque: options.opaque, + projection: options.projection, + state: options.state, + tileGrid: options.tileGrid, + tilePixelRatio: options.tilePixelRatio, + wrapX: options.wrapX + }); + + /** + * @protected + * @type {ol.TileLoadFunctionType} + */ + this.tileLoadFunction = options.tileLoadFunction; + + /** + * @protected + * @type {ol.TileUrlFunctionType} + */ + this.tileUrlFunction = this.fixedTileUrlFunction ? + this.fixedTileUrlFunction.bind(this) : + ol.TileUrlFunction.nullTileUrlFunction; + + /** + * @protected + * @type {!Array.<string>|null} + */ + this.urls = null; + + if (options.urls) { + this.setUrls(options.urls); + } else if (options.url) { + this.setUrl(options.url); + } + if (options.tileUrlFunction) { + this.setTileUrlFunction(options.tileUrlFunction); + } + +}; +ol.inherits(ol.source.UrlTile, ol.source.Tile); + + +/** + * @type {ol.TileUrlFunctionType|undefined} + * @protected + */ +ol.source.UrlTile.prototype.fixedTileUrlFunction; + +/** + * Return the tile load function of the source. + * @return {ol.TileLoadFunctionType} TileLoadFunction + * @api + */ +ol.source.UrlTile.prototype.getTileLoadFunction = function() { + return this.tileLoadFunction; +}; + + +/** + * Return the tile URL function of the source. + * @return {ol.TileUrlFunctionType} TileUrlFunction + * @api + */ +ol.source.UrlTile.prototype.getTileUrlFunction = function() { + return this.tileUrlFunction; +}; + + +/** + * Return the URLs used for this source. + * When a tileUrlFunction is used instead of url or urls, + * null will be returned. + * @return {!Array.<string>|null} URLs. + * @api + */ +ol.source.UrlTile.prototype.getUrls = function() { + return this.urls; +}; + + +/** + * Handle tile change events. + * @param {ol.events.Event} event Event. + * @protected + */ +ol.source.UrlTile.prototype.handleTileChange = function(event) { + var tile = /** @type {ol.Tile} */ (event.target); + switch (tile.getState()) { + case ol.Tile.State.LOADING: + this.dispatchEvent( + new ol.source.Tile.Event(ol.source.Tile.EventType.TILELOADSTART, tile)); + break; + case ol.Tile.State.LOADED: + this.dispatchEvent( + new ol.source.Tile.Event(ol.source.Tile.EventType.TILELOADEND, tile)); + break; + case ol.Tile.State.ERROR: + this.dispatchEvent( + new ol.source.Tile.Event(ol.source.Tile.EventType.TILELOADERROR, tile)); + break; + default: + // pass + } +}; + + +/** + * Set the tile load function of the source. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. + * @api + */ +ol.source.UrlTile.prototype.setTileLoadFunction = function(tileLoadFunction) { + this.tileCache.clear(); + this.tileLoadFunction = tileLoadFunction; + this.changed(); +}; + + +/** + * Set the tile URL function of the source. + * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function. + * @param {string=} opt_key Optional new tile key for the source. + * @api + */ +ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction, opt_key) { + this.tileUrlFunction = tileUrlFunction; + if (typeof opt_key !== 'undefined') { + this.setKey(opt_key); + } else { + this.changed(); + } +}; + + +/** + * Set the URL to use for requests. + * @param {string} url URL. + * @api stable + */ +ol.source.UrlTile.prototype.setUrl = function(url) { + var urls = this.urls = ol.TileUrlFunction.expandUrl(url); + this.setTileUrlFunction(this.fixedTileUrlFunction ? + this.fixedTileUrlFunction.bind(this) : + ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), url); +}; + + +/** + * Set the URLs to use for requests. + * @param {Array.<string>} urls URLs. + * @api stable + */ +ol.source.UrlTile.prototype.setUrls = function(urls) { + this.urls = urls; + var key = urls.join('\n'); + this.setTileUrlFunction(this.fixedTileUrlFunction ? + this.fixedTileUrlFunction.bind(this) : + ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), key); +}; + + +/** + * @inheritDoc + */ +ol.source.UrlTile.prototype.useTile = function(z, x, y) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + this.tileCache.get(tileCoordKey); + } +}; + +goog.provide('ol.source.TileImage'); + +goog.require('ol'); +goog.require('ol.ImageTile'); +goog.require('ol.Tile'); +goog.require('ol.TileCache'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.proj'); +goog.require('ol.reproj.Tile'); +goog.require('ol.source.UrlTile'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * Base class for sources providing images divided into a tile grid. + * + * @constructor + * @fires ol.source.Tile.Event + * @extends {ol.source.UrlTile} + * @param {olx.source.TileImageOptions} options Image tile options. + * @api + */ +ol.source.TileImage = function(options) { + + ol.source.UrlTile.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + extent: options.extent, + logo: options.logo, + opaque: options.opaque, + projection: options.projection, + state: options.state, + tileGrid: options.tileGrid, + tileLoadFunction: options.tileLoadFunction ? + options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction, + tilePixelRatio: options.tilePixelRatio, + tileUrlFunction: options.tileUrlFunction, + url: options.url, + urls: options.urls, + wrapX: options.wrapX + }); + + /** + * @protected + * @type {?string} + */ + this.crossOrigin = + options.crossOrigin !== undefined ? options.crossOrigin : null; + + /** + * @protected + * @type {function(new: ol.ImageTile, ol.TileCoord, ol.Tile.State, string, + * ?string, ol.TileLoadFunctionType)} + */ + this.tileClass = options.tileClass !== undefined ? + options.tileClass : ol.ImageTile; + + /** + * @protected + * @type {Object.<string, ol.TileCache>} + */ + this.tileCacheForProjection = {}; + + /** + * @protected + * @type {Object.<string, ol.tilegrid.TileGrid>} + */ + this.tileGridForProjection = {}; + + /** + * @private + * @type {number|undefined} + */ + this.reprojectionErrorThreshold_ = options.reprojectionErrorThreshold; + + /** + * @private + * @type {boolean} + */ + this.renderReprojectionEdges_ = false; +}; +ol.inherits(ol.source.TileImage, ol.source.UrlTile); + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.canExpireCache = function() { + if (!ol.ENABLE_RASTER_REPROJECTION) { + return ol.source.UrlTile.prototype.canExpireCache.call(this); + } + if (this.tileCache.canExpireCache()) { + return true; + } else { + for (var key in this.tileCacheForProjection) { + if (this.tileCacheForProjection[key].canExpireCache()) { + return true; + } + } + } + return false; +}; + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.expireCache = function(projection, usedTiles) { + if (!ol.ENABLE_RASTER_REPROJECTION) { + ol.source.UrlTile.prototype.expireCache.call(this, projection, usedTiles); + return; + } + var usedTileCache = this.getTileCacheForProjection(projection); + + this.tileCache.expireCache(this.tileCache == usedTileCache ? usedTiles : {}); + for (var id in this.tileCacheForProjection) { + var tileCache = this.tileCacheForProjection[id]; + tileCache.expireCache(tileCache == usedTileCache ? usedTiles : {}); + } +}; + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.getGutter = function(projection) { + if (ol.ENABLE_RASTER_REPROJECTION && + this.getProjection() && projection && + !ol.proj.equivalent(this.getProjection(), projection)) { + return 0; + } else { + return this.getGutterInternal(); + } +}; + + +/** + * @protected + * @return {number} Gutter. + */ +ol.source.TileImage.prototype.getGutterInternal = function() { + return 0; +}; + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.getOpaque = function(projection) { + if (ol.ENABLE_RASTER_REPROJECTION && + this.getProjection() && projection && + !ol.proj.equivalent(this.getProjection(), projection)) { + return false; + } else { + return ol.source.UrlTile.prototype.getOpaque.call(this, projection); + } +}; + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.getTileGridForProjection = function(projection) { + if (!ol.ENABLE_RASTER_REPROJECTION) { + return ol.source.UrlTile.prototype.getTileGridForProjection.call(this, projection); + } + var thisProj = this.getProjection(); + if (this.tileGrid && + (!thisProj || ol.proj.equivalent(thisProj, projection))) { + return this.tileGrid; + } else { + var projKey = ol.getUid(projection).toString(); + if (!(projKey in this.tileGridForProjection)) { + this.tileGridForProjection[projKey] = + ol.tilegrid.getForProjection(projection); + } + return /** @type {!ol.tilegrid.TileGrid} */ (this.tileGridForProjection[projKey]); + } +}; + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.getTileCacheForProjection = function(projection) { + if (!ol.ENABLE_RASTER_REPROJECTION) { + return ol.source.UrlTile.prototype.getTileCacheForProjection.call(this, projection); + } + var thisProj = this.getProjection(); + if (!thisProj || ol.proj.equivalent(thisProj, projection)) { + return this.tileCache; + } else { + var projKey = ol.getUid(projection).toString(); + if (!(projKey in this.tileCacheForProjection)) { + this.tileCacheForProjection[projKey] = new ol.TileCache(); + } + return this.tileCacheForProjection[projKey]; + } +}; + + +/** + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {string} key The key set on the tile. + * @return {!ol.Tile} Tile. + * @private + */ +ol.source.TileImage.prototype.createTile_ = function(z, x, y, pixelRatio, projection, key) { + var tileCoord = [z, x, y]; + var urlTileCoord = this.getTileCoordForTileUrlFunction( + tileCoord, projection); + var tileUrl = urlTileCoord ? + this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined; + var tile = new this.tileClass( + tileCoord, + tileUrl !== undefined ? ol.Tile.State.IDLE : ol.Tile.State.EMPTY, + tileUrl !== undefined ? tileUrl : '', + this.crossOrigin, + this.tileLoadFunction); + tile.key = key; + ol.events.listen(tile, ol.events.EventType.CHANGE, + this.handleTileChange, this); + return tile; +}; + + +/** + * @inheritDoc + */ +ol.source.TileImage.prototype.getTile = function(z, x, y, pixelRatio, projection) { + if (!ol.ENABLE_RASTER_REPROJECTION || + !this.getProjection() || + !projection || + ol.proj.equivalent(this.getProjection(), projection)) { + return this.getTileInternal(z, x, y, pixelRatio, /** @type {!ol.proj.Projection} */ (projection)); + } else { + var cache = this.getTileCacheForProjection(projection); + var tileCoord = [z, x, y]; + var tile; + var tileCoordKey = this.getKeyZXY.apply(this, tileCoord); + if (cache.containsKey(tileCoordKey)) { + tile = /** @type {!ol.Tile} */ (cache.get(tileCoordKey)); + } + var key = this.getKey(); + if (tile && tile.key == key) { + return tile; + } else { + var sourceProjection = /** @type {!ol.proj.Projection} */ (this.getProjection()); + var sourceTileGrid = this.getTileGridForProjection(sourceProjection); + var targetTileGrid = this.getTileGridForProjection(projection); + var wrappedTileCoord = + this.getTileCoordForTileUrlFunction(tileCoord, projection); + var newTile = new ol.reproj.Tile( + sourceProjection, sourceTileGrid, + projection, targetTileGrid, + tileCoord, wrappedTileCoord, this.getTilePixelRatio(pixelRatio), + this.getGutterInternal(), + function(z, x, y, pixelRatio) { + return this.getTileInternal(z, x, y, pixelRatio, sourceProjection); + }.bind(this), this.reprojectionErrorThreshold_, + this.renderReprojectionEdges_); + newTile.key = key; + + if (tile) { + newTile.interimTile = tile; + cache.replace(tileCoordKey, newTile); + } else { + cache.set(tileCoordKey, newTile); + } + return newTile; + } + } +}; + + +/** + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @param {number} pixelRatio Pixel ratio. + * @param {!ol.proj.Projection} projection Projection. + * @return {!ol.Tile} Tile. + * @protected + */ +ol.source.TileImage.prototype.getTileInternal = function(z, x, y, pixelRatio, projection) { + var tile = null; + var tileCoordKey = this.getKeyZXY(z, x, y); + var key = this.getKey(); + if (!this.tileCache.containsKey(tileCoordKey)) { + tile = this.createTile_(z, x, y, pixelRatio, projection, key); + this.tileCache.set(tileCoordKey, tile); + } else { + tile = this.tileCache.get(tileCoordKey); + if (tile.key != key) { + // The source's params changed. If the tile has an interim tile and if we + // can use it then we use it. Otherwise we create a new tile. In both + // cases we attempt to assign an interim tile to the new tile. + var interimTile = tile; + tile = this.createTile_(z, x, y, pixelRatio, projection, key); + + //make the new tile the head of the list, + if (interimTile.getState() == ol.Tile.State.IDLE) { + //the old tile hasn't begun loading yet, and is now outdated, so we can simply discard it + tile.interimTile = interimTile.interimTile; + } else { + tile.interimTile = interimTile; + } + tile.refreshInterimChain(); + this.tileCache.replace(tileCoordKey, tile); + } + } + return tile; +}; + + +/** + * Sets whether to render reprojection edges or not (usually for debugging). + * @param {boolean} render Render the edges. + * @api + */ +ol.source.TileImage.prototype.setRenderReprojectionEdges = function(render) { + if (!ol.ENABLE_RASTER_REPROJECTION || + this.renderReprojectionEdges_ == render) { + return; + } + this.renderReprojectionEdges_ = render; + for (var id in this.tileCacheForProjection) { + this.tileCacheForProjection[id].clear(); + } + this.changed(); +}; + + +/** + * Sets the tile grid to use when reprojecting the tiles to the given + * projection instead of the default tile grid for the projection. + * + * This can be useful when the default tile grid cannot be created + * (e.g. projection has no extent defined) or + * for optimization reasons (custom tile size, resolutions, ...). + * + * @param {ol.ProjectionLike} projection Projection. + * @param {ol.tilegrid.TileGrid} tilegrid Tile grid to use for the projection. + * @api + */ +ol.source.TileImage.prototype.setTileGridForProjection = function(projection, tilegrid) { + if (ol.ENABLE_RASTER_REPROJECTION) { + var proj = ol.proj.get(projection); + if (proj) { + var projKey = ol.getUid(proj).toString(); + if (!(projKey in this.tileGridForProjection)) { + this.tileGridForProjection[projKey] = tilegrid; + } + } + } +}; + + +/** + * @param {ol.ImageTile} imageTile Image tile. + * @param {string} src Source. + */ +ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) { + imageTile.getImage().src = src; +}; + +goog.provide('ol.source.BingMaps'); + +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.extent'); +goog.require('ol.net'); +goog.require('ol.proj'); +goog.require('ol.source.State'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilecoord'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * Layer source for Bing Maps tile data. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.BingMapsOptions} options Bing Maps options. + * @api stable + */ +ol.source.BingMaps = function(options) { + + /** + * @private + * @type {boolean} + */ + this.hidpi_ = options.hidpi !== undefined ? options.hidpi : false; + + ol.source.TileImage.call(this, { + cacheSize: options.cacheSize, + crossOrigin: 'anonymous', + opaque: true, + projection: ol.proj.get('EPSG:3857'), + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + state: ol.source.State.LOADING, + tileLoadFunction: options.tileLoadFunction, + tilePixelRatio: this.hidpi_ ? 2 : 1, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + + /** + * @private + * @type {string} + */ + this.culture_ = options.culture !== undefined ? options.culture : 'en-us'; + + /** + * @private + * @type {number} + */ + this.maxZoom_ = options.maxZoom !== undefined ? options.maxZoom : -1; + + /** + * @private + * @type {string} + */ + this.apiKey_ = options.key; + + /** + * @private + * @type {string} + */ + this.imagerySet_ = options.imagerySet; + + var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/' + + this.imagerySet_ + + '?uriScheme=https&include=ImageryProviders&key=' + this.apiKey_; + + ol.net.jsonp(url, this.handleImageryMetadataResponse.bind(this), undefined, + 'jsonp'); + +}; +ol.inherits(ol.source.BingMaps, ol.source.TileImage); + + +/** + * The attribution containing a link to the Microsoft® Bingâ„¢ Maps Platform APIs’ + * Terms Of Use. + * @const + * @type {ol.Attribution} + * @api + */ +ol.source.BingMaps.TOS_ATTRIBUTION = new ol.Attribution({ + html: '<a class="ol-attribution-bing-tos" ' + + 'href="http://www.microsoft.com/maps/product/terms.html">' + + 'Terms of Use</a>' +}); + + +/** + * Get the api key used for this source. + * + * @return {string} The api key. + * @api + */ +ol.source.BingMaps.prototype.getApiKey = function() { + return this.apiKey_; +}; + + +/** + * Get the imagery set associated with this source. + * + * @return {string} The imagery set. + * @api + */ +ol.source.BingMaps.prototype.getImagerySet = function() { + return this.imagerySet_; +}; + + +/** + * @param {BingMapsImageryMetadataResponse} response Response. + */ +ol.source.BingMaps.prototype.handleImageryMetadataResponse = function(response) { + + if (response.statusCode != 200 || + response.statusDescription != 'OK' || + response.authenticationResultCode != 'ValidCredentials' || + response.resourceSets.length != 1 || + response.resourceSets[0].resources.length != 1) { + this.setState(ol.source.State.ERROR); + return; + } + + var brandLogoUri = response.brandLogoUri; + if (brandLogoUri.indexOf('https') == -1) { + brandLogoUri = brandLogoUri.replace('http', 'https'); + } + //var copyright = response.copyright; // FIXME do we need to display this? + var resource = response.resourceSets[0].resources[0]; + ol.DEBUG && console.assert(resource.imageWidth == resource.imageHeight, + 'resource has imageWidth equal to imageHeight, i.e. is square'); + var maxZoom = this.maxZoom_ == -1 ? resource.zoomMax : this.maxZoom_; + + var sourceProjection = this.getProjection(); + var extent = ol.tilegrid.extentFromProjection(sourceProjection); + var tileSize = resource.imageWidth == resource.imageHeight ? + resource.imageWidth : [resource.imageWidth, resource.imageHeight]; + var tileGrid = ol.tilegrid.createXYZ({ + extent: extent, + minZoom: resource.zoomMin, + maxZoom: maxZoom, + tileSize: tileSize / this.getTilePixelRatio() + }); + this.tileGrid = tileGrid; + + var culture = this.culture_; + var hidpi = this.hidpi_; + this.tileUrlFunction = ol.TileUrlFunction.createFromTileUrlFunctions( + resource.imageUrlSubdomains.map(function(subdomain) { + var quadKeyTileCoord = [0, 0, 0]; + var imageUrl = resource.imageUrl + .replace('{subdomain}', subdomain) + .replace('{culture}', culture); + return ( + /** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function(tileCoord, pixelRatio, projection) { + ol.DEBUG && console.assert(ol.proj.equivalent( + projection, sourceProjection), + 'projections are equivalent'); + if (!tileCoord) { + return undefined; + } else { + ol.tilecoord.createOrUpdate(tileCoord[0], tileCoord[1], + -tileCoord[2] - 1, quadKeyTileCoord); + var url = imageUrl; + if (hidpi) { + url += '&dpi=d1&device=mobile'; + } + return url.replace('{quadkey}', ol.tilecoord.quadKey( + quadKeyTileCoord)); + } + }); + })); + + if (resource.imageryProviders) { + var transform = ol.proj.getTransformFromProjections( + ol.proj.get('EPSG:4326'), this.getProjection()); + + var attributions = resource.imageryProviders.map(function(imageryProvider) { + var html = imageryProvider.attribution; + /** @type {Object.<string, Array.<ol.TileRange>>} */ + var tileRanges = {}; + imageryProvider.coverageAreas.forEach(function(coverageArea) { + var minZ = coverageArea.zoomMin; + var maxZ = Math.min(coverageArea.zoomMax, maxZoom); + var bbox = coverageArea.bbox; + var epsg4326Extent = [bbox[1], bbox[0], bbox[3], bbox[2]]; + var extent = ol.extent.applyTransform(epsg4326Extent, transform); + var tileRange, z, zKey; + for (z = minZ; z <= maxZ; ++z) { + zKey = z.toString(); + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + if (zKey in tileRanges) { + tileRanges[zKey].push(tileRange); + } else { + tileRanges[zKey] = [tileRange]; + } + } + }); + return new ol.Attribution({html: html, tileRanges: tileRanges}); + }); + attributions.push(ol.source.BingMaps.TOS_ATTRIBUTION); + this.setAttributions(attributions); + } + + this.setLogo(brandLogoUri); + + this.setState(ol.source.State.READY); + +}; + +goog.provide('ol.source.XYZ'); + +goog.require('ol'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * Layer source for tile data with URLs in a set XYZ format that are + * defined in a URL template. By default, this follows the widely-used + * Google grid where `x` 0 and `y` 0 are in the top left. Grids like + * TMS where `x` 0 and `y` 0 are in the bottom left can be used by + * using the `{-y}` placeholder in the URL template, so long as the + * source does not have a custom tile grid. In this case, + * {@link ol.source.TileImage} can be used with a `tileUrlFunction` + * such as: + * + * tileUrlFunction: function(coordinate) { + * return 'http://mapserver.com/' + coordinate[0] + '/' + + * coordinate[1] + '/' + coordinate[2] + '.png'; + * } + * + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.XYZOptions=} opt_options XYZ options. + * @api stable + */ +ol.source.XYZ = function(opt_options) { + var options = opt_options || {}; + var projection = options.projection !== undefined ? + options.projection : 'EPSG:3857'; + + var tileGrid = options.tileGrid !== undefined ? options.tileGrid : + ol.tilegrid.createXYZ({ + extent: ol.tilegrid.extentFromProjection(projection), + maxZoom: options.maxZoom, + minZoom: options.minZoom, + tileSize: options.tileSize + }); + + ol.source.TileImage.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + logo: options.logo, + opaque: options.opaque, + projection: projection, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileGrid: tileGrid, + tileLoadFunction: options.tileLoadFunction, + tilePixelRatio: options.tilePixelRatio, + tileUrlFunction: options.tileUrlFunction, + url: options.url, + urls: options.urls, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + +}; +ol.inherits(ol.source.XYZ, ol.source.TileImage); + +goog.provide('ol.source.CartoDB'); + +goog.require('ol'); +goog.require('ol.obj'); +goog.require('ol.source.State'); +goog.require('ol.source.XYZ'); + + +/** + * @classdesc + * Layer source for the CartoDB tiles. + * + * @constructor + * @extends {ol.source.XYZ} + * @param {olx.source.CartoDBOptions} options CartoDB options. + * @api + */ +ol.source.CartoDB = function(options) { + + /** + * @type {string} + * @private + */ + this.account_ = options.account; + + /** + * @type {string} + * @private + */ + this.mapId_ = options.map || ''; + + /** + * @type {!Object} + * @private + */ + this.config_ = options.config || {}; + + /** + * @type {!Object.<string, CartoDBLayerInfo>} + * @private + */ + this.templateCache_ = {}; + + ol.source.XYZ.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + logo: options.logo, + maxZoom: options.maxZoom !== undefined ? options.maxZoom : 18, + minZoom: options.minZoom, + projection: options.projection, + state: ol.source.State.LOADING, + wrapX: options.wrapX + }); + this.initializeMap_(); +}; +ol.inherits(ol.source.CartoDB, ol.source.XYZ); + + +/** + * Returns the current config. + * @return {!Object} The current configuration. + * @api + */ +ol.source.CartoDB.prototype.getConfig = function() { + return this.config_; +}; + + +/** + * Updates the carto db config. + * @param {Object} config a key-value lookup. Values will replace current values + * in the config. + * @api + */ +ol.source.CartoDB.prototype.updateConfig = function(config) { + ol.obj.assign(this.config_, config); + this.initializeMap_(); +}; + + +/** + * Sets the CartoDB config + * @param {Object} config In the case of anonymous maps, a CartoDB configuration + * object. + * If using named maps, a key-value lookup with the template parameters. + * @api + */ +ol.source.CartoDB.prototype.setConfig = function(config) { + this.config_ = config || {}; + this.initializeMap_(); +}; + + +/** + * Issue a request to initialize the CartoDB map. + * @private + */ +ol.source.CartoDB.prototype.initializeMap_ = function() { + var paramHash = JSON.stringify(this.config_); + if (this.templateCache_[paramHash]) { + this.applyTemplate_(this.templateCache_[paramHash]); + return; + } + var mapUrl = 'https://' + this.account_ + '.cartodb.com/api/v1/map'; + + if (this.mapId_) { + mapUrl += '/named/' + this.mapId_; + } + + var client = new XMLHttpRequest(); + client.addEventListener('load', this.handleInitResponse_.bind(this, paramHash)); + client.addEventListener('error', this.handleInitError_.bind(this)); + client.open('POST', mapUrl); + client.setRequestHeader('Content-type', 'application/json'); + client.send(JSON.stringify(this.config_)); +}; + + +/** + * Handle map initialization response. + * @param {string} paramHash a hash representing the parameter set that was used + * for the request + * @param {Event} event Event. + * @private + */ +ol.source.CartoDB.prototype.handleInitResponse_ = function(paramHash, event) { + var client = /** @type {XMLHttpRequest} */ (event.target); + // status will be 0 for file:// urls + if (!client.status || client.status >= 200 && client.status < 300) { + var response; + try { + response = /** @type {CartoDBLayerInfo} */(JSON.parse(client.responseText)); + } catch (err) { + this.setState(ol.source.State.ERROR); + return; + } + this.applyTemplate_(response); + this.templateCache_[paramHash] = response; + this.setState(ol.source.State.READY); + } else { + this.setState(ol.source.State.ERROR); + } +}; + + +/** + * @private + * @param {Event} event Event. + */ +ol.source.CartoDB.prototype.handleInitError_ = function(event) { + this.setState(ol.source.State.ERROR); +}; + + +/** + * Apply the new tile urls returned by carto db + * @param {CartoDBLayerInfo} data Result of carto db call. + * @private + */ +ol.source.CartoDB.prototype.applyTemplate_ = function(data) { + var tilesUrl = 'https://' + data.cdn_url.https + '/' + this.account_ + + '/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png'; + this.setUrl(tilesUrl); +}; + +// FIXME keep cluster cache by resolution ? +// FIXME distance not respected because of the centroid + +goog.provide('ol.source.Cluster'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.Feature'); +goog.require('ol.coordinate'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.geom.Point'); +goog.require('ol.source.Vector'); + + +/** + * @classdesc + * Layer source to cluster vector data. Works out of the box with point + * geometries. For other geometry types, or if not all geometries should be + * considered for clustering, a custom `geometryFunction` can be defined. + * + * @constructor + * @param {olx.source.ClusterOptions} options Constructor options. + * @extends {ol.source.Vector} + * @api + */ +ol.source.Cluster = function(options) { + ol.source.Vector.call(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + projection: options.projection, + wrapX: options.wrapX + }); + + /** + * @type {number|undefined} + * @private + */ + this.resolution_ = undefined; + + /** + * @type {number} + * @private + */ + this.distance_ = options.distance !== undefined ? options.distance : 20; + + /** + * @type {Array.<ol.Feature>} + * @private + */ + this.features_ = []; + + /** + * @param {ol.Feature} feature Feature. + * @return {ol.geom.Point} Cluster calculation point. + */ + this.geometryFunction_ = options.geometryFunction || function(feature) { + var geometry = /** @type {ol.geom.Point} */ (feature.getGeometry()); + ol.asserts.assert(geometry instanceof ol.geom.Point, + 10); // The default `geometryFunction` can only handle `ol.geom.Point` geometries + return geometry; + }; + + /** + * @type {ol.source.Vector} + * @private + */ + this.source_ = options.source; + + this.source_.on(ol.events.EventType.CHANGE, + ol.source.Cluster.prototype.refresh_, this); +}; +ol.inherits(ol.source.Cluster, ol.source.Vector); + + +/** + * Get a reference to the wrapped source. + * @return {ol.source.Vector} Source. + * @api + */ +ol.source.Cluster.prototype.getSource = function() { + return this.source_; +}; + + +/** + * @inheritDoc + */ +ol.source.Cluster.prototype.loadFeatures = function(extent, resolution, + projection) { + this.source_.loadFeatures(extent, resolution, projection); + if (resolution !== this.resolution_) { + this.clear(); + this.resolution_ = resolution; + this.cluster_(); + this.addFeatures(this.features_); + } +}; + + +/** + * Set the distance in pixels between clusters. + * @param {number} distance The distance in pixels. + * @api + */ +ol.source.Cluster.prototype.setDistance = function(distance) { + this.distance_ = distance; + this.refresh_(); +}; + + +/** + * handle the source changing + * @private + */ +ol.source.Cluster.prototype.refresh_ = function() { + this.clear(); + this.cluster_(); + this.addFeatures(this.features_); + this.changed(); +}; + + +/** + * @private + */ +ol.source.Cluster.prototype.cluster_ = function() { + if (this.resolution_ === undefined) { + return; + } + this.features_.length = 0; + var extent = ol.extent.createEmpty(); + var mapDistance = this.distance_ * this.resolution_; + var features = this.source_.getFeatures(); + + /** + * @type {!Object.<string, boolean>} + */ + var clustered = {}; + + for (var i = 0, ii = features.length; i < ii; i++) { + var feature = features[i]; + if (!(ol.getUid(feature).toString() in clustered)) { + var geometry = this.geometryFunction_(feature); + if (geometry) { + var coordinates = geometry.getCoordinates(); + ol.extent.createOrUpdateFromCoordinate(coordinates, extent); + ol.extent.buffer(extent, mapDistance, extent); + + var neighbors = this.source_.getFeaturesInExtent(extent); + ol.DEBUG && console.assert(neighbors.length >= 1, 'at least one neighbor found'); + neighbors = neighbors.filter(function(neighbor) { + var uid = ol.getUid(neighbor).toString(); + if (!(uid in clustered)) { + clustered[uid] = true; + return true; + } else { + return false; + } + }); + this.features_.push(this.createCluster_(neighbors)); + } + } + } + ol.DEBUG && console.assert( + Object.keys(clustered).length == this.source_.getFeatures().length, + 'number of clustered equals number of features in the source'); +}; + + +/** + * @param {Array.<ol.Feature>} features Features + * @return {ol.Feature} The cluster feature. + * @private + */ +ol.source.Cluster.prototype.createCluster_ = function(features) { + var centroid = [0, 0]; + for (var i = features.length - 1; i >= 0; --i) { + var geometry = this.geometryFunction_(features[i]); + if (geometry) { + ol.coordinate.add(centroid, geometry.getCoordinates()); + } else { + features.splice(i, 1); + } + } + ol.coordinate.scale(centroid, 1 / features.length); + + var cluster = new ol.Feature(new ol.geom.Point(centroid)); + cluster.set('features', features); + return cluster; +}; + +goog.provide('ol.uri'); + + +/** + * Appends query parameters to a URI. + * + * @param {string} uri The original URI, which may already have query data. + * @param {!Object} params An object where keys are URI-encoded parameter keys, + * and the values are arbitrary types or arrays. + * @return {string} The new URI. + */ +ol.uri.appendParams = function(uri, params) { + var keyParams = []; + // Skip any null or undefined parameter values + Object.keys(params).forEach(function(k) { + if (params[k] !== null && params[k] !== undefined) { + keyParams.push(k + '=' + encodeURIComponent(params[k])); + } + }); + var qs = keyParams.join('&'); + // remove any trailing ? or & + uri = uri.replace(/[?&]$/, ''); + // append ? or & depending on whether uri has existing parameters + uri = uri.indexOf('?') === -1 ? uri + '?' : uri + '&'; + return uri + qs; +}; + +goog.provide('ol.source.ImageArcGISRest'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.asserts'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.source.Image'); +goog.require('ol.uri'); + + +/** + * @classdesc + * Source for data from ArcGIS Rest services providing single, untiled images. + * Useful when underlying map service has labels. + * + * If underlying map service is not using labels, + * take advantage of ol image caching and use + * {@link ol.source.TileArcGISRest} data source. + * + * @constructor + * @fires ol.source.Image.Event + * @extends {ol.source.Image} + * @param {olx.source.ImageArcGISRestOptions=} opt_options Image ArcGIS Rest Options. + * @api + */ +ol.source.ImageArcGISRest = function(opt_options) { + + var options = opt_options || {}; + + ol.source.Image.call(this, { + attributions: options.attributions, + logo: options.logo, + projection: options.projection, + resolutions: options.resolutions + }); + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + options.crossOrigin !== undefined ? options.crossOrigin : null; + + /** + * @private + * @type {string|undefined} + */ + this.url_ = options.url; + + /** + * @private + * @type {ol.ImageLoadFunctionType} + */ + this.imageLoadFunction_ = options.imageLoadFunction !== undefined ? + options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction; + + + /** + * @private + * @type {!Object} + */ + this.params_ = options.params || {}; + + /** + * @private + * @type {ol.Image} + */ + this.image_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.imageSize_ = [0, 0]; + + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = 0; + + /** + * @private + * @type {number} + */ + this.ratio_ = options.ratio !== undefined ? options.ratio : 1.5; + +}; +ol.inherits(ol.source.ImageArcGISRest, ol.source.Image); + + +/** + * Get the user-provided params, i.e. those passed to the constructor through + * the "params" option, and possibly updated using the updateParams method. + * @return {Object} Params. + * @api stable + */ +ol.source.ImageArcGISRest.prototype.getParams = function() { + return this.params_; +}; + + +/** + * @inheritDoc + */ +ol.source.ImageArcGISRest.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) { + + if (this.url_ === undefined) { + return null; + } + + resolution = this.findNearestResolution(resolution); + + var image = this.image_; + if (image && + this.renderedRevision_ == this.getRevision() && + image.getResolution() == resolution && + image.getPixelRatio() == pixelRatio && + ol.extent.containsExtent(image.getExtent(), extent)) { + return image; + } + + var params = { + 'F': 'image', + 'FORMAT': 'PNG32', + 'TRANSPARENT': true + }; + ol.obj.assign(params, this.params_); + + extent = extent.slice(); + var centerX = (extent[0] + extent[2]) / 2; + var centerY = (extent[1] + extent[3]) / 2; + if (this.ratio_ != 1) { + var halfWidth = this.ratio_ * ol.extent.getWidth(extent) / 2; + var halfHeight = this.ratio_ * ol.extent.getHeight(extent) / 2; + extent[0] = centerX - halfWidth; + extent[1] = centerY - halfHeight; + extent[2] = centerX + halfWidth; + extent[3] = centerY + halfHeight; + } + + var imageResolution = resolution / pixelRatio; + + // Compute an integer width and height. + var width = Math.ceil(ol.extent.getWidth(extent) / imageResolution); + var height = Math.ceil(ol.extent.getHeight(extent) / imageResolution); + + // Modify the extent to match the integer width and height. + extent[0] = centerX - imageResolution * width / 2; + extent[2] = centerX + imageResolution * width / 2; + extent[1] = centerY - imageResolution * height / 2; + extent[3] = centerY + imageResolution * height / 2; + + this.imageSize_[0] = width; + this.imageSize_[1] = height; + + var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio, + projection, params); + + this.image_ = new ol.Image(extent, resolution, pixelRatio, + this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_); + + this.renderedRevision_ = this.getRevision(); + + ol.events.listen(this.image_, ol.events.EventType.CHANGE, + this.handleImageChange, this); + + return this.image_; + +}; + + +/** + * Return the image load function of the source. + * @return {ol.ImageLoadFunctionType} The image load function. + * @api + */ +ol.source.ImageArcGISRest.prototype.getImageLoadFunction = function() { + return this.imageLoadFunction_; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {Object} params Params. + * @return {string} Request URL. + * @private + */ +ol.source.ImageArcGISRest.prototype.getRequestUrl_ = function(extent, size, pixelRatio, projection, params) { + + ol.DEBUG && console.assert(this.url_ !== undefined, 'url is defined'); + + // ArcGIS Server only wants the numeric portion of the projection ID. + var srid = projection.getCode().split(':').pop(); + + params['SIZE'] = size[0] + ',' + size[1]; + params['BBOX'] = extent.join(','); + params['BBOXSR'] = srid; + params['IMAGESR'] = srid; + params['DPI'] = 90 * pixelRatio; + + var url = this.url_; + + var modifiedUrl = url + .replace(/MapServer\/?$/, 'MapServer/export') + .replace(/ImageServer\/?$/, 'ImageServer/exportImage'); + if (modifiedUrl == url) { + ol.asserts.assert(false, 50); // `options.featureTypes` should be an Array + } + return ol.uri.appendParams(modifiedUrl, params); +}; + + +/** + * Return the URL used for this ArcGIS source. + * @return {string|undefined} URL. + * @api stable + */ +ol.source.ImageArcGISRest.prototype.getUrl = function() { + return this.url_; +}; + + +/** + * Set the image load function of the source. + * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function. + * @api + */ +ol.source.ImageArcGISRest.prototype.setImageLoadFunction = function(imageLoadFunction) { + this.image_ = null; + this.imageLoadFunction_ = imageLoadFunction; + this.changed(); +}; + + +/** + * Set the URL to use for requests. + * @param {string|undefined} url URL. + * @api stable + */ +ol.source.ImageArcGISRest.prototype.setUrl = function(url) { + if (url != this.url_) { + this.url_ = url; + this.image_ = null; + this.changed(); + } +}; + + +/** + * Update the user-provided params. + * @param {Object} params Params. + * @api stable + */ +ol.source.ImageArcGISRest.prototype.updateParams = function(params) { + ol.obj.assign(this.params_, params); + this.image_ = null; + this.changed(); +}; + +goog.provide('ol.source.ImageMapGuide'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.source.Image'); +goog.require('ol.uri'); + + +/** + * @classdesc + * Source for images from Mapguide servers + * + * @constructor + * @fires ol.source.Image.Event + * @extends {ol.source.Image} + * @param {olx.source.ImageMapGuideOptions} options Options. + * @api stable + */ +ol.source.ImageMapGuide = function(options) { + + ol.source.Image.call(this, { + projection: options.projection, + resolutions: options.resolutions + }); + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + options.crossOrigin !== undefined ? options.crossOrigin : null; + + /** + * @private + * @type {number} + */ + this.displayDpi_ = options.displayDpi !== undefined ? + options.displayDpi : 96; + + /** + * @private + * @type {!Object} + */ + this.params_ = options.params || {}; + + /** + * @private + * @type {string|undefined} + */ + this.url_ = options.url; + + /** + * @private + * @type {ol.ImageLoadFunctionType} + */ + this.imageLoadFunction_ = options.imageLoadFunction !== undefined ? + options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction; + + /** + * @private + * @type {boolean} + */ + this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true; + + /** + * @private + * @type {number} + */ + this.metersPerUnit_ = options.metersPerUnit !== undefined ? + options.metersPerUnit : 1; + + /** + * @private + * @type {number} + */ + this.ratio_ = options.ratio !== undefined ? options.ratio : 1; + + /** + * @private + * @type {boolean} + */ + this.useOverlay_ = options.useOverlay !== undefined ? + options.useOverlay : false; + + /** + * @private + * @type {ol.Image} + */ + this.image_ = null; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = 0; + +}; +ol.inherits(ol.source.ImageMapGuide, ol.source.Image); + + +/** + * Get the user-provided params, i.e. those passed to the constructor through + * the "params" option, and possibly updated using the updateParams method. + * @return {Object} Params. + * @api stable + */ +ol.source.ImageMapGuide.prototype.getParams = function() { + return this.params_; +}; + + +/** + * @inheritDoc + */ +ol.source.ImageMapGuide.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) { + resolution = this.findNearestResolution(resolution); + pixelRatio = this.hidpi_ ? pixelRatio : 1; + + var image = this.image_; + if (image && + this.renderedRevision_ == this.getRevision() && + image.getResolution() == resolution && + image.getPixelRatio() == pixelRatio && + ol.extent.containsExtent(image.getExtent(), extent)) { + return image; + } + + if (this.ratio_ != 1) { + extent = extent.slice(); + ol.extent.scaleFromCenter(extent, this.ratio_); + } + var width = ol.extent.getWidth(extent) / resolution; + var height = ol.extent.getHeight(extent) / resolution; + var size = [width * pixelRatio, height * pixelRatio]; + + if (this.url_ !== undefined) { + var imageUrl = this.getUrl(this.url_, this.params_, extent, size, + projection); + image = new ol.Image(extent, resolution, pixelRatio, + this.getAttributions(), imageUrl, this.crossOrigin_, + this.imageLoadFunction_); + ol.events.listen(image, ol.events.EventType.CHANGE, + this.handleImageChange, this); + } else { + image = null; + } + this.image_ = image; + this.renderedRevision_ = this.getRevision(); + + return image; +}; + + +/** + * Return the image load function of the source. + * @return {ol.ImageLoadFunctionType} The image load function. + * @api + */ +ol.source.ImageMapGuide.prototype.getImageLoadFunction = function() { + return this.imageLoadFunction_; +}; + + +/** + * @param {ol.Extent} extent The map extents. + * @param {ol.Size} size The viewport size. + * @param {number} metersPerUnit The meters-per-unit value. + * @param {number} dpi The display resolution. + * @return {number} The computed map scale. + */ +ol.source.ImageMapGuide.getScale = function(extent, size, metersPerUnit, dpi) { + var mcsW = ol.extent.getWidth(extent); + var mcsH = ol.extent.getHeight(extent); + var devW = size[0]; + var devH = size[1]; + var mpp = 0.0254 / dpi; + if (devH * mcsW > devW * mcsH) { + return mcsW * metersPerUnit / (devW * mpp); // width limited + } else { + return mcsH * metersPerUnit / (devH * mpp); // height limited + } +}; + + +/** + * Update the user-provided params. + * @param {Object} params Params. + * @api stable + */ +ol.source.ImageMapGuide.prototype.updateParams = function(params) { + ol.obj.assign(this.params_, params); + this.changed(); +}; + + +/** + * @param {string} baseUrl The mapagent url. + * @param {Object.<string, string|number>} params Request parameters. + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Size. + * @param {ol.proj.Projection} projection Projection. + * @return {string} The mapagent map image request URL. + */ +ol.source.ImageMapGuide.prototype.getUrl = function(baseUrl, params, extent, size, projection) { + var scale = ol.source.ImageMapGuide.getScale(extent, size, + this.metersPerUnit_, this.displayDpi_); + var center = ol.extent.getCenter(extent); + var baseParams = { + 'OPERATION': this.useOverlay_ ? 'GETDYNAMICMAPOVERLAYIMAGE' : 'GETMAPIMAGE', + 'VERSION': '2.0.0', + 'LOCALE': 'en', + 'CLIENTAGENT': 'ol.source.ImageMapGuide source', + 'CLIP': '1', + 'SETDISPLAYDPI': this.displayDpi_, + 'SETDISPLAYWIDTH': Math.round(size[0]), + 'SETDISPLAYHEIGHT': Math.round(size[1]), + 'SETVIEWSCALE': scale, + 'SETVIEWCENTERX': center[0], + 'SETVIEWCENTERY': center[1] + }; + ol.obj.assign(baseParams, params); + return ol.uri.appendParams(baseUrl, baseParams); +}; + + +/** + * Set the image load function of the MapGuide source. + * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function. + * @api + */ +ol.source.ImageMapGuide.prototype.setImageLoadFunction = function( + imageLoadFunction) { + this.image_ = null; + this.imageLoadFunction_ = imageLoadFunction; + this.changed(); +}; + +goog.provide('ol.source.ImageStatic'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.proj'); +goog.require('ol.source.Image'); + + +/** + * @classdesc + * A layer source for displaying a single, static image. + * + * @constructor + * @extends {ol.source.Image} + * @param {olx.source.ImageStaticOptions} options Options. + * @api stable + */ +ol.source.ImageStatic = function(options) { + var imageExtent = options.imageExtent; + + var crossOrigin = options.crossOrigin !== undefined ? + options.crossOrigin : null; + + var /** @type {ol.ImageLoadFunctionType} */ imageLoadFunction = + options.imageLoadFunction !== undefined ? + options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction; + + ol.source.Image.call(this, { + attributions: options.attributions, + logo: options.logo, + projection: ol.proj.get(options.projection) + }); + + /** + * @private + * @type {ol.Image} + */ + this.image_ = new ol.Image(imageExtent, undefined, 1, this.getAttributions(), + options.url, crossOrigin, imageLoadFunction); + + /** + * @private + * @type {ol.Size} + */ + this.imageSize_ = options.imageSize ? options.imageSize : null; + + ol.events.listen(this.image_, ol.events.EventType.CHANGE, + this.handleImageChange, this); + +}; +ol.inherits(ol.source.ImageStatic, ol.source.Image); + + +/** + * @inheritDoc + */ +ol.source.ImageStatic.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) { + if (ol.extent.intersects(extent, this.image_.getExtent())) { + return this.image_; + } + return null; +}; + + +/** + * @inheritDoc + */ +ol.source.ImageStatic.prototype.handleImageChange = function(evt) { + if (this.image_.getState() == ol.Image.State.LOADED) { + var imageExtent = this.image_.getExtent(); + var image = this.image_.getImage(); + var imageWidth, imageHeight; + if (this.imageSize_) { + imageWidth = this.imageSize_[0]; + imageHeight = this.imageSize_[1]; + } else { + // TODO: remove the type cast when a closure-compiler > 20160315 is used. + // see: https://github.com/google/closure-compiler/pull/1664 + imageWidth = /** @type {number} */ (image.width); + imageHeight = /** @type {number} */ (image.height); + } + var resolution = ol.extent.getHeight(imageExtent) / imageHeight; + var targetWidth = Math.ceil(ol.extent.getWidth(imageExtent) / resolution); + if (targetWidth != imageWidth) { + var context = ol.dom.createCanvasContext2D(targetWidth, imageHeight); + var canvas = context.canvas; + context.drawImage(image, 0, 0, imageWidth, imageHeight, + 0, 0, canvas.width, canvas.height); + this.image_.setImage(canvas); + } + } + ol.source.Image.prototype.handleImageChange.call(this, evt); +}; + +goog.provide('ol.source.WMSServerType'); + + +/** + * Available server types: `'carmentaserver'`, `'geoserver'`, `'mapserver'`, + * `'qgis'`. These are servers that have vendor parameters beyond the WMS + * specification that OpenLayers can make use of. + * @enum {string} + */ +ol.source.WMSServerType = { + CARMENTA_SERVER: 'carmentaserver', + GEOSERVER: 'geoserver', + MAPSERVER: 'mapserver', + QGIS: 'qgis' +}; + +// FIXME cannot be shared between maps with different projections + +goog.provide('ol.source.ImageWMS'); + +goog.require('ol'); +goog.require('ol.Image'); +goog.require('ol.asserts'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.source.Image'); +goog.require('ol.source.WMSServerType'); +goog.require('ol.string'); +goog.require('ol.uri'); + + +/** + * @classdesc + * Source for WMS servers providing single, untiled images. + * + * @constructor + * @fires ol.source.Image.Event + * @extends {ol.source.Image} + * @param {olx.source.ImageWMSOptions=} opt_options Options. + * @api stable + */ +ol.source.ImageWMS = function(opt_options) { + + var options = opt_options || {}; + + ol.source.Image.call(this, { + attributions: options.attributions, + logo: options.logo, + projection: options.projection, + resolutions: options.resolutions + }); + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + options.crossOrigin !== undefined ? options.crossOrigin : null; + + /** + * @private + * @type {string|undefined} + */ + this.url_ = options.url; + + /** + * @private + * @type {ol.ImageLoadFunctionType} + */ + this.imageLoadFunction_ = options.imageLoadFunction !== undefined ? + options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction; + + /** + * @private + * @type {!Object} + */ + this.params_ = options.params || {}; + + /** + * @private + * @type {boolean} + */ + this.v13_ = true; + this.updateV13_(); + + /** + * @private + * @type {ol.source.WMSServerType|undefined} + */ + this.serverType_ = + /** @type {ol.source.WMSServerType|undefined} */ (options.serverType); + + /** + * @private + * @type {boolean} + */ + this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true; + + /** + * @private + * @type {ol.Image} + */ + this.image_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.imageSize_ = [0, 0]; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = 0; + + /** + * @private + * @type {number} + */ + this.ratio_ = options.ratio !== undefined ? options.ratio : 1.5; + +}; +ol.inherits(ol.source.ImageWMS, ol.source.Image); + + +/** + * @const + * @type {ol.Size} + * @private + */ +ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_ = [101, 101]; + + +/** + * Return the GetFeatureInfo URL for the passed coordinate, resolution, and + * projection. Return `undefined` if the GetFeatureInfo URL cannot be + * constructed. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {ol.ProjectionLike} projection Projection. + * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should + * be provided. If `QUERY_LAYERS` is not provided then the layers specified + * in the `LAYERS` parameter will be used. `VERSION` should not be + * specified here. + * @return {string|undefined} GetFeatureInfo URL. + * @api stable + */ +ol.source.ImageWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, params) { + + ol.DEBUG && console.assert(!('VERSION' in params), + 'key VERSION is not allowed in params'); + + if (this.url_ === undefined) { + return undefined; + } + + var extent = ol.extent.getForViewAndSize( + coordinate, resolution, 0, + ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_); + + var baseParams = { + 'SERVICE': 'WMS', + 'VERSION': ol.DEFAULT_WMS_VERSION, + 'REQUEST': 'GetFeatureInfo', + 'FORMAT': 'image/png', + 'TRANSPARENT': true, + 'QUERY_LAYERS': this.params_['LAYERS'] + }; + ol.obj.assign(baseParams, this.params_, params); + + var x = Math.floor((coordinate[0] - extent[0]) / resolution); + var y = Math.floor((extent[3] - coordinate[1]) / resolution); + baseParams[this.v13_ ? 'I' : 'X'] = x; + baseParams[this.v13_ ? 'J' : 'Y'] = y; + + return this.getRequestUrl_( + extent, ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_, + 1, ol.proj.get(projection), baseParams); +}; + + +/** + * Get the user-provided params, i.e. those passed to the constructor through + * the "params" option, and possibly updated using the updateParams method. + * @return {Object} Params. + * @api stable + */ +ol.source.ImageWMS.prototype.getParams = function() { + return this.params_; +}; + + +/** + * @inheritDoc + */ +ol.source.ImageWMS.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) { + + if (this.url_ === undefined) { + return null; + } + + resolution = this.findNearestResolution(resolution); + + if (pixelRatio != 1 && (!this.hidpi_ || this.serverType_ === undefined)) { + pixelRatio = 1; + } + + extent = extent.slice(); + var centerX = (extent[0] + extent[2]) / 2; + var centerY = (extent[1] + extent[3]) / 2; + + var imageResolution = resolution / pixelRatio; + var imageWidth = ol.extent.getWidth(extent) / imageResolution; + var imageHeight = ol.extent.getHeight(extent) / imageResolution; + + var image = this.image_; + if (image && + this.renderedRevision_ == this.getRevision() && + image.getResolution() == resolution && + image.getPixelRatio() == pixelRatio && + ol.extent.containsExtent(image.getExtent(), extent)) { + return image; + } + + if (this.ratio_ != 1) { + var halfWidth = this.ratio_ * ol.extent.getWidth(extent) / 2; + var halfHeight = this.ratio_ * ol.extent.getHeight(extent) / 2; + extent[0] = centerX - halfWidth; + extent[1] = centerY - halfHeight; + extent[2] = centerX + halfWidth; + extent[3] = centerY + halfHeight; + } + + var params = { + 'SERVICE': 'WMS', + 'VERSION': ol.DEFAULT_WMS_VERSION, + 'REQUEST': 'GetMap', + 'FORMAT': 'image/png', + 'TRANSPARENT': true + }; + ol.obj.assign(params, this.params_); + + this.imageSize_[0] = Math.ceil(imageWidth * this.ratio_); + this.imageSize_[1] = Math.ceil(imageHeight * this.ratio_); + + var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio, + projection, params); + + this.image_ = new ol.Image(extent, resolution, pixelRatio, + this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_); + + this.renderedRevision_ = this.getRevision(); + + ol.events.listen(this.image_, ol.events.EventType.CHANGE, + this.handleImageChange, this); + + return this.image_; + +}; + + +/** + * Return the image load function of the source. + * @return {ol.ImageLoadFunctionType} The image load function. + * @api + */ +ol.source.ImageWMS.prototype.getImageLoadFunction = function() { + return this.imageLoadFunction_; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {Object} params Params. + * @return {string} Request URL. + * @private + */ +ol.source.ImageWMS.prototype.getRequestUrl_ = function(extent, size, pixelRatio, projection, params) { + + ol.asserts.assert(this.url_ !== undefined, 9); // `url` must be configured or set using `#setUrl()` + + params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode(); + + if (!('STYLES' in this.params_)) { + params['STYLES'] = ''; + } + + if (pixelRatio != 1) { + switch (this.serverType_) { + case ol.source.WMSServerType.GEOSERVER: + var dpi = (90 * pixelRatio + 0.5) | 0; + if ('FORMAT_OPTIONS' in params) { + params['FORMAT_OPTIONS'] += ';dpi:' + dpi; + } else { + params['FORMAT_OPTIONS'] = 'dpi:' + dpi; + } + break; + case ol.source.WMSServerType.MAPSERVER: + params['MAP_RESOLUTION'] = 90 * pixelRatio; + break; + case ol.source.WMSServerType.CARMENTA_SERVER: + case ol.source.WMSServerType.QGIS: + params['DPI'] = 90 * pixelRatio; + break; + default: + ol.asserts.assert(false, 8); // Unknown `serverType` configured + break; + } + } + + params['WIDTH'] = size[0]; + params['HEIGHT'] = size[1]; + + var axisOrientation = projection.getAxisOrientation(); + var bbox; + if (this.v13_ && axisOrientation.substr(0, 2) == 'ne') { + bbox = [extent[1], extent[0], extent[3], extent[2]]; + } else { + bbox = extent; + } + params['BBOX'] = bbox.join(','); + + return ol.uri.appendParams(/** @type {string} */ (this.url_), params); +}; + + +/** + * Return the URL used for this WMS source. + * @return {string|undefined} URL. + * @api stable + */ +ol.source.ImageWMS.prototype.getUrl = function() { + return this.url_; +}; + + +/** + * Set the image load function of the source. + * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function. + * @api + */ +ol.source.ImageWMS.prototype.setImageLoadFunction = function( + imageLoadFunction) { + this.image_ = null; + this.imageLoadFunction_ = imageLoadFunction; + this.changed(); +}; + + +/** + * Set the URL to use for requests. + * @param {string|undefined} url URL. + * @api stable + */ +ol.source.ImageWMS.prototype.setUrl = function(url) { + if (url != this.url_) { + this.url_ = url; + this.image_ = null; + this.changed(); + } +}; + + +/** + * Update the user-provided params. + * @param {Object} params Params. + * @api stable + */ +ol.source.ImageWMS.prototype.updateParams = function(params) { + ol.obj.assign(this.params_, params); + this.updateV13_(); + this.image_ = null; + this.changed(); +}; + + +/** + * @private + */ +ol.source.ImageWMS.prototype.updateV13_ = function() { + var version = this.params_['VERSION'] || ol.DEFAULT_WMS_VERSION; + this.v13_ = ol.string.compareVersions(version, '1.3') >= 0; +}; + +goog.provide('ol.source.OSM'); + +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.source.XYZ'); + + +/** + * @classdesc + * Layer source for the OpenStreetMap tile server. + * + * @constructor + * @extends {ol.source.XYZ} + * @param {olx.source.OSMOptions=} opt_options Open Street Map options. + * @api stable + */ +ol.source.OSM = function(opt_options) { + + var options = opt_options || {}; + + var attributions; + if (options.attributions !== undefined) { + attributions = options.attributions; + } else { + attributions = [ol.source.OSM.ATTRIBUTION]; + } + + var crossOrigin = options.crossOrigin !== undefined ? + options.crossOrigin : 'anonymous'; + + var url = options.url !== undefined ? + options.url : 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + + ol.source.XYZ.call(this, { + attributions: attributions, + cacheSize: options.cacheSize, + crossOrigin: crossOrigin, + opaque: options.opaque !== undefined ? options.opaque : true, + maxZoom: options.maxZoom !== undefined ? options.maxZoom : 19, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileLoadFunction: options.tileLoadFunction, + url: url, + wrapX: options.wrapX + }); + +}; +ol.inherits(ol.source.OSM, ol.source.XYZ); + + +/** + * The attribution containing a link to the OpenStreetMap Copyright and License + * page. + * @const + * @type {ol.Attribution} + * @api + */ +ol.source.OSM.ATTRIBUTION = new ol.Attribution({ + html: '© ' + + '<a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> ' + + 'contributors.' +}); + +goog.provide('ol.ext.pixelworks'); +/** @typedef {function(*)} */ +ol.ext.pixelworks; +(function() { +var exports = {}; +var module = {exports: exports}; +var define; +/** + * @fileoverview + * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility} + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pixelworks = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ +var Processor = _dereq_('./processor'); + +exports.Processor = Processor; + +},{"./processor":2}],2:[function(_dereq_,module,exports){ +var newImageData = _dereq_('./util').newImageData; + +/** + * Create a function for running operations. This function is serialized for + * use in a worker. + * @param {function(Array, Object):*} operation The operation. + * @return {function(Object):ArrayBuffer} A function that takes an object with + * buffers, meta, imageOps, width, and height properties and returns an array + * buffer. + */ +function createMinion(operation) { + var workerHasImageData = true; + try { + new ImageData(10, 10); + } catch (_) { + workerHasImageData = false; + } + + function newWorkerImageData(data, width, height) { + if (workerHasImageData) { + return new ImageData(data, width, height); + } else { + return {data: data, width: width, height: height}; + } + } + + return function(data) { + // bracket notation for minification support + var buffers = data['buffers']; + var meta = data['meta']; + var imageOps = data['imageOps']; + var width = data['width']; + var height = data['height']; + + var numBuffers = buffers.length; + var numBytes = buffers[0].byteLength; + var output, b; + + if (imageOps) { + var images = new Array(numBuffers); + for (b = 0; b < numBuffers; ++b) { + images[b] = newWorkerImageData( + new Uint8ClampedArray(buffers[b]), width, height); + } + output = operation(images, meta).data; + } else { + output = new Uint8ClampedArray(numBytes); + var arrays = new Array(numBuffers); + var pixels = new Array(numBuffers); + for (b = 0; b < numBuffers; ++b) { + arrays[b] = new Uint8ClampedArray(buffers[b]); + pixels[b] = [0, 0, 0, 0]; + } + for (var i = 0; i < numBytes; i += 4) { + for (var j = 0; j < numBuffers; ++j) { + var array = arrays[j]; + pixels[j][0] = array[i]; + pixels[j][1] = array[i + 1]; + pixels[j][2] = array[i + 2]; + pixels[j][3] = array[i + 3]; + } + var pixel = operation(pixels, meta); + output[i] = pixel[0]; + output[i + 1] = pixel[1]; + output[i + 2] = pixel[2]; + output[i + 3] = pixel[3]; + } + } + return output.buffer; + }; +} + +/** + * Create a worker for running operations. + * @param {Object} config Configuration. + * @param {function(MessageEvent)} onMessage Called with a message event. + * @return {Worker} The worker. + */ +function createWorker(config, onMessage) { + var lib = Object.keys(config.lib || {}).map(function(name) { + return 'var ' + name + ' = ' + config.lib[name].toString() + ';'; + }); + + var lines = lib.concat([ + 'var __minion__ = (' + createMinion.toString() + ')(', config.operation.toString(), ');', + 'self.addEventListener("message", function(event) {', + ' var buffer = __minion__(event.data);', + ' self.postMessage({buffer: buffer, meta: event.data.meta}, [buffer]);', + '});' + ]); + + var blob = new Blob(lines, {type: 'text/javascript'}); + var source = URL.createObjectURL(blob); + var worker = new Worker(source); + worker.addEventListener('message', onMessage); + return worker; +} + +/** + * Create a faux worker for running operations. + * @param {Object} config Configuration. + * @param {function(MessageEvent)} onMessage Called with a message event. + * @return {Object} The faux worker. + */ +function createFauxWorker(config, onMessage) { + var minion = createMinion(config.operation); + return { + postMessage: function(data) { + setTimeout(function() { + onMessage({'data': {'buffer': minion(data), 'meta': data['meta']}}); + }, 0); + } + }; +} + +/** + * A processor runs pixel or image operations in workers. + * @param {Object} config Configuration. + */ +function Processor(config) { + this._imageOps = !!config.imageOps; + var threads; + if (config.threads === 0) { + threads = 0; + } else if (this._imageOps) { + threads = 1; + } else { + threads = config.threads || 1; + } + var workers = []; + if (threads) { + for (var i = 0; i < threads; ++i) { + workers[i] = createWorker(config, this._onWorkerMessage.bind(this, i)); + } + } else { + workers[0] = createFauxWorker(config, this._onWorkerMessage.bind(this, 0)); + } + this._workers = workers; + this._queue = []; + this._maxQueueLength = config.queue || Infinity; + this._running = 0; + this._dataLookup = {}; + this._job = null; +} + +/** + * Run operation on input data. + * @param {Array.<Array|ImageData>} inputs Array of pixels or image data + * (depending on the operation type). + * @param {Object} meta A user data object. This is passed to all operations + * and must be serializable. + * @param {function(Error, ImageData, Object)} callback Called when work + * completes. The first argument is any error. The second is the ImageData + * generated by operations. The third is the user data object. + */ +Processor.prototype.process = function(inputs, meta, callback) { + this._enqueue({ + inputs: inputs, + meta: meta, + callback: callback + }); + this._dispatch(); +}; + +/** + * Stop responding to any completed work and destroy the processor. + */ +Processor.prototype.destroy = function() { + for (var key in this) { + this[key] = null; + } + this._destroyed = true; +}; + +/** + * Add a job to the queue. + * @param {Object} job The job. + */ +Processor.prototype._enqueue = function(job) { + this._queue.push(job); + while (this._queue.length > this._maxQueueLength) { + this._queue.shift().callback(null, null); + } +}; + +/** + * Dispatch a job. + */ +Processor.prototype._dispatch = function() { + if (this._running === 0 && this._queue.length > 0) { + var job = this._job = this._queue.shift(); + var width = job.inputs[0].width; + var height = job.inputs[0].height; + var buffers = job.inputs.map(function(input) { + return input.data.buffer; + }); + var threads = this._workers.length; + this._running = threads; + if (threads === 1) { + this._workers[0].postMessage({ + 'buffers': buffers, + 'meta': job.meta, + 'imageOps': this._imageOps, + 'width': width, + 'height': height + }, buffers); + } else { + var length = job.inputs[0].data.length; + var segmentLength = 4 * Math.ceil(length / 4 / threads); + for (var i = 0; i < threads; ++i) { + var offset = i * segmentLength; + var slices = []; + for (var j = 0, jj = buffers.length; j < jj; ++j) { + slices.push(buffers[i].slice(offset, offset + segmentLength)); + } + this._workers[i].postMessage({ + 'buffers': slices, + 'meta': job.meta, + 'imageOps': this._imageOps, + 'width': width, + 'height': height + }, slices); + } + } + } +}; + +/** + * Handle messages from the worker. + * @param {number} index The worker index. + * @param {MessageEvent} event The message event. + */ +Processor.prototype._onWorkerMessage = function(index, event) { + if (this._destroyed) { + return; + } + this._dataLookup[index] = event.data; + --this._running; + if (this._running === 0) { + this._resolveJob(); + } +}; + +/** + * Resolve a job. If there are no more worker threads, the processor callback + * will be called. + */ +Processor.prototype._resolveJob = function() { + var job = this._job; + var threads = this._workers.length; + var data, meta; + if (threads === 1) { + data = new Uint8ClampedArray(this._dataLookup[0]['buffer']); + meta = this._dataLookup[0]['meta']; + } else { + var length = job.inputs[0].data.length; + data = new Uint8ClampedArray(length); + meta = new Array(length); + var segmentLength = 4 * Math.ceil(length / 4 / threads); + for (var i = 0; i < threads; ++i) { + var buffer = this._dataLookup[i]['buffer']; + var offset = i * segmentLength; + data.set(new Uint8ClampedArray(buffer), offset); + meta[i] = this._dataLookup[i]['meta']; + } + } + this._job = null; + this._dataLookup = {}; + job.callback(null, + newImageData(data, job.inputs[0].width, job.inputs[0].height), meta); + this._dispatch(); +}; + +module.exports = Processor; + +},{"./util":3}],3:[function(_dereq_,module,exports){ +var hasImageData = true; +try { + new ImageData(10, 10); +} catch (_) { + hasImageData = false; +} + +var context = document.createElement('canvas').getContext('2d'); + +function newImageData(data, width, height) { + if (hasImageData) { + return new ImageData(data, width, height); + } else { + var imageData = context.createImageData(width, height); + imageData.data.set(data); + return imageData; + } +} + +exports.newImageData = newImageData; + +},{}]},{},[1])(1) +}); +ol.ext.pixelworks = module.exports; +})(); + +goog.provide('ol.source.Raster'); + +goog.require('ol'); +goog.require('ol.transform'); +goog.require('ol.ImageCanvas'); +goog.require('ol.TileQueue'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.events.Event'); +goog.require('ol.events.EventType'); +goog.require('ol.ext.pixelworks'); +goog.require('ol.extent'); +goog.require('ol.layer.Image'); +goog.require('ol.layer.Tile'); +goog.require('ol.obj'); +goog.require('ol.renderer.canvas.ImageLayer'); +goog.require('ol.renderer.canvas.TileLayer'); +goog.require('ol.source.Image'); +goog.require('ol.source.State'); +goog.require('ol.source.Tile'); + + +/** + * @classdesc + * A source that transforms data from any number of input sources using an array + * of {@link ol.RasterOperation} functions to transform input pixel values into + * output pixel values. + * + * @constructor + * @extends {ol.source.Image} + * @fires ol.source.Raster.Event + * @param {olx.source.RasterOptions} options Options. + * @api + */ +ol.source.Raster = function(options) { + + /** + * @private + * @type {*} + */ + this.worker_ = null; + + /** + * @private + * @type {ol.source.Raster.OperationType} + */ + this.operationType_ = options.operationType !== undefined ? + options.operationType : ol.source.Raster.OperationType.PIXEL; + + /** + * @private + * @type {number} + */ + this.threads_ = options.threads !== undefined ? options.threads : 1; + + /** + * @private + * @type {Array.<ol.renderer.canvas.Layer>} + */ + this.renderers_ = ol.source.Raster.createRenderers_(options.sources); + + for (var r = 0, rr = this.renderers_.length; r < rr; ++r) { + ol.events.listen(this.renderers_[r], ol.events.EventType.CHANGE, + this.changed, this); + } + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.canvasContext_ = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {ol.TileQueue} + */ + this.tileQueue_ = new ol.TileQueue( + function() { + return 1; + }, + this.changed.bind(this)); + + var layerStatesArray = ol.source.Raster.getLayerStatesArray_(this.renderers_); + var layerStates = {}; + for (var i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerStates[ol.getUid(layerStatesArray[i].layer)] = layerStatesArray[i]; + } + + /** + * The most recently rendered state. + * @type {?ol.SourceRasterRenderedState} + * @private + */ + this.renderedState_ = null; + + /** + * The most recently rendered image canvas. + * @type {ol.ImageCanvas} + * @private + */ + this.renderedImageCanvas_ = null; + + /** + * @private + * @type {olx.FrameState} + */ + this.frameState_ = { + animate: false, + attributions: {}, + coordinateToPixelTransform: ol.transform.create(), + extent: null, + focus: null, + index: 0, + layerStates: layerStates, + layerStatesArray: layerStatesArray, + logos: {}, + pixelRatio: 1, + pixelToCoordinateTransform: ol.transform.create(), + postRenderFunctions: [], + size: [0, 0], + skippedFeatureUids: {}, + tileQueue: this.tileQueue_, + time: Date.now(), + usedTiles: {}, + viewState: /** @type {olx.ViewState} */ ({ + rotation: 0 + }), + viewHints: [], + wantedTiles: {} + }; + + ol.source.Image.call(this, {}); + + if (options.operation !== undefined) { + this.setOperation(options.operation, options.lib); + } + +}; +ol.inherits(ol.source.Raster, ol.source.Image); + + +/** + * Set the operation. + * @param {ol.RasterOperation} operation New operation. + * @param {Object=} opt_lib Functions that will be available to operations run + * in a worker. + * @api + */ +ol.source.Raster.prototype.setOperation = function(operation, opt_lib) { + this.worker_ = new ol.ext.pixelworks.Processor({ + operation: operation, + imageOps: this.operationType_ === ol.source.Raster.OperationType.IMAGE, + queue: 1, + lib: opt_lib, + threads: this.threads_ + }); + this.changed(); +}; + + +/** + * Update the stored frame state. + * @param {ol.Extent} extent The view extent (in map units). + * @param {number} resolution The view resolution. + * @param {ol.proj.Projection} projection The view projection. + * @return {olx.FrameState} The updated frame state. + * @private + */ +ol.source.Raster.prototype.updateFrameState_ = function(extent, resolution, projection) { + + var frameState = /** @type {olx.FrameState} */ ( + ol.obj.assign({}, this.frameState_)); + + frameState.viewState = /** @type {olx.ViewState} */ ( + ol.obj.assign({}, frameState.viewState)); + + var center = ol.extent.getCenter(extent); + var width = Math.round(ol.extent.getWidth(extent) / resolution); + var height = Math.round(ol.extent.getHeight(extent) / resolution); + + frameState.extent = extent; + frameState.focus = ol.extent.getCenter(extent); + frameState.size[0] = width; + frameState.size[1] = height; + + var viewState = frameState.viewState; + viewState.center = center; + viewState.projection = projection; + viewState.resolution = resolution; + return frameState; +}; + + +/** + * Determine if the most recently rendered image canvas is dirty. + * @param {ol.Extent} extent The requested extent. + * @param {number} resolution The requested resolution. + * @return {boolean} The image is dirty. + * @private + */ +ol.source.Raster.prototype.isDirty_ = function(extent, resolution) { + var state = this.renderedState_; + return !state || + this.getRevision() !== state.revision || + resolution !== state.resolution || + !ol.extent.equals(extent, state.extent); +}; + + +/** + * @inheritDoc + */ +ol.source.Raster.prototype.getImage = function(extent, resolution, pixelRatio, projection) { + + if (!this.allSourcesReady_()) { + return null; + } + + var currentExtent = extent.slice(); + if (!this.isDirty_(currentExtent, resolution)) { + return this.renderedImageCanvas_; + } + + var context = this.canvasContext_; + var canvas = context.canvas; + + var width = Math.round(ol.extent.getWidth(currentExtent) / resolution); + var height = Math.round(ol.extent.getHeight(currentExtent) / resolution); + + if (width !== canvas.width || + height !== canvas.height) { + canvas.width = width; + canvas.height = height; + } + + var frameState = this.updateFrameState_(currentExtent, resolution, projection); + + var imageCanvas = new ol.ImageCanvas( + currentExtent, resolution, 1, this.getAttributions(), canvas, + this.composeFrame_.bind(this, frameState)); + + this.renderedImageCanvas_ = imageCanvas; + + this.renderedState_ = { + extent: currentExtent, + resolution: resolution, + revision: this.getRevision() + }; + + return imageCanvas; +}; + + +/** + * Determine if all sources are ready. + * @return {boolean} All sources are ready. + * @private + */ +ol.source.Raster.prototype.allSourcesReady_ = function() { + var ready = true; + var source; + for (var i = 0, ii = this.renderers_.length; i < ii; ++i) { + source = this.renderers_[i].getLayer().getSource(); + if (source.getState() !== ol.source.State.READY) { + ready = false; + break; + } + } + return ready; +}; + + +/** + * Compose the frame. This renders data from all sources, runs pixel-wise + * operations, and renders the result to the stored canvas context. + * @param {olx.FrameState} frameState The frame state. + * @param {function(Error)} callback Called when composition is complete. + * @private + */ +ol.source.Raster.prototype.composeFrame_ = function(frameState, callback) { + var len = this.renderers_.length; + var imageDatas = new Array(len); + for (var i = 0; i < len; ++i) { + var imageData = ol.source.Raster.getImageData_( + this.renderers_[i], frameState, frameState.layerStatesArray[i]); + if (imageData) { + imageDatas[i] = imageData; + } else { + // image not yet ready + imageDatas = null; + break; + } + } + + if (imageDatas) { + var data = {}; + this.dispatchEvent(new ol.source.Raster.Event( + ol.source.Raster.EventType.BEFOREOPERATIONS, frameState, data)); + + this.worker_.process(imageDatas, data, + this.onWorkerComplete_.bind(this, frameState, callback)); + } + + frameState.tileQueue.loadMoreTiles(16, 16); +}; + + +/** + * Called when pixel processing is complete. + * @param {olx.FrameState} frameState The frame state. + * @param {function(Error)} callback Called when rendering is complete. + * @param {Error} err Any error during processing. + * @param {ImageData} output The output image data. + * @param {Object} data The user data. + * @private + */ +ol.source.Raster.prototype.onWorkerComplete_ = function(frameState, callback, err, output, data) { + if (err) { + callback(err); + return; + } + if (!output) { + // job aborted + return; + } + + this.dispatchEvent(new ol.source.Raster.Event( + ol.source.Raster.EventType.AFTEROPERATIONS, frameState, data)); + + var resolution = frameState.viewState.resolution / frameState.pixelRatio; + if (!this.isDirty_(frameState.extent, resolution)) { + this.canvasContext_.putImageData(output, 0, 0); + } + + callback(null); +}; + + +/** + * Get image data from a renderer. + * @param {ol.renderer.canvas.Layer} renderer Layer renderer. + * @param {olx.FrameState} frameState The frame state. + * @param {ol.LayerState} layerState The layer state. + * @return {ImageData} The image data. + * @private + */ +ol.source.Raster.getImageData_ = function(renderer, frameState, layerState) { + if (!renderer.prepareFrame(frameState, layerState)) { + return null; + } + var width = frameState.size[0]; + var height = frameState.size[1]; + if (!ol.source.Raster.context_) { + ol.source.Raster.context_ = ol.dom.createCanvasContext2D(width, height); + } else { + var canvas = ol.source.Raster.context_.canvas; + if (canvas.width !== width || canvas.height !== height) { + ol.source.Raster.context_ = ol.dom.createCanvasContext2D(width, height); + } else { + ol.source.Raster.context_.clearRect(0, 0, width, height); + } + } + renderer.composeFrame(frameState, layerState, ol.source.Raster.context_); + return ol.source.Raster.context_.getImageData(0, 0, width, height); +}; + + +/** + * A reusable canvas context. + * @type {CanvasRenderingContext2D} + * @private + */ +ol.source.Raster.context_ = null; + + +/** + * Get a list of layer states from a list of renderers. + * @param {Array.<ol.renderer.canvas.Layer>} renderers Layer renderers. + * @return {Array.<ol.LayerState>} The layer states. + * @private + */ +ol.source.Raster.getLayerStatesArray_ = function(renderers) { + return renderers.map(function(renderer) { + return renderer.getLayer().getLayerState(); + }); +}; + + +/** + * Create renderers for all sources. + * @param {Array.<ol.source.Source>} sources The sources. + * @return {Array.<ol.renderer.canvas.Layer>} Array of layer renderers. + * @private + */ +ol.source.Raster.createRenderers_ = function(sources) { + var len = sources.length; + var renderers = new Array(len); + for (var i = 0; i < len; ++i) { + renderers[i] = ol.source.Raster.createRenderer_(sources[i]); + } + return renderers; +}; + + +/** + * Create a renderer for the provided source. + * @param {ol.source.Source} source The source. + * @return {ol.renderer.canvas.Layer} The renderer. + * @private + */ +ol.source.Raster.createRenderer_ = function(source) { + var renderer = null; + if (source instanceof ol.source.Tile) { + renderer = ol.source.Raster.createTileRenderer_(source); + } else if (source instanceof ol.source.Image) { + renderer = ol.source.Raster.createImageRenderer_(source); + } else { + ol.DEBUG && console.assert(false, 'Unsupported source type: ' + source); + } + return renderer; +}; + + +/** + * Create an image renderer for the provided source. + * @param {ol.source.Image} source The source. + * @return {ol.renderer.canvas.Layer} The renderer. + * @private + */ +ol.source.Raster.createImageRenderer_ = function(source) { + var layer = new ol.layer.Image({source: source}); + return new ol.renderer.canvas.ImageLayer(layer); +}; + + +/** + * Create a tile renderer for the provided source. + * @param {ol.source.Tile} source The source. + * @return {ol.renderer.canvas.Layer} The renderer. + * @private + */ +ol.source.Raster.createTileRenderer_ = function(source) { + var layer = new ol.layer.Tile({source: source}); + return new ol.renderer.canvas.TileLayer(layer); +}; + + +/** + * @classdesc + * Events emitted by {@link ol.source.Raster} instances are instances of this + * type. + * + * @constructor + * @extends {ol.events.Event} + * @implements {oli.source.RasterEvent} + * @param {string} type Type. + * @param {olx.FrameState} frameState The frame state. + * @param {Object} data An object made available to operations. + */ +ol.source.Raster.Event = function(type, frameState, data) { + ol.events.Event.call(this, type); + + /** + * The raster extent. + * @type {ol.Extent} + * @api + */ + this.extent = frameState.extent; + + /** + * The pixel resolution (map units per pixel). + * @type {number} + * @api + */ + this.resolution = frameState.viewState.resolution / frameState.pixelRatio; + + /** + * An object made available to all operations. This can be used by operations + * as a storage object (e.g. for calculating statistics). + * @type {Object} + * @api + */ + this.data = data; + +}; +ol.inherits(ol.source.Raster.Event, ol.events.Event); + + +/** + * @enum {string} + */ +ol.source.Raster.EventType = { + /** + * Triggered before operations are run. + * @event ol.source.Raster.Event#beforeoperations + * @api + */ + BEFOREOPERATIONS: 'beforeoperations', + + /** + * Triggered after operations are run. + * @event ol.source.Raster.Event#afteroperations + * @api + */ + AFTEROPERATIONS: 'afteroperations' +}; + + +/** + * Raster operation type. Supported values are `'pixel'` and `'image'`. + * @enum {string} + */ +ol.source.Raster.OperationType = { + PIXEL: 'pixel', + IMAGE: 'image' +}; + +goog.provide('ol.source.Stamen'); + +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.source.OSM'); +goog.require('ol.source.XYZ'); + + +/** + * @classdesc + * Layer source for the Stamen tile server. + * + * @constructor + * @extends {ol.source.XYZ} + * @param {olx.source.StamenOptions} options Stamen options. + * @api stable + */ +ol.source.Stamen = function(options) { + + var i = options.layer.indexOf('-'); + var provider = i == -1 ? options.layer : options.layer.slice(0, i); + ol.DEBUG && console.assert(provider in ol.source.Stamen.ProviderConfig, + 'known provider configured'); + var providerConfig = ol.source.Stamen.ProviderConfig[provider]; + + ol.DEBUG && console.assert(options.layer in ol.source.Stamen.LayerConfig, + 'known layer configured'); + var layerConfig = ol.source.Stamen.LayerConfig[options.layer]; + + var url = options.url !== undefined ? options.url : + 'https://stamen-tiles-{a-d}.a.ssl.fastly.net/' + options.layer + + '/{z}/{x}/{y}.' + layerConfig.extension; + + ol.source.XYZ.call(this, { + attributions: ol.source.Stamen.ATTRIBUTIONS, + cacheSize: options.cacheSize, + crossOrigin: 'anonymous', + maxZoom: options.maxZoom != undefined ? options.maxZoom : providerConfig.maxZoom, + minZoom: options.minZoom != undefined ? options.minZoom : providerConfig.minZoom, + opaque: layerConfig.opaque, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileLoadFunction: options.tileLoadFunction, + url: url + }); + +}; +ol.inherits(ol.source.Stamen, ol.source.XYZ); + + +/** + * @const + * @type {Array.<ol.Attribution>} + */ +ol.source.Stamen.ATTRIBUTIONS = [ + new ol.Attribution({ + html: 'Map tiles by <a href="http://stamen.com/">Stamen Design</a>, ' + + 'under <a href="http://creativecommons.org/licenses/by/3.0/">CC BY' + + ' 3.0</a>.' + }), + ol.source.OSM.ATTRIBUTION +]; + +/** + * @type {Object.<string, {extension: string, opaque: boolean}>} + */ +ol.source.Stamen.LayerConfig = { + 'terrain': { + extension: 'jpg', + opaque: true + }, + 'terrain-background': { + extension: 'jpg', + opaque: true + }, + 'terrain-labels': { + extension: 'png', + opaque: false + }, + 'terrain-lines': { + extension: 'png', + opaque: false + }, + 'toner-background': { + extension: 'png', + opaque: true + }, + 'toner': { + extension: 'png', + opaque: true + }, + 'toner-hybrid': { + extension: 'png', + opaque: false + }, + 'toner-labels': { + extension: 'png', + opaque: false + }, + 'toner-lines': { + extension: 'png', + opaque: false + }, + 'toner-lite': { + extension: 'png', + opaque: true + }, + 'watercolor': { + extension: 'jpg', + opaque: true + } +}; + +/** + * @type {Object.<string, {minZoom: number, maxZoom: number}>} + */ +ol.source.Stamen.ProviderConfig = { + 'terrain': { + minZoom: 4, + maxZoom: 18 + }, + 'toner': { + minZoom: 0, + maxZoom: 20 + }, + 'watercolor': { + minZoom: 1, + maxZoom: 16 + } +}; + +goog.provide('ol.source.TileArcGISRest'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.math'); +goog.require('ol.obj'); +goog.require('ol.size'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilecoord'); +goog.require('ol.uri'); + + +/** + * @classdesc + * Layer source for tile data from ArcGIS Rest services. Map and Image + * Services are supported. + * + * For cached ArcGIS services, better performance is available using the + * {@link ol.source.XYZ} data source. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.TileArcGISRestOptions=} opt_options Tile ArcGIS Rest + * options. + * @api + */ +ol.source.TileArcGISRest = function(opt_options) { + + var options = opt_options || {}; + + ol.source.TileImage.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + logo: options.logo, + projection: options.projection, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileGrid: options.tileGrid, + tileLoadFunction: options.tileLoadFunction, + url: options.url, + urls: options.urls, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + + /** + * @private + * @type {!Object} + */ + this.params_ = options.params || {}; + + /** + * @private + * @type {ol.Extent} + */ + this.tmpExtent_ = ol.extent.createEmpty(); + + this.setKey(this.getKeyForParams_()); +}; +ol.inherits(ol.source.TileArcGISRest, ol.source.TileImage); + + +/** + * @private + * @return {string} The key for the current params. + */ +ol.source.TileArcGISRest.prototype.getKeyForParams_ = function() { + var i = 0; + var res = []; + for (var key in this.params_) { + res[i++] = key + '-' + this.params_[key]; + } + return res.join('/'); +}; + + +/** + * Get the user-provided params, i.e. those passed to the constructor through + * the "params" option, and possibly updated using the updateParams method. + * @return {Object} Params. + * @api + */ +ol.source.TileArcGISRest.prototype.getParams = function() { + return this.params_; +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Size} tileSize Tile size. + * @param {ol.Extent} tileExtent Tile extent. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {Object} params Params. + * @return {string|undefined} Request URL. + * @private + */ +ol.source.TileArcGISRest.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent, + pixelRatio, projection, params) { + + var urls = this.urls; + if (!urls) { + return undefined; + } + + // ArcGIS Server only wants the numeric portion of the projection ID. + var srid = projection.getCode().split(':').pop(); + + params['SIZE'] = tileSize[0] + ',' + tileSize[1]; + params['BBOX'] = tileExtent.join(','); + params['BBOXSR'] = srid; + params['IMAGESR'] = srid; + params['DPI'] = Math.round( + params['DPI'] ? params['DPI'] * pixelRatio : 90 * pixelRatio + ); + + var url; + if (urls.length == 1) { + url = urls[0]; + } else { + var index = ol.math.modulo(ol.tilecoord.hash(tileCoord), urls.length); + url = urls[index]; + } + + var modifiedUrl = url + .replace(/MapServer\/?$/, 'MapServer/export') + .replace(/ImageServer\/?$/, 'ImageServer/exportImage'); + return ol.uri.appendParams(modifiedUrl, params); +}; + + +/** + * @inheritDoc + */ +ol.source.TileArcGISRest.prototype.getTilePixelRatio = function(pixelRatio) { + return /** @type {number} */ (pixelRatio); +}; + + +/** + * @inheritDoc + */ +ol.source.TileArcGISRest.prototype.fixedTileUrlFunction = function(tileCoord, pixelRatio, projection) { + + var tileGrid = this.getTileGrid(); + if (!tileGrid) { + tileGrid = this.getTileGridForProjection(projection); + } + + if (tileGrid.getResolutions().length <= tileCoord[0]) { + return undefined; + } + + var tileExtent = tileGrid.getTileCoordExtent( + tileCoord, this.tmpExtent_); + var tileSize = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0]), this.tmpSize); + + if (pixelRatio != 1) { + tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize); + } + + // Apply default params and override with user specified values. + var baseParams = { + 'F': 'image', + 'FORMAT': 'PNG32', + 'TRANSPARENT': true + }; + ol.obj.assign(baseParams, this.params_); + + return this.getRequestUrl_(tileCoord, tileSize, tileExtent, + pixelRatio, projection, baseParams); +}; + + +/** + * Update the user-provided params. + * @param {Object} params Params. + * @api stable + */ +ol.source.TileArcGISRest.prototype.updateParams = function(params) { + ol.obj.assign(this.params_, params); + this.setKey(this.getKeyForParams_()); +}; + +goog.provide('ol.source.TileDebug'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.dom'); +goog.require('ol.size'); +goog.require('ol.source.Tile'); + + +/** + * @classdesc + * A pseudo tile source, which does not fetch tiles from a server, but renders + * a grid outline for the tile grid/projection along with the coordinates for + * each tile. See examples/canvas-tiles for an example. + * + * Uses Canvas context2d, so requires Canvas support. + * + * @constructor + * @extends {ol.source.Tile} + * @param {olx.source.TileDebugOptions} options Debug tile options. + * @api + */ +ol.source.TileDebug = function(options) { + + ol.source.Tile.call(this, { + opaque: false, + projection: options.projection, + tileGrid: options.tileGrid, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + +}; +ol.inherits(ol.source.TileDebug, ol.source.Tile); + + +/** + * @inheritDoc + */ +ol.source.TileDebug.prototype.getTile = function(z, x, y) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + return /** @type {!ol.source.TileDebug.Tile_} */ (this.tileCache.get(tileCoordKey)); + } else { + var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z)); + var tileCoord = [z, x, y]; + var textTileCoord = this.getTileCoordForTileUrlFunction(tileCoord); + var text = !textTileCoord ? '' : + this.getTileCoordForTileUrlFunction(textTileCoord).toString(); + var tile = new ol.source.TileDebug.Tile_(tileCoord, tileSize, text); + this.tileCache.set(tileCoordKey, tile); + return tile; + } +}; + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Size} tileSize Tile size. + * @param {string} text Text. + * @private + */ +ol.source.TileDebug.Tile_ = function(tileCoord, tileSize, text) { + + ol.Tile.call(this, tileCoord, ol.Tile.State.LOADED); + + /** + * @private + * @type {ol.Size} + */ + this.tileSize_ = tileSize; + + /** + * @private + * @type {string} + */ + this.text_ = text; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = null; + +}; +ol.inherits(ol.source.TileDebug.Tile_, ol.Tile); + + +/** + * Get the image element for this tile. + * @return {HTMLCanvasElement} Image. + */ +ol.source.TileDebug.Tile_.prototype.getImage = function() { + if (this.canvas_) { + return this.canvas_; + } else { + var tileSize = this.tileSize_; + var context = ol.dom.createCanvasContext2D(tileSize[0], tileSize[1]); + + context.strokeStyle = 'black'; + context.strokeRect(0.5, 0.5, tileSize[0] + 0.5, tileSize[1] + 0.5); + + context.fillStyle = 'black'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.font = '24px sans-serif'; + context.fillText(this.text_, tileSize[0] / 2, tileSize[1] / 2); + + this.canvas_ = context.canvas; + return context.canvas; + } +}; + +// FIXME check order of async callbacks + +/** + * @see http://mapbox.com/developers/api/ + */ + +goog.provide('ol.source.TileJSON'); + +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.extent'); +goog.require('ol.net'); +goog.require('ol.proj'); +goog.require('ol.source.State'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * Layer source for tile data in TileJSON format. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.TileJSONOptions} options TileJSON options. + * @api stable + */ +ol.source.TileJSON = function(options) { + + /** + * @type {TileJSON} + * @private + */ + this.tileJSON_ = null; + + ol.source.TileImage.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + projection: ol.proj.get('EPSG:3857'), + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + state: ol.source.State.LOADING, + tileLoadFunction: options.tileLoadFunction, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + + if (options.jsonp) { + ol.net.jsonp(options.url, this.handleTileJSONResponse.bind(this), + this.handleTileJSONError.bind(this)); + } else { + var client = new XMLHttpRequest(); + client.addEventListener('load', this.onXHRLoad_.bind(this)); + client.addEventListener('error', this.onXHRError_.bind(this)); + client.open('GET', options.url); + client.send(); + } + +}; +ol.inherits(ol.source.TileJSON, ol.source.TileImage); + + +/** + * @private + * @param {Event} event The load event. + */ +ol.source.TileJSON.prototype.onXHRLoad_ = function(event) { + var client = /** @type {XMLHttpRequest} */ (event.target); + // status will be 0 for file:// urls + if (!client.status || client.status >= 200 && client.status < 300) { + var response; + try { + response = /** @type {TileJSON} */(JSON.parse(client.responseText)); + } catch (err) { + this.handleTileJSONError(); + return; + } + this.handleTileJSONResponse(response); + } else { + this.handleTileJSONError(); + } +}; + + +/** + * @private + * @param {Event} event The error event. + */ +ol.source.TileJSON.prototype.onXHRError_ = function(event) { + this.handleTileJSONError(); +}; + + +/** + * @return {TileJSON} The tilejson object. + * @api + */ +ol.source.TileJSON.prototype.getTileJSON = function() { + return this.tileJSON_; +}; + + +/** + * @protected + * @param {TileJSON} tileJSON Tile JSON. + */ +ol.source.TileJSON.prototype.handleTileJSONResponse = function(tileJSON) { + + var epsg4326Projection = ol.proj.get('EPSG:4326'); + + var sourceProjection = this.getProjection(); + var extent; + if (tileJSON.bounds !== undefined) { + var transform = ol.proj.getTransformFromProjections( + epsg4326Projection, sourceProjection); + extent = ol.extent.applyTransform(tileJSON.bounds, transform); + } + + if (tileJSON.scheme !== undefined) { + ol.DEBUG && console.assert(tileJSON.scheme == 'xyz', 'tileJSON-scheme is "xyz"'); + } + var minZoom = tileJSON.minzoom || 0; + var maxZoom = tileJSON.maxzoom || 22; + var tileGrid = ol.tilegrid.createXYZ({ + extent: ol.tilegrid.extentFromProjection(sourceProjection), + maxZoom: maxZoom, + minZoom: minZoom + }); + this.tileGrid = tileGrid; + + this.tileUrlFunction = + ol.TileUrlFunction.createFromTemplates(tileJSON.tiles, tileGrid); + + if (tileJSON.attribution !== undefined && !this.getAttributions()) { + var attributionExtent = extent !== undefined ? + extent : epsg4326Projection.getExtent(); + /** @type {Object.<string, Array.<ol.TileRange>>} */ + var tileRanges = {}; + var z, zKey; + for (z = minZoom; z <= maxZoom; ++z) { + zKey = z.toString(); + tileRanges[zKey] = + [tileGrid.getTileRangeForExtentAndZ(attributionExtent, z)]; + } + this.setAttributions([ + new ol.Attribution({ + html: tileJSON.attribution, + tileRanges: tileRanges + }) + ]); + } + this.tileJSON_ = tileJSON; + this.setState(ol.source.State.READY); + +}; + + +/** + * @protected + */ +ol.source.TileJSON.prototype.handleTileJSONError = function() { + this.setState(ol.source.State.ERROR); +}; + +goog.provide('ol.source.TileUTFGrid'); + +goog.require('ol'); +goog.require('ol.Attribution'); +goog.require('ol.Tile'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.asserts'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.extent'); +goog.require('ol.net'); +goog.require('ol.proj'); +goog.require('ol.source.State'); +goog.require('ol.source.Tile'); +goog.require('ol.tilegrid'); + + +/** + * @classdesc + * Layer source for UTFGrid interaction data loaded from TileJSON format. + * + * @constructor + * @extends {ol.source.Tile} + * @param {olx.source.TileUTFGridOptions} options Source options. + * @api + */ +ol.source.TileUTFGrid = function(options) { + ol.source.Tile.call(this, { + projection: ol.proj.get('EPSG:3857'), + state: ol.source.State.LOADING + }); + + /** + * @private + * @type {boolean} + */ + this.preemptive_ = options.preemptive !== undefined ? + options.preemptive : true; + + /** + * @private + * @type {!ol.TileUrlFunctionType} + */ + this.tileUrlFunction_ = ol.TileUrlFunction.nullTileUrlFunction; + + /** + * @private + * @type {string|undefined} + */ + this.template_ = undefined; + + /** + * @private + * @type {boolean} + */ + this.jsonp_ = options.jsonp || false; + + if (options.url) { + if (this.jsonp_) { + ol.net.jsonp(options.url, this.handleTileJSONResponse.bind(this), + this.handleTileJSONError.bind(this)); + } else { + var client = new XMLHttpRequest(); + client.addEventListener('load', this.onXHRLoad_.bind(this)); + client.addEventListener('error', this.onXHRError_.bind(this)); + client.open('GET', options.url); + client.send(); + } + } else if (options.tileJSON) { + this.handleTileJSONResponse(options.tileJSON); + } else { + ol.asserts.assert(false, 51); // Either `url` or `tileJSON` options must be provided + } +}; +ol.inherits(ol.source.TileUTFGrid, ol.source.Tile); + + +/** + * @private + * @param {Event} event The load event. + */ +ol.source.TileUTFGrid.prototype.onXHRLoad_ = function(event) { + var client = /** @type {XMLHttpRequest} */ (event.target); + // status will be 0 for file:// urls + if (!client.status || client.status >= 200 && client.status < 300) { + var response; + try { + response = /** @type {TileJSON} */(JSON.parse(client.responseText)); + } catch (err) { + this.handleTileJSONError(); + return; + } + this.handleTileJSONResponse(response); + } else { + this.handleTileJSONError(); + } +}; + + +/** + * @private + * @param {Event} event The error event. + */ +ol.source.TileUTFGrid.prototype.onXHRError_ = function(event) { + this.handleTileJSONError(); +}; + + +/** + * Return the template from TileJSON. + * @return {string|undefined} The template from TileJSON. + * @api + */ +ol.source.TileUTFGrid.prototype.getTemplate = function() { + return this.template_; +}; + + +/** + * Calls the callback (synchronously by default) with the available data + * for given coordinate and resolution (or `null` if not yet loaded or + * in case of an error). + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {function(this: T, *)} callback Callback. + * @param {T=} opt_this The object to use as `this` in the callback. + * @param {boolean=} opt_request If `true` the callback is always async. + * The tile data is requested if not yet loaded. + * @template T + * @api + */ +ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution = function( + coordinate, resolution, callback, opt_this, opt_request) { + if (this.tileGrid) { + var tileCoord = this.tileGrid.getTileCoordForCoordAndResolution( + coordinate, resolution); + var tile = /** @type {!ol.source.TileUTFGrid.Tile_} */(this.getTile( + tileCoord[0], tileCoord[1], tileCoord[2], 1, this.getProjection())); + tile.forDataAtCoordinate(coordinate, callback, opt_this, opt_request); + } else { + if (opt_request === true) { + setTimeout(function() { + callback.call(opt_this, null); + }, 0); + } else { + callback.call(opt_this, null); + } + } +}; + + +/** + * @protected + */ +ol.source.TileUTFGrid.prototype.handleTileJSONError = function() { + this.setState(ol.source.State.ERROR); +}; + + +/** + * TODO: very similar to ol.source.TileJSON#handleTileJSONResponse + * @protected + * @param {TileJSON} tileJSON Tile JSON. + */ +ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) { + + var epsg4326Projection = ol.proj.get('EPSG:4326'); + + var sourceProjection = this.getProjection(); + var extent; + if (tileJSON.bounds !== undefined) { + var transform = ol.proj.getTransformFromProjections( + epsg4326Projection, sourceProjection); + extent = ol.extent.applyTransform(tileJSON.bounds, transform); + } + + if (tileJSON.scheme !== undefined) { + ol.DEBUG && console.assert(tileJSON.scheme == 'xyz', 'tileJSON-scheme is "xyz"'); + } + var minZoom = tileJSON.minzoom || 0; + var maxZoom = tileJSON.maxzoom || 22; + var tileGrid = ol.tilegrid.createXYZ({ + extent: ol.tilegrid.extentFromProjection(sourceProjection), + maxZoom: maxZoom, + minZoom: minZoom + }); + this.tileGrid = tileGrid; + + this.template_ = tileJSON.template; + + var grids = tileJSON.grids; + if (!grids) { + this.setState(ol.source.State.ERROR); + return; + } + + this.tileUrlFunction_ = + ol.TileUrlFunction.createFromTemplates(grids, tileGrid); + + if (tileJSON.attribution !== undefined) { + var attributionExtent = extent !== undefined ? + extent : epsg4326Projection.getExtent(); + /** @type {Object.<string, Array.<ol.TileRange>>} */ + var tileRanges = {}; + var z, zKey; + for (z = minZoom; z <= maxZoom; ++z) { + zKey = z.toString(); + tileRanges[zKey] = + [tileGrid.getTileRangeForExtentAndZ(attributionExtent, z)]; + } + this.setAttributions([ + new ol.Attribution({ + html: tileJSON.attribution, + tileRanges: tileRanges + }) + ]); + } + + this.setState(ol.source.State.READY); + +}; + + +/** + * @inheritDoc + */ +ol.source.TileUTFGrid.prototype.getTile = function(z, x, y, pixelRatio, projection) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey)); + } else { + ol.DEBUG && console.assert(projection, 'argument projection is truthy'); + var tileCoord = [z, x, y]; + var urlTileCoord = + this.getTileCoordForTileUrlFunction(tileCoord, projection); + var tileUrl = this.tileUrlFunction_(urlTileCoord, pixelRatio, projection); + var tile = new ol.source.TileUTFGrid.Tile_( + tileCoord, + tileUrl !== undefined ? ol.Tile.State.IDLE : ol.Tile.State.EMPTY, + tileUrl !== undefined ? tileUrl : '', + this.tileGrid.getTileCoordExtent(tileCoord), + this.preemptive_, + this.jsonp_); + this.tileCache.set(tileCoordKey, tile); + return tile; + } +}; + + +/** + * @inheritDoc + */ +ol.source.TileUTFGrid.prototype.useTile = function(z, x, y) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + this.tileCache.get(tileCoordKey); + } +}; + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Tile.State} state State. + * @param {string} src Image source URI. + * @param {ol.Extent} extent Extent of the tile. + * @param {boolean} preemptive Load the tile when visible (before it's needed). + * @param {boolean} jsonp Load the tile as a script. + * @private + */ +ol.source.TileUTFGrid.Tile_ = function(tileCoord, state, src, extent, preemptive, jsonp) { + + ol.Tile.call(this, tileCoord, state); + + /** + * @private + * @type {string} + */ + this.src_ = src; + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = extent; + + /** + * @private + * @type {boolean} + */ + this.preemptive_ = preemptive; + + /** + * @private + * @type {Array.<string>} + */ + this.grid_ = null; + + /** + * @private + * @type {Array.<string>} + */ + this.keys_ = null; + + /** + * @private + * @type {Object.<string, Object>|undefined} + */ + this.data_ = null; + + + /** + * @private + * @type {boolean} + */ + this.jsonp_ = jsonp; + +}; +ol.inherits(ol.source.TileUTFGrid.Tile_, ol.Tile); + + +/** + * Get the image element for this tile. + * @return {Image} Image. + */ +ol.source.TileUTFGrid.Tile_.prototype.getImage = function() { + return null; +}; + + +/** + * Synchronously returns data at given coordinate (if available). + * @param {ol.Coordinate} coordinate Coordinate. + * @return {*} The data. + */ +ol.source.TileUTFGrid.Tile_.prototype.getData = function(coordinate) { + if (!this.grid_ || !this.keys_) { + return null; + } + var xRelative = (coordinate[0] - this.extent_[0]) / + (this.extent_[2] - this.extent_[0]); + var yRelative = (coordinate[1] - this.extent_[1]) / + (this.extent_[3] - this.extent_[1]); + + var row = this.grid_[Math.floor((1 - yRelative) * this.grid_.length)]; + + if (typeof row !== 'string') { + return null; + } + + var code = row.charCodeAt(Math.floor(xRelative * row.length)); + if (code >= 93) { + code--; + } + if (code >= 35) { + code--; + } + code -= 32; + + var data = null; + if (code in this.keys_) { + var id = this.keys_[code]; + if (this.data_ && id in this.data_) { + data = this.data_[id]; + } else { + data = id; + } + } + return data; +}; + + +/** + * Calls the callback (synchronously by default) with the available data + * for given coordinate (or `null` if not yet loaded). + * @param {ol.Coordinate} coordinate Coordinate. + * @param {function(this: T, *)} callback Callback. + * @param {T=} opt_this The object to use as `this` in the callback. + * @param {boolean=} opt_request If `true` the callback is always async. + * The tile data is requested if not yet loaded. + * @template T + */ +ol.source.TileUTFGrid.Tile_.prototype.forDataAtCoordinate = function(coordinate, callback, opt_this, opt_request) { + if (this.state == ol.Tile.State.IDLE && opt_request === true) { + ol.events.listenOnce(this, ol.events.EventType.CHANGE, function(e) { + callback.call(opt_this, this.getData(coordinate)); + }, this); + this.loadInternal_(); + } else { + if (opt_request === true) { + setTimeout(function() { + callback.call(opt_this, this.getData(coordinate)); + }.bind(this), 0); + } else { + callback.call(opt_this, this.getData(coordinate)); + } + } +}; + + +/** + * @inheritDoc + */ +ol.source.TileUTFGrid.Tile_.prototype.getKey = function() { + return this.src_; +}; + + +/** + * @private + */ +ol.source.TileUTFGrid.Tile_.prototype.handleError_ = function() { + this.state = ol.Tile.State.ERROR; + this.changed(); +}; + + +/** + * @param {!UTFGridJSON} json UTFGrid data. + * @private + */ +ol.source.TileUTFGrid.Tile_.prototype.handleLoad_ = function(json) { + this.grid_ = json.grid; + this.keys_ = json.keys; + this.data_ = json.data; + + this.state = ol.Tile.State.EMPTY; + this.changed(); +}; + + +/** + * @private + */ +ol.source.TileUTFGrid.Tile_.prototype.loadInternal_ = function() { + if (this.state == ol.Tile.State.IDLE) { + this.state = ol.Tile.State.LOADING; + if (this.jsonp_) { + ol.net.jsonp(this.src_, this.handleLoad_.bind(this), + this.handleError_.bind(this)); + } else { + var client = new XMLHttpRequest(); + client.addEventListener('load', this.onXHRLoad_.bind(this)); + client.addEventListener('error', this.onXHRError_.bind(this)); + client.open('GET', this.src_); + client.send(); + } + } +}; + + +/** + * @private + * @param {Event} event The load event. + */ +ol.source.TileUTFGrid.Tile_.prototype.onXHRLoad_ = function(event) { + var client = /** @type {XMLHttpRequest} */ (event.target); + // status will be 0 for file:// urls + if (!client.status || client.status >= 200 && client.status < 300) { + var response; + try { + response = /** @type {!UTFGridJSON} */(JSON.parse(client.responseText)); + } catch (err) { + this.handleError_(); + return; + } + this.handleLoad_(response); + } else { + this.handleError_(); + } +}; + + +/** + * @private + * @param {Event} event The error event. + */ +ol.source.TileUTFGrid.Tile_.prototype.onXHRError_ = function(event) { + this.handleError_(); +}; + + +/** + * Load not yet loaded URI. + */ +ol.source.TileUTFGrid.Tile_.prototype.load = function() { + if (this.preemptive_) { + this.loadInternal_(); + } +}; + +// FIXME add minZoom support +// FIXME add date line wrap (tile coord transform) +// FIXME cannot be shared between maps with different projections + +goog.provide('ol.source.TileWMS'); + +goog.require('ol'); +goog.require('ol.asserts'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.math'); +goog.require('ol.proj'); +goog.require('ol.size'); +goog.require('ol.source.TileImage'); +goog.require('ol.source.WMSServerType'); +goog.require('ol.tilecoord'); +goog.require('ol.string'); +goog.require('ol.uri'); + +/** + * @classdesc + * Layer source for tile data from WMS servers. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.TileWMSOptions=} opt_options Tile WMS options. + * @api stable + */ +ol.source.TileWMS = function(opt_options) { + + var options = opt_options || {}; + + var params = options.params || {}; + + var transparent = 'TRANSPARENT' in params ? params['TRANSPARENT'] : true; + + ol.source.TileImage.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + logo: options.logo, + opaque: !transparent, + projection: options.projection, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileGrid: options.tileGrid, + tileLoadFunction: options.tileLoadFunction, + url: options.url, + urls: options.urls, + wrapX: options.wrapX !== undefined ? options.wrapX : true + }); + + /** + * @private + * @type {number} + */ + this.gutter_ = options.gutter !== undefined ? options.gutter : 0; + + /** + * @private + * @type {!Object} + */ + this.params_ = params; + + /** + * @private + * @type {boolean} + */ + this.v13_ = true; + + /** + * @private + * @type {ol.source.WMSServerType|undefined} + */ + this.serverType_ = + /** @type {ol.source.WMSServerType|undefined} */ (options.serverType); + + /** + * @private + * @type {boolean} + */ + this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true; + + /** + * @private + * @type {string} + */ + this.coordKeyPrefix_ = ''; + this.resetCoordKeyPrefix_(); + + /** + * @private + * @type {ol.Extent} + */ + this.tmpExtent_ = ol.extent.createEmpty(); + + this.updateV13_(); + this.setKey(this.getKeyForParams_()); + +}; +ol.inherits(ol.source.TileWMS, ol.source.TileImage); + + +/** + * Return the GetFeatureInfo URL for the passed coordinate, resolution, and + * projection. Return `undefined` if the GetFeatureInfo URL cannot be + * constructed. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {ol.ProjectionLike} projection Projection. + * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should + * be provided. If `QUERY_LAYERS` is not provided then the layers specified + * in the `LAYERS` parameter will be used. `VERSION` should not be + * specified here. + * @return {string|undefined} GetFeatureInfo URL. + * @api stable + */ +ol.source.TileWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, params) { + + ol.DEBUG && console.assert(!('VERSION' in params), + 'key VERSION is not allowed in params'); + + var projectionObj = ol.proj.get(projection); + + var tileGrid = this.getTileGrid(); + if (!tileGrid) { + tileGrid = this.getTileGridForProjection(projectionObj); + } + + var tileCoord = tileGrid.getTileCoordForCoordAndResolution( + coordinate, resolution); + + if (tileGrid.getResolutions().length <= tileCoord[0]) { + return undefined; + } + + var tileResolution = tileGrid.getResolution(tileCoord[0]); + var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_); + var tileSize = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0]), this.tmpSize); + + var gutter = this.gutter_; + if (gutter !== 0) { + tileSize = ol.size.buffer(tileSize, gutter, this.tmpSize); + tileExtent = ol.extent.buffer(tileExtent, + tileResolution * gutter, tileExtent); + } + + var baseParams = { + 'SERVICE': 'WMS', + 'VERSION': ol.DEFAULT_WMS_VERSION, + 'REQUEST': 'GetFeatureInfo', + 'FORMAT': 'image/png', + 'TRANSPARENT': true, + 'QUERY_LAYERS': this.params_['LAYERS'] + }; + ol.obj.assign(baseParams, this.params_, params); + + var x = Math.floor((coordinate[0] - tileExtent[0]) / tileResolution); + var y = Math.floor((tileExtent[3] - coordinate[1]) / tileResolution); + + baseParams[this.v13_ ? 'I' : 'X'] = x; + baseParams[this.v13_ ? 'J' : 'Y'] = y; + + return this.getRequestUrl_(tileCoord, tileSize, tileExtent, + 1, projectionObj, baseParams); +}; + + +/** + * @inheritDoc + */ +ol.source.TileWMS.prototype.getGutterInternal = function() { + return this.gutter_; +}; + + +/** + * @inheritDoc + */ +ol.source.TileWMS.prototype.getKeyZXY = function(z, x, y) { + return this.coordKeyPrefix_ + ol.source.TileImage.prototype.getKeyZXY.call(this, z, x, y); +}; + + +/** + * Get the user-provided params, i.e. those passed to the constructor through + * the "params" option, and possibly updated using the updateParams method. + * @return {Object} Params. + * @api stable + */ +ol.source.TileWMS.prototype.getParams = function() { + return this.params_; +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Size} tileSize Tile size. + * @param {ol.Extent} tileExtent Tile extent. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {Object} params Params. + * @return {string|undefined} Request URL. + * @private + */ +ol.source.TileWMS.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent, + pixelRatio, projection, params) { + + var urls = this.urls; + if (!urls) { + return undefined; + } + + params['WIDTH'] = tileSize[0]; + params['HEIGHT'] = tileSize[1]; + + params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode(); + + if (!('STYLES' in this.params_)) { + params['STYLES'] = ''; + } + + if (pixelRatio != 1) { + switch (this.serverType_) { + case ol.source.WMSServerType.GEOSERVER: + var dpi = (90 * pixelRatio + 0.5) | 0; + if ('FORMAT_OPTIONS' in params) { + params['FORMAT_OPTIONS'] += ';dpi:' + dpi; + } else { + params['FORMAT_OPTIONS'] = 'dpi:' + dpi; + } + break; + case ol.source.WMSServerType.MAPSERVER: + params['MAP_RESOLUTION'] = 90 * pixelRatio; + break; + case ol.source.WMSServerType.CARMENTA_SERVER: + case ol.source.WMSServerType.QGIS: + params['DPI'] = 90 * pixelRatio; + break; + default: + ol.asserts.assert(false, 52); // Unknown `serverType` configured + break; + } + } + + var axisOrientation = projection.getAxisOrientation(); + var bbox = tileExtent; + if (this.v13_ && axisOrientation.substr(0, 2) == 'ne') { + var tmp; + tmp = tileExtent[0]; + bbox[0] = tileExtent[1]; + bbox[1] = tmp; + tmp = tileExtent[2]; + bbox[2] = tileExtent[3]; + bbox[3] = tmp; + } + params['BBOX'] = bbox.join(','); + + var url; + if (urls.length == 1) { + url = urls[0]; + } else { + var index = ol.math.modulo(ol.tilecoord.hash(tileCoord), urls.length); + url = urls[index]; + } + return ol.uri.appendParams(url, params); +}; + + +/** + * @inheritDoc + */ +ol.source.TileWMS.prototype.getTilePixelRatio = function(pixelRatio) { + return (!this.hidpi_ || this.serverType_ === undefined) ? 1 : + /** @type {number} */ (pixelRatio); +}; + + +/** + * @private + */ +ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() { + var i = 0; + var res = []; + + if (this.urls) { + var j, jj; + for (j = 0, jj = this.urls.length; j < jj; ++j) { + res[i++] = this.urls[j]; + } + } + + this.coordKeyPrefix_ = res.join('#'); +}; + + +/** + * @private + * @return {string} The key for the current params. + */ +ol.source.TileWMS.prototype.getKeyForParams_ = function() { + var i = 0; + var res = []; + for (var key in this.params_) { + res[i++] = key + '-' + this.params_[key]; + } + return res.join('/'); +}; + + +/** + * @inheritDoc + */ +ol.source.TileWMS.prototype.fixedTileUrlFunction = function(tileCoord, pixelRatio, projection) { + + var tileGrid = this.getTileGrid(); + if (!tileGrid) { + tileGrid = this.getTileGridForProjection(projection); + } + + if (tileGrid.getResolutions().length <= tileCoord[0]) { + return undefined; + } + + if (pixelRatio != 1 && (!this.hidpi_ || this.serverType_ === undefined)) { + pixelRatio = 1; + } + + var tileResolution = tileGrid.getResolution(tileCoord[0]); + var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_); + var tileSize = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0]), this.tmpSize); + + var gutter = this.gutter_; + if (gutter !== 0) { + tileSize = ol.size.buffer(tileSize, gutter, this.tmpSize); + tileExtent = ol.extent.buffer(tileExtent, + tileResolution * gutter, tileExtent); + } + + if (pixelRatio != 1) { + tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize); + } + + var baseParams = { + 'SERVICE': 'WMS', + 'VERSION': ol.DEFAULT_WMS_VERSION, + 'REQUEST': 'GetMap', + 'FORMAT': 'image/png', + 'TRANSPARENT': true + }; + ol.obj.assign(baseParams, this.params_); + + return this.getRequestUrl_(tileCoord, tileSize, tileExtent, + pixelRatio, projection, baseParams); +}; + +/** + * @inheritDoc + */ +ol.source.TileWMS.prototype.setUrls = function(urls) { + ol.source.TileImage.prototype.setUrls.call(this, urls); + this.resetCoordKeyPrefix_(); +}; + + +/** + * Update the user-provided params. + * @param {Object} params Params. + * @api stable + */ +ol.source.TileWMS.prototype.updateParams = function(params) { + ol.obj.assign(this.params_, params); + this.resetCoordKeyPrefix_(); + this.updateV13_(); + this.setKey(this.getKeyForParams_()); +}; + + +/** + * @private + */ +ol.source.TileWMS.prototype.updateV13_ = function() { + var version = this.params_['VERSION'] || ol.DEFAULT_WMS_VERSION; + this.v13_ = ol.string.compareVersions(version, '1.3') >= 0; +}; + +goog.provide('ol.VectorTile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.dom'); +goog.require('ol.featureloader'); + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Tile.State} state State. + * @param {string} src Data source url. + * @param {ol.format.Feature} format Feature format. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. + */ +ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) { + + ol.Tile.call(this, tileCoord, state); + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {ol.format.Feature} + */ + this.format_ = format; + + /** + * @private + * @type {Array.<ol.Feature>} + */ + this.features_ = null; + + /** + * @private + * @type {ol.FeatureLoader} + */ + this.loader_; + + /** + * Data projection + * @private + * @type {ol.proj.Projection} + */ + this.projection_; + + /** + * @private + * @type {ol.TileReplayState} + */ + this.replayState_ = { + dirty: false, + renderedRenderOrder: null, + renderedRevision: -1, + renderedTileRevision: -1, + replayGroup: null + }; + + /** + * @private + * @type {ol.TileLoadFunctionType} + */ + this.tileLoadFunction_ = tileLoadFunction; + + /** + * @private + * @type {string} + */ + this.url_ = src; + +}; +ol.inherits(ol.VectorTile, ol.Tile); + + +/** + * @return {CanvasRenderingContext2D} The rendering context. + */ +ol.VectorTile.prototype.getContext = function() { + return this.context_; +}; + + +/** + * @inheritDoc + */ +ol.VectorTile.prototype.getImage = function() { + return this.replayState_.renderedTileRevision == -1 ? + null : this.context_.canvas; +}; + + +/** + * Get the feature format assigned for reading this tile's features. + * @return {ol.format.Feature} Feature format. + * @api + */ +ol.VectorTile.prototype.getFormat = function() { + return this.format_; +}; + + +/** + * @return {Array.<ol.Feature>} Features. + */ +ol.VectorTile.prototype.getFeatures = function() { + return this.features_; +}; + + +/** + * @return {ol.TileReplayState} The replay state. + */ +ol.VectorTile.prototype.getReplayState = function() { + return this.replayState_; +}; + + +/** + * @inheritDoc + */ +ol.VectorTile.prototype.getKey = function() { + return this.url_; +}; + + +/** + * @return {ol.proj.Projection} Feature projection. + */ +ol.VectorTile.prototype.getProjection = function() { + return this.projection_; +}; + + +/** + * Load the tile. + */ +ol.VectorTile.prototype.load = function() { + if (this.state == ol.Tile.State.IDLE) { + this.setState(ol.Tile.State.LOADING); + this.tileLoadFunction_(this, this.url_); + this.loader_(null, NaN, null); + } +}; + + +/** + * Handler for successful tile load. + * @param {Array.<ol.Feature>} features The loaded features. + * @param {ol.proj.Projection} dataProjection Data projection. + */ +ol.VectorTile.prototype.onLoad_ = function(features, dataProjection) { + this.setProjection(dataProjection); + this.setFeatures(features); +}; + + +/** + * Handler for tile load errors. + */ +ol.VectorTile.prototype.onError_ = function() { + this.setState(ol.Tile.State.ERROR); +}; + + +/** + * @param {Array.<ol.Feature>} features Features. + * @api + */ +ol.VectorTile.prototype.setFeatures = function(features) { + this.features_ = features; + this.setState(ol.Tile.State.LOADED); +}; + + +/** + * Set the projection of the features that were added with {@link #setFeatures}. + * @param {ol.proj.Projection} projection Feature projection. + * @api + */ +ol.VectorTile.prototype.setProjection = function(projection) { + this.projection_ = projection; +}; + + +/** + * @param {ol.Tile.State} tileState Tile state. + */ +ol.VectorTile.prototype.setState = function(tileState) { + this.state = tileState; + this.changed(); +}; + + +/** + * Set the feature loader for reading this tile's features. + * @param {ol.FeatureLoader} loader Feature loader. + * @api + */ +ol.VectorTile.prototype.setLoader = function(loader) { + this.loader_ = loader; +}; + + +/** + * Sets the loader for a tile. + * @param {ol.VectorTile} tile Vector tile. + * @param {string} url URL. + */ +ol.VectorTile.defaultLoadFunction = function(tile, url) { + var loader = ol.featureloader.loadFeaturesXhr( + url, tile.getFormat(), tile.onLoad_.bind(tile), tile.onError_.bind(tile)); + + tile.setLoader(loader); +}; + +goog.provide('ol.source.VectorTile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.VectorTile'); +goog.require('ol.events'); +goog.require('ol.events.EventType'); +goog.require('ol.size'); +goog.require('ol.source.UrlTile'); + + +/** + * @classdesc + * Class for layer sources providing vector data divided into a tile grid, to be + * used with {@link ol.layer.VectorTile}. Although this source receives tiles + * with vector features from the server, it is not meant for feature editing. + * Features are optimized for rendering, their geometries are clipped at or near + * tile boundaries and simplified for a view resolution. See + * {@link ol.source.Vector} for vector sources that are suitable for feature + * editing. + * + * @constructor + * @fires ol.source.Tile.Event + * @extends {ol.source.UrlTile} + * @param {olx.source.VectorTileOptions} options Vector tile options. + * @api + */ +ol.source.VectorTile = function(options) { + + ol.source.UrlTile.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize !== undefined ? options.cacheSize : 128, + extent: options.extent, + logo: options.logo, + opaque: false, + projection: options.projection, + state: options.state, + tileGrid: options.tileGrid, + tileLoadFunction: options.tileLoadFunction ? + options.tileLoadFunction : ol.VectorTile.defaultLoadFunction, + tileUrlFunction: options.tileUrlFunction, + tilePixelRatio: options.tilePixelRatio, + url: options.url, + urls: options.urls, + wrapX: options.wrapX === undefined ? true : options.wrapX + }); + + /** + * @private + * @type {ol.format.Feature} + */ + this.format_ = options.format ? options.format : null; + + /** + * @private + * @type {boolean} + */ + this.overlaps_ = options.overlaps == undefined ? true : options.overlaps; + + /** + * @protected + * @type {function(new: ol.VectorTile, ol.TileCoord, ol.Tile.State, string, + * ol.format.Feature, ol.TileLoadFunctionType)} + */ + this.tileClass = options.tileClass ? options.tileClass : ol.VectorTile; + +}; +ol.inherits(ol.source.VectorTile, ol.source.UrlTile); + + +/** + * @return {boolean} The source can have overlapping geometries. + */ +ol.source.VectorTile.prototype.getOverlaps = function() { + return this.overlaps_; +}; + + +/** + * @inheritDoc + */ +ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projection) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey)); + } else { + var tileCoord = [z, x, y]; + var urlTileCoord = this.getTileCoordForTileUrlFunction( + tileCoord, projection); + var tileUrl = urlTileCoord ? + this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined; + var tile = new this.tileClass( + tileCoord, + tileUrl !== undefined ? ol.Tile.State.IDLE : ol.Tile.State.EMPTY, + tileUrl !== undefined ? tileUrl : '', + this.format_, this.tileLoadFunction); + ol.events.listen(tile, ol.events.EventType.CHANGE, + this.handleTileChange, this); + + this.tileCache.set(tileCoordKey, tile); + return tile; + } +}; + + +/** + * @inheritDoc + */ +ol.source.VectorTile.prototype.getTilePixelRatio = function(opt_pixelRatio) { + return opt_pixelRatio == undefined ? + ol.source.UrlTile.prototype.getTilePixelRatio.call(this, opt_pixelRatio) : + opt_pixelRatio; +}; + + +/** + * @inheritDoc + */ +ol.source.VectorTile.prototype.getTilePixelSize = function(z, pixelRatio, projection) { + var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z)); + return [Math.round(tileSize[0] * pixelRatio), Math.round(tileSize[1] * pixelRatio)]; +}; + +goog.provide('ol.tilegrid.WMTS'); + +goog.require('ol'); +goog.require('ol.array'); +goog.require('ol.proj'); +goog.require('ol.tilegrid.TileGrid'); + + +/** + * @classdesc + * Set the grid pattern for sources accessing WMTS tiled-image servers. + * + * @constructor + * @extends {ol.tilegrid.TileGrid} + * @param {olx.tilegrid.WMTSOptions} options WMTS options. + * @struct + * @api + */ +ol.tilegrid.WMTS = function(options) { + + ol.DEBUG && console.assert( + options.resolutions.length == options.matrixIds.length, + 'options resolutions and matrixIds must have equal length (%s == %s)', + options.resolutions.length, options.matrixIds.length); + + /** + * @private + * @type {!Array.<string>} + */ + this.matrixIds_ = options.matrixIds; + // FIXME: should the matrixIds become optionnal? + + ol.tilegrid.TileGrid.call(this, { + extent: options.extent, + origin: options.origin, + origins: options.origins, + resolutions: options.resolutions, + tileSize: options.tileSize, + tileSizes: options.tileSizes, + sizes: options.sizes + }); + +}; +ol.inherits(ol.tilegrid.WMTS, ol.tilegrid.TileGrid); + + +/** + * @param {number} z Z. + * @return {string} MatrixId.. + */ +ol.tilegrid.WMTS.prototype.getMatrixId = function(z) { + ol.DEBUG && console.assert(0 <= z && z < this.matrixIds_.length, + 'attempted to retrieve matrixId for illegal z (%s)', z); + return this.matrixIds_[z]; +}; + + +/** + * Get the list of matrix identifiers. + * @return {Array.<string>} MatrixIds. + * @api + */ +ol.tilegrid.WMTS.prototype.getMatrixIds = function() { + return this.matrixIds_; +}; + + +/** + * Create a tile grid from a WMTS capabilities matrix set and an + * optional TileMatrixSetLimits. + * @param {Object} matrixSet An object representing a matrixSet in the + * capabilities document. + * @param {ol.Extent=} opt_extent An optional extent to restrict the tile + * ranges the server provides. + * @param {Array.<Object>=} opt_matrixLimits An optional object representing + * the available matrices for tileGrid. + * @return {ol.tilegrid.WMTS} WMTS tileGrid instance. + * @api + */ +ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = function(matrixSet, opt_extent, + opt_matrixLimits) { + + /** @type {!Array.<number>} */ + var resolutions = []; + /** @type {!Array.<string>} */ + var matrixIds = []; + /** @type {!Array.<ol.Coordinate>} */ + var origins = []; + /** @type {!Array.<ol.Size>} */ + var tileSizes = []; + /** @type {!Array.<ol.Size>} */ + var sizes = []; + + var matrixLimits = opt_matrixLimits !== undefined ? opt_matrixLimits : []; + + var supportedCRSPropName = 'SupportedCRS'; + var matrixIdsPropName = 'TileMatrix'; + var identifierPropName = 'Identifier'; + var scaleDenominatorPropName = 'ScaleDenominator'; + var topLeftCornerPropName = 'TopLeftCorner'; + var tileWidthPropName = 'TileWidth'; + var tileHeightPropName = 'TileHeight'; + + var projection; + projection = ol.proj.get(matrixSet[supportedCRSPropName].replace( + /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3')); + var metersPerUnit = projection.getMetersPerUnit(); + // swap origin x and y coordinates if axis orientation is lat/long + var switchOriginXY = projection.getAxisOrientation().substr(0, 2) == 'ne'; + + matrixSet[matrixIdsPropName].sort(function(a, b) { + return b[scaleDenominatorPropName] - a[scaleDenominatorPropName]; + }); + + matrixSet[matrixIdsPropName].forEach(function(elt, index, array) { + + var matrixAvailable; + // use of matrixLimits to filter TileMatrices from GetCapabilities + // TileMatrixSet from unavailable matrix levels. + if (matrixLimits.length > 0) { + matrixAvailable = ol.array.find(matrixLimits, + function(elt_ml, index_ml, array_ml) { + return elt[identifierPropName] == elt_ml[matrixIdsPropName]; + }); + } else { + matrixAvailable = true; + } + + if (matrixAvailable) { + matrixIds.push(elt[identifierPropName]); + var resolution = elt[scaleDenominatorPropName] * 0.28E-3 / metersPerUnit; + var tileWidth = elt[tileWidthPropName]; + var tileHeight = elt[tileHeightPropName]; + if (switchOriginXY) { + origins.push([elt[topLeftCornerPropName][1], + elt[topLeftCornerPropName][0]]); + } else { + origins.push(elt[topLeftCornerPropName]); + } + resolutions.push(resolution); + tileSizes.push(tileWidth == tileHeight ? + tileWidth : [tileWidth, tileHeight]); + // top-left origin, so height is negative + sizes.push([elt['MatrixWidth'], -elt['MatrixHeight']]); + } + }); + + return new ol.tilegrid.WMTS({ + extent: opt_extent, + origins: origins, + resolutions: resolutions, + matrixIds: matrixIds, + tileSizes: tileSizes, + sizes: sizes + }); +}; + +goog.provide('ol.source.WMTS'); + +goog.require('ol'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.proj'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilegrid.WMTS'); +goog.require('ol.uri'); + + +/** + * @classdesc + * Layer source for tile data from WMTS servers. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.WMTSOptions} options WMTS options. + * @api stable + */ +ol.source.WMTS = function(options) { + + // TODO: add support for TileMatrixLimits + + /** + * @private + * @type {string} + */ + this.version_ = options.version !== undefined ? options.version : '1.0.0'; + + /** + * @private + * @type {string} + */ + this.format_ = options.format !== undefined ? options.format : 'image/jpeg'; + + /** + * @private + * @type {!Object} + */ + this.dimensions_ = options.dimensions !== undefined ? options.dimensions : {}; + + /** + * @private + * @type {string} + */ + this.layer_ = options.layer; + + /** + * @private + * @type {string} + */ + this.matrixSet_ = options.matrixSet; + + /** + * @private + * @type {string} + */ + this.style_ = options.style; + + var urls = options.urls; + if (urls === undefined && options.url !== undefined) { + urls = ol.TileUrlFunction.expandUrl(options.url); + } + + // FIXME: should we guess this requestEncoding from options.url(s) + // structure? that would mean KVP only if a template is not provided. + + /** + * @private + * @type {ol.source.WMTS.RequestEncoding} + */ + this.requestEncoding_ = options.requestEncoding !== undefined ? + /** @type {ol.source.WMTS.RequestEncoding} */ (options.requestEncoding) : + ol.source.WMTS.RequestEncoding.KVP; + + var requestEncoding = this.requestEncoding_; + + // FIXME: should we create a default tileGrid? + // we could issue a getCapabilities xhr to retrieve missing configuration + var tileGrid = options.tileGrid; + + // context property names are lower case to allow for a case insensitive + // replacement as some services use different naming conventions + var context = { + 'layer': this.layer_, + 'style': this.style_, + 'tilematrixset': this.matrixSet_ + }; + + if (requestEncoding == ol.source.WMTS.RequestEncoding.KVP) { + ol.obj.assign(context, { + 'Service': 'WMTS', + 'Request': 'GetTile', + 'Version': this.version_, + 'Format': this.format_ + }); + } + + var dimensions = this.dimensions_; + + /** + * @param {string} template Template. + * @return {ol.TileUrlFunctionType} Tile URL function. + */ + function createFromWMTSTemplate(template) { + + // TODO: we may want to create our own appendParams function so that params + // order conforms to wmts spec guidance, and so that we can avoid to escape + // special template params + + template = (requestEncoding == ol.source.WMTS.RequestEncoding.KVP) ? + ol.uri.appendParams(template, context) : + template.replace(/\{(\w+?)\}/g, function(m, p) { + return (p.toLowerCase() in context) ? context[p.toLowerCase()] : m; + }); + + return ( + /** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function(tileCoord, pixelRatio, projection) { + if (!tileCoord) { + return undefined; + } else { + var localContext = { + 'TileMatrix': tileGrid.getMatrixId(tileCoord[0]), + 'TileCol': tileCoord[1], + 'TileRow': -tileCoord[2] - 1 + }; + ol.obj.assign(localContext, dimensions); + var url = template; + if (requestEncoding == ol.source.WMTS.RequestEncoding.KVP) { + url = ol.uri.appendParams(url, localContext); + } else { + url = url.replace(/\{(\w+?)\}/g, function(m, p) { + return localContext[p]; + }); + } + return url; + } + }); + } + + var tileUrlFunction = (urls && urls.length > 0) ? + ol.TileUrlFunction.createFromTileUrlFunctions( + urls.map(createFromWMTSTemplate)) : + ol.TileUrlFunction.nullTileUrlFunction; + + ol.source.TileImage.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + logo: options.logo, + projection: options.projection, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileClass: options.tileClass, + tileGrid: tileGrid, + tileLoadFunction: options.tileLoadFunction, + tilePixelRatio: options.tilePixelRatio, + tileUrlFunction: tileUrlFunction, + urls: urls, + wrapX: options.wrapX !== undefined ? options.wrapX : false + }); + + this.setKey(this.getKeyForDimensions_()); + +}; +ol.inherits(ol.source.WMTS, ol.source.TileImage); + + +/** + * Get the dimensions, i.e. those passed to the constructor through the + * "dimensions" option, and possibly updated using the updateDimensions + * method. + * @return {!Object} Dimensions. + * @api + */ +ol.source.WMTS.prototype.getDimensions = function() { + return this.dimensions_; +}; + + +/** + * Return the image format of the WMTS source. + * @return {string} Format. + * @api + */ +ol.source.WMTS.prototype.getFormat = function() { + return this.format_; +}; + + +/** + * Return the layer of the WMTS source. + * @return {string} Layer. + * @api + */ +ol.source.WMTS.prototype.getLayer = function() { + return this.layer_; +}; + + +/** + * Return the matrix set of the WMTS source. + * @return {string} MatrixSet. + * @api + */ +ol.source.WMTS.prototype.getMatrixSet = function() { + return this.matrixSet_; +}; + + +/** + * Return the request encoding, either "KVP" or "REST". + * @return {ol.source.WMTS.RequestEncoding} Request encoding. + * @api + */ +ol.source.WMTS.prototype.getRequestEncoding = function() { + return this.requestEncoding_; +}; + + +/** + * Return the style of the WMTS source. + * @return {string} Style. + * @api + */ +ol.source.WMTS.prototype.getStyle = function() { + return this.style_; +}; + + +/** + * Return the version of the WMTS source. + * @return {string} Version. + * @api + */ +ol.source.WMTS.prototype.getVersion = function() { + return this.version_; +}; + + +/** + * @private + * @return {string} The key for the current dimensions. + */ +ol.source.WMTS.prototype.getKeyForDimensions_ = function() { + var i = 0; + var res = []; + for (var key in this.dimensions_) { + res[i++] = key + '-' + this.dimensions_[key]; + } + return res.join('/'); +}; + + +/** + * Update the dimensions. + * @param {Object} dimensions Dimensions. + * @api + */ +ol.source.WMTS.prototype.updateDimensions = function(dimensions) { + ol.obj.assign(this.dimensions_, dimensions); + this.setKey(this.getKeyForDimensions_()); +}; + + +/** + * Generate source options from a capabilities object. + * @param {Object} wmtsCap An object representing the capabilities document. + * @param {Object} config Configuration properties for the layer. Defaults for + * the layer will apply if not provided. + * + * Required config properties: + * - layer - {string} The layer identifier. + * + * Optional config properties: + * - matrixSet - {string} The matrix set identifier, required if there is + * more than one matrix set in the layer capabilities. + * - projection - {string} The desired CRS when no matrixSet is specified. + * eg: "EPSG:3857". If the desired projection is not available, + * an error is thrown. + * - requestEncoding - {string} url encoding format for the layer. Default is + * the first tile url format found in the GetCapabilities response. + * - style - {string} The name of the style + * - format - {string} Image format for the layer. Default is the first + * format returned in the GetCapabilities response. + * @return {olx.source.WMTSOptions} WMTS source options object. + * @api + */ +ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) { + + // TODO: add support for TileMatrixLimits + ol.DEBUG && console.assert(config['layer'], + 'config "layer" must not be null'); + + var layers = wmtsCap['Contents']['Layer']; + var l = ol.array.find(layers, function(elt, index, array) { + return elt['Identifier'] == config['layer']; + }); + ol.DEBUG && console.assert(l, 'found a matching layer in Contents/Layer'); + + ol.DEBUG && console.assert(l['TileMatrixSetLink'].length > 0, + 'layer has TileMatrixSetLink'); + var tileMatrixSets = wmtsCap['Contents']['TileMatrixSet']; + var idx, matrixSet, matrixLimits; + if (l['TileMatrixSetLink'].length > 1) { + if ('projection' in config) { + idx = ol.array.findIndex(l['TileMatrixSetLink'], + function(elt, index, array) { + var tileMatrixSet = ol.array.find(tileMatrixSets, function(el) { + return el['Identifier'] == elt['TileMatrixSet']; + }); + var supportedCRS = tileMatrixSet['SupportedCRS'].replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3'); + var proj1 = ol.proj.get(supportedCRS); + var proj2 = ol.proj.get(config['projection']); + if (proj1 && proj2) { + return ol.proj.equivalent(proj1, proj2); + } else { + return supportedCRS == config['projection']; + } + }); + } else { + idx = ol.array.findIndex(l['TileMatrixSetLink'], + function(elt, index, array) { + return elt['TileMatrixSet'] == config['matrixSet']; + }); + } + } else { + idx = 0; + } + if (idx < 0) { + idx = 0; + } + matrixSet = /** @type {string} */ + (l['TileMatrixSetLink'][idx]['TileMatrixSet']); + matrixLimits = /** @type {Array.<Object>} */ + (l['TileMatrixSetLink'][idx]['TileMatrixSetLimits']); + + ol.DEBUG && console.assert(matrixSet, 'TileMatrixSet must not be null'); + + var format = /** @type {string} */ (l['Format'][0]); + if ('format' in config) { + format = config['format']; + } + idx = ol.array.findIndex(l['Style'], function(elt, index, array) { + if ('style' in config) { + return elt['Title'] == config['style']; + } else { + return elt['isDefault']; + } + }); + if (idx < 0) { + idx = 0; + } + var style = /** @type {string} */ (l['Style'][idx]['Identifier']); + + var dimensions = {}; + if ('Dimension' in l) { + l['Dimension'].forEach(function(elt, index, array) { + var key = elt['Identifier']; + var value = elt['Default']; + if (value !== undefined) { + ol.DEBUG && console.assert(ol.array.includes(elt['Value'], value), + 'default value contained in values'); + } else { + value = elt['Value'][0]; + } + ol.DEBUG && console.assert(value !== undefined, 'value could be found'); + dimensions[key] = value; + }); + } + + var matrixSets = wmtsCap['Contents']['TileMatrixSet']; + var matrixSetObj = ol.array.find(matrixSets, function(elt, index, array) { + return elt['Identifier'] == matrixSet; + }); + ol.DEBUG && console.assert(matrixSetObj, + 'found matrixSet in Contents/TileMatrixSet'); + + var projection; + if ('projection' in config) { + projection = ol.proj.get(config['projection']); + } else { + projection = ol.proj.get(matrixSetObj['SupportedCRS'].replace( + /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3')); + } + + var wgs84BoundingBox = l['WGS84BoundingBox']; + var extent, wrapX; + if (wgs84BoundingBox !== undefined) { + var wgs84ProjectionExtent = ol.proj.get('EPSG:4326').getExtent(); + wrapX = (wgs84BoundingBox[0] == wgs84ProjectionExtent[0] && + wgs84BoundingBox[2] == wgs84ProjectionExtent[2]); + extent = ol.proj.transformExtent( + wgs84BoundingBox, 'EPSG:4326', projection); + var projectionExtent = projection.getExtent(); + if (projectionExtent) { + // If possible, do a sanity check on the extent - it should never be + // bigger than the validity extent of the projection of a matrix set. + if (!ol.extent.containsExtent(projectionExtent, extent)) { + extent = undefined; + } + } + } + + var tileGrid = ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet( + matrixSetObj, extent, matrixLimits); + + /** @type {!Array.<string>} */ + var urls = []; + var requestEncoding = config['requestEncoding']; + requestEncoding = requestEncoding !== undefined ? requestEncoding : ''; + + ol.DEBUG && console.assert( + ol.array.includes(['REST', 'RESTful', 'KVP', ''], requestEncoding), + 'requestEncoding (%s) is one of "REST", "RESTful", "KVP" or ""', + requestEncoding); + + if ('OperationsMetadata' in wmtsCap && 'GetTile' in wmtsCap['OperationsMetadata']) { + var gets = wmtsCap['OperationsMetadata']['GetTile']['DCP']['HTTP']['Get']; + ol.DEBUG && console.assert(gets.length >= 1); + + for (var i = 0, ii = gets.length; i < ii; ++i) { + var constraint = ol.array.find(gets[i]['Constraint'], function(element) { + return element['name'] == 'GetEncoding'; + }); + var encodings = constraint['AllowedValues']['Value']; + ol.DEBUG && console.assert(encodings.length >= 1); + + if (requestEncoding === '') { + // requestEncoding not provided, use the first encoding from the list + requestEncoding = encodings[0]; + } + if (requestEncoding === ol.source.WMTS.RequestEncoding.KVP) { + if (ol.array.includes(encodings, ol.source.WMTS.RequestEncoding.KVP)) { + urls.push(/** @type {string} */ (gets[i]['href'])); + } + } else { + break; + } + } + } + if (urls.length === 0) { + requestEncoding = ol.source.WMTS.RequestEncoding.REST; + l['ResourceURL'].forEach(function(element) { + if (element['resourceType'] === 'tile') { + format = element['format']; + urls.push(/** @type {string} */ (element['template'])); + } + }); + } + ol.DEBUG && console.assert(urls.length > 0, 'At least one URL found'); + + return { + urls: urls, + layer: config['layer'], + matrixSet: matrixSet, + format: format, + projection: projection, + requestEncoding: requestEncoding, + tileGrid: tileGrid, + style: style, + dimensions: dimensions, + wrapX: wrapX + }; + +}; + + +/** + * Request encoding. One of 'KVP', 'REST'. + * @enum {string} + */ +ol.source.WMTS.RequestEncoding = { + KVP: 'KVP', // see spec §8 + REST: 'REST' // see spec §10 +}; + +goog.provide('ol.source.Zoomify'); + +goog.require('ol'); +goog.require('ol.ImageTile'); +goog.require('ol.Tile'); +goog.require('ol.asserts'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilegrid.TileGrid'); + + +/** + * @classdesc + * Layer source for tile data in Zoomify format. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.ZoomifyOptions=} opt_options Options. + * @api stable + */ +ol.source.Zoomify = function(opt_options) { + + var options = opt_options || {}; + + var size = options.size; + var tierSizeCalculation = options.tierSizeCalculation !== undefined ? + options.tierSizeCalculation : + ol.source.Zoomify.TierSizeCalculation.DEFAULT; + + var imageWidth = size[0]; + var imageHeight = size[1]; + var tierSizeInTiles = []; + var tileSize = ol.DEFAULT_TILE_SIZE; + + switch (tierSizeCalculation) { + case ol.source.Zoomify.TierSizeCalculation.DEFAULT: + while (imageWidth > tileSize || imageHeight > tileSize) { + tierSizeInTiles.push([ + Math.ceil(imageWidth / tileSize), + Math.ceil(imageHeight / tileSize) + ]); + tileSize += tileSize; + } + break; + case ol.source.Zoomify.TierSizeCalculation.TRUNCATED: + var width = imageWidth; + var height = imageHeight; + while (width > tileSize || height > tileSize) { + tierSizeInTiles.push([ + Math.ceil(width / tileSize), + Math.ceil(height / tileSize) + ]); + width >>= 1; + height >>= 1; + } + break; + default: + ol.asserts.assert(false, 53); // Unknown `tierSizeCalculation` configured + break; + } + + tierSizeInTiles.push([1, 1]); + tierSizeInTiles.reverse(); + + var resolutions = [1]; + var tileCountUpToTier = [0]; + var i, ii; + for (i = 1, ii = tierSizeInTiles.length; i < ii; i++) { + resolutions.push(1 << i); + tileCountUpToTier.push( + tierSizeInTiles[i - 1][0] * tierSizeInTiles[i - 1][1] + + tileCountUpToTier[i - 1] + ); + } + resolutions.reverse(); + + var extent = [0, -size[1], size[0], 0]; + var tileGrid = new ol.tilegrid.TileGrid({ + extent: extent, + origin: ol.extent.getTopLeft(extent), + resolutions: resolutions + }); + + var url = options.url; + + /** + * @this {ol.source.TileImage} + * @param {ol.TileCoord} tileCoord Tile Coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function tileUrlFunction(tileCoord, pixelRatio, projection) { + if (!tileCoord) { + return undefined; + } else { + var tileCoordZ = tileCoord[0]; + var tileCoordX = tileCoord[1]; + var tileCoordY = -tileCoord[2] - 1; + var tileIndex = + tileCoordX + + tileCoordY * tierSizeInTiles[tileCoordZ][0] + + tileCountUpToTier[tileCoordZ]; + var tileGroup = (tileIndex / ol.DEFAULT_TILE_SIZE) | 0; + return url + 'TileGroup' + tileGroup + '/' + + tileCoordZ + '-' + tileCoordX + '-' + tileCoordY + '.jpg'; + } + } + + ol.source.TileImage.call(this, { + attributions: options.attributions, + cacheSize: options.cacheSize, + crossOrigin: options.crossOrigin, + logo: options.logo, + reprojectionErrorThreshold: options.reprojectionErrorThreshold, + tileClass: ol.source.Zoomify.Tile_, + tileGrid: tileGrid, + tileUrlFunction: tileUrlFunction + }); + +}; +ol.inherits(ol.source.Zoomify, ol.source.TileImage); + + +/** + * @constructor + * @extends {ol.ImageTile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Tile.State} state State. + * @param {string} src Image source URI. + * @param {?string} crossOrigin Cross origin. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. + * @private + */ +ol.source.Zoomify.Tile_ = function( + tileCoord, state, src, crossOrigin, tileLoadFunction) { + + ol.ImageTile.call(this, tileCoord, state, src, crossOrigin, tileLoadFunction); + + /** + * @private + * @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} + */ + this.zoomifyImage_ = null; + +}; +ol.inherits(ol.source.Zoomify.Tile_, ol.ImageTile); + + +/** + * @inheritDoc + */ +ol.source.Zoomify.Tile_.prototype.getImage = function() { + if (this.zoomifyImage_) { + return this.zoomifyImage_; + } + var tileSize = ol.DEFAULT_TILE_SIZE; + var image = ol.ImageTile.prototype.getImage.call(this); + if (this.state == ol.Tile.State.LOADED) { + if (image.width == tileSize && image.height == tileSize) { + this.zoomifyImage_ = image; + return image; + } else { + var context = ol.dom.createCanvasContext2D(tileSize, tileSize); + context.drawImage(image, 0, 0); + this.zoomifyImage_ = context.canvas; + return context.canvas; + } + } else { + return image; + } +}; + + +/** + * @enum {string} + */ +ol.source.Zoomify.TierSizeCalculation = { + DEFAULT: 'default', + TRUNCATED: 'truncated' +}; + +goog.provide('ol.style.Atlas'); + +goog.require('ol'); +goog.require('ol.dom'); + + +/** + * This class facilitates the creation of image atlases. + * + * Images added to an atlas will be rendered onto a single + * atlas canvas. The distribution of images on the canvas is + * managed with the bin packing algorithm described in: + * http://www.blackpawn.com/texts/lightmaps/ + * + * @constructor + * @struct + * @param {number} size The size in pixels of the sprite image. + * @param {number} space The space in pixels between images. + * Because texture coordinates are float values, the edges of + * images might not be completely correct (in a way that the + * edges overlap when being rendered). To avoid this we add a + * padding around each image. + */ +ol.style.Atlas = function(size, space) { + + /** + * @private + * @type {number} + */ + this.space_ = space; + + /** + * @private + * @type {Array.<ol.AtlasBlock>} + */ + this.emptyBlocks_ = [{x: 0, y: 0, width: size, height: size}]; + + /** + * @private + * @type {Object.<string, ol.AtlasInfo>} + */ + this.entries_ = {}; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = ol.dom.createCanvasContext2D(size, size); + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = this.context_.canvas; +}; + + +/** + * @param {string} id The identifier of the entry to check. + * @return {?ol.AtlasInfo} The atlas info. + */ +ol.style.Atlas.prototype.get = function(id) { + return this.entries_[id] || null; +}; + + +/** + * @param {string} id The identifier of the entry to add. + * @param {number} width The width. + * @param {number} height The height. + * @param {function(CanvasRenderingContext2D, number, number)} renderCallback + * Called to render the new image onto an atlas image. + * @param {Object=} opt_this Value to use as `this` when executing + * `renderCallback`. + * @return {?ol.AtlasInfo} The position and atlas image for the entry. + */ +ol.style.Atlas.prototype.add = function(id, width, height, renderCallback, opt_this) { + var block, i, ii; + for (i = 0, ii = this.emptyBlocks_.length; i < ii; ++i) { + block = this.emptyBlocks_[i]; + if (block.width >= width + this.space_ && + block.height >= height + this.space_) { + // we found a block that is big enough for our entry + var entry = { + offsetX: block.x + this.space_, + offsetY: block.y + this.space_, + image: this.canvas_ + }; + this.entries_[id] = entry; + + // render the image on the atlas image + renderCallback.call(opt_this, this.context_, + block.x + this.space_, block.y + this.space_); + + // split the block after the insertion, either horizontally or vertically + this.split_(i, block, width + this.space_, height + this.space_); + + return entry; + } + } + + // there is no space for the new entry in this atlas + return null; +}; + + +/** + * @private + * @param {number} index The index of the block. + * @param {ol.AtlasBlock} block The block to split. + * @param {number} width The width of the entry to insert. + * @param {number} height The height of the entry to insert. + */ +ol.style.Atlas.prototype.split_ = function(index, block, width, height) { + var deltaWidth = block.width - width; + var deltaHeight = block.height - height; + + /** @type {ol.AtlasBlock} */ + var newBlock1; + /** @type {ol.AtlasBlock} */ + var newBlock2; + + if (deltaWidth > deltaHeight) { + // split vertically + // block right of the inserted entry + newBlock1 = { + x: block.x + width, + y: block.y, + width: block.width - width, + height: block.height + }; + + // block below the inserted entry + newBlock2 = { + x: block.x, + y: block.y + height, + width: width, + height: block.height - height + }; + this.updateBlocks_(index, newBlock1, newBlock2); + } else { + // split horizontally + // block right of the inserted entry + newBlock1 = { + x: block.x + width, + y: block.y, + width: block.width - width, + height: height + }; + + // block below the inserted entry + newBlock2 = { + x: block.x, + y: block.y + height, + width: block.width, + height: block.height - height + }; + this.updateBlocks_(index, newBlock1, newBlock2); + } +}; + + +/** + * Remove the old block and insert new blocks at the same array position. + * The new blocks are inserted at the same position, so that splitted + * blocks (that are potentially smaller) are filled first. + * @private + * @param {number} index The index of the block to remove. + * @param {ol.AtlasBlock} newBlock1 The 1st block to add. + * @param {ol.AtlasBlock} newBlock2 The 2nd block to add. + */ +ol.style.Atlas.prototype.updateBlocks_ = function(index, newBlock1, newBlock2) { + var args = [index, 1]; + if (newBlock1.width > 0 && newBlock1.height > 0) { + args.push(newBlock1); + } + if (newBlock2.width > 0 && newBlock2.height > 0) { + args.push(newBlock2); + } + this.emptyBlocks_.splice.apply(this.emptyBlocks_, args); +}; + +goog.provide('ol.style.AtlasManager'); + +goog.require('ol'); +goog.require('ol.style.Atlas'); + + +/** + * Manages the creation of image atlases. + * + * Images added to this manager will be inserted into an atlas, which + * will be used for rendering. + * The `size` given in the constructor is the size for the first + * atlas. After that, when new atlases are created, they will have + * twice the size as the latest atlas (until `maxSize` is reached). + * + * If an application uses many images or very large images, it is recommended + * to set a higher `size` value to avoid the creation of too many atlases. + * + * @constructor + * @struct + * @api + * @param {olx.style.AtlasManagerOptions=} opt_options Options. + */ +ol.style.AtlasManager = function(opt_options) { + + var options = opt_options || {}; + + /** + * The size in pixels of the latest atlas image. + * @private + * @type {number} + */ + this.currentSize_ = options.initialSize !== undefined ? + options.initialSize : ol.INITIAL_ATLAS_SIZE; + + /** + * The maximum size in pixels of atlas images. + * @private + * @type {number} + */ + this.maxSize_ = options.maxSize !== undefined ? + options.maxSize : ol.MAX_ATLAS_SIZE != -1 ? + ol.MAX_ATLAS_SIZE : ol.WEBGL_MAX_TEXTURE_SIZE !== undefined ? + ol.WEBGL_MAX_TEXTURE_SIZE : 2048; + + /** + * The size in pixels between images. + * @private + * @type {number} + */ + this.space_ = options.space !== undefined ? options.space : 1; + + /** + * @private + * @type {Array.<ol.style.Atlas>} + */ + this.atlases_ = [new ol.style.Atlas(this.currentSize_, this.space_)]; + + /** + * The size in pixels of the latest atlas image for hit-detection images. + * @private + * @type {number} + */ + this.currentHitSize_ = this.currentSize_; + + /** + * @private + * @type {Array.<ol.style.Atlas>} + */ + this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)]; +}; + + +/** + * @param {string} id The identifier of the entry to check. + * @return {?ol.AtlasManagerInfo} The position and atlas image for the + * entry, or `null` if the entry is not part of the atlas manager. + */ +ol.style.AtlasManager.prototype.getInfo = function(id) { + /** @type {?ol.AtlasInfo} */ + var info = this.getInfo_(this.atlases_, id); + + if (!info) { + return null; + } + var hitInfo = /** @type {ol.AtlasInfo} */ (this.getInfo_(this.hitAtlases_, id)); + + return this.mergeInfos_(info, hitInfo); +}; + + +/** + * @private + * @param {Array.<ol.style.Atlas>} atlases The atlases to search. + * @param {string} id The identifier of the entry to check. + * @return {?ol.AtlasInfo} The position and atlas image for the entry, + * or `null` if the entry is not part of the atlases. + */ +ol.style.AtlasManager.prototype.getInfo_ = function(atlases, id) { + var atlas, info, i, ii; + for (i = 0, ii = atlases.length; i < ii; ++i) { + atlas = atlases[i]; + info = atlas.get(id); + if (info) { + return info; + } + } + return null; +}; + + +/** + * @private + * @param {ol.AtlasInfo} info The info for the real image. + * @param {ol.AtlasInfo} hitInfo The info for the hit-detection + * image. + * @return {?ol.AtlasManagerInfo} The position and atlas image for the + * entry, or `null` if the entry is not part of the atlases. + */ +ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) { + ol.DEBUG && console.assert(info.offsetX === hitInfo.offsetX, + 'in order to merge, offsetX of info and hitInfo must be equal'); + ol.DEBUG && console.assert(info.offsetY === hitInfo.offsetY, + 'in order to merge, offsetY of info and hitInfo must be equal'); + return /** @type {ol.AtlasManagerInfo} */ ({ + offsetX: info.offsetX, + offsetY: info.offsetY, + image: info.image, + hitImage: hitInfo.image + }); +}; + + +/** + * Add an image to the atlas manager. + * + * If an entry for the given id already exists, the entry will + * be overridden (but the space on the atlas graphic will not be freed). + * + * If `renderHitCallback` is provided, the image (or the hit-detection version + * of the image) will be rendered into a separate hit-detection atlas image. + * + * @param {string} id The identifier of the entry to add. + * @param {number} width The width. + * @param {number} height The height. + * @param {function(CanvasRenderingContext2D, number, number)} renderCallback + * Called to render the new image onto an atlas image. + * @param {function(CanvasRenderingContext2D, number, number)=} + * opt_renderHitCallback Called to render a hit-detection image onto a hit + * detection atlas image. + * @param {Object=} opt_this Value to use as `this` when executing + * `renderCallback` and `renderHitCallback`. + * @return {?ol.AtlasManagerInfo} The position and atlas image for the + * entry, or `null` if the image is too big. + */ +ol.style.AtlasManager.prototype.add = function(id, width, height, + renderCallback, opt_renderHitCallback, opt_this) { + if (width + this.space_ > this.maxSize_ || + height + this.space_ > this.maxSize_) { + return null; + } + + /** @type {?ol.AtlasInfo} */ + var info = this.add_(false, + id, width, height, renderCallback, opt_this); + if (!info) { + return null; + } + + // even if no hit-detection entry is requested, we insert a fake entry into + // the hit-detection atlas, to make sure that the offset is the same for + // the original image and the hit-detection image. + var renderHitCallback = opt_renderHitCallback !== undefined ? + opt_renderHitCallback : ol.nullFunction; + + var hitInfo = /** @type {ol.AtlasInfo} */ (this.add_(true, + id, width, height, renderHitCallback, opt_this)); + + return this.mergeInfos_(info, hitInfo); +}; + + +/** + * @private + * @param {boolean} isHitAtlas If the hit-detection atlases are used. + * @param {string} id The identifier of the entry to add. + * @param {number} width The width. + * @param {number} height The height. + * @param {function(CanvasRenderingContext2D, number, number)} renderCallback + * Called to render the new image onto an atlas image. + * @param {Object=} opt_this Value to use as `this` when executing + * `renderCallback` and `renderHitCallback`. + * @return {?ol.AtlasInfo} The position and atlas image for the entry, + * or `null` if the image is too big. + */ +ol.style.AtlasManager.prototype.add_ = function(isHitAtlas, id, width, height, + renderCallback, opt_this) { + var atlases = (isHitAtlas) ? this.hitAtlases_ : this.atlases_; + var atlas, info, i, ii; + for (i = 0, ii = atlases.length; i < ii; ++i) { + atlas = atlases[i]; + info = atlas.add(id, width, height, renderCallback, opt_this); + if (info) { + return info; + } else if (!info && i === ii - 1) { + // the entry could not be added to one of the existing atlases, + // create a new atlas that is twice as big and try to add to this one. + var size; + if (isHitAtlas) { + size = Math.min(this.currentHitSize_ * 2, this.maxSize_); + this.currentHitSize_ = size; + } else { + size = Math.min(this.currentSize_ * 2, this.maxSize_); + this.currentSize_ = size; + } + atlas = new ol.style.Atlas(size, this.space_); + atlases.push(atlas); + // run the loop another time + ++ii; + } + } + ol.DEBUG && console.assert(false, 'Failed to add to atlasmanager'); + return null; +}; + +// Copyright 2009 The Closure Library Authors. +// All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// This file has been auto-generated by GenJsDeps, please do not edit. + +goog.addDependency( + 'demos/editor/equationeditor.js', ['goog.demos.editor.EquationEditor'], + ['goog.ui.equation.EquationEditorDialog']); +goog.addDependency( + 'demos/editor/helloworld.js', ['goog.demos.editor.HelloWorld'], + ['goog.dom', 'goog.dom.TagName', 'goog.editor.Plugin']); +goog.addDependency( + 'demos/editor/helloworlddialog.js', + [ + 'goog.demos.editor.HelloWorldDialog', + 'goog.demos.editor.HelloWorldDialog.OkEvent' + ], + [ + 'goog.dom.TagName', 'goog.events.Event', 'goog.string', + 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.AbstractDialog.Builder', + 'goog.ui.editor.AbstractDialog.EventType' + ]); +goog.addDependency( + 'demos/editor/helloworlddialogplugin.js', + [ + 'goog.demos.editor.HelloWorldDialogPlugin', + 'goog.demos.editor.HelloWorldDialogPlugin.Command' + ], + [ + 'goog.demos.editor.HelloWorldDialog', 'goog.dom.TagName', + 'goog.editor.plugins.AbstractDialogPlugin', 'goog.editor.range', + 'goog.functions', 'goog.ui.editor.AbstractDialog.EventType' + ]); + +/** + * @fileoverview Custom exports file. + * @suppress {checkVars,extraRequire} + */ + +goog.require('ol'); +goog.require('ol.AssertionError'); +goog.require('ol.Attribution'); +goog.require('ol.Collection'); +goog.require('ol.DeviceOrientation'); +goog.require('ol.Feature'); +goog.require('ol.Geolocation'); +goog.require('ol.Graticule'); +goog.require('ol.Image'); +goog.require('ol.ImageTile'); +goog.require('ol.Kinetic'); +goog.require('ol.Map'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapEvent'); +goog.require('ol.Object'); +goog.require('ol.Observable'); +goog.require('ol.Overlay'); +goog.require('ol.Sphere'); +goog.require('ol.Tile'); +goog.require('ol.VectorTile'); +goog.require('ol.View'); +goog.require('ol.animation'); +goog.require('ol.color'); +goog.require('ol.colorlike'); +goog.require('ol.control'); +goog.require('ol.control.Attribution'); +goog.require('ol.control.Control'); +goog.require('ol.control.FullScreen'); +goog.require('ol.control.MousePosition'); +goog.require('ol.control.OverviewMap'); +goog.require('ol.control.Rotate'); +goog.require('ol.control.ScaleLine'); +goog.require('ol.control.Zoom'); +goog.require('ol.control.ZoomSlider'); +goog.require('ol.control.ZoomToExtent'); +goog.require('ol.coordinate'); +goog.require('ol.easing'); +goog.require('ol.events.Event'); +goog.require('ol.events.condition'); +goog.require('ol.extent'); +goog.require('ol.featureloader'); +goog.require('ol.format.EsriJSON'); +goog.require('ol.format.Feature'); +goog.require('ol.format.GML'); +goog.require('ol.format.GML2'); +goog.require('ol.format.GML3'); +goog.require('ol.format.GMLBase'); +goog.require('ol.format.GPX'); +goog.require('ol.format.GeoJSON'); +goog.require('ol.format.IGC'); +goog.require('ol.format.KML'); +goog.require('ol.format.MVT'); +goog.require('ol.format.OSMXML'); +goog.require('ol.format.Polyline'); +goog.require('ol.format.TopoJSON'); +goog.require('ol.format.WFS'); +goog.require('ol.format.WKT'); +goog.require('ol.format.WMSCapabilities'); +goog.require('ol.format.WMSGetFeatureInfo'); +goog.require('ol.format.WMTSCapabilities'); +goog.require('ol.format.filter'); +goog.require('ol.format.filter.And'); +goog.require('ol.format.filter.Bbox'); +goog.require('ol.format.filter.Comparison'); +goog.require('ol.format.filter.ComparisonBinary'); +goog.require('ol.format.filter.EqualTo'); +goog.require('ol.format.filter.Filter'); +goog.require('ol.format.filter.GreaterThan'); +goog.require('ol.format.filter.GreaterThanOrEqualTo'); +goog.require('ol.format.filter.Intersects'); +goog.require('ol.format.filter.IsBetween'); +goog.require('ol.format.filter.IsLike'); +goog.require('ol.format.filter.IsNull'); +goog.require('ol.format.filter.LessThan'); +goog.require('ol.format.filter.LessThanOrEqualTo'); +goog.require('ol.format.filter.Not'); +goog.require('ol.format.filter.NotEqualTo'); +goog.require('ol.format.filter.Or'); +goog.require('ol.format.filter.Spatial'); +goog.require('ol.format.filter.Within'); +goog.require('ol.geom.Circle'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.has'); +goog.require('ol.interaction'); +goog.require('ol.interaction.DoubleClickZoom'); +goog.require('ol.interaction.DragAndDrop'); +goog.require('ol.interaction.DragBox'); +goog.require('ol.interaction.DragPan'); +goog.require('ol.interaction.DragRotate'); +goog.require('ol.interaction.DragRotateAndZoom'); +goog.require('ol.interaction.DragZoom'); +goog.require('ol.interaction.Draw'); +goog.require('ol.interaction.Extent'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.KeyboardPan'); +goog.require('ol.interaction.KeyboardZoom'); +goog.require('ol.interaction.Modify'); +goog.require('ol.interaction.MouseWheelZoom'); +goog.require('ol.interaction.PinchRotate'); +goog.require('ol.interaction.PinchZoom'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.interaction.Select'); +goog.require('ol.interaction.Snap'); +goog.require('ol.interaction.Translate'); +goog.require('ol.layer.Base'); +goog.require('ol.layer.Group'); +goog.require('ol.layer.Heatmap'); +goog.require('ol.layer.Image'); +goog.require('ol.layer.Layer'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); +goog.require('ol.layer.VectorTile'); +goog.require('ol.loadingstrategy'); +goog.require('ol.proj'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); +goog.require('ol.proj.common'); +goog.require('ol.render'); +goog.require('ol.render.Event'); +goog.require('ol.render.Feature'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.canvas.Immediate'); +goog.require('ol.render.webgl.Immediate'); +goog.require('ol.size'); +goog.require('ol.source.BingMaps'); +goog.require('ol.source.CartoDB'); +goog.require('ol.source.Cluster'); +goog.require('ol.source.Image'); +goog.require('ol.source.ImageArcGISRest'); +goog.require('ol.source.ImageCanvas'); +goog.require('ol.source.ImageMapGuide'); +goog.require('ol.source.ImageStatic'); +goog.require('ol.source.ImageVector'); +goog.require('ol.source.ImageWMS'); +goog.require('ol.source.OSM'); +goog.require('ol.source.Raster'); +goog.require('ol.source.Source'); +goog.require('ol.source.Stamen'); +goog.require('ol.source.Tile'); +goog.require('ol.source.TileArcGISRest'); +goog.require('ol.source.TileDebug'); +goog.require('ol.source.TileImage'); +goog.require('ol.source.TileJSON'); +goog.require('ol.source.TileUTFGrid'); +goog.require('ol.source.TileWMS'); +goog.require('ol.source.UrlTile'); +goog.require('ol.source.Vector'); +goog.require('ol.source.VectorTile'); +goog.require('ol.source.WMTS'); +goog.require('ol.source.XYZ'); +goog.require('ol.source.Zoomify'); +goog.require('ol.style.AtlasManager'); +goog.require('ol.style.Circle'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Icon'); +goog.require('ol.style.Image'); +goog.require('ol.style.RegularShape'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Style'); +goog.require('ol.style.Text'); +goog.require('ol.tilegrid'); +goog.require('ol.tilegrid.TileGrid'); +goog.require('ol.tilegrid.WMTS'); +goog.require('ol.webgl.Context'); +goog.require('ol.xml'); + + + +goog.exportSymbol( + 'ol.animation.bounce', + ol.animation.bounce, + OPENLAYERS); + +goog.exportSymbol( + 'ol.animation.pan', + ol.animation.pan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.animation.rotate', + ol.animation.rotate, + OPENLAYERS); + +goog.exportSymbol( + 'ol.animation.zoom', + ol.animation.zoom, + OPENLAYERS); + +goog.exportProperty( + ol.AssertionError.prototype, + 'code', + ol.AssertionError.prototype.code); + +goog.exportSymbol( + 'ol.Attribution', + ol.Attribution, + OPENLAYERS); + +goog.exportProperty( + ol.Attribution.prototype, + 'getHTML', + ol.Attribution.prototype.getHTML); + +goog.exportSymbol( + 'ol.Collection', + ol.Collection, + OPENLAYERS); + +goog.exportProperty( + ol.Collection.prototype, + 'clear', + ol.Collection.prototype.clear); + +goog.exportProperty( + ol.Collection.prototype, + 'extend', + ol.Collection.prototype.extend); + +goog.exportProperty( + ol.Collection.prototype, + 'forEach', + ol.Collection.prototype.forEach); + +goog.exportProperty( + ol.Collection.prototype, + 'getArray', + ol.Collection.prototype.getArray); + +goog.exportProperty( + ol.Collection.prototype, + 'item', + ol.Collection.prototype.item); + +goog.exportProperty( + ol.Collection.prototype, + 'getLength', + ol.Collection.prototype.getLength); + +goog.exportProperty( + ol.Collection.prototype, + 'insertAt', + ol.Collection.prototype.insertAt); + +goog.exportProperty( + ol.Collection.prototype, + 'pop', + ol.Collection.prototype.pop); + +goog.exportProperty( + ol.Collection.prototype, + 'push', + ol.Collection.prototype.push); + +goog.exportProperty( + ol.Collection.prototype, + 'remove', + ol.Collection.prototype.remove); + +goog.exportProperty( + ol.Collection.prototype, + 'removeAt', + ol.Collection.prototype.removeAt); + +goog.exportProperty( + ol.Collection.prototype, + 'setAt', + ol.Collection.prototype.setAt); + +goog.exportProperty( + ol.Collection.Event.prototype, + 'element', + ol.Collection.Event.prototype.element); + +goog.exportSymbol( + 'ol.color.asArray', + ol.color.asArray, + OPENLAYERS); + +goog.exportSymbol( + 'ol.color.asString', + ol.color.asString, + OPENLAYERS); + +goog.exportSymbol( + 'ol.colorlike.asColorLike', + ol.colorlike.asColorLike, + OPENLAYERS); + +goog.exportSymbol( + 'ol.coordinate.add', + ol.coordinate.add, + OPENLAYERS); + +goog.exportSymbol( + 'ol.coordinate.createStringXY', + ol.coordinate.createStringXY, + OPENLAYERS); + +goog.exportSymbol( + 'ol.coordinate.format', + ol.coordinate.format, + OPENLAYERS); + +goog.exportSymbol( + 'ol.coordinate.rotate', + ol.coordinate.rotate, + OPENLAYERS); + +goog.exportSymbol( + 'ol.coordinate.toStringHDMS', + ol.coordinate.toStringHDMS, + OPENLAYERS); + +goog.exportSymbol( + 'ol.coordinate.toStringXY', + ol.coordinate.toStringXY, + OPENLAYERS); + +goog.exportSymbol( + 'ol.DeviceOrientation', + ol.DeviceOrientation, + OPENLAYERS); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getAlpha', + ol.DeviceOrientation.prototype.getAlpha); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getBeta', + ol.DeviceOrientation.prototype.getBeta); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getGamma', + ol.DeviceOrientation.prototype.getGamma); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getHeading', + ol.DeviceOrientation.prototype.getHeading); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getTracking', + ol.DeviceOrientation.prototype.getTracking); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'setTracking', + ol.DeviceOrientation.prototype.setTracking); + +goog.exportSymbol( + 'ol.easing.easeIn', + ol.easing.easeIn, + OPENLAYERS); + +goog.exportSymbol( + 'ol.easing.easeOut', + ol.easing.easeOut, + OPENLAYERS); + +goog.exportSymbol( + 'ol.easing.inAndOut', + ol.easing.inAndOut, + OPENLAYERS); + +goog.exportSymbol( + 'ol.easing.linear', + ol.easing.linear, + OPENLAYERS); + +goog.exportSymbol( + 'ol.easing.upAndDown', + ol.easing.upAndDown, + OPENLAYERS); + +goog.exportSymbol( + 'ol.Feature', + ol.Feature, + OPENLAYERS); + +goog.exportProperty( + ol.Feature.prototype, + 'clone', + ol.Feature.prototype.clone); + +goog.exportProperty( + ol.Feature.prototype, + 'getGeometry', + ol.Feature.prototype.getGeometry); + +goog.exportProperty( + ol.Feature.prototype, + 'getId', + ol.Feature.prototype.getId); + +goog.exportProperty( + ol.Feature.prototype, + 'getGeometryName', + ol.Feature.prototype.getGeometryName); + +goog.exportProperty( + ol.Feature.prototype, + 'getStyle', + ol.Feature.prototype.getStyle); + +goog.exportProperty( + ol.Feature.prototype, + 'getStyleFunction', + ol.Feature.prototype.getStyleFunction); + +goog.exportProperty( + ol.Feature.prototype, + 'setGeometry', + ol.Feature.prototype.setGeometry); + +goog.exportProperty( + ol.Feature.prototype, + 'setStyle', + ol.Feature.prototype.setStyle); + +goog.exportProperty( + ol.Feature.prototype, + 'setId', + ol.Feature.prototype.setId); + +goog.exportProperty( + ol.Feature.prototype, + 'setGeometryName', + ol.Feature.prototype.setGeometryName); + +goog.exportSymbol( + 'ol.featureloader.xhr', + ol.featureloader.xhr, + OPENLAYERS); + +goog.exportSymbol( + 'ol.Geolocation', + ol.Geolocation, + OPENLAYERS); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getAccuracy', + ol.Geolocation.prototype.getAccuracy); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getAccuracyGeometry', + ol.Geolocation.prototype.getAccuracyGeometry); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getAltitude', + ol.Geolocation.prototype.getAltitude); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getAltitudeAccuracy', + ol.Geolocation.prototype.getAltitudeAccuracy); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getHeading', + ol.Geolocation.prototype.getHeading); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getPosition', + ol.Geolocation.prototype.getPosition); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getProjection', + ol.Geolocation.prototype.getProjection); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getSpeed', + ol.Geolocation.prototype.getSpeed); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getTracking', + ol.Geolocation.prototype.getTracking); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getTrackingOptions', + ol.Geolocation.prototype.getTrackingOptions); + +goog.exportProperty( + ol.Geolocation.prototype, + 'setProjection', + ol.Geolocation.prototype.setProjection); + +goog.exportProperty( + ol.Geolocation.prototype, + 'setTracking', + ol.Geolocation.prototype.setTracking); + +goog.exportProperty( + ol.Geolocation.prototype, + 'setTrackingOptions', + ol.Geolocation.prototype.setTrackingOptions); + +goog.exportSymbol( + 'ol.Graticule', + ol.Graticule, + OPENLAYERS); + +goog.exportProperty( + ol.Graticule.prototype, + 'getMap', + ol.Graticule.prototype.getMap); + +goog.exportProperty( + ol.Graticule.prototype, + 'getMeridians', + ol.Graticule.prototype.getMeridians); + +goog.exportProperty( + ol.Graticule.prototype, + 'getParallels', + ol.Graticule.prototype.getParallels); + +goog.exportProperty( + ol.Graticule.prototype, + 'setMap', + ol.Graticule.prototype.setMap); + +goog.exportSymbol( + 'ol.has.DEVICE_PIXEL_RATIO', + ol.has.DEVICE_PIXEL_RATIO, + OPENLAYERS); + +goog.exportSymbol( + 'ol.has.CANVAS', + ol.has.CANVAS, + OPENLAYERS); + +goog.exportSymbol( + 'ol.has.DEVICE_ORIENTATION', + ol.has.DEVICE_ORIENTATION, + OPENLAYERS); + +goog.exportSymbol( + 'ol.has.GEOLOCATION', + ol.has.GEOLOCATION, + OPENLAYERS); + +goog.exportSymbol( + 'ol.has.TOUCH', + ol.has.TOUCH, + OPENLAYERS); + +goog.exportSymbol( + 'ol.has.WEBGL', + ol.has.WEBGL, + OPENLAYERS); + +goog.exportProperty( + ol.Image.prototype, + 'getImage', + ol.Image.prototype.getImage); + +goog.exportProperty( + ol.Image.prototype, + 'load', + ol.Image.prototype.load); + +goog.exportProperty( + ol.ImageTile.prototype, + 'getImage', + ol.ImageTile.prototype.getImage); + +goog.exportProperty( + ol.ImageTile.prototype, + 'load', + ol.ImageTile.prototype.load); + +goog.exportSymbol( + 'ol.inherits', + ol.inherits, + OPENLAYERS); + +goog.exportSymbol( + 'ol.Kinetic', + ol.Kinetic, + OPENLAYERS); + +goog.exportSymbol( + 'ol.loadingstrategy.all', + ol.loadingstrategy.all, + OPENLAYERS); + +goog.exportSymbol( + 'ol.loadingstrategy.bbox', + ol.loadingstrategy.bbox, + OPENLAYERS); + +goog.exportSymbol( + 'ol.loadingstrategy.tile', + ol.loadingstrategy.tile, + OPENLAYERS); + +goog.exportSymbol( + 'ol.Map', + ol.Map, + OPENLAYERS); + +goog.exportProperty( + ol.Map.prototype, + 'addControl', + ol.Map.prototype.addControl); + +goog.exportProperty( + ol.Map.prototype, + 'addInteraction', + ol.Map.prototype.addInteraction); + +goog.exportProperty( + ol.Map.prototype, + 'addLayer', + ol.Map.prototype.addLayer); + +goog.exportProperty( + ol.Map.prototype, + 'addOverlay', + ol.Map.prototype.addOverlay); + +goog.exportProperty( + ol.Map.prototype, + 'beforeRender', + ol.Map.prototype.beforeRender); + +goog.exportProperty( + ol.Map.prototype, + 'forEachFeatureAtPixel', + ol.Map.prototype.forEachFeatureAtPixel); + +goog.exportProperty( + ol.Map.prototype, + 'forEachLayerAtPixel', + ol.Map.prototype.forEachLayerAtPixel); + +goog.exportProperty( + ol.Map.prototype, + 'hasFeatureAtPixel', + ol.Map.prototype.hasFeatureAtPixel); + +goog.exportProperty( + ol.Map.prototype, + 'getEventCoordinate', + ol.Map.prototype.getEventCoordinate); + +goog.exportProperty( + ol.Map.prototype, + 'getEventPixel', + ol.Map.prototype.getEventPixel); + +goog.exportProperty( + ol.Map.prototype, + 'getTarget', + ol.Map.prototype.getTarget); + +goog.exportProperty( + ol.Map.prototype, + 'getTargetElement', + ol.Map.prototype.getTargetElement); + +goog.exportProperty( + ol.Map.prototype, + 'getCoordinateFromPixel', + ol.Map.prototype.getCoordinateFromPixel); + +goog.exportProperty( + ol.Map.prototype, + 'getControls', + ol.Map.prototype.getControls); + +goog.exportProperty( + ol.Map.prototype, + 'getOverlays', + ol.Map.prototype.getOverlays); + +goog.exportProperty( + ol.Map.prototype, + 'getOverlayById', + ol.Map.prototype.getOverlayById); + +goog.exportProperty( + ol.Map.prototype, + 'getInteractions', + ol.Map.prototype.getInteractions); + +goog.exportProperty( + ol.Map.prototype, + 'getLayerGroup', + ol.Map.prototype.getLayerGroup); + +goog.exportProperty( + ol.Map.prototype, + 'getLayers', + ol.Map.prototype.getLayers); + +goog.exportProperty( + ol.Map.prototype, + 'getPixelFromCoordinate', + ol.Map.prototype.getPixelFromCoordinate); + +goog.exportProperty( + ol.Map.prototype, + 'getSize', + ol.Map.prototype.getSize); + +goog.exportProperty( + ol.Map.prototype, + 'getView', + ol.Map.prototype.getView); + +goog.exportProperty( + ol.Map.prototype, + 'getViewport', + ol.Map.prototype.getViewport); + +goog.exportProperty( + ol.Map.prototype, + 'renderSync', + ol.Map.prototype.renderSync); + +goog.exportProperty( + ol.Map.prototype, + 'render', + ol.Map.prototype.render); + +goog.exportProperty( + ol.Map.prototype, + 'removeControl', + ol.Map.prototype.removeControl); + +goog.exportProperty( + ol.Map.prototype, + 'removeInteraction', + ol.Map.prototype.removeInteraction); + +goog.exportProperty( + ol.Map.prototype, + 'removeLayer', + ol.Map.prototype.removeLayer); + +goog.exportProperty( + ol.Map.prototype, + 'removeOverlay', + ol.Map.prototype.removeOverlay); + +goog.exportProperty( + ol.Map.prototype, + 'setLayerGroup', + ol.Map.prototype.setLayerGroup); + +goog.exportProperty( + ol.Map.prototype, + 'setSize', + ol.Map.prototype.setSize); + +goog.exportProperty( + ol.Map.prototype, + 'setTarget', + ol.Map.prototype.setTarget); + +goog.exportProperty( + ol.Map.prototype, + 'setView', + ol.Map.prototype.setView); + +goog.exportProperty( + ol.Map.prototype, + 'updateSize', + ol.Map.prototype.updateSize); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'originalEvent', + ol.MapBrowserEvent.prototype.originalEvent); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'pixel', + ol.MapBrowserEvent.prototype.pixel); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'coordinate', + ol.MapBrowserEvent.prototype.coordinate); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'dragging', + ol.MapBrowserEvent.prototype.dragging); + +goog.exportProperty( + ol.MapEvent.prototype, + 'map', + ol.MapEvent.prototype.map); + +goog.exportProperty( + ol.MapEvent.prototype, + 'frameState', + ol.MapEvent.prototype.frameState); + +goog.exportSymbol( + 'ol.Object', + ol.Object, + OPENLAYERS); + +goog.exportProperty( + ol.Object.prototype, + 'get', + ol.Object.prototype.get); + +goog.exportProperty( + ol.Object.prototype, + 'getKeys', + ol.Object.prototype.getKeys); + +goog.exportProperty( + ol.Object.prototype, + 'getProperties', + ol.Object.prototype.getProperties); + +goog.exportProperty( + ol.Object.prototype, + 'set', + ol.Object.prototype.set); + +goog.exportProperty( + ol.Object.prototype, + 'setProperties', + ol.Object.prototype.setProperties); + +goog.exportProperty( + ol.Object.prototype, + 'unset', + ol.Object.prototype.unset); + +goog.exportProperty( + ol.Object.Event.prototype, + 'key', + ol.Object.Event.prototype.key); + +goog.exportProperty( + ol.Object.Event.prototype, + 'oldValue', + ol.Object.Event.prototype.oldValue); + +goog.exportSymbol( + 'ol.Observable', + ol.Observable, + OPENLAYERS); + +goog.exportSymbol( + 'ol.Observable.unByKey', + ol.Observable.unByKey, + OPENLAYERS); + +goog.exportProperty( + ol.Observable.prototype, + 'changed', + ol.Observable.prototype.changed); + +goog.exportProperty( + ol.Observable.prototype, + 'dispatchEvent', + ol.Observable.prototype.dispatchEvent); + +goog.exportProperty( + ol.Observable.prototype, + 'getRevision', + ol.Observable.prototype.getRevision); + +goog.exportProperty( + ol.Observable.prototype, + 'on', + ol.Observable.prototype.on); + +goog.exportProperty( + ol.Observable.prototype, + 'once', + ol.Observable.prototype.once); + +goog.exportProperty( + ol.Observable.prototype, + 'un', + ol.Observable.prototype.un); + +goog.exportProperty( + ol.Observable.prototype, + 'unByKey', + ol.Observable.prototype.unByKey); + +goog.exportSymbol( + 'ol.Overlay', + ol.Overlay, + OPENLAYERS); + +goog.exportProperty( + ol.Overlay.prototype, + 'getElement', + ol.Overlay.prototype.getElement); + +goog.exportProperty( + ol.Overlay.prototype, + 'getId', + ol.Overlay.prototype.getId); + +goog.exportProperty( + ol.Overlay.prototype, + 'getMap', + ol.Overlay.prototype.getMap); + +goog.exportProperty( + ol.Overlay.prototype, + 'getOffset', + ol.Overlay.prototype.getOffset); + +goog.exportProperty( + ol.Overlay.prototype, + 'getPosition', + ol.Overlay.prototype.getPosition); + +goog.exportProperty( + ol.Overlay.prototype, + 'getPositioning', + ol.Overlay.prototype.getPositioning); + +goog.exportProperty( + ol.Overlay.prototype, + 'setElement', + ol.Overlay.prototype.setElement); + +goog.exportProperty( + ol.Overlay.prototype, + 'setMap', + ol.Overlay.prototype.setMap); + +goog.exportProperty( + ol.Overlay.prototype, + 'setOffset', + ol.Overlay.prototype.setOffset); + +goog.exportProperty( + ol.Overlay.prototype, + 'setPosition', + ol.Overlay.prototype.setPosition); + +goog.exportProperty( + ol.Overlay.prototype, + 'setPositioning', + ol.Overlay.prototype.setPositioning); + +goog.exportSymbol( + 'ol.render.toContext', + ol.render.toContext, + OPENLAYERS); + +goog.exportSymbol( + 'ol.size.toSize', + ol.size.toSize, + OPENLAYERS); + +goog.exportProperty( + ol.Tile.prototype, + 'getTileCoord', + ol.Tile.prototype.getTileCoord); + +goog.exportProperty( + ol.Tile.prototype, + 'load', + ol.Tile.prototype.load); + +goog.exportProperty( + ol.VectorTile.prototype, + 'getFormat', + ol.VectorTile.prototype.getFormat); + +goog.exportProperty( + ol.VectorTile.prototype, + 'setFeatures', + ol.VectorTile.prototype.setFeatures); + +goog.exportProperty( + ol.VectorTile.prototype, + 'setProjection', + ol.VectorTile.prototype.setProjection); + +goog.exportProperty( + ol.VectorTile.prototype, + 'setLoader', + ol.VectorTile.prototype.setLoader); + +goog.exportSymbol( + 'ol.View', + ol.View, + OPENLAYERS); + +goog.exportProperty( + ol.View.prototype, + 'animate', + ol.View.prototype.animate); + +goog.exportProperty( + ol.View.prototype, + 'constrainCenter', + ol.View.prototype.constrainCenter); + +goog.exportProperty( + ol.View.prototype, + 'constrainResolution', + ol.View.prototype.constrainResolution); + +goog.exportProperty( + ol.View.prototype, + 'constrainRotation', + ol.View.prototype.constrainRotation); + +goog.exportProperty( + ol.View.prototype, + 'getCenter', + ol.View.prototype.getCenter); + +goog.exportProperty( + ol.View.prototype, + 'calculateExtent', + ol.View.prototype.calculateExtent); + +goog.exportProperty( + ol.View.prototype, + 'getMaxResolution', + ol.View.prototype.getMaxResolution); + +goog.exportProperty( + ol.View.prototype, + 'getMinResolution', + ol.View.prototype.getMinResolution); + +goog.exportProperty( + ol.View.prototype, + 'getProjection', + ol.View.prototype.getProjection); + +goog.exportProperty( + ol.View.prototype, + 'getResolution', + ol.View.prototype.getResolution); + +goog.exportProperty( + ol.View.prototype, + 'getResolutions', + ol.View.prototype.getResolutions); + +goog.exportProperty( + ol.View.prototype, + 'getRotation', + ol.View.prototype.getRotation); + +goog.exportProperty( + ol.View.prototype, + 'getZoom', + ol.View.prototype.getZoom); + +goog.exportProperty( + ol.View.prototype, + 'fit', + ol.View.prototype.fit); + +goog.exportProperty( + ol.View.prototype, + 'centerOn', + ol.View.prototype.centerOn); + +goog.exportProperty( + ol.View.prototype, + 'rotate', + ol.View.prototype.rotate); + +goog.exportProperty( + ol.View.prototype, + 'setCenter', + ol.View.prototype.setCenter); + +goog.exportProperty( + ol.View.prototype, + 'setResolution', + ol.View.prototype.setResolution); + +goog.exportProperty( + ol.View.prototype, + 'setRotation', + ol.View.prototype.setRotation); + +goog.exportProperty( + ol.View.prototype, + 'setZoom', + ol.View.prototype.setZoom); + +goog.exportSymbol( + 'ol.xml.getAllTextContent', + ol.xml.getAllTextContent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.xml.parse', + ol.xml.parse, + OPENLAYERS); + +goog.exportProperty( + ol.webgl.Context.prototype, + 'getGL', + ol.webgl.Context.prototype.getGL); + +goog.exportProperty( + ol.webgl.Context.prototype, + 'useProgram', + ol.webgl.Context.prototype.useProgram); + +goog.exportSymbol( + 'ol.tilegrid.createXYZ', + ol.tilegrid.createXYZ, + OPENLAYERS); + +goog.exportSymbol( + 'ol.tilegrid.TileGrid', + ol.tilegrid.TileGrid, + OPENLAYERS); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'forEachTileCoord', + ol.tilegrid.TileGrid.prototype.forEachTileCoord); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getMaxZoom', + ol.tilegrid.TileGrid.prototype.getMaxZoom); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getMinZoom', + ol.tilegrid.TileGrid.prototype.getMinZoom); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getOrigin', + ol.tilegrid.TileGrid.prototype.getOrigin); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getResolution', + ol.tilegrid.TileGrid.prototype.getResolution); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getResolutions', + ol.tilegrid.TileGrid.prototype.getResolutions); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getTileCoordExtent', + ol.tilegrid.TileGrid.prototype.getTileCoordExtent); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getTileCoordForCoordAndResolution', + ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getTileCoordForCoordAndZ', + ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getTileSize', + ol.tilegrid.TileGrid.prototype.getTileSize); + +goog.exportProperty( + ol.tilegrid.TileGrid.prototype, + 'getZForResolution', + ol.tilegrid.TileGrid.prototype.getZForResolution); + +goog.exportSymbol( + 'ol.tilegrid.WMTS', + ol.tilegrid.WMTS, + OPENLAYERS); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getMatrixIds', + ol.tilegrid.WMTS.prototype.getMatrixIds); + +goog.exportSymbol( + 'ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet', + ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet, + OPENLAYERS); + +goog.exportSymbol( + 'ol.style.AtlasManager', + ol.style.AtlasManager, + OPENLAYERS); + +goog.exportSymbol( + 'ol.style.Circle', + ol.style.Circle, + OPENLAYERS); + +goog.exportProperty( + ol.style.Circle.prototype, + 'clone', + ol.style.Circle.prototype.clone); + +goog.exportProperty( + ol.style.Circle.prototype, + 'setRadius', + ol.style.Circle.prototype.setRadius); + +goog.exportSymbol( + 'ol.style.Fill', + ol.style.Fill, + OPENLAYERS); + +goog.exportProperty( + ol.style.Fill.prototype, + 'clone', + ol.style.Fill.prototype.clone); + +goog.exportProperty( + ol.style.Fill.prototype, + 'getColor', + ol.style.Fill.prototype.getColor); + +goog.exportProperty( + ol.style.Fill.prototype, + 'setColor', + ol.style.Fill.prototype.setColor); + +goog.exportSymbol( + 'ol.style.Icon', + ol.style.Icon, + OPENLAYERS); + +goog.exportProperty( + ol.style.Icon.prototype, + 'clone', + ol.style.Icon.prototype.clone); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getAnchor', + ol.style.Icon.prototype.getAnchor); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getColor', + ol.style.Icon.prototype.getColor); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getImage', + ol.style.Icon.prototype.getImage); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getOrigin', + ol.style.Icon.prototype.getOrigin); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getSrc', + ol.style.Icon.prototype.getSrc); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getSize', + ol.style.Icon.prototype.getSize); + +goog.exportProperty( + ol.style.Icon.prototype, + 'load', + ol.style.Icon.prototype.load); + +goog.exportSymbol( + 'ol.style.Image', + ol.style.Image, + OPENLAYERS); + +goog.exportProperty( + ol.style.Image.prototype, + 'getOpacity', + ol.style.Image.prototype.getOpacity); + +goog.exportProperty( + ol.style.Image.prototype, + 'getRotateWithView', + ol.style.Image.prototype.getRotateWithView); + +goog.exportProperty( + ol.style.Image.prototype, + 'getRotation', + ol.style.Image.prototype.getRotation); + +goog.exportProperty( + ol.style.Image.prototype, + 'getScale', + ol.style.Image.prototype.getScale); + +goog.exportProperty( + ol.style.Image.prototype, + 'getSnapToPixel', + ol.style.Image.prototype.getSnapToPixel); + +goog.exportProperty( + ol.style.Image.prototype, + 'setOpacity', + ol.style.Image.prototype.setOpacity); + +goog.exportProperty( + ol.style.Image.prototype, + 'setRotation', + ol.style.Image.prototype.setRotation); + +goog.exportProperty( + ol.style.Image.prototype, + 'setScale', + ol.style.Image.prototype.setScale); + +goog.exportSymbol( + 'ol.style.RegularShape', + ol.style.RegularShape, + OPENLAYERS); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'clone', + ol.style.RegularShape.prototype.clone); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getAnchor', + ol.style.RegularShape.prototype.getAnchor); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getAngle', + ol.style.RegularShape.prototype.getAngle); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getFill', + ol.style.RegularShape.prototype.getFill); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getImage', + ol.style.RegularShape.prototype.getImage); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getOrigin', + ol.style.RegularShape.prototype.getOrigin); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getPoints', + ol.style.RegularShape.prototype.getPoints); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getRadius', + ol.style.RegularShape.prototype.getRadius); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getRadius2', + ol.style.RegularShape.prototype.getRadius2); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getSize', + ol.style.RegularShape.prototype.getSize); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getStroke', + ol.style.RegularShape.prototype.getStroke); + +goog.exportSymbol( + 'ol.style.Stroke', + ol.style.Stroke, + OPENLAYERS); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'clone', + ol.style.Stroke.prototype.clone); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'getColor', + ol.style.Stroke.prototype.getColor); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'getLineCap', + ol.style.Stroke.prototype.getLineCap); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'getLineDash', + ol.style.Stroke.prototype.getLineDash); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'getLineJoin', + ol.style.Stroke.prototype.getLineJoin); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'getMiterLimit', + ol.style.Stroke.prototype.getMiterLimit); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'getWidth', + ol.style.Stroke.prototype.getWidth); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'setColor', + ol.style.Stroke.prototype.setColor); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'setLineCap', + ol.style.Stroke.prototype.setLineCap); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'setLineDash', + ol.style.Stroke.prototype.setLineDash); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'setLineJoin', + ol.style.Stroke.prototype.setLineJoin); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'setMiterLimit', + ol.style.Stroke.prototype.setMiterLimit); + +goog.exportProperty( + ol.style.Stroke.prototype, + 'setWidth', + ol.style.Stroke.prototype.setWidth); + +goog.exportSymbol( + 'ol.style.Style', + ol.style.Style, + OPENLAYERS); + +goog.exportProperty( + ol.style.Style.prototype, + 'clone', + ol.style.Style.prototype.clone); + +goog.exportProperty( + ol.style.Style.prototype, + 'getGeometry', + ol.style.Style.prototype.getGeometry); + +goog.exportProperty( + ol.style.Style.prototype, + 'getGeometryFunction', + ol.style.Style.prototype.getGeometryFunction); + +goog.exportProperty( + ol.style.Style.prototype, + 'getFill', + ol.style.Style.prototype.getFill); + +goog.exportProperty( + ol.style.Style.prototype, + 'setFill', + ol.style.Style.prototype.setFill); + +goog.exportProperty( + ol.style.Style.prototype, + 'getImage', + ol.style.Style.prototype.getImage); + +goog.exportProperty( + ol.style.Style.prototype, + 'setImage', + ol.style.Style.prototype.setImage); + +goog.exportProperty( + ol.style.Style.prototype, + 'getStroke', + ol.style.Style.prototype.getStroke); + +goog.exportProperty( + ol.style.Style.prototype, + 'setStroke', + ol.style.Style.prototype.setStroke); + +goog.exportProperty( + ol.style.Style.prototype, + 'getText', + ol.style.Style.prototype.getText); + +goog.exportProperty( + ol.style.Style.prototype, + 'setText', + ol.style.Style.prototype.setText); + +goog.exportProperty( + ol.style.Style.prototype, + 'getZIndex', + ol.style.Style.prototype.getZIndex); + +goog.exportProperty( + ol.style.Style.prototype, + 'setGeometry', + ol.style.Style.prototype.setGeometry); + +goog.exportProperty( + ol.style.Style.prototype, + 'setZIndex', + ol.style.Style.prototype.setZIndex); + +goog.exportSymbol( + 'ol.style.Text', + ol.style.Text, + OPENLAYERS); + +goog.exportProperty( + ol.style.Text.prototype, + 'clone', + ol.style.Text.prototype.clone); + +goog.exportProperty( + ol.style.Text.prototype, + 'getFont', + ol.style.Text.prototype.getFont); + +goog.exportProperty( + ol.style.Text.prototype, + 'getOffsetX', + ol.style.Text.prototype.getOffsetX); + +goog.exportProperty( + ol.style.Text.prototype, + 'getOffsetY', + ol.style.Text.prototype.getOffsetY); + +goog.exportProperty( + ol.style.Text.prototype, + 'getFill', + ol.style.Text.prototype.getFill); + +goog.exportProperty( + ol.style.Text.prototype, + 'getRotateWithView', + ol.style.Text.prototype.getRotateWithView); + +goog.exportProperty( + ol.style.Text.prototype, + 'getRotation', + ol.style.Text.prototype.getRotation); + +goog.exportProperty( + ol.style.Text.prototype, + 'getScale', + ol.style.Text.prototype.getScale); + +goog.exportProperty( + ol.style.Text.prototype, + 'getStroke', + ol.style.Text.prototype.getStroke); + +goog.exportProperty( + ol.style.Text.prototype, + 'getText', + ol.style.Text.prototype.getText); + +goog.exportProperty( + ol.style.Text.prototype, + 'getTextAlign', + ol.style.Text.prototype.getTextAlign); + +goog.exportProperty( + ol.style.Text.prototype, + 'getTextBaseline', + ol.style.Text.prototype.getTextBaseline); + +goog.exportProperty( + ol.style.Text.prototype, + 'setFont', + ol.style.Text.prototype.setFont); + +goog.exportProperty( + ol.style.Text.prototype, + 'setOffsetX', + ol.style.Text.prototype.setOffsetX); + +goog.exportProperty( + ol.style.Text.prototype, + 'setOffsetY', + ol.style.Text.prototype.setOffsetY); + +goog.exportProperty( + ol.style.Text.prototype, + 'setFill', + ol.style.Text.prototype.setFill); + +goog.exportProperty( + ol.style.Text.prototype, + 'setRotation', + ol.style.Text.prototype.setRotation); + +goog.exportProperty( + ol.style.Text.prototype, + 'setScale', + ol.style.Text.prototype.setScale); + +goog.exportProperty( + ol.style.Text.prototype, + 'setStroke', + ol.style.Text.prototype.setStroke); + +goog.exportProperty( + ol.style.Text.prototype, + 'setText', + ol.style.Text.prototype.setText); + +goog.exportProperty( + ol.style.Text.prototype, + 'setTextAlign', + ol.style.Text.prototype.setTextAlign); + +goog.exportProperty( + ol.style.Text.prototype, + 'setTextBaseline', + ol.style.Text.prototype.setTextBaseline); + +goog.exportSymbol( + 'ol.Sphere', + ol.Sphere, + OPENLAYERS); + +goog.exportProperty( + ol.Sphere.prototype, + 'geodesicArea', + ol.Sphere.prototype.geodesicArea); + +goog.exportProperty( + ol.Sphere.prototype, + 'haversineDistance', + ol.Sphere.prototype.haversineDistance); + +goog.exportSymbol( + 'ol.source.BingMaps', + ol.source.BingMaps, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.BingMaps.TOS_ATTRIBUTION', + ol.source.BingMaps.TOS_ATTRIBUTION, + OPENLAYERS); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getApiKey', + ol.source.BingMaps.prototype.getApiKey); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getImagerySet', + ol.source.BingMaps.prototype.getImagerySet); + +goog.exportSymbol( + 'ol.source.CartoDB', + ol.source.CartoDB, + OPENLAYERS); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getConfig', + ol.source.CartoDB.prototype.getConfig); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'updateConfig', + ol.source.CartoDB.prototype.updateConfig); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setConfig', + ol.source.CartoDB.prototype.setConfig); + +goog.exportSymbol( + 'ol.source.Cluster', + ol.source.Cluster, + OPENLAYERS); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getSource', + ol.source.Cluster.prototype.getSource); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'setDistance', + ol.source.Cluster.prototype.setDistance); + +goog.exportSymbol( + 'ol.source.Image', + ol.source.Image, + OPENLAYERS); + +goog.exportProperty( + ol.source.Image.Event.prototype, + 'image', + ol.source.Image.Event.prototype.image); + +goog.exportSymbol( + 'ol.source.ImageArcGISRest', + ol.source.ImageArcGISRest, + OPENLAYERS); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getParams', + ol.source.ImageArcGISRest.prototype.getParams); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getImageLoadFunction', + ol.source.ImageArcGISRest.prototype.getImageLoadFunction); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getUrl', + ol.source.ImageArcGISRest.prototype.getUrl); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'setImageLoadFunction', + ol.source.ImageArcGISRest.prototype.setImageLoadFunction); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'setUrl', + ol.source.ImageArcGISRest.prototype.setUrl); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'updateParams', + ol.source.ImageArcGISRest.prototype.updateParams); + +goog.exportSymbol( + 'ol.source.ImageCanvas', + ol.source.ImageCanvas, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.ImageMapGuide', + ol.source.ImageMapGuide, + OPENLAYERS); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getParams', + ol.source.ImageMapGuide.prototype.getParams); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getImageLoadFunction', + ol.source.ImageMapGuide.prototype.getImageLoadFunction); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'updateParams', + ol.source.ImageMapGuide.prototype.updateParams); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'setImageLoadFunction', + ol.source.ImageMapGuide.prototype.setImageLoadFunction); + +goog.exportSymbol( + 'ol.source.ImageStatic', + ol.source.ImageStatic, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.ImageVector', + ol.source.ImageVector, + OPENLAYERS); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getSource', + ol.source.ImageVector.prototype.getSource); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getStyle', + ol.source.ImageVector.prototype.getStyle); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getStyleFunction', + ol.source.ImageVector.prototype.getStyleFunction); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'setStyle', + ol.source.ImageVector.prototype.setStyle); + +goog.exportSymbol( + 'ol.source.ImageWMS', + ol.source.ImageWMS, + OPENLAYERS); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getGetFeatureInfoUrl', + ol.source.ImageWMS.prototype.getGetFeatureInfoUrl); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getParams', + ol.source.ImageWMS.prototype.getParams); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getImageLoadFunction', + ol.source.ImageWMS.prototype.getImageLoadFunction); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getUrl', + ol.source.ImageWMS.prototype.getUrl); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'setImageLoadFunction', + ol.source.ImageWMS.prototype.setImageLoadFunction); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'setUrl', + ol.source.ImageWMS.prototype.setUrl); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'updateParams', + ol.source.ImageWMS.prototype.updateParams); + +goog.exportSymbol( + 'ol.source.OSM', + ol.source.OSM, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.OSM.ATTRIBUTION', + ol.source.OSM.ATTRIBUTION, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.Raster', + ol.source.Raster, + OPENLAYERS); + +goog.exportProperty( + ol.source.Raster.prototype, + 'setOperation', + ol.source.Raster.prototype.setOperation); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'extent', + ol.source.Raster.Event.prototype.extent); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'resolution', + ol.source.Raster.Event.prototype.resolution); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'data', + ol.source.Raster.Event.prototype.data); + +goog.exportSymbol( + 'ol.source.Source', + ol.source.Source, + OPENLAYERS); + +goog.exportProperty( + ol.source.Source.prototype, + 'getAttributions', + ol.source.Source.prototype.getAttributions); + +goog.exportProperty( + ol.source.Source.prototype, + 'getLogo', + ol.source.Source.prototype.getLogo); + +goog.exportProperty( + ol.source.Source.prototype, + 'getProjection', + ol.source.Source.prototype.getProjection); + +goog.exportProperty( + ol.source.Source.prototype, + 'getState', + ol.source.Source.prototype.getState); + +goog.exportProperty( + ol.source.Source.prototype, + 'refresh', + ol.source.Source.prototype.refresh); + +goog.exportProperty( + ol.source.Source.prototype, + 'setAttributions', + ol.source.Source.prototype.setAttributions); + +goog.exportSymbol( + 'ol.source.Stamen', + ol.source.Stamen, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.Tile', + ol.source.Tile, + OPENLAYERS); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getTileGrid', + ol.source.Tile.prototype.getTileGrid); + +goog.exportProperty( + ol.source.Tile.Event.prototype, + 'tile', + ol.source.Tile.Event.prototype.tile); + +goog.exportSymbol( + 'ol.source.TileArcGISRest', + ol.source.TileArcGISRest, + OPENLAYERS); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getParams', + ol.source.TileArcGISRest.prototype.getParams); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'updateParams', + ol.source.TileArcGISRest.prototype.updateParams); + +goog.exportSymbol( + 'ol.source.TileDebug', + ol.source.TileDebug, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.TileImage', + ol.source.TileImage, + OPENLAYERS); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setRenderReprojectionEdges', + ol.source.TileImage.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setTileGridForProjection', + ol.source.TileImage.prototype.setTileGridForProjection); + +goog.exportSymbol( + 'ol.source.TileJSON', + ol.source.TileJSON, + OPENLAYERS); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getTileJSON', + ol.source.TileJSON.prototype.getTileJSON); + +goog.exportSymbol( + 'ol.source.TileUTFGrid', + ol.source.TileUTFGrid, + OPENLAYERS); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getTemplate', + ol.source.TileUTFGrid.prototype.getTemplate); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'forDataAtCoordinateAndResolution', + ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution); + +goog.exportSymbol( + 'ol.source.TileWMS', + ol.source.TileWMS, + OPENLAYERS); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getGetFeatureInfoUrl', + ol.source.TileWMS.prototype.getGetFeatureInfoUrl); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getParams', + ol.source.TileWMS.prototype.getParams); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'updateParams', + ol.source.TileWMS.prototype.updateParams); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getTileLoadFunction', + ol.source.UrlTile.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getTileUrlFunction', + ol.source.UrlTile.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getUrls', + ol.source.UrlTile.prototype.getUrls); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'setTileLoadFunction', + ol.source.UrlTile.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'setTileUrlFunction', + ol.source.UrlTile.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'setUrl', + ol.source.UrlTile.prototype.setUrl); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'setUrls', + ol.source.UrlTile.prototype.setUrls); + +goog.exportSymbol( + 'ol.source.Vector', + ol.source.Vector, + OPENLAYERS); + +goog.exportProperty( + ol.source.Vector.prototype, + 'addFeature', + ol.source.Vector.prototype.addFeature); + +goog.exportProperty( + ol.source.Vector.prototype, + 'addFeatures', + ol.source.Vector.prototype.addFeatures); + +goog.exportProperty( + ol.source.Vector.prototype, + 'clear', + ol.source.Vector.prototype.clear); + +goog.exportProperty( + ol.source.Vector.prototype, + 'forEachFeature', + ol.source.Vector.prototype.forEachFeature); + +goog.exportProperty( + ol.source.Vector.prototype, + 'forEachFeatureInExtent', + ol.source.Vector.prototype.forEachFeatureInExtent); + +goog.exportProperty( + ol.source.Vector.prototype, + 'forEachFeatureIntersectingExtent', + ol.source.Vector.prototype.forEachFeatureIntersectingExtent); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getFeaturesCollection', + ol.source.Vector.prototype.getFeaturesCollection); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getFeatures', + ol.source.Vector.prototype.getFeatures); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getFeaturesAtCoordinate', + ol.source.Vector.prototype.getFeaturesAtCoordinate); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getFeaturesInExtent', + ol.source.Vector.prototype.getFeaturesInExtent); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getClosestFeatureToCoordinate', + ol.source.Vector.prototype.getClosestFeatureToCoordinate); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getExtent', + ol.source.Vector.prototype.getExtent); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getFeatureById', + ol.source.Vector.prototype.getFeatureById); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getFormat', + ol.source.Vector.prototype.getFormat); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getUrl', + ol.source.Vector.prototype.getUrl); + +goog.exportProperty( + ol.source.Vector.prototype, + 'removeFeature', + ol.source.Vector.prototype.removeFeature); + +goog.exportProperty( + ol.source.Vector.Event.prototype, + 'feature', + ol.source.Vector.Event.prototype.feature); + +goog.exportSymbol( + 'ol.source.VectorTile', + ol.source.VectorTile, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.WMTS', + ol.source.WMTS, + OPENLAYERS); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getDimensions', + ol.source.WMTS.prototype.getDimensions); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getFormat', + ol.source.WMTS.prototype.getFormat); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getLayer', + ol.source.WMTS.prototype.getLayer); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getMatrixSet', + ol.source.WMTS.prototype.getMatrixSet); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getRequestEncoding', + ol.source.WMTS.prototype.getRequestEncoding); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getStyle', + ol.source.WMTS.prototype.getStyle); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getVersion', + ol.source.WMTS.prototype.getVersion); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'updateDimensions', + ol.source.WMTS.prototype.updateDimensions); + +goog.exportSymbol( + 'ol.source.WMTS.optionsFromCapabilities', + ol.source.WMTS.optionsFromCapabilities, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.XYZ', + ol.source.XYZ, + OPENLAYERS); + +goog.exportSymbol( + 'ol.source.Zoomify', + ol.source.Zoomify, + OPENLAYERS); + +goog.exportProperty( + ol.render.Event.prototype, + 'vectorContext', + ol.render.Event.prototype.vectorContext); + +goog.exportProperty( + ol.render.Event.prototype, + 'frameState', + ol.render.Event.prototype.frameState); + +goog.exportProperty( + ol.render.Event.prototype, + 'context', + ol.render.Event.prototype.context); + +goog.exportProperty( + ol.render.Event.prototype, + 'glContext', + ol.render.Event.prototype.glContext); + +goog.exportProperty( + ol.render.Feature.prototype, + 'get', + ol.render.Feature.prototype.get); + +goog.exportProperty( + ol.render.Feature.prototype, + 'getExtent', + ol.render.Feature.prototype.getExtent); + +goog.exportProperty( + ol.render.Feature.prototype, + 'getGeometry', + ol.render.Feature.prototype.getGeometry); + +goog.exportProperty( + ol.render.Feature.prototype, + 'getProperties', + ol.render.Feature.prototype.getProperties); + +goog.exportProperty( + ol.render.Feature.prototype, + 'getType', + ol.render.Feature.prototype.getType); + +goog.exportSymbol( + 'ol.render.VectorContext', + ol.render.VectorContext, + OPENLAYERS); + +goog.exportProperty( + ol.render.webgl.Immediate.prototype, + 'setStyle', + ol.render.webgl.Immediate.prototype.setStyle); + +goog.exportProperty( + ol.render.webgl.Immediate.prototype, + 'drawGeometry', + ol.render.webgl.Immediate.prototype.drawGeometry); + +goog.exportProperty( + ol.render.webgl.Immediate.prototype, + 'drawFeature', + ol.render.webgl.Immediate.prototype.drawFeature); + +goog.exportProperty( + ol.render.canvas.Immediate.prototype, + 'drawCircle', + ol.render.canvas.Immediate.prototype.drawCircle); + +goog.exportProperty( + ol.render.canvas.Immediate.prototype, + 'setStyle', + ol.render.canvas.Immediate.prototype.setStyle); + +goog.exportProperty( + ol.render.canvas.Immediate.prototype, + 'drawGeometry', + ol.render.canvas.Immediate.prototype.drawGeometry); + +goog.exportProperty( + ol.render.canvas.Immediate.prototype, + 'drawFeature', + ol.render.canvas.Immediate.prototype.drawFeature); + +goog.exportSymbol( + 'ol.proj.common.add', + ol.proj.common.add, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.METERS_PER_UNIT', + ol.proj.METERS_PER_UNIT, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.setProj4', + ol.proj.setProj4, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.getPointResolution', + ol.proj.getPointResolution, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.addEquivalentProjections', + ol.proj.addEquivalentProjections, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.addProjection', + ol.proj.addProjection, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.addCoordinateTransforms', + ol.proj.addCoordinateTransforms, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.fromLonLat', + ol.proj.fromLonLat, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.toLonLat', + ol.proj.toLonLat, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.get', + ol.proj.get, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.equivalent', + ol.proj.equivalent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.getTransform', + ol.proj.getTransform, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.transform', + ol.proj.transform, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.transformExtent', + ol.proj.transformExtent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.Projection', + ol.proj.Projection, + OPENLAYERS); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'getCode', + ol.proj.Projection.prototype.getCode); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'getExtent', + ol.proj.Projection.prototype.getExtent); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'getUnits', + ol.proj.Projection.prototype.getUnits); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'getMetersPerUnit', + ol.proj.Projection.prototype.getMetersPerUnit); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'getWorldExtent', + ol.proj.Projection.prototype.getWorldExtent); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'isGlobal', + ol.proj.Projection.prototype.isGlobal); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'setGlobal', + ol.proj.Projection.prototype.setGlobal); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'setExtent', + ol.proj.Projection.prototype.setExtent); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'setWorldExtent', + ol.proj.Projection.prototype.setWorldExtent); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'setGetPointResolution', + ol.proj.Projection.prototype.setGetPointResolution); + +goog.exportSymbol( + 'ol.proj.Units.METERS_PER_UNIT', + ol.proj.Units.METERS_PER_UNIT, + OPENLAYERS); + +goog.exportSymbol( + 'ol.layer.Base', + ol.layer.Base, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getExtent', + ol.layer.Base.prototype.getExtent); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getMaxResolution', + ol.layer.Base.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getMinResolution', + ol.layer.Base.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getOpacity', + ol.layer.Base.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getVisible', + ol.layer.Base.prototype.getVisible); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getZIndex', + ol.layer.Base.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setExtent', + ol.layer.Base.prototype.setExtent); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setMaxResolution', + ol.layer.Base.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setMinResolution', + ol.layer.Base.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setOpacity', + ol.layer.Base.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setVisible', + ol.layer.Base.prototype.setVisible); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setZIndex', + ol.layer.Base.prototype.setZIndex); + +goog.exportSymbol( + 'ol.layer.Group', + ol.layer.Group, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getLayers', + ol.layer.Group.prototype.getLayers); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setLayers', + ol.layer.Group.prototype.setLayers); + +goog.exportSymbol( + 'ol.layer.Heatmap', + ol.layer.Heatmap, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getBlur', + ol.layer.Heatmap.prototype.getBlur); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getGradient', + ol.layer.Heatmap.prototype.getGradient); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getRadius', + ol.layer.Heatmap.prototype.getRadius); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setBlur', + ol.layer.Heatmap.prototype.setBlur); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setGradient', + ol.layer.Heatmap.prototype.setGradient); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setRadius', + ol.layer.Heatmap.prototype.setRadius); + +goog.exportSymbol( + 'ol.layer.Image', + ol.layer.Image, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getSource', + ol.layer.Image.prototype.getSource); + +goog.exportSymbol( + 'ol.layer.Layer', + ol.layer.Layer, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getSource', + ol.layer.Layer.prototype.getSource); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setMap', + ol.layer.Layer.prototype.setMap); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setSource', + ol.layer.Layer.prototype.setSource); + +goog.exportSymbol( + 'ol.layer.Tile', + ol.layer.Tile, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getPreload', + ol.layer.Tile.prototype.getPreload); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getSource', + ol.layer.Tile.prototype.getSource); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setPreload', + ol.layer.Tile.prototype.setPreload); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getUseInterimTilesOnError', + ol.layer.Tile.prototype.getUseInterimTilesOnError); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setUseInterimTilesOnError', + ol.layer.Tile.prototype.setUseInterimTilesOnError); + +goog.exportSymbol( + 'ol.layer.Vector', + ol.layer.Vector, + OPENLAYERS); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getSource', + ol.layer.Vector.prototype.getSource); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getStyle', + ol.layer.Vector.prototype.getStyle); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getStyleFunction', + ol.layer.Vector.prototype.getStyleFunction); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setStyle', + ol.layer.Vector.prototype.setStyle); + +goog.exportSymbol( + 'ol.layer.VectorTile', + ol.layer.VectorTile, + OPENLAYERS); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getPreload', + ol.layer.VectorTile.prototype.getPreload); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getUseInterimTilesOnError', + ol.layer.VectorTile.prototype.getUseInterimTilesOnError); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setPreload', + ol.layer.VectorTile.prototype.setPreload); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setUseInterimTilesOnError', + ol.layer.VectorTile.prototype.setUseInterimTilesOnError); + +goog.exportSymbol( + 'ol.interaction.DoubleClickZoom', + ol.interaction.DoubleClickZoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.DoubleClickZoom.handleEvent', + ol.interaction.DoubleClickZoom.handleEvent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.DragAndDrop', + ol.interaction.DragAndDrop, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.DragAndDrop.handleEvent', + ol.interaction.DragAndDrop.handleEvent, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'features', + ol.interaction.DragAndDrop.Event.prototype.features); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'file', + ol.interaction.DragAndDrop.Event.prototype.file); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'projection', + ol.interaction.DragAndDrop.Event.prototype.projection); + +goog.exportSymbol( + 'ol.interaction.DragBox', + ol.interaction.DragBox, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'getGeometry', + ol.interaction.DragBox.prototype.getGeometry); + +goog.exportProperty( + ol.interaction.DragBox.Event.prototype, + 'coordinate', + ol.interaction.DragBox.Event.prototype.coordinate); + +goog.exportProperty( + ol.interaction.DragBox.Event.prototype, + 'mapBrowserEvent', + ol.interaction.DragBox.Event.prototype.mapBrowserEvent); + +goog.exportSymbol( + 'ol.interaction.DragPan', + ol.interaction.DragPan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.DragRotate', + ol.interaction.DragRotate, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.DragRotateAndZoom', + ol.interaction.DragRotateAndZoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.DragZoom', + ol.interaction.DragZoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Draw', + ol.interaction.Draw, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Draw.handleEvent', + ol.interaction.Draw.handleEvent, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'removeLastPoint', + ol.interaction.Draw.prototype.removeLastPoint); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'finishDrawing', + ol.interaction.Draw.prototype.finishDrawing); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'extend', + ol.interaction.Draw.prototype.extend); + +goog.exportSymbol( + 'ol.interaction.Draw.createRegularPolygon', + ol.interaction.Draw.createRegularPolygon, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Draw.createBox', + ol.interaction.Draw.createBox, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Draw.Event.prototype, + 'feature', + ol.interaction.Draw.Event.prototype.feature); + +goog.exportSymbol( + 'ol.interaction.Extent', + ol.interaction.Extent, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'getExtent', + ol.interaction.Extent.prototype.getExtent); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'setExtent', + ol.interaction.Extent.prototype.setExtent); + +goog.exportProperty( + ol.interaction.Extent.Event.prototype, + 'extent_', + ol.interaction.Extent.Event.prototype.extent_); + +goog.exportSymbol( + 'ol.interaction.defaults', + ol.interaction.defaults, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Interaction', + ol.interaction.Interaction, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'getActive', + ol.interaction.Interaction.prototype.getActive); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'getMap', + ol.interaction.Interaction.prototype.getMap); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'setActive', + ol.interaction.Interaction.prototype.setActive); + +goog.exportSymbol( + 'ol.interaction.KeyboardPan', + ol.interaction.KeyboardPan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.KeyboardPan.handleEvent', + ol.interaction.KeyboardPan.handleEvent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.KeyboardZoom', + ol.interaction.KeyboardZoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.KeyboardZoom.handleEvent', + ol.interaction.KeyboardZoom.handleEvent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Modify', + ol.interaction.Modify, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Modify.handleEvent', + ol.interaction.Modify.handleEvent, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'removePoint', + ol.interaction.Modify.prototype.removePoint); + +goog.exportProperty( + ol.interaction.Modify.Event.prototype, + 'features', + ol.interaction.Modify.Event.prototype.features); + +goog.exportProperty( + ol.interaction.Modify.Event.prototype, + 'mapBrowserEvent', + ol.interaction.Modify.Event.prototype.mapBrowserEvent); + +goog.exportSymbol( + 'ol.interaction.MouseWheelZoom', + ol.interaction.MouseWheelZoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.MouseWheelZoom.handleEvent', + ol.interaction.MouseWheelZoom.handleEvent, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'setMouseAnchor', + ol.interaction.MouseWheelZoom.prototype.setMouseAnchor); + +goog.exportSymbol( + 'ol.interaction.PinchRotate', + ol.interaction.PinchRotate, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.PinchZoom', + ol.interaction.PinchZoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Pointer', + ol.interaction.Pointer, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Pointer.handleEvent', + ol.interaction.Pointer.handleEvent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.interaction.Select', + ol.interaction.Select, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getFeatures', + ol.interaction.Select.prototype.getFeatures); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getHitTolerance', + ol.interaction.Select.prototype.getHitTolerance); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getLayer', + ol.interaction.Select.prototype.getLayer); + +goog.exportSymbol( + 'ol.interaction.Select.handleEvent', + ol.interaction.Select.handleEvent, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'setHitTolerance', + ol.interaction.Select.prototype.setHitTolerance); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'setMap', + ol.interaction.Select.prototype.setMap); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'selected', + ol.interaction.Select.Event.prototype.selected); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'deselected', + ol.interaction.Select.Event.prototype.deselected); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'mapBrowserEvent', + ol.interaction.Select.Event.prototype.mapBrowserEvent); + +goog.exportSymbol( + 'ol.interaction.Snap', + ol.interaction.Snap, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'addFeature', + ol.interaction.Snap.prototype.addFeature); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'removeFeature', + ol.interaction.Snap.prototype.removeFeature); + +goog.exportSymbol( + 'ol.interaction.Translate', + ol.interaction.Translate, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'getHitTolerance', + ol.interaction.Translate.prototype.getHitTolerance); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'setHitTolerance', + ol.interaction.Translate.prototype.setHitTolerance); + +goog.exportProperty( + ol.interaction.Translate.Event.prototype, + 'features', + ol.interaction.Translate.Event.prototype.features); + +goog.exportProperty( + ol.interaction.Translate.Event.prototype, + 'coordinate', + ol.interaction.Translate.Event.prototype.coordinate); + +goog.exportSymbol( + 'ol.geom.Circle', + ol.geom.Circle, + OPENLAYERS); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'clone', + ol.geom.Circle.prototype.clone); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getCenter', + ol.geom.Circle.prototype.getCenter); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getRadius', + ol.geom.Circle.prototype.getRadius); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getType', + ol.geom.Circle.prototype.getType); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'intersectsExtent', + ol.geom.Circle.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'setCenter', + ol.geom.Circle.prototype.setCenter); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'setCenterAndRadius', + ol.geom.Circle.prototype.setCenterAndRadius); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'setRadius', + ol.geom.Circle.prototype.setRadius); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'transform', + ol.geom.Circle.prototype.transform); + +goog.exportSymbol( + 'ol.geom.Geometry', + ol.geom.Geometry, + OPENLAYERS); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'getClosestPoint', + ol.geom.Geometry.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'intersectsCoordinate', + ol.geom.Geometry.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'getExtent', + ol.geom.Geometry.prototype.getExtent); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'rotate', + ol.geom.Geometry.prototype.rotate); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'scale', + ol.geom.Geometry.prototype.scale); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'simplify', + ol.geom.Geometry.prototype.simplify); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'transform', + ol.geom.Geometry.prototype.transform); + +goog.exportSymbol( + 'ol.geom.GeometryCollection', + ol.geom.GeometryCollection, + OPENLAYERS); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'clone', + ol.geom.GeometryCollection.prototype.clone); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getGeometries', + ol.geom.GeometryCollection.prototype.getGeometries); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getType', + ol.geom.GeometryCollection.prototype.getType); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'intersectsExtent', + ol.geom.GeometryCollection.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'setGeometries', + ol.geom.GeometryCollection.prototype.setGeometries); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'applyTransform', + ol.geom.GeometryCollection.prototype.applyTransform); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'translate', + ol.geom.GeometryCollection.prototype.translate); + +goog.exportSymbol( + 'ol.geom.LinearRing', + ol.geom.LinearRing, + OPENLAYERS); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'clone', + ol.geom.LinearRing.prototype.clone); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getArea', + ol.geom.LinearRing.prototype.getArea); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getCoordinates', + ol.geom.LinearRing.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getType', + ol.geom.LinearRing.prototype.getType); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'setCoordinates', + ol.geom.LinearRing.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.LineString', + ol.geom.LineString, + OPENLAYERS); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'appendCoordinate', + ol.geom.LineString.prototype.appendCoordinate); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'clone', + ol.geom.LineString.prototype.clone); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'forEachSegment', + ol.geom.LineString.prototype.forEachSegment); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getCoordinateAtM', + ol.geom.LineString.prototype.getCoordinateAtM); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getCoordinates', + ol.geom.LineString.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getCoordinateAt', + ol.geom.LineString.prototype.getCoordinateAt); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getLength', + ol.geom.LineString.prototype.getLength); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getType', + ol.geom.LineString.prototype.getType); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'intersectsExtent', + ol.geom.LineString.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'setCoordinates', + ol.geom.LineString.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.MultiLineString', + ol.geom.MultiLineString, + OPENLAYERS); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'appendLineString', + ol.geom.MultiLineString.prototype.appendLineString); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'clone', + ol.geom.MultiLineString.prototype.clone); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getCoordinateAtM', + ol.geom.MultiLineString.prototype.getCoordinateAtM); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getCoordinates', + ol.geom.MultiLineString.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getLineString', + ol.geom.MultiLineString.prototype.getLineString); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getLineStrings', + ol.geom.MultiLineString.prototype.getLineStrings); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getType', + ol.geom.MultiLineString.prototype.getType); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'intersectsExtent', + ol.geom.MultiLineString.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'setCoordinates', + ol.geom.MultiLineString.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.MultiPoint', + ol.geom.MultiPoint, + OPENLAYERS); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'appendPoint', + ol.geom.MultiPoint.prototype.appendPoint); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'clone', + ol.geom.MultiPoint.prototype.clone); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getCoordinates', + ol.geom.MultiPoint.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getPoint', + ol.geom.MultiPoint.prototype.getPoint); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getPoints', + ol.geom.MultiPoint.prototype.getPoints); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getType', + ol.geom.MultiPoint.prototype.getType); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'intersectsExtent', + ol.geom.MultiPoint.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'setCoordinates', + ol.geom.MultiPoint.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.MultiPolygon', + ol.geom.MultiPolygon, + OPENLAYERS); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'appendPolygon', + ol.geom.MultiPolygon.prototype.appendPolygon); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'clone', + ol.geom.MultiPolygon.prototype.clone); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getArea', + ol.geom.MultiPolygon.prototype.getArea); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getCoordinates', + ol.geom.MultiPolygon.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getInteriorPoints', + ol.geom.MultiPolygon.prototype.getInteriorPoints); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getPolygon', + ol.geom.MultiPolygon.prototype.getPolygon); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getPolygons', + ol.geom.MultiPolygon.prototype.getPolygons); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getType', + ol.geom.MultiPolygon.prototype.getType); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'intersectsExtent', + ol.geom.MultiPolygon.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'setCoordinates', + ol.geom.MultiPolygon.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.Point', + ol.geom.Point, + OPENLAYERS); + +goog.exportProperty( + ol.geom.Point.prototype, + 'clone', + ol.geom.Point.prototype.clone); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getCoordinates', + ol.geom.Point.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getType', + ol.geom.Point.prototype.getType); + +goog.exportProperty( + ol.geom.Point.prototype, + 'intersectsExtent', + ol.geom.Point.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.Point.prototype, + 'setCoordinates', + ol.geom.Point.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.Polygon', + ol.geom.Polygon, + OPENLAYERS); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'appendLinearRing', + ol.geom.Polygon.prototype.appendLinearRing); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'clone', + ol.geom.Polygon.prototype.clone); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getArea', + ol.geom.Polygon.prototype.getArea); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getCoordinates', + ol.geom.Polygon.prototype.getCoordinates); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getInteriorPoint', + ol.geom.Polygon.prototype.getInteriorPoint); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getLinearRingCount', + ol.geom.Polygon.prototype.getLinearRingCount); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getLinearRing', + ol.geom.Polygon.prototype.getLinearRing); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getLinearRings', + ol.geom.Polygon.prototype.getLinearRings); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getType', + ol.geom.Polygon.prototype.getType); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'intersectsExtent', + ol.geom.Polygon.prototype.intersectsExtent); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'setCoordinates', + ol.geom.Polygon.prototype.setCoordinates); + +goog.exportSymbol( + 'ol.geom.Polygon.circular', + ol.geom.Polygon.circular, + OPENLAYERS); + +goog.exportSymbol( + 'ol.geom.Polygon.fromExtent', + ol.geom.Polygon.fromExtent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.geom.Polygon.fromCircle', + ol.geom.Polygon.fromCircle, + OPENLAYERS); + +goog.exportSymbol( + 'ol.geom.SimpleGeometry', + ol.geom.SimpleGeometry, + OPENLAYERS); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getFirstCoordinate', + ol.geom.SimpleGeometry.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getLastCoordinate', + ol.geom.SimpleGeometry.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getLayout', + ol.geom.SimpleGeometry.prototype.getLayout); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'applyTransform', + ol.geom.SimpleGeometry.prototype.applyTransform); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'translate', + ol.geom.SimpleGeometry.prototype.translate); + +goog.exportSymbol( + 'ol.format.EsriJSON', + ol.format.EsriJSON, + OPENLAYERS); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'readFeature', + ol.format.EsriJSON.prototype.readFeature); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'readFeatures', + ol.format.EsriJSON.prototype.readFeatures); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'readGeometry', + ol.format.EsriJSON.prototype.readGeometry); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'readProjection', + ol.format.EsriJSON.prototype.readProjection); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeGeometry', + ol.format.EsriJSON.prototype.writeGeometry); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeGeometryObject', + ol.format.EsriJSON.prototype.writeGeometryObject); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeFeature', + ol.format.EsriJSON.prototype.writeFeature); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeFeatureObject', + ol.format.EsriJSON.prototype.writeFeatureObject); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeFeatures', + ol.format.EsriJSON.prototype.writeFeatures); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeFeaturesObject', + ol.format.EsriJSON.prototype.writeFeaturesObject); + +goog.exportSymbol( + 'ol.format.Feature', + ol.format.Feature, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.GeoJSON', + ol.format.GeoJSON, + OPENLAYERS); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'readFeature', + ol.format.GeoJSON.prototype.readFeature); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'readFeatures', + ol.format.GeoJSON.prototype.readFeatures); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'readGeometry', + ol.format.GeoJSON.prototype.readGeometry); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'readProjection', + ol.format.GeoJSON.prototype.readProjection); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'writeFeature', + ol.format.GeoJSON.prototype.writeFeature); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'writeFeatureObject', + ol.format.GeoJSON.prototype.writeFeatureObject); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'writeFeatures', + ol.format.GeoJSON.prototype.writeFeatures); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'writeFeaturesObject', + ol.format.GeoJSON.prototype.writeFeaturesObject); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'writeGeometry', + ol.format.GeoJSON.prototype.writeGeometry); + +goog.exportProperty( + ol.format.GeoJSON.prototype, + 'writeGeometryObject', + ol.format.GeoJSON.prototype.writeGeometryObject); + +goog.exportSymbol( + 'ol.format.GML', + ol.format.GML, + OPENLAYERS); + +goog.exportProperty( + ol.format.GML.prototype, + 'writeFeatures', + ol.format.GML.prototype.writeFeatures); + +goog.exportProperty( + ol.format.GML.prototype, + 'writeFeaturesNode', + ol.format.GML.prototype.writeFeaturesNode); + +goog.exportSymbol( + 'ol.format.GML2', + ol.format.GML2, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.GML3', + ol.format.GML3, + OPENLAYERS); + +goog.exportProperty( + ol.format.GML3.prototype, + 'writeGeometryNode', + ol.format.GML3.prototype.writeGeometryNode); + +goog.exportProperty( + ol.format.GML3.prototype, + 'writeFeatures', + ol.format.GML3.prototype.writeFeatures); + +goog.exportProperty( + ol.format.GML3.prototype, + 'writeFeaturesNode', + ol.format.GML3.prototype.writeFeaturesNode); + +goog.exportProperty( + ol.format.GMLBase.prototype, + 'readFeatures', + ol.format.GMLBase.prototype.readFeatures); + +goog.exportSymbol( + 'ol.format.GPX', + ol.format.GPX, + OPENLAYERS); + +goog.exportProperty( + ol.format.GPX.prototype, + 'readFeature', + ol.format.GPX.prototype.readFeature); + +goog.exportProperty( + ol.format.GPX.prototype, + 'readFeatures', + ol.format.GPX.prototype.readFeatures); + +goog.exportProperty( + ol.format.GPX.prototype, + 'readProjection', + ol.format.GPX.prototype.readProjection); + +goog.exportProperty( + ol.format.GPX.prototype, + 'writeFeatures', + ol.format.GPX.prototype.writeFeatures); + +goog.exportProperty( + ol.format.GPX.prototype, + 'writeFeaturesNode', + ol.format.GPX.prototype.writeFeaturesNode); + +goog.exportSymbol( + 'ol.format.IGC', + ol.format.IGC, + OPENLAYERS); + +goog.exportProperty( + ol.format.IGC.prototype, + 'readFeature', + ol.format.IGC.prototype.readFeature); + +goog.exportProperty( + ol.format.IGC.prototype, + 'readFeatures', + ol.format.IGC.prototype.readFeatures); + +goog.exportProperty( + ol.format.IGC.prototype, + 'readProjection', + ol.format.IGC.prototype.readProjection); + +goog.exportSymbol( + 'ol.format.KML', + ol.format.KML, + OPENLAYERS); + +goog.exportProperty( + ol.format.KML.prototype, + 'readFeature', + ol.format.KML.prototype.readFeature); + +goog.exportProperty( + ol.format.KML.prototype, + 'readFeatures', + ol.format.KML.prototype.readFeatures); + +goog.exportProperty( + ol.format.KML.prototype, + 'readName', + ol.format.KML.prototype.readName); + +goog.exportProperty( + ol.format.KML.prototype, + 'readNetworkLinks', + ol.format.KML.prototype.readNetworkLinks); + +goog.exportProperty( + ol.format.KML.prototype, + 'readRegion', + ol.format.KML.prototype.readRegion); + +goog.exportProperty( + ol.format.KML.prototype, + 'readRegionFromNode', + ol.format.KML.prototype.readRegionFromNode); + +goog.exportProperty( + ol.format.KML.prototype, + 'readProjection', + ol.format.KML.prototype.readProjection); + +goog.exportProperty( + ol.format.KML.prototype, + 'writeFeatures', + ol.format.KML.prototype.writeFeatures); + +goog.exportProperty( + ol.format.KML.prototype, + 'writeFeaturesNode', + ol.format.KML.prototype.writeFeaturesNode); + +goog.exportSymbol( + 'ol.format.MVT', + ol.format.MVT, + OPENLAYERS); + +goog.exportProperty( + ol.format.MVT.prototype, + 'readFeatures', + ol.format.MVT.prototype.readFeatures); + +goog.exportProperty( + ol.format.MVT.prototype, + 'readProjection', + ol.format.MVT.prototype.readProjection); + +goog.exportProperty( + ol.format.MVT.prototype, + 'setLayers', + ol.format.MVT.prototype.setLayers); + +goog.exportSymbol( + 'ol.format.OSMXML', + ol.format.OSMXML, + OPENLAYERS); + +goog.exportProperty( + ol.format.OSMXML.prototype, + 'readFeatures', + ol.format.OSMXML.prototype.readFeatures); + +goog.exportProperty( + ol.format.OSMXML.prototype, + 'readProjection', + ol.format.OSMXML.prototype.readProjection); + +goog.exportSymbol( + 'ol.format.Polyline', + ol.format.Polyline, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.Polyline.encodeDeltas', + ol.format.Polyline.encodeDeltas, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.Polyline.decodeDeltas', + ol.format.Polyline.decodeDeltas, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.Polyline.encodeFloats', + ol.format.Polyline.encodeFloats, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.Polyline.decodeFloats', + ol.format.Polyline.decodeFloats, + OPENLAYERS); + +goog.exportProperty( + ol.format.Polyline.prototype, + 'readFeature', + ol.format.Polyline.prototype.readFeature); + +goog.exportProperty( + ol.format.Polyline.prototype, + 'readFeatures', + ol.format.Polyline.prototype.readFeatures); + +goog.exportProperty( + ol.format.Polyline.prototype, + 'readGeometry', + ol.format.Polyline.prototype.readGeometry); + +goog.exportProperty( + ol.format.Polyline.prototype, + 'readProjection', + ol.format.Polyline.prototype.readProjection); + +goog.exportProperty( + ol.format.Polyline.prototype, + 'writeGeometry', + ol.format.Polyline.prototype.writeGeometry); + +goog.exportSymbol( + 'ol.format.TopoJSON', + ol.format.TopoJSON, + OPENLAYERS); + +goog.exportProperty( + ol.format.TopoJSON.prototype, + 'readFeatures', + ol.format.TopoJSON.prototype.readFeatures); + +goog.exportProperty( + ol.format.TopoJSON.prototype, + 'readProjection', + ol.format.TopoJSON.prototype.readProjection); + +goog.exportSymbol( + 'ol.format.WFS', + ol.format.WFS, + OPENLAYERS); + +goog.exportProperty( + ol.format.WFS.prototype, + 'readFeatures', + ol.format.WFS.prototype.readFeatures); + +goog.exportProperty( + ol.format.WFS.prototype, + 'readTransactionResponse', + ol.format.WFS.prototype.readTransactionResponse); + +goog.exportProperty( + ol.format.WFS.prototype, + 'readFeatureCollectionMetadata', + ol.format.WFS.prototype.readFeatureCollectionMetadata); + +goog.exportProperty( + ol.format.WFS.prototype, + 'writeGetFeature', + ol.format.WFS.prototype.writeGetFeature); + +goog.exportProperty( + ol.format.WFS.prototype, + 'writeTransaction', + ol.format.WFS.prototype.writeTransaction); + +goog.exportProperty( + ol.format.WFS.prototype, + 'readProjection', + ol.format.WFS.prototype.readProjection); + +goog.exportSymbol( + 'ol.format.WKT', + ol.format.WKT, + OPENLAYERS); + +goog.exportProperty( + ol.format.WKT.prototype, + 'readFeature', + ol.format.WKT.prototype.readFeature); + +goog.exportProperty( + ol.format.WKT.prototype, + 'readFeatures', + ol.format.WKT.prototype.readFeatures); + +goog.exportProperty( + ol.format.WKT.prototype, + 'readGeometry', + ol.format.WKT.prototype.readGeometry); + +goog.exportProperty( + ol.format.WKT.prototype, + 'writeFeature', + ol.format.WKT.prototype.writeFeature); + +goog.exportProperty( + ol.format.WKT.prototype, + 'writeFeatures', + ol.format.WKT.prototype.writeFeatures); + +goog.exportProperty( + ol.format.WKT.prototype, + 'writeGeometry', + ol.format.WKT.prototype.writeGeometry); + +goog.exportSymbol( + 'ol.format.WMSCapabilities', + ol.format.WMSCapabilities, + OPENLAYERS); + +goog.exportProperty( + ol.format.WMSCapabilities.prototype, + 'read', + ol.format.WMSCapabilities.prototype.read); + +goog.exportSymbol( + 'ol.format.WMSGetFeatureInfo', + ol.format.WMSGetFeatureInfo, + OPENLAYERS); + +goog.exportProperty( + ol.format.WMSGetFeatureInfo.prototype, + 'readFeatures', + ol.format.WMSGetFeatureInfo.prototype.readFeatures); + +goog.exportSymbol( + 'ol.format.WMTSCapabilities', + ol.format.WMTSCapabilities, + OPENLAYERS); + +goog.exportProperty( + ol.format.WMTSCapabilities.prototype, + 'read', + ol.format.WMTSCapabilities.prototype.read); + +goog.exportSymbol( + 'ol.format.filter.And', + ol.format.filter.And, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Bbox', + ol.format.filter.Bbox, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Comparison', + ol.format.filter.Comparison, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.ComparisonBinary', + ol.format.filter.ComparisonBinary, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.EqualTo', + ol.format.filter.EqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Filter', + ol.format.filter.Filter, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.GreaterThan', + ol.format.filter.GreaterThan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.GreaterThanOrEqualTo', + ol.format.filter.GreaterThanOrEqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.and', + ol.format.filter.and, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.or', + ol.format.filter.or, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.not', + ol.format.filter.not, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.bbox', + ol.format.filter.bbox, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.intersects', + ol.format.filter.intersects, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.within', + ol.format.filter.within, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.equalTo', + ol.format.filter.equalTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.notEqualTo', + ol.format.filter.notEqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.lessThan', + ol.format.filter.lessThan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.lessThanOrEqualTo', + ol.format.filter.lessThanOrEqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.greaterThan', + ol.format.filter.greaterThan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.greaterThanOrEqualTo', + ol.format.filter.greaterThanOrEqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.isNull', + ol.format.filter.isNull, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.between', + ol.format.filter.between, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.like', + ol.format.filter.like, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Intersects', + ol.format.filter.Intersects, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.IsBetween', + ol.format.filter.IsBetween, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.IsLike', + ol.format.filter.IsLike, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.IsNull', + ol.format.filter.IsNull, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.LessThan', + ol.format.filter.LessThan, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.LessThanOrEqualTo', + ol.format.filter.LessThanOrEqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Not', + ol.format.filter.Not, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.NotEqualTo', + ol.format.filter.NotEqualTo, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Or', + ol.format.filter.Or, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Spatial', + ol.format.filter.Spatial, + OPENLAYERS); + +goog.exportSymbol( + 'ol.format.filter.Within', + ol.format.filter.Within, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.boundingExtent', + ol.extent.boundingExtent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.buffer', + ol.extent.buffer, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.containsCoordinate', + ol.extent.containsCoordinate, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.containsExtent', + ol.extent.containsExtent, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.containsXY', + ol.extent.containsXY, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.createEmpty', + ol.extent.createEmpty, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.equals', + ol.extent.equals, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.extend', + ol.extent.extend, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getBottomLeft', + ol.extent.getBottomLeft, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getBottomRight', + ol.extent.getBottomRight, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getCenter', + ol.extent.getCenter, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getHeight', + ol.extent.getHeight, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getIntersection', + ol.extent.getIntersection, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getSize', + ol.extent.getSize, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getTopLeft', + ol.extent.getTopLeft, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getTopRight', + ol.extent.getTopRight, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.getWidth', + ol.extent.getWidth, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.intersects', + ol.extent.intersects, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.isEmpty', + ol.extent.isEmpty, + OPENLAYERS); + +goog.exportSymbol( + 'ol.extent.applyTransform', + ol.extent.applyTransform, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.altKeyOnly', + ol.events.condition.altKeyOnly, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.altShiftKeysOnly', + ol.events.condition.altShiftKeysOnly, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.always', + ol.events.condition.always, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.click', + ol.events.condition.click, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.never', + ol.events.condition.never, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.pointerMove', + ol.events.condition.pointerMove, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.singleClick', + ol.events.condition.singleClick, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.doubleClick', + ol.events.condition.doubleClick, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.noModifierKeys', + ol.events.condition.noModifierKeys, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.platformModifierKeyOnly', + ol.events.condition.platformModifierKeyOnly, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.shiftKeyOnly', + ol.events.condition.shiftKeyOnly, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.targetNotEditable', + ol.events.condition.targetNotEditable, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.mouseOnly', + ol.events.condition.mouseOnly, + OPENLAYERS); + +goog.exportSymbol( + 'ol.events.condition.primaryAction', + ol.events.condition.primaryAction, + OPENLAYERS); + +goog.exportProperty( + ol.events.Event.prototype, + 'type', + ol.events.Event.prototype.type); + +goog.exportProperty( + ol.events.Event.prototype, + 'target', + ol.events.Event.prototype.target); + +goog.exportProperty( + ol.events.Event.prototype, + 'preventDefault', + ol.events.Event.prototype.preventDefault); + +goog.exportProperty( + ol.events.Event.prototype, + 'stopPropagation', + ol.events.Event.prototype.stopPropagation); + +goog.exportSymbol( + 'ol.control.Attribution', + ol.control.Attribution, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.Attribution.render', + ol.control.Attribution.render, + OPENLAYERS); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'getCollapsible', + ol.control.Attribution.prototype.getCollapsible); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'setCollapsible', + ol.control.Attribution.prototype.setCollapsible); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'setCollapsed', + ol.control.Attribution.prototype.setCollapsed); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'getCollapsed', + ol.control.Attribution.prototype.getCollapsed); + +goog.exportSymbol( + 'ol.control.Control', + ol.control.Control, + OPENLAYERS); + +goog.exportProperty( + ol.control.Control.prototype, + 'getMap', + ol.control.Control.prototype.getMap); + +goog.exportProperty( + ol.control.Control.prototype, + 'setMap', + ol.control.Control.prototype.setMap); + +goog.exportProperty( + ol.control.Control.prototype, + 'setTarget', + ol.control.Control.prototype.setTarget); + +goog.exportSymbol( + 'ol.control.FullScreen', + ol.control.FullScreen, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.defaults', + ol.control.defaults, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.MousePosition', + ol.control.MousePosition, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.MousePosition.render', + ol.control.MousePosition.render, + OPENLAYERS); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'getCoordinateFormat', + ol.control.MousePosition.prototype.getCoordinateFormat); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'getProjection', + ol.control.MousePosition.prototype.getProjection); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'setCoordinateFormat', + ol.control.MousePosition.prototype.setCoordinateFormat); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'setProjection', + ol.control.MousePosition.prototype.setProjection); + +goog.exportSymbol( + 'ol.control.OverviewMap', + ol.control.OverviewMap, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.OverviewMap.render', + ol.control.OverviewMap.render, + OPENLAYERS); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getCollapsible', + ol.control.OverviewMap.prototype.getCollapsible); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'setCollapsible', + ol.control.OverviewMap.prototype.setCollapsible); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'setCollapsed', + ol.control.OverviewMap.prototype.setCollapsed); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getCollapsed', + ol.control.OverviewMap.prototype.getCollapsed); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getOverviewMap', + ol.control.OverviewMap.prototype.getOverviewMap); + +goog.exportSymbol( + 'ol.control.Rotate', + ol.control.Rotate, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.Rotate.render', + ol.control.Rotate.render, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.ScaleLine', + ol.control.ScaleLine, + OPENLAYERS); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'getUnits', + ol.control.ScaleLine.prototype.getUnits); + +goog.exportSymbol( + 'ol.control.ScaleLine.render', + ol.control.ScaleLine.render, + OPENLAYERS); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'setUnits', + ol.control.ScaleLine.prototype.setUnits); + +goog.exportSymbol( + 'ol.control.Zoom', + ol.control.Zoom, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.ZoomSlider', + ol.control.ZoomSlider, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.ZoomSlider.render', + ol.control.ZoomSlider.render, + OPENLAYERS); + +goog.exportSymbol( + 'ol.control.ZoomToExtent', + ol.control.ZoomToExtent, + OPENLAYERS); + +goog.exportProperty( + ol.Object.prototype, + 'changed', + ol.Object.prototype.changed); + +goog.exportProperty( + ol.Object.prototype, + 'dispatchEvent', + ol.Object.prototype.dispatchEvent); + +goog.exportProperty( + ol.Object.prototype, + 'getRevision', + ol.Object.prototype.getRevision); + +goog.exportProperty( + ol.Object.prototype, + 'on', + ol.Object.prototype.on); + +goog.exportProperty( + ol.Object.prototype, + 'once', + ol.Object.prototype.once); + +goog.exportProperty( + ol.Object.prototype, + 'un', + ol.Object.prototype.un); + +goog.exportProperty( + ol.Object.prototype, + 'unByKey', + ol.Object.prototype.unByKey); + +goog.exportProperty( + ol.Collection.prototype, + 'get', + ol.Collection.prototype.get); + +goog.exportProperty( + ol.Collection.prototype, + 'getKeys', + ol.Collection.prototype.getKeys); + +goog.exportProperty( + ol.Collection.prototype, + 'getProperties', + ol.Collection.prototype.getProperties); + +goog.exportProperty( + ol.Collection.prototype, + 'set', + ol.Collection.prototype.set); + +goog.exportProperty( + ol.Collection.prototype, + 'setProperties', + ol.Collection.prototype.setProperties); + +goog.exportProperty( + ol.Collection.prototype, + 'unset', + ol.Collection.prototype.unset); + +goog.exportProperty( + ol.Collection.prototype, + 'changed', + ol.Collection.prototype.changed); + +goog.exportProperty( + ol.Collection.prototype, + 'dispatchEvent', + ol.Collection.prototype.dispatchEvent); + +goog.exportProperty( + ol.Collection.prototype, + 'getRevision', + ol.Collection.prototype.getRevision); + +goog.exportProperty( + ol.Collection.prototype, + 'on', + ol.Collection.prototype.on); + +goog.exportProperty( + ol.Collection.prototype, + 'once', + ol.Collection.prototype.once); + +goog.exportProperty( + ol.Collection.prototype, + 'un', + ol.Collection.prototype.un); + +goog.exportProperty( + ol.Collection.prototype, + 'unByKey', + ol.Collection.prototype.unByKey); + +goog.exportProperty( + ol.Collection.Event.prototype, + 'type', + ol.Collection.Event.prototype.type); + +goog.exportProperty( + ol.Collection.Event.prototype, + 'target', + ol.Collection.Event.prototype.target); + +goog.exportProperty( + ol.Collection.Event.prototype, + 'preventDefault', + ol.Collection.Event.prototype.preventDefault); + +goog.exportProperty( + ol.Collection.Event.prototype, + 'stopPropagation', + ol.Collection.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'get', + ol.DeviceOrientation.prototype.get); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getKeys', + ol.DeviceOrientation.prototype.getKeys); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getProperties', + ol.DeviceOrientation.prototype.getProperties); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'set', + ol.DeviceOrientation.prototype.set); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'setProperties', + ol.DeviceOrientation.prototype.setProperties); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'unset', + ol.DeviceOrientation.prototype.unset); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'changed', + ol.DeviceOrientation.prototype.changed); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'dispatchEvent', + ol.DeviceOrientation.prototype.dispatchEvent); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'getRevision', + ol.DeviceOrientation.prototype.getRevision); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'on', + ol.DeviceOrientation.prototype.on); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'once', + ol.DeviceOrientation.prototype.once); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'un', + ol.DeviceOrientation.prototype.un); + +goog.exportProperty( + ol.DeviceOrientation.prototype, + 'unByKey', + ol.DeviceOrientation.prototype.unByKey); + +goog.exportProperty( + ol.Feature.prototype, + 'get', + ol.Feature.prototype.get); + +goog.exportProperty( + ol.Feature.prototype, + 'getKeys', + ol.Feature.prototype.getKeys); + +goog.exportProperty( + ol.Feature.prototype, + 'getProperties', + ol.Feature.prototype.getProperties); + +goog.exportProperty( + ol.Feature.prototype, + 'set', + ol.Feature.prototype.set); + +goog.exportProperty( + ol.Feature.prototype, + 'setProperties', + ol.Feature.prototype.setProperties); + +goog.exportProperty( + ol.Feature.prototype, + 'unset', + ol.Feature.prototype.unset); + +goog.exportProperty( + ol.Feature.prototype, + 'changed', + ol.Feature.prototype.changed); + +goog.exportProperty( + ol.Feature.prototype, + 'dispatchEvent', + ol.Feature.prototype.dispatchEvent); + +goog.exportProperty( + ol.Feature.prototype, + 'getRevision', + ol.Feature.prototype.getRevision); + +goog.exportProperty( + ol.Feature.prototype, + 'on', + ol.Feature.prototype.on); + +goog.exportProperty( + ol.Feature.prototype, + 'once', + ol.Feature.prototype.once); + +goog.exportProperty( + ol.Feature.prototype, + 'un', + ol.Feature.prototype.un); + +goog.exportProperty( + ol.Feature.prototype, + 'unByKey', + ol.Feature.prototype.unByKey); + +goog.exportProperty( + ol.Geolocation.prototype, + 'get', + ol.Geolocation.prototype.get); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getKeys', + ol.Geolocation.prototype.getKeys); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getProperties', + ol.Geolocation.prototype.getProperties); + +goog.exportProperty( + ol.Geolocation.prototype, + 'set', + ol.Geolocation.prototype.set); + +goog.exportProperty( + ol.Geolocation.prototype, + 'setProperties', + ol.Geolocation.prototype.setProperties); + +goog.exportProperty( + ol.Geolocation.prototype, + 'unset', + ol.Geolocation.prototype.unset); + +goog.exportProperty( + ol.Geolocation.prototype, + 'changed', + ol.Geolocation.prototype.changed); + +goog.exportProperty( + ol.Geolocation.prototype, + 'dispatchEvent', + ol.Geolocation.prototype.dispatchEvent); + +goog.exportProperty( + ol.Geolocation.prototype, + 'getRevision', + ol.Geolocation.prototype.getRevision); + +goog.exportProperty( + ol.Geolocation.prototype, + 'on', + ol.Geolocation.prototype.on); + +goog.exportProperty( + ol.Geolocation.prototype, + 'once', + ol.Geolocation.prototype.once); + +goog.exportProperty( + ol.Geolocation.prototype, + 'un', + ol.Geolocation.prototype.un); + +goog.exportProperty( + ol.Geolocation.prototype, + 'unByKey', + ol.Geolocation.prototype.unByKey); + +goog.exportProperty( + ol.ImageTile.prototype, + 'getTileCoord', + ol.ImageTile.prototype.getTileCoord); + +goog.exportProperty( + ol.Map.prototype, + 'get', + ol.Map.prototype.get); + +goog.exportProperty( + ol.Map.prototype, + 'getKeys', + ol.Map.prototype.getKeys); + +goog.exportProperty( + ol.Map.prototype, + 'getProperties', + ol.Map.prototype.getProperties); + +goog.exportProperty( + ol.Map.prototype, + 'set', + ol.Map.prototype.set); + +goog.exportProperty( + ol.Map.prototype, + 'setProperties', + ol.Map.prototype.setProperties); + +goog.exportProperty( + ol.Map.prototype, + 'unset', + ol.Map.prototype.unset); + +goog.exportProperty( + ol.Map.prototype, + 'changed', + ol.Map.prototype.changed); + +goog.exportProperty( + ol.Map.prototype, + 'dispatchEvent', + ol.Map.prototype.dispatchEvent); + +goog.exportProperty( + ol.Map.prototype, + 'getRevision', + ol.Map.prototype.getRevision); + +goog.exportProperty( + ol.Map.prototype, + 'on', + ol.Map.prototype.on); + +goog.exportProperty( + ol.Map.prototype, + 'once', + ol.Map.prototype.once); + +goog.exportProperty( + ol.Map.prototype, + 'un', + ol.Map.prototype.un); + +goog.exportProperty( + ol.Map.prototype, + 'unByKey', + ol.Map.prototype.unByKey); + +goog.exportProperty( + ol.MapEvent.prototype, + 'type', + ol.MapEvent.prototype.type); + +goog.exportProperty( + ol.MapEvent.prototype, + 'target', + ol.MapEvent.prototype.target); + +goog.exportProperty( + ol.MapEvent.prototype, + 'preventDefault', + ol.MapEvent.prototype.preventDefault); + +goog.exportProperty( + ol.MapEvent.prototype, + 'stopPropagation', + ol.MapEvent.prototype.stopPropagation); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'map', + ol.MapBrowserEvent.prototype.map); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'frameState', + ol.MapBrowserEvent.prototype.frameState); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'type', + ol.MapBrowserEvent.prototype.type); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'target', + ol.MapBrowserEvent.prototype.target); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'preventDefault', + ol.MapBrowserEvent.prototype.preventDefault); + +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'stopPropagation', + ol.MapBrowserEvent.prototype.stopPropagation); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'originalEvent', + ol.MapBrowserPointerEvent.prototype.originalEvent); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'pixel', + ol.MapBrowserPointerEvent.prototype.pixel); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'coordinate', + ol.MapBrowserPointerEvent.prototype.coordinate); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'dragging', + ol.MapBrowserPointerEvent.prototype.dragging); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'preventDefault', + ol.MapBrowserPointerEvent.prototype.preventDefault); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'stopPropagation', + ol.MapBrowserPointerEvent.prototype.stopPropagation); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'map', + ol.MapBrowserPointerEvent.prototype.map); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'frameState', + ol.MapBrowserPointerEvent.prototype.frameState); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'type', + ol.MapBrowserPointerEvent.prototype.type); + +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'target', + ol.MapBrowserPointerEvent.prototype.target); + +goog.exportProperty( + ol.Object.Event.prototype, + 'type', + ol.Object.Event.prototype.type); + +goog.exportProperty( + ol.Object.Event.prototype, + 'target', + ol.Object.Event.prototype.target); + +goog.exportProperty( + ol.Object.Event.prototype, + 'preventDefault', + ol.Object.Event.prototype.preventDefault); + +goog.exportProperty( + ol.Object.Event.prototype, + 'stopPropagation', + ol.Object.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.Overlay.prototype, + 'get', + ol.Overlay.prototype.get); + +goog.exportProperty( + ol.Overlay.prototype, + 'getKeys', + ol.Overlay.prototype.getKeys); + +goog.exportProperty( + ol.Overlay.prototype, + 'getProperties', + ol.Overlay.prototype.getProperties); + +goog.exportProperty( + ol.Overlay.prototype, + 'set', + ol.Overlay.prototype.set); + +goog.exportProperty( + ol.Overlay.prototype, + 'setProperties', + ol.Overlay.prototype.setProperties); + +goog.exportProperty( + ol.Overlay.prototype, + 'unset', + ol.Overlay.prototype.unset); + +goog.exportProperty( + ol.Overlay.prototype, + 'changed', + ol.Overlay.prototype.changed); + +goog.exportProperty( + ol.Overlay.prototype, + 'dispatchEvent', + ol.Overlay.prototype.dispatchEvent); + +goog.exportProperty( + ol.Overlay.prototype, + 'getRevision', + ol.Overlay.prototype.getRevision); + +goog.exportProperty( + ol.Overlay.prototype, + 'on', + ol.Overlay.prototype.on); + +goog.exportProperty( + ol.Overlay.prototype, + 'once', + ol.Overlay.prototype.once); + +goog.exportProperty( + ol.Overlay.prototype, + 'un', + ol.Overlay.prototype.un); + +goog.exportProperty( + ol.Overlay.prototype, + 'unByKey', + ol.Overlay.prototype.unByKey); + +goog.exportProperty( + ol.VectorTile.prototype, + 'getTileCoord', + ol.VectorTile.prototype.getTileCoord); + +goog.exportProperty( + ol.View.prototype, + 'get', + ol.View.prototype.get); + +goog.exportProperty( + ol.View.prototype, + 'getKeys', + ol.View.prototype.getKeys); + +goog.exportProperty( + ol.View.prototype, + 'getProperties', + ol.View.prototype.getProperties); + +goog.exportProperty( + ol.View.prototype, + 'set', + ol.View.prototype.set); + +goog.exportProperty( + ol.View.prototype, + 'setProperties', + ol.View.prototype.setProperties); + +goog.exportProperty( + ol.View.prototype, + 'unset', + ol.View.prototype.unset); + +goog.exportProperty( + ol.View.prototype, + 'changed', + ol.View.prototype.changed); + +goog.exportProperty( + ol.View.prototype, + 'dispatchEvent', + ol.View.prototype.dispatchEvent); + +goog.exportProperty( + ol.View.prototype, + 'getRevision', + ol.View.prototype.getRevision); + +goog.exportProperty( + ol.View.prototype, + 'on', + ol.View.prototype.on); + +goog.exportProperty( + ol.View.prototype, + 'once', + ol.View.prototype.once); + +goog.exportProperty( + ol.View.prototype, + 'un', + ol.View.prototype.un); + +goog.exportProperty( + ol.View.prototype, + 'unByKey', + ol.View.prototype.unByKey); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'forEachTileCoord', + ol.tilegrid.WMTS.prototype.forEachTileCoord); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getMaxZoom', + ol.tilegrid.WMTS.prototype.getMaxZoom); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getMinZoom', + ol.tilegrid.WMTS.prototype.getMinZoom); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getOrigin', + ol.tilegrid.WMTS.prototype.getOrigin); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getResolution', + ol.tilegrid.WMTS.prototype.getResolution); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getResolutions', + ol.tilegrid.WMTS.prototype.getResolutions); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getTileCoordExtent', + ol.tilegrid.WMTS.prototype.getTileCoordExtent); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getTileCoordForCoordAndResolution', + ol.tilegrid.WMTS.prototype.getTileCoordForCoordAndResolution); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getTileCoordForCoordAndZ', + ol.tilegrid.WMTS.prototype.getTileCoordForCoordAndZ); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getTileSize', + ol.tilegrid.WMTS.prototype.getTileSize); + +goog.exportProperty( + ol.tilegrid.WMTS.prototype, + 'getZForResolution', + ol.tilegrid.WMTS.prototype.getZForResolution); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getOpacity', + ol.style.RegularShape.prototype.getOpacity); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getRotateWithView', + ol.style.RegularShape.prototype.getRotateWithView); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getRotation', + ol.style.RegularShape.prototype.getRotation); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getScale', + ol.style.RegularShape.prototype.getScale); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'getSnapToPixel', + ol.style.RegularShape.prototype.getSnapToPixel); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'setOpacity', + ol.style.RegularShape.prototype.setOpacity); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'setRotation', + ol.style.RegularShape.prototype.setRotation); + +goog.exportProperty( + ol.style.RegularShape.prototype, + 'setScale', + ol.style.RegularShape.prototype.setScale); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getAngle', + ol.style.Circle.prototype.getAngle); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getFill', + ol.style.Circle.prototype.getFill); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getPoints', + ol.style.Circle.prototype.getPoints); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getRadius', + ol.style.Circle.prototype.getRadius); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getRadius2', + ol.style.Circle.prototype.getRadius2); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getStroke', + ol.style.Circle.prototype.getStroke); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getOpacity', + ol.style.Circle.prototype.getOpacity); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getRotateWithView', + ol.style.Circle.prototype.getRotateWithView); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getRotation', + ol.style.Circle.prototype.getRotation); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getScale', + ol.style.Circle.prototype.getScale); + +goog.exportProperty( + ol.style.Circle.prototype, + 'getSnapToPixel', + ol.style.Circle.prototype.getSnapToPixel); + +goog.exportProperty( + ol.style.Circle.prototype, + 'setOpacity', + ol.style.Circle.prototype.setOpacity); + +goog.exportProperty( + ol.style.Circle.prototype, + 'setRotation', + ol.style.Circle.prototype.setRotation); + +goog.exportProperty( + ol.style.Circle.prototype, + 'setScale', + ol.style.Circle.prototype.setScale); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getOpacity', + ol.style.Icon.prototype.getOpacity); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getRotateWithView', + ol.style.Icon.prototype.getRotateWithView); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getRotation', + ol.style.Icon.prototype.getRotation); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getScale', + ol.style.Icon.prototype.getScale); + +goog.exportProperty( + ol.style.Icon.prototype, + 'getSnapToPixel', + ol.style.Icon.prototype.getSnapToPixel); + +goog.exportProperty( + ol.style.Icon.prototype, + 'setOpacity', + ol.style.Icon.prototype.setOpacity); + +goog.exportProperty( + ol.style.Icon.prototype, + 'setRotation', + ol.style.Icon.prototype.setRotation); + +goog.exportProperty( + ol.style.Icon.prototype, + 'setScale', + ol.style.Icon.prototype.setScale); + +goog.exportProperty( + ol.source.Source.prototype, + 'get', + ol.source.Source.prototype.get); + +goog.exportProperty( + ol.source.Source.prototype, + 'getKeys', + ol.source.Source.prototype.getKeys); + +goog.exportProperty( + ol.source.Source.prototype, + 'getProperties', + ol.source.Source.prototype.getProperties); + +goog.exportProperty( + ol.source.Source.prototype, + 'set', + ol.source.Source.prototype.set); + +goog.exportProperty( + ol.source.Source.prototype, + 'setProperties', + ol.source.Source.prototype.setProperties); + +goog.exportProperty( + ol.source.Source.prototype, + 'unset', + ol.source.Source.prototype.unset); + +goog.exportProperty( + ol.source.Source.prototype, + 'changed', + ol.source.Source.prototype.changed); + +goog.exportProperty( + ol.source.Source.prototype, + 'dispatchEvent', + ol.source.Source.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Source.prototype, + 'getRevision', + ol.source.Source.prototype.getRevision); + +goog.exportProperty( + ol.source.Source.prototype, + 'on', + ol.source.Source.prototype.on); + +goog.exportProperty( + ol.source.Source.prototype, + 'once', + ol.source.Source.prototype.once); + +goog.exportProperty( + ol.source.Source.prototype, + 'un', + ol.source.Source.prototype.un); + +goog.exportProperty( + ol.source.Source.prototype, + 'unByKey', + ol.source.Source.prototype.unByKey); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getAttributions', + ol.source.Tile.prototype.getAttributions); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getLogo', + ol.source.Tile.prototype.getLogo); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getProjection', + ol.source.Tile.prototype.getProjection); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getState', + ol.source.Tile.prototype.getState); + +goog.exportProperty( + ol.source.Tile.prototype, + 'refresh', + ol.source.Tile.prototype.refresh); + +goog.exportProperty( + ol.source.Tile.prototype, + 'setAttributions', + ol.source.Tile.prototype.setAttributions); + +goog.exportProperty( + ol.source.Tile.prototype, + 'get', + ol.source.Tile.prototype.get); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getKeys', + ol.source.Tile.prototype.getKeys); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getProperties', + ol.source.Tile.prototype.getProperties); + +goog.exportProperty( + ol.source.Tile.prototype, + 'set', + ol.source.Tile.prototype.set); + +goog.exportProperty( + ol.source.Tile.prototype, + 'setProperties', + ol.source.Tile.prototype.setProperties); + +goog.exportProperty( + ol.source.Tile.prototype, + 'unset', + ol.source.Tile.prototype.unset); + +goog.exportProperty( + ol.source.Tile.prototype, + 'changed', + ol.source.Tile.prototype.changed); + +goog.exportProperty( + ol.source.Tile.prototype, + 'dispatchEvent', + ol.source.Tile.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Tile.prototype, + 'getRevision', + ol.source.Tile.prototype.getRevision); + +goog.exportProperty( + ol.source.Tile.prototype, + 'on', + ol.source.Tile.prototype.on); + +goog.exportProperty( + ol.source.Tile.prototype, + 'once', + ol.source.Tile.prototype.once); + +goog.exportProperty( + ol.source.Tile.prototype, + 'un', + ol.source.Tile.prototype.un); + +goog.exportProperty( + ol.source.Tile.prototype, + 'unByKey', + ol.source.Tile.prototype.unByKey); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getTileGrid', + ol.source.UrlTile.prototype.getTileGrid); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'refresh', + ol.source.UrlTile.prototype.refresh); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getAttributions', + ol.source.UrlTile.prototype.getAttributions); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getLogo', + ol.source.UrlTile.prototype.getLogo); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getProjection', + ol.source.UrlTile.prototype.getProjection); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getState', + ol.source.UrlTile.prototype.getState); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'setAttributions', + ol.source.UrlTile.prototype.setAttributions); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'get', + ol.source.UrlTile.prototype.get); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getKeys', + ol.source.UrlTile.prototype.getKeys); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getProperties', + ol.source.UrlTile.prototype.getProperties); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'set', + ol.source.UrlTile.prototype.set); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'setProperties', + ol.source.UrlTile.prototype.setProperties); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'unset', + ol.source.UrlTile.prototype.unset); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'changed', + ol.source.UrlTile.prototype.changed); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'dispatchEvent', + ol.source.UrlTile.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'getRevision', + ol.source.UrlTile.prototype.getRevision); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'on', + ol.source.UrlTile.prototype.on); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'once', + ol.source.UrlTile.prototype.once); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'un', + ol.source.UrlTile.prototype.un); + +goog.exportProperty( + ol.source.UrlTile.prototype, + 'unByKey', + ol.source.UrlTile.prototype.unByKey); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getTileLoadFunction', + ol.source.TileImage.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getTileUrlFunction', + ol.source.TileImage.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getUrls', + ol.source.TileImage.prototype.getUrls); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setTileLoadFunction', + ol.source.TileImage.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setTileUrlFunction', + ol.source.TileImage.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setUrl', + ol.source.TileImage.prototype.setUrl); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setUrls', + ol.source.TileImage.prototype.setUrls); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getTileGrid', + ol.source.TileImage.prototype.getTileGrid); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'refresh', + ol.source.TileImage.prototype.refresh); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getAttributions', + ol.source.TileImage.prototype.getAttributions); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getLogo', + ol.source.TileImage.prototype.getLogo); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getProjection', + ol.source.TileImage.prototype.getProjection); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getState', + ol.source.TileImage.prototype.getState); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setAttributions', + ol.source.TileImage.prototype.setAttributions); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'get', + ol.source.TileImage.prototype.get); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getKeys', + ol.source.TileImage.prototype.getKeys); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getProperties', + ol.source.TileImage.prototype.getProperties); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'set', + ol.source.TileImage.prototype.set); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'setProperties', + ol.source.TileImage.prototype.setProperties); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'unset', + ol.source.TileImage.prototype.unset); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'changed', + ol.source.TileImage.prototype.changed); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'dispatchEvent', + ol.source.TileImage.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'getRevision', + ol.source.TileImage.prototype.getRevision); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'on', + ol.source.TileImage.prototype.on); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'once', + ol.source.TileImage.prototype.once); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'un', + ol.source.TileImage.prototype.un); + +goog.exportProperty( + ol.source.TileImage.prototype, + 'unByKey', + ol.source.TileImage.prototype.unByKey); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setRenderReprojectionEdges', + ol.source.BingMaps.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setTileGridForProjection', + ol.source.BingMaps.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getTileLoadFunction', + ol.source.BingMaps.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getTileUrlFunction', + ol.source.BingMaps.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getUrls', + ol.source.BingMaps.prototype.getUrls); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setTileLoadFunction', + ol.source.BingMaps.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setTileUrlFunction', + ol.source.BingMaps.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setUrl', + ol.source.BingMaps.prototype.setUrl); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setUrls', + ol.source.BingMaps.prototype.setUrls); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getTileGrid', + ol.source.BingMaps.prototype.getTileGrid); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'refresh', + ol.source.BingMaps.prototype.refresh); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getAttributions', + ol.source.BingMaps.prototype.getAttributions); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getLogo', + ol.source.BingMaps.prototype.getLogo); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getProjection', + ol.source.BingMaps.prototype.getProjection); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getState', + ol.source.BingMaps.prototype.getState); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setAttributions', + ol.source.BingMaps.prototype.setAttributions); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'get', + ol.source.BingMaps.prototype.get); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getKeys', + ol.source.BingMaps.prototype.getKeys); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getProperties', + ol.source.BingMaps.prototype.getProperties); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'set', + ol.source.BingMaps.prototype.set); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'setProperties', + ol.source.BingMaps.prototype.setProperties); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'unset', + ol.source.BingMaps.prototype.unset); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'changed', + ol.source.BingMaps.prototype.changed); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'dispatchEvent', + ol.source.BingMaps.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'getRevision', + ol.source.BingMaps.prototype.getRevision); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'on', + ol.source.BingMaps.prototype.on); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'once', + ol.source.BingMaps.prototype.once); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'un', + ol.source.BingMaps.prototype.un); + +goog.exportProperty( + ol.source.BingMaps.prototype, + 'unByKey', + ol.source.BingMaps.prototype.unByKey); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setRenderReprojectionEdges', + ol.source.XYZ.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setTileGridForProjection', + ol.source.XYZ.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getTileLoadFunction', + ol.source.XYZ.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getTileUrlFunction', + ol.source.XYZ.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getUrls', + ol.source.XYZ.prototype.getUrls); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setTileLoadFunction', + ol.source.XYZ.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setTileUrlFunction', + ol.source.XYZ.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setUrl', + ol.source.XYZ.prototype.setUrl); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setUrls', + ol.source.XYZ.prototype.setUrls); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getTileGrid', + ol.source.XYZ.prototype.getTileGrid); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'refresh', + ol.source.XYZ.prototype.refresh); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getAttributions', + ol.source.XYZ.prototype.getAttributions); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getLogo', + ol.source.XYZ.prototype.getLogo); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getProjection', + ol.source.XYZ.prototype.getProjection); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getState', + ol.source.XYZ.prototype.getState); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setAttributions', + ol.source.XYZ.prototype.setAttributions); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'get', + ol.source.XYZ.prototype.get); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getKeys', + ol.source.XYZ.prototype.getKeys); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getProperties', + ol.source.XYZ.prototype.getProperties); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'set', + ol.source.XYZ.prototype.set); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setProperties', + ol.source.XYZ.prototype.setProperties); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'unset', + ol.source.XYZ.prototype.unset); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'changed', + ol.source.XYZ.prototype.changed); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'dispatchEvent', + ol.source.XYZ.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getRevision', + ol.source.XYZ.prototype.getRevision); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'on', + ol.source.XYZ.prototype.on); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'once', + ol.source.XYZ.prototype.once); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'un', + ol.source.XYZ.prototype.un); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'unByKey', + ol.source.XYZ.prototype.unByKey); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setRenderReprojectionEdges', + ol.source.CartoDB.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setTileGridForProjection', + ol.source.CartoDB.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getTileLoadFunction', + ol.source.CartoDB.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getTileUrlFunction', + ol.source.CartoDB.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getUrls', + ol.source.CartoDB.prototype.getUrls); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setTileLoadFunction', + ol.source.CartoDB.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setTileUrlFunction', + ol.source.CartoDB.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setUrl', + ol.source.CartoDB.prototype.setUrl); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setUrls', + ol.source.CartoDB.prototype.setUrls); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getTileGrid', + ol.source.CartoDB.prototype.getTileGrid); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'refresh', + ol.source.CartoDB.prototype.refresh); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getAttributions', + ol.source.CartoDB.prototype.getAttributions); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getLogo', + ol.source.CartoDB.prototype.getLogo); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getProjection', + ol.source.CartoDB.prototype.getProjection); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getState', + ol.source.CartoDB.prototype.getState); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setAttributions', + ol.source.CartoDB.prototype.setAttributions); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'get', + ol.source.CartoDB.prototype.get); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getKeys', + ol.source.CartoDB.prototype.getKeys); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getProperties', + ol.source.CartoDB.prototype.getProperties); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'set', + ol.source.CartoDB.prototype.set); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'setProperties', + ol.source.CartoDB.prototype.setProperties); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'unset', + ol.source.CartoDB.prototype.unset); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'changed', + ol.source.CartoDB.prototype.changed); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'dispatchEvent', + ol.source.CartoDB.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'getRevision', + ol.source.CartoDB.prototype.getRevision); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'on', + ol.source.CartoDB.prototype.on); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'once', + ol.source.CartoDB.prototype.once); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'un', + ol.source.CartoDB.prototype.un); + +goog.exportProperty( + ol.source.CartoDB.prototype, + 'unByKey', + ol.source.CartoDB.prototype.unByKey); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getAttributions', + ol.source.Vector.prototype.getAttributions); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getLogo', + ol.source.Vector.prototype.getLogo); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getProjection', + ol.source.Vector.prototype.getProjection); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getState', + ol.source.Vector.prototype.getState); + +goog.exportProperty( + ol.source.Vector.prototype, + 'refresh', + ol.source.Vector.prototype.refresh); + +goog.exportProperty( + ol.source.Vector.prototype, + 'setAttributions', + ol.source.Vector.prototype.setAttributions); + +goog.exportProperty( + ol.source.Vector.prototype, + 'get', + ol.source.Vector.prototype.get); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getKeys', + ol.source.Vector.prototype.getKeys); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getProperties', + ol.source.Vector.prototype.getProperties); + +goog.exportProperty( + ol.source.Vector.prototype, + 'set', + ol.source.Vector.prototype.set); + +goog.exportProperty( + ol.source.Vector.prototype, + 'setProperties', + ol.source.Vector.prototype.setProperties); + +goog.exportProperty( + ol.source.Vector.prototype, + 'unset', + ol.source.Vector.prototype.unset); + +goog.exportProperty( + ol.source.Vector.prototype, + 'changed', + ol.source.Vector.prototype.changed); + +goog.exportProperty( + ol.source.Vector.prototype, + 'dispatchEvent', + ol.source.Vector.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Vector.prototype, + 'getRevision', + ol.source.Vector.prototype.getRevision); + +goog.exportProperty( + ol.source.Vector.prototype, + 'on', + ol.source.Vector.prototype.on); + +goog.exportProperty( + ol.source.Vector.prototype, + 'once', + ol.source.Vector.prototype.once); + +goog.exportProperty( + ol.source.Vector.prototype, + 'un', + ol.source.Vector.prototype.un); + +goog.exportProperty( + ol.source.Vector.prototype, + 'unByKey', + ol.source.Vector.prototype.unByKey); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'addFeature', + ol.source.Cluster.prototype.addFeature); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'addFeatures', + ol.source.Cluster.prototype.addFeatures); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'clear', + ol.source.Cluster.prototype.clear); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'forEachFeature', + ol.source.Cluster.prototype.forEachFeature); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'forEachFeatureInExtent', + ol.source.Cluster.prototype.forEachFeatureInExtent); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'forEachFeatureIntersectingExtent', + ol.source.Cluster.prototype.forEachFeatureIntersectingExtent); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFeaturesCollection', + ol.source.Cluster.prototype.getFeaturesCollection); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFeatures', + ol.source.Cluster.prototype.getFeatures); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFeaturesAtCoordinate', + ol.source.Cluster.prototype.getFeaturesAtCoordinate); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFeaturesInExtent', + ol.source.Cluster.prototype.getFeaturesInExtent); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getClosestFeatureToCoordinate', + ol.source.Cluster.prototype.getClosestFeatureToCoordinate); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getExtent', + ol.source.Cluster.prototype.getExtent); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFeatureById', + ol.source.Cluster.prototype.getFeatureById); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFormat', + ol.source.Cluster.prototype.getFormat); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getUrl', + ol.source.Cluster.prototype.getUrl); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'removeFeature', + ol.source.Cluster.prototype.removeFeature); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getAttributions', + ol.source.Cluster.prototype.getAttributions); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getLogo', + ol.source.Cluster.prototype.getLogo); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getProjection', + ol.source.Cluster.prototype.getProjection); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getState', + ol.source.Cluster.prototype.getState); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'refresh', + ol.source.Cluster.prototype.refresh); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'setAttributions', + ol.source.Cluster.prototype.setAttributions); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'get', + ol.source.Cluster.prototype.get); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getKeys', + ol.source.Cluster.prototype.getKeys); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getProperties', + ol.source.Cluster.prototype.getProperties); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'set', + ol.source.Cluster.prototype.set); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'setProperties', + ol.source.Cluster.prototype.setProperties); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'unset', + ol.source.Cluster.prototype.unset); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'changed', + ol.source.Cluster.prototype.changed); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'dispatchEvent', + ol.source.Cluster.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getRevision', + ol.source.Cluster.prototype.getRevision); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'on', + ol.source.Cluster.prototype.on); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'once', + ol.source.Cluster.prototype.once); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'un', + ol.source.Cluster.prototype.un); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'unByKey', + ol.source.Cluster.prototype.unByKey); + +goog.exportProperty( + ol.source.Image.prototype, + 'getAttributions', + ol.source.Image.prototype.getAttributions); + +goog.exportProperty( + ol.source.Image.prototype, + 'getLogo', + ol.source.Image.prototype.getLogo); + +goog.exportProperty( + ol.source.Image.prototype, + 'getProjection', + ol.source.Image.prototype.getProjection); + +goog.exportProperty( + ol.source.Image.prototype, + 'getState', + ol.source.Image.prototype.getState); + +goog.exportProperty( + ol.source.Image.prototype, + 'refresh', + ol.source.Image.prototype.refresh); + +goog.exportProperty( + ol.source.Image.prototype, + 'setAttributions', + ol.source.Image.prototype.setAttributions); + +goog.exportProperty( + ol.source.Image.prototype, + 'get', + ol.source.Image.prototype.get); + +goog.exportProperty( + ol.source.Image.prototype, + 'getKeys', + ol.source.Image.prototype.getKeys); + +goog.exportProperty( + ol.source.Image.prototype, + 'getProperties', + ol.source.Image.prototype.getProperties); + +goog.exportProperty( + ol.source.Image.prototype, + 'set', + ol.source.Image.prototype.set); + +goog.exportProperty( + ol.source.Image.prototype, + 'setProperties', + ol.source.Image.prototype.setProperties); + +goog.exportProperty( + ol.source.Image.prototype, + 'unset', + ol.source.Image.prototype.unset); + +goog.exportProperty( + ol.source.Image.prototype, + 'changed', + ol.source.Image.prototype.changed); + +goog.exportProperty( + ol.source.Image.prototype, + 'dispatchEvent', + ol.source.Image.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Image.prototype, + 'getRevision', + ol.source.Image.prototype.getRevision); + +goog.exportProperty( + ol.source.Image.prototype, + 'on', + ol.source.Image.prototype.on); + +goog.exportProperty( + ol.source.Image.prototype, + 'once', + ol.source.Image.prototype.once); + +goog.exportProperty( + ol.source.Image.prototype, + 'un', + ol.source.Image.prototype.un); + +goog.exportProperty( + ol.source.Image.prototype, + 'unByKey', + ol.source.Image.prototype.unByKey); + +goog.exportProperty( + ol.source.Image.Event.prototype, + 'type', + ol.source.Image.Event.prototype.type); + +goog.exportProperty( + ol.source.Image.Event.prototype, + 'target', + ol.source.Image.Event.prototype.target); + +goog.exportProperty( + ol.source.Image.Event.prototype, + 'preventDefault', + ol.source.Image.Event.prototype.preventDefault); + +goog.exportProperty( + ol.source.Image.Event.prototype, + 'stopPropagation', + ol.source.Image.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getAttributions', + ol.source.ImageArcGISRest.prototype.getAttributions); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getLogo', + ol.source.ImageArcGISRest.prototype.getLogo); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getProjection', + ol.source.ImageArcGISRest.prototype.getProjection); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getState', + ol.source.ImageArcGISRest.prototype.getState); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'refresh', + ol.source.ImageArcGISRest.prototype.refresh); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'setAttributions', + ol.source.ImageArcGISRest.prototype.setAttributions); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'get', + ol.source.ImageArcGISRest.prototype.get); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getKeys', + ol.source.ImageArcGISRest.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getProperties', + ol.source.ImageArcGISRest.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'set', + ol.source.ImageArcGISRest.prototype.set); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'setProperties', + ol.source.ImageArcGISRest.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'unset', + ol.source.ImageArcGISRest.prototype.unset); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'changed', + ol.source.ImageArcGISRest.prototype.changed); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'dispatchEvent', + ol.source.ImageArcGISRest.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'getRevision', + ol.source.ImageArcGISRest.prototype.getRevision); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'on', + ol.source.ImageArcGISRest.prototype.on); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'once', + ol.source.ImageArcGISRest.prototype.once); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'un', + ol.source.ImageArcGISRest.prototype.un); + +goog.exportProperty( + ol.source.ImageArcGISRest.prototype, + 'unByKey', + ol.source.ImageArcGISRest.prototype.unByKey); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getAttributions', + ol.source.ImageCanvas.prototype.getAttributions); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getLogo', + ol.source.ImageCanvas.prototype.getLogo); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getProjection', + ol.source.ImageCanvas.prototype.getProjection); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getState', + ol.source.ImageCanvas.prototype.getState); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'refresh', + ol.source.ImageCanvas.prototype.refresh); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'setAttributions', + ol.source.ImageCanvas.prototype.setAttributions); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'get', + ol.source.ImageCanvas.prototype.get); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getKeys', + ol.source.ImageCanvas.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getProperties', + ol.source.ImageCanvas.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'set', + ol.source.ImageCanvas.prototype.set); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'setProperties', + ol.source.ImageCanvas.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'unset', + ol.source.ImageCanvas.prototype.unset); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'changed', + ol.source.ImageCanvas.prototype.changed); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'dispatchEvent', + ol.source.ImageCanvas.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getRevision', + ol.source.ImageCanvas.prototype.getRevision); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'on', + ol.source.ImageCanvas.prototype.on); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'once', + ol.source.ImageCanvas.prototype.once); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'un', + ol.source.ImageCanvas.prototype.un); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'unByKey', + ol.source.ImageCanvas.prototype.unByKey); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getAttributions', + ol.source.ImageMapGuide.prototype.getAttributions); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getLogo', + ol.source.ImageMapGuide.prototype.getLogo); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getProjection', + ol.source.ImageMapGuide.prototype.getProjection); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getState', + ol.source.ImageMapGuide.prototype.getState); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'refresh', + ol.source.ImageMapGuide.prototype.refresh); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'setAttributions', + ol.source.ImageMapGuide.prototype.setAttributions); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'get', + ol.source.ImageMapGuide.prototype.get); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getKeys', + ol.source.ImageMapGuide.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getProperties', + ol.source.ImageMapGuide.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'set', + ol.source.ImageMapGuide.prototype.set); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'setProperties', + ol.source.ImageMapGuide.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'unset', + ol.source.ImageMapGuide.prototype.unset); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'changed', + ol.source.ImageMapGuide.prototype.changed); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'dispatchEvent', + ol.source.ImageMapGuide.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getRevision', + ol.source.ImageMapGuide.prototype.getRevision); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'on', + ol.source.ImageMapGuide.prototype.on); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'once', + ol.source.ImageMapGuide.prototype.once); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'un', + ol.source.ImageMapGuide.prototype.un); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'unByKey', + ol.source.ImageMapGuide.prototype.unByKey); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getAttributions', + ol.source.ImageStatic.prototype.getAttributions); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getLogo', + ol.source.ImageStatic.prototype.getLogo); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getProjection', + ol.source.ImageStatic.prototype.getProjection); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getState', + ol.source.ImageStatic.prototype.getState); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'refresh', + ol.source.ImageStatic.prototype.refresh); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'setAttributions', + ol.source.ImageStatic.prototype.setAttributions); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'get', + ol.source.ImageStatic.prototype.get); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getKeys', + ol.source.ImageStatic.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getProperties', + ol.source.ImageStatic.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'set', + ol.source.ImageStatic.prototype.set); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'setProperties', + ol.source.ImageStatic.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'unset', + ol.source.ImageStatic.prototype.unset); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'changed', + ol.source.ImageStatic.prototype.changed); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'dispatchEvent', + ol.source.ImageStatic.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getRevision', + ol.source.ImageStatic.prototype.getRevision); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'on', + ol.source.ImageStatic.prototype.on); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'once', + ol.source.ImageStatic.prototype.once); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'un', + ol.source.ImageStatic.prototype.un); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'unByKey', + ol.source.ImageStatic.prototype.unByKey); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getAttributions', + ol.source.ImageVector.prototype.getAttributions); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getLogo', + ol.source.ImageVector.prototype.getLogo); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getProjection', + ol.source.ImageVector.prototype.getProjection); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getState', + ol.source.ImageVector.prototype.getState); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'refresh', + ol.source.ImageVector.prototype.refresh); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'setAttributions', + ol.source.ImageVector.prototype.setAttributions); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'get', + ol.source.ImageVector.prototype.get); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getKeys', + ol.source.ImageVector.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getProperties', + ol.source.ImageVector.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'set', + ol.source.ImageVector.prototype.set); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'setProperties', + ol.source.ImageVector.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'unset', + ol.source.ImageVector.prototype.unset); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'changed', + ol.source.ImageVector.prototype.changed); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'dispatchEvent', + ol.source.ImageVector.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getRevision', + ol.source.ImageVector.prototype.getRevision); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'on', + ol.source.ImageVector.prototype.on); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'once', + ol.source.ImageVector.prototype.once); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'un', + ol.source.ImageVector.prototype.un); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'unByKey', + ol.source.ImageVector.prototype.unByKey); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getAttributions', + ol.source.ImageWMS.prototype.getAttributions); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getLogo', + ol.source.ImageWMS.prototype.getLogo); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getProjection', + ol.source.ImageWMS.prototype.getProjection); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getState', + ol.source.ImageWMS.prototype.getState); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'refresh', + ol.source.ImageWMS.prototype.refresh); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'setAttributions', + ol.source.ImageWMS.prototype.setAttributions); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'get', + ol.source.ImageWMS.prototype.get); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getKeys', + ol.source.ImageWMS.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getProperties', + ol.source.ImageWMS.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'set', + ol.source.ImageWMS.prototype.set); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'setProperties', + ol.source.ImageWMS.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'unset', + ol.source.ImageWMS.prototype.unset); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'changed', + ol.source.ImageWMS.prototype.changed); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'dispatchEvent', + ol.source.ImageWMS.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getRevision', + ol.source.ImageWMS.prototype.getRevision); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'on', + ol.source.ImageWMS.prototype.on); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'once', + ol.source.ImageWMS.prototype.once); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'un', + ol.source.ImageWMS.prototype.un); + +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'unByKey', + ol.source.ImageWMS.prototype.unByKey); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setRenderReprojectionEdges', + ol.source.OSM.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setTileGridForProjection', + ol.source.OSM.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getTileLoadFunction', + ol.source.OSM.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getTileUrlFunction', + ol.source.OSM.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getUrls', + ol.source.OSM.prototype.getUrls); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setTileLoadFunction', + ol.source.OSM.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setTileUrlFunction', + ol.source.OSM.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setUrl', + ol.source.OSM.prototype.setUrl); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setUrls', + ol.source.OSM.prototype.setUrls); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getTileGrid', + ol.source.OSM.prototype.getTileGrid); + +goog.exportProperty( + ol.source.OSM.prototype, + 'refresh', + ol.source.OSM.prototype.refresh); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getAttributions', + ol.source.OSM.prototype.getAttributions); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getLogo', + ol.source.OSM.prototype.getLogo); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getProjection', + ol.source.OSM.prototype.getProjection); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getState', + ol.source.OSM.prototype.getState); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setAttributions', + ol.source.OSM.prototype.setAttributions); + +goog.exportProperty( + ol.source.OSM.prototype, + 'get', + ol.source.OSM.prototype.get); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getKeys', + ol.source.OSM.prototype.getKeys); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getProperties', + ol.source.OSM.prototype.getProperties); + +goog.exportProperty( + ol.source.OSM.prototype, + 'set', + ol.source.OSM.prototype.set); + +goog.exportProperty( + ol.source.OSM.prototype, + 'setProperties', + ol.source.OSM.prototype.setProperties); + +goog.exportProperty( + ol.source.OSM.prototype, + 'unset', + ol.source.OSM.prototype.unset); + +goog.exportProperty( + ol.source.OSM.prototype, + 'changed', + ol.source.OSM.prototype.changed); + +goog.exportProperty( + ol.source.OSM.prototype, + 'dispatchEvent', + ol.source.OSM.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.OSM.prototype, + 'getRevision', + ol.source.OSM.prototype.getRevision); + +goog.exportProperty( + ol.source.OSM.prototype, + 'on', + ol.source.OSM.prototype.on); + +goog.exportProperty( + ol.source.OSM.prototype, + 'once', + ol.source.OSM.prototype.once); + +goog.exportProperty( + ol.source.OSM.prototype, + 'un', + ol.source.OSM.prototype.un); + +goog.exportProperty( + ol.source.OSM.prototype, + 'unByKey', + ol.source.OSM.prototype.unByKey); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getAttributions', + ol.source.Raster.prototype.getAttributions); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getLogo', + ol.source.Raster.prototype.getLogo); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getProjection', + ol.source.Raster.prototype.getProjection); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getState', + ol.source.Raster.prototype.getState); + +goog.exportProperty( + ol.source.Raster.prototype, + 'refresh', + ol.source.Raster.prototype.refresh); + +goog.exportProperty( + ol.source.Raster.prototype, + 'setAttributions', + ol.source.Raster.prototype.setAttributions); + +goog.exportProperty( + ol.source.Raster.prototype, + 'get', + ol.source.Raster.prototype.get); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getKeys', + ol.source.Raster.prototype.getKeys); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getProperties', + ol.source.Raster.prototype.getProperties); + +goog.exportProperty( + ol.source.Raster.prototype, + 'set', + ol.source.Raster.prototype.set); + +goog.exportProperty( + ol.source.Raster.prototype, + 'setProperties', + ol.source.Raster.prototype.setProperties); + +goog.exportProperty( + ol.source.Raster.prototype, + 'unset', + ol.source.Raster.prototype.unset); + +goog.exportProperty( + ol.source.Raster.prototype, + 'changed', + ol.source.Raster.prototype.changed); + +goog.exportProperty( + ol.source.Raster.prototype, + 'dispatchEvent', + ol.source.Raster.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Raster.prototype, + 'getRevision', + ol.source.Raster.prototype.getRevision); + +goog.exportProperty( + ol.source.Raster.prototype, + 'on', + ol.source.Raster.prototype.on); + +goog.exportProperty( + ol.source.Raster.prototype, + 'once', + ol.source.Raster.prototype.once); + +goog.exportProperty( + ol.source.Raster.prototype, + 'un', + ol.source.Raster.prototype.un); + +goog.exportProperty( + ol.source.Raster.prototype, + 'unByKey', + ol.source.Raster.prototype.unByKey); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'type', + ol.source.Raster.Event.prototype.type); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'target', + ol.source.Raster.Event.prototype.target); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'preventDefault', + ol.source.Raster.Event.prototype.preventDefault); + +goog.exportProperty( + ol.source.Raster.Event.prototype, + 'stopPropagation', + ol.source.Raster.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setRenderReprojectionEdges', + ol.source.Stamen.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setTileGridForProjection', + ol.source.Stamen.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getTileLoadFunction', + ol.source.Stamen.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getTileUrlFunction', + ol.source.Stamen.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getUrls', + ol.source.Stamen.prototype.getUrls); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setTileLoadFunction', + ol.source.Stamen.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setTileUrlFunction', + ol.source.Stamen.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setUrl', + ol.source.Stamen.prototype.setUrl); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setUrls', + ol.source.Stamen.prototype.setUrls); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getTileGrid', + ol.source.Stamen.prototype.getTileGrid); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'refresh', + ol.source.Stamen.prototype.refresh); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getAttributions', + ol.source.Stamen.prototype.getAttributions); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getLogo', + ol.source.Stamen.prototype.getLogo); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getProjection', + ol.source.Stamen.prototype.getProjection); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getState', + ol.source.Stamen.prototype.getState); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setAttributions', + ol.source.Stamen.prototype.setAttributions); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'get', + ol.source.Stamen.prototype.get); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getKeys', + ol.source.Stamen.prototype.getKeys); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getProperties', + ol.source.Stamen.prototype.getProperties); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'set', + ol.source.Stamen.prototype.set); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'setProperties', + ol.source.Stamen.prototype.setProperties); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'unset', + ol.source.Stamen.prototype.unset); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'changed', + ol.source.Stamen.prototype.changed); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'dispatchEvent', + ol.source.Stamen.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'getRevision', + ol.source.Stamen.prototype.getRevision); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'on', + ol.source.Stamen.prototype.on); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'once', + ol.source.Stamen.prototype.once); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'un', + ol.source.Stamen.prototype.un); + +goog.exportProperty( + ol.source.Stamen.prototype, + 'unByKey', + ol.source.Stamen.prototype.unByKey); + +goog.exportProperty( + ol.source.Tile.Event.prototype, + 'type', + ol.source.Tile.Event.prototype.type); + +goog.exportProperty( + ol.source.Tile.Event.prototype, + 'target', + ol.source.Tile.Event.prototype.target); + +goog.exportProperty( + ol.source.Tile.Event.prototype, + 'preventDefault', + ol.source.Tile.Event.prototype.preventDefault); + +goog.exportProperty( + ol.source.Tile.Event.prototype, + 'stopPropagation', + ol.source.Tile.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setRenderReprojectionEdges', + ol.source.TileArcGISRest.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setTileGridForProjection', + ol.source.TileArcGISRest.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getTileLoadFunction', + ol.source.TileArcGISRest.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getTileUrlFunction', + ol.source.TileArcGISRest.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getUrls', + ol.source.TileArcGISRest.prototype.getUrls); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setTileLoadFunction', + ol.source.TileArcGISRest.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setTileUrlFunction', + ol.source.TileArcGISRest.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setUrl', + ol.source.TileArcGISRest.prototype.setUrl); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setUrls', + ol.source.TileArcGISRest.prototype.setUrls); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getTileGrid', + ol.source.TileArcGISRest.prototype.getTileGrid); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'refresh', + ol.source.TileArcGISRest.prototype.refresh); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getAttributions', + ol.source.TileArcGISRest.prototype.getAttributions); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getLogo', + ol.source.TileArcGISRest.prototype.getLogo); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getProjection', + ol.source.TileArcGISRest.prototype.getProjection); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getState', + ol.source.TileArcGISRest.prototype.getState); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setAttributions', + ol.source.TileArcGISRest.prototype.setAttributions); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'get', + ol.source.TileArcGISRest.prototype.get); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getKeys', + ol.source.TileArcGISRest.prototype.getKeys); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getProperties', + ol.source.TileArcGISRest.prototype.getProperties); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'set', + ol.source.TileArcGISRest.prototype.set); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setProperties', + ol.source.TileArcGISRest.prototype.setProperties); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'unset', + ol.source.TileArcGISRest.prototype.unset); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'changed', + ol.source.TileArcGISRest.prototype.changed); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'dispatchEvent', + ol.source.TileArcGISRest.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getRevision', + ol.source.TileArcGISRest.prototype.getRevision); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'on', + ol.source.TileArcGISRest.prototype.on); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'once', + ol.source.TileArcGISRest.prototype.once); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'un', + ol.source.TileArcGISRest.prototype.un); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'unByKey', + ol.source.TileArcGISRest.prototype.unByKey); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getTileGrid', + ol.source.TileDebug.prototype.getTileGrid); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'refresh', + ol.source.TileDebug.prototype.refresh); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getAttributions', + ol.source.TileDebug.prototype.getAttributions); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getLogo', + ol.source.TileDebug.prototype.getLogo); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getProjection', + ol.source.TileDebug.prototype.getProjection); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getState', + ol.source.TileDebug.prototype.getState); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'setAttributions', + ol.source.TileDebug.prototype.setAttributions); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'get', + ol.source.TileDebug.prototype.get); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getKeys', + ol.source.TileDebug.prototype.getKeys); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getProperties', + ol.source.TileDebug.prototype.getProperties); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'set', + ol.source.TileDebug.prototype.set); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'setProperties', + ol.source.TileDebug.prototype.setProperties); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'unset', + ol.source.TileDebug.prototype.unset); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'changed', + ol.source.TileDebug.prototype.changed); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'dispatchEvent', + ol.source.TileDebug.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getRevision', + ol.source.TileDebug.prototype.getRevision); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'on', + ol.source.TileDebug.prototype.on); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'once', + ol.source.TileDebug.prototype.once); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'un', + ol.source.TileDebug.prototype.un); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'unByKey', + ol.source.TileDebug.prototype.unByKey); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setRenderReprojectionEdges', + ol.source.TileJSON.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setTileGridForProjection', + ol.source.TileJSON.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getTileLoadFunction', + ol.source.TileJSON.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getTileUrlFunction', + ol.source.TileJSON.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getUrls', + ol.source.TileJSON.prototype.getUrls); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setTileLoadFunction', + ol.source.TileJSON.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setTileUrlFunction', + ol.source.TileJSON.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setUrl', + ol.source.TileJSON.prototype.setUrl); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setUrls', + ol.source.TileJSON.prototype.setUrls); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getTileGrid', + ol.source.TileJSON.prototype.getTileGrid); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'refresh', + ol.source.TileJSON.prototype.refresh); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getAttributions', + ol.source.TileJSON.prototype.getAttributions); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getLogo', + ol.source.TileJSON.prototype.getLogo); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getProjection', + ol.source.TileJSON.prototype.getProjection); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getState', + ol.source.TileJSON.prototype.getState); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setAttributions', + ol.source.TileJSON.prototype.setAttributions); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'get', + ol.source.TileJSON.prototype.get); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getKeys', + ol.source.TileJSON.prototype.getKeys); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getProperties', + ol.source.TileJSON.prototype.getProperties); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'set', + ol.source.TileJSON.prototype.set); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setProperties', + ol.source.TileJSON.prototype.setProperties); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'unset', + ol.source.TileJSON.prototype.unset); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'changed', + ol.source.TileJSON.prototype.changed); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'dispatchEvent', + ol.source.TileJSON.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getRevision', + ol.source.TileJSON.prototype.getRevision); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'on', + ol.source.TileJSON.prototype.on); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'once', + ol.source.TileJSON.prototype.once); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'un', + ol.source.TileJSON.prototype.un); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'unByKey', + ol.source.TileJSON.prototype.unByKey); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getTileGrid', + ol.source.TileUTFGrid.prototype.getTileGrid); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'refresh', + ol.source.TileUTFGrid.prototype.refresh); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getAttributions', + ol.source.TileUTFGrid.prototype.getAttributions); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getLogo', + ol.source.TileUTFGrid.prototype.getLogo); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getProjection', + ol.source.TileUTFGrid.prototype.getProjection); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getState', + ol.source.TileUTFGrid.prototype.getState); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'setAttributions', + ol.source.TileUTFGrid.prototype.setAttributions); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'get', + ol.source.TileUTFGrid.prototype.get); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getKeys', + ol.source.TileUTFGrid.prototype.getKeys); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getProperties', + ol.source.TileUTFGrid.prototype.getProperties); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'set', + ol.source.TileUTFGrid.prototype.set); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'setProperties', + ol.source.TileUTFGrid.prototype.setProperties); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'unset', + ol.source.TileUTFGrid.prototype.unset); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'changed', + ol.source.TileUTFGrid.prototype.changed); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'dispatchEvent', + ol.source.TileUTFGrid.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getRevision', + ol.source.TileUTFGrid.prototype.getRevision); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'on', + ol.source.TileUTFGrid.prototype.on); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'once', + ol.source.TileUTFGrid.prototype.once); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'un', + ol.source.TileUTFGrid.prototype.un); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'unByKey', + ol.source.TileUTFGrid.prototype.unByKey); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setRenderReprojectionEdges', + ol.source.TileWMS.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setTileGridForProjection', + ol.source.TileWMS.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getTileLoadFunction', + ol.source.TileWMS.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getTileUrlFunction', + ol.source.TileWMS.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getUrls', + ol.source.TileWMS.prototype.getUrls); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setTileLoadFunction', + ol.source.TileWMS.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setTileUrlFunction', + ol.source.TileWMS.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setUrl', + ol.source.TileWMS.prototype.setUrl); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setUrls', + ol.source.TileWMS.prototype.setUrls); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getTileGrid', + ol.source.TileWMS.prototype.getTileGrid); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'refresh', + ol.source.TileWMS.prototype.refresh); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getAttributions', + ol.source.TileWMS.prototype.getAttributions); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getLogo', + ol.source.TileWMS.prototype.getLogo); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getProjection', + ol.source.TileWMS.prototype.getProjection); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getState', + ol.source.TileWMS.prototype.getState); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setAttributions', + ol.source.TileWMS.prototype.setAttributions); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'get', + ol.source.TileWMS.prototype.get); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getKeys', + ol.source.TileWMS.prototype.getKeys); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getProperties', + ol.source.TileWMS.prototype.getProperties); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'set', + ol.source.TileWMS.prototype.set); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'setProperties', + ol.source.TileWMS.prototype.setProperties); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'unset', + ol.source.TileWMS.prototype.unset); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'changed', + ol.source.TileWMS.prototype.changed); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'dispatchEvent', + ol.source.TileWMS.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'getRevision', + ol.source.TileWMS.prototype.getRevision); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'on', + ol.source.TileWMS.prototype.on); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'once', + ol.source.TileWMS.prototype.once); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'un', + ol.source.TileWMS.prototype.un); + +goog.exportProperty( + ol.source.TileWMS.prototype, + 'unByKey', + ol.source.TileWMS.prototype.unByKey); + +goog.exportProperty( + ol.source.Vector.Event.prototype, + 'type', + ol.source.Vector.Event.prototype.type); + +goog.exportProperty( + ol.source.Vector.Event.prototype, + 'target', + ol.source.Vector.Event.prototype.target); + +goog.exportProperty( + ol.source.Vector.Event.prototype, + 'preventDefault', + ol.source.Vector.Event.prototype.preventDefault); + +goog.exportProperty( + ol.source.Vector.Event.prototype, + 'stopPropagation', + ol.source.Vector.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getTileLoadFunction', + ol.source.VectorTile.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getTileUrlFunction', + ol.source.VectorTile.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getUrls', + ol.source.VectorTile.prototype.getUrls); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'setTileLoadFunction', + ol.source.VectorTile.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'setTileUrlFunction', + ol.source.VectorTile.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'setUrl', + ol.source.VectorTile.prototype.setUrl); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'setUrls', + ol.source.VectorTile.prototype.setUrls); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getTileGrid', + ol.source.VectorTile.prototype.getTileGrid); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'refresh', + ol.source.VectorTile.prototype.refresh); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getAttributions', + ol.source.VectorTile.prototype.getAttributions); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getLogo', + ol.source.VectorTile.prototype.getLogo); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getProjection', + ol.source.VectorTile.prototype.getProjection); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getState', + ol.source.VectorTile.prototype.getState); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'setAttributions', + ol.source.VectorTile.prototype.setAttributions); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'get', + ol.source.VectorTile.prototype.get); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getKeys', + ol.source.VectorTile.prototype.getKeys); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getProperties', + ol.source.VectorTile.prototype.getProperties); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'set', + ol.source.VectorTile.prototype.set); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'setProperties', + ol.source.VectorTile.prototype.setProperties); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'unset', + ol.source.VectorTile.prototype.unset); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'changed', + ol.source.VectorTile.prototype.changed); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'dispatchEvent', + ol.source.VectorTile.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'getRevision', + ol.source.VectorTile.prototype.getRevision); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'on', + ol.source.VectorTile.prototype.on); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'once', + ol.source.VectorTile.prototype.once); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'un', + ol.source.VectorTile.prototype.un); + +goog.exportProperty( + ol.source.VectorTile.prototype, + 'unByKey', + ol.source.VectorTile.prototype.unByKey); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setRenderReprojectionEdges', + ol.source.WMTS.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setTileGridForProjection', + ol.source.WMTS.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getTileLoadFunction', + ol.source.WMTS.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getTileUrlFunction', + ol.source.WMTS.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getUrls', + ol.source.WMTS.prototype.getUrls); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setTileLoadFunction', + ol.source.WMTS.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setTileUrlFunction', + ol.source.WMTS.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setUrl', + ol.source.WMTS.prototype.setUrl); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setUrls', + ol.source.WMTS.prototype.setUrls); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getTileGrid', + ol.source.WMTS.prototype.getTileGrid); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'refresh', + ol.source.WMTS.prototype.refresh); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getAttributions', + ol.source.WMTS.prototype.getAttributions); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getLogo', + ol.source.WMTS.prototype.getLogo); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getProjection', + ol.source.WMTS.prototype.getProjection); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getState', + ol.source.WMTS.prototype.getState); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setAttributions', + ol.source.WMTS.prototype.setAttributions); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'get', + ol.source.WMTS.prototype.get); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getKeys', + ol.source.WMTS.prototype.getKeys); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getProperties', + ol.source.WMTS.prototype.getProperties); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'set', + ol.source.WMTS.prototype.set); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setProperties', + ol.source.WMTS.prototype.setProperties); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'unset', + ol.source.WMTS.prototype.unset); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'changed', + ol.source.WMTS.prototype.changed); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'dispatchEvent', + ol.source.WMTS.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getRevision', + ol.source.WMTS.prototype.getRevision); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'on', + ol.source.WMTS.prototype.on); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'once', + ol.source.WMTS.prototype.once); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'un', + ol.source.WMTS.prototype.un); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'unByKey', + ol.source.WMTS.prototype.unByKey); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setRenderReprojectionEdges', + ol.source.Zoomify.prototype.setRenderReprojectionEdges); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setTileGridForProjection', + ol.source.Zoomify.prototype.setTileGridForProjection); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getTileLoadFunction', + ol.source.Zoomify.prototype.getTileLoadFunction); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getTileUrlFunction', + ol.source.Zoomify.prototype.getTileUrlFunction); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getUrls', + ol.source.Zoomify.prototype.getUrls); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setTileLoadFunction', + ol.source.Zoomify.prototype.setTileLoadFunction); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setTileUrlFunction', + ol.source.Zoomify.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setUrl', + ol.source.Zoomify.prototype.setUrl); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setUrls', + ol.source.Zoomify.prototype.setUrls); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getTileGrid', + ol.source.Zoomify.prototype.getTileGrid); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'refresh', + ol.source.Zoomify.prototype.refresh); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getAttributions', + ol.source.Zoomify.prototype.getAttributions); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getLogo', + ol.source.Zoomify.prototype.getLogo); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getProjection', + ol.source.Zoomify.prototype.getProjection); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getState', + ol.source.Zoomify.prototype.getState); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setAttributions', + ol.source.Zoomify.prototype.setAttributions); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'get', + ol.source.Zoomify.prototype.get); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getKeys', + ol.source.Zoomify.prototype.getKeys); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getProperties', + ol.source.Zoomify.prototype.getProperties); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'set', + ol.source.Zoomify.prototype.set); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setProperties', + ol.source.Zoomify.prototype.setProperties); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'unset', + ol.source.Zoomify.prototype.unset); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'changed', + ol.source.Zoomify.prototype.changed); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'dispatchEvent', + ol.source.Zoomify.prototype.dispatchEvent); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getRevision', + ol.source.Zoomify.prototype.getRevision); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'on', + ol.source.Zoomify.prototype.on); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'once', + ol.source.Zoomify.prototype.once); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'un', + ol.source.Zoomify.prototype.un); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'unByKey', + ol.source.Zoomify.prototype.unByKey); + +goog.exportProperty( + ol.reproj.Tile.prototype, + 'getTileCoord', + ol.reproj.Tile.prototype.getTileCoord); + +goog.exportProperty( + ol.reproj.Tile.prototype, + 'load', + ol.reproj.Tile.prototype.load); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'changed', + ol.renderer.Layer.prototype.changed); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'dispatchEvent', + ol.renderer.Layer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'getRevision', + ol.renderer.Layer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'on', + ol.renderer.Layer.prototype.on); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'once', + ol.renderer.Layer.prototype.once); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'un', + ol.renderer.Layer.prototype.un); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'unByKey', + ol.renderer.Layer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'changed', + ol.renderer.webgl.Layer.prototype.changed); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'dispatchEvent', + ol.renderer.webgl.Layer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'getRevision', + ol.renderer.webgl.Layer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'on', + ol.renderer.webgl.Layer.prototype.on); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'once', + ol.renderer.webgl.Layer.prototype.once); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'un', + ol.renderer.webgl.Layer.prototype.un); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'unByKey', + ol.renderer.webgl.Layer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'changed', + ol.renderer.webgl.ImageLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'dispatchEvent', + ol.renderer.webgl.ImageLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'getRevision', + ol.renderer.webgl.ImageLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'on', + ol.renderer.webgl.ImageLayer.prototype.on); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'once', + ol.renderer.webgl.ImageLayer.prototype.once); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'un', + ol.renderer.webgl.ImageLayer.prototype.un); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'unByKey', + ol.renderer.webgl.ImageLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'changed', + ol.renderer.webgl.TileLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'dispatchEvent', + ol.renderer.webgl.TileLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'getRevision', + ol.renderer.webgl.TileLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'on', + ol.renderer.webgl.TileLayer.prototype.on); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'once', + ol.renderer.webgl.TileLayer.prototype.once); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'un', + ol.renderer.webgl.TileLayer.prototype.un); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'unByKey', + ol.renderer.webgl.TileLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'changed', + ol.renderer.webgl.VectorLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'dispatchEvent', + ol.renderer.webgl.VectorLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'getRevision', + ol.renderer.webgl.VectorLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'on', + ol.renderer.webgl.VectorLayer.prototype.on); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'once', + ol.renderer.webgl.VectorLayer.prototype.once); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'un', + ol.renderer.webgl.VectorLayer.prototype.un); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'unByKey', + ol.renderer.webgl.VectorLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'changed', + ol.renderer.canvas.Layer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'dispatchEvent', + ol.renderer.canvas.Layer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'getRevision', + ol.renderer.canvas.Layer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'on', + ol.renderer.canvas.Layer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'once', + ol.renderer.canvas.Layer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'un', + ol.renderer.canvas.Layer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'unByKey', + ol.renderer.canvas.Layer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'changed', + ol.renderer.canvas.IntermediateCanvas.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'dispatchEvent', + ol.renderer.canvas.IntermediateCanvas.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'getRevision', + ol.renderer.canvas.IntermediateCanvas.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'on', + ol.renderer.canvas.IntermediateCanvas.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'once', + ol.renderer.canvas.IntermediateCanvas.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'un', + ol.renderer.canvas.IntermediateCanvas.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.IntermediateCanvas.prototype, + 'unByKey', + ol.renderer.canvas.IntermediateCanvas.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'changed', + ol.renderer.canvas.ImageLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'dispatchEvent', + ol.renderer.canvas.ImageLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'getRevision', + ol.renderer.canvas.ImageLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'on', + ol.renderer.canvas.ImageLayer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'once', + ol.renderer.canvas.ImageLayer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'un', + ol.renderer.canvas.ImageLayer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'unByKey', + ol.renderer.canvas.ImageLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'changed', + ol.renderer.canvas.TileLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'dispatchEvent', + ol.renderer.canvas.TileLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'getRevision', + ol.renderer.canvas.TileLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'on', + ol.renderer.canvas.TileLayer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'once', + ol.renderer.canvas.TileLayer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'un', + ol.renderer.canvas.TileLayer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'unByKey', + ol.renderer.canvas.TileLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'changed', + ol.renderer.canvas.VectorLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'dispatchEvent', + ol.renderer.canvas.VectorLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'getRevision', + ol.renderer.canvas.VectorLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'on', + ol.renderer.canvas.VectorLayer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'once', + ol.renderer.canvas.VectorLayer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'un', + ol.renderer.canvas.VectorLayer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'unByKey', + ol.renderer.canvas.VectorLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'changed', + ol.renderer.canvas.VectorTileLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'dispatchEvent', + ol.renderer.canvas.VectorTileLayer.prototype.dispatchEvent); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'getRevision', + ol.renderer.canvas.VectorTileLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'on', + ol.renderer.canvas.VectorTileLayer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'once', + ol.renderer.canvas.VectorTileLayer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'un', + ol.renderer.canvas.VectorTileLayer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.VectorTileLayer.prototype, + 'unByKey', + ol.renderer.canvas.VectorTileLayer.prototype.unByKey); + +goog.exportProperty( + ol.render.Event.prototype, + 'type', + ol.render.Event.prototype.type); + +goog.exportProperty( + ol.render.Event.prototype, + 'target', + ol.render.Event.prototype.target); + +goog.exportProperty( + ol.render.Event.prototype, + 'preventDefault', + ol.render.Event.prototype.preventDefault); + +goog.exportProperty( + ol.render.Event.prototype, + 'stopPropagation', + ol.render.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.pointer.PointerEvent.prototype, + 'type', + ol.pointer.PointerEvent.prototype.type); + +goog.exportProperty( + ol.pointer.PointerEvent.prototype, + 'target', + ol.pointer.PointerEvent.prototype.target); + +goog.exportProperty( + ol.pointer.PointerEvent.prototype, + 'preventDefault', + ol.pointer.PointerEvent.prototype.preventDefault); + +goog.exportProperty( + ol.pointer.PointerEvent.prototype, + 'stopPropagation', + ol.pointer.PointerEvent.prototype.stopPropagation); + +goog.exportProperty( + ol.layer.Base.prototype, + 'get', + ol.layer.Base.prototype.get); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getKeys', + ol.layer.Base.prototype.getKeys); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getProperties', + ol.layer.Base.prototype.getProperties); + +goog.exportProperty( + ol.layer.Base.prototype, + 'set', + ol.layer.Base.prototype.set); + +goog.exportProperty( + ol.layer.Base.prototype, + 'setProperties', + ol.layer.Base.prototype.setProperties); + +goog.exportProperty( + ol.layer.Base.prototype, + 'unset', + ol.layer.Base.prototype.unset); + +goog.exportProperty( + ol.layer.Base.prototype, + 'changed', + ol.layer.Base.prototype.changed); + +goog.exportProperty( + ol.layer.Base.prototype, + 'dispatchEvent', + ol.layer.Base.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Base.prototype, + 'getRevision', + ol.layer.Base.prototype.getRevision); + +goog.exportProperty( + ol.layer.Base.prototype, + 'on', + ol.layer.Base.prototype.on); + +goog.exportProperty( + ol.layer.Base.prototype, + 'once', + ol.layer.Base.prototype.once); + +goog.exportProperty( + ol.layer.Base.prototype, + 'un', + ol.layer.Base.prototype.un); + +goog.exportProperty( + ol.layer.Base.prototype, + 'unByKey', + ol.layer.Base.prototype.unByKey); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getExtent', + ol.layer.Group.prototype.getExtent); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getMaxResolution', + ol.layer.Group.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getMinResolution', + ol.layer.Group.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getOpacity', + ol.layer.Group.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getVisible', + ol.layer.Group.prototype.getVisible); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getZIndex', + ol.layer.Group.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setExtent', + ol.layer.Group.prototype.setExtent); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setMaxResolution', + ol.layer.Group.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setMinResolution', + ol.layer.Group.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setOpacity', + ol.layer.Group.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setVisible', + ol.layer.Group.prototype.setVisible); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setZIndex', + ol.layer.Group.prototype.setZIndex); + +goog.exportProperty( + ol.layer.Group.prototype, + 'get', + ol.layer.Group.prototype.get); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getKeys', + ol.layer.Group.prototype.getKeys); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getProperties', + ol.layer.Group.prototype.getProperties); + +goog.exportProperty( + ol.layer.Group.prototype, + 'set', + ol.layer.Group.prototype.set); + +goog.exportProperty( + ol.layer.Group.prototype, + 'setProperties', + ol.layer.Group.prototype.setProperties); + +goog.exportProperty( + ol.layer.Group.prototype, + 'unset', + ol.layer.Group.prototype.unset); + +goog.exportProperty( + ol.layer.Group.prototype, + 'changed', + ol.layer.Group.prototype.changed); + +goog.exportProperty( + ol.layer.Group.prototype, + 'dispatchEvent', + ol.layer.Group.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Group.prototype, + 'getRevision', + ol.layer.Group.prototype.getRevision); + +goog.exportProperty( + ol.layer.Group.prototype, + 'on', + ol.layer.Group.prototype.on); + +goog.exportProperty( + ol.layer.Group.prototype, + 'once', + ol.layer.Group.prototype.once); + +goog.exportProperty( + ol.layer.Group.prototype, + 'un', + ol.layer.Group.prototype.un); + +goog.exportProperty( + ol.layer.Group.prototype, + 'unByKey', + ol.layer.Group.prototype.unByKey); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getExtent', + ol.layer.Layer.prototype.getExtent); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getMaxResolution', + ol.layer.Layer.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getMinResolution', + ol.layer.Layer.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getOpacity', + ol.layer.Layer.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getVisible', + ol.layer.Layer.prototype.getVisible); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getZIndex', + ol.layer.Layer.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setExtent', + ol.layer.Layer.prototype.setExtent); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setMaxResolution', + ol.layer.Layer.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setMinResolution', + ol.layer.Layer.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setOpacity', + ol.layer.Layer.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setVisible', + ol.layer.Layer.prototype.setVisible); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setZIndex', + ol.layer.Layer.prototype.setZIndex); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'get', + ol.layer.Layer.prototype.get); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getKeys', + ol.layer.Layer.prototype.getKeys); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getProperties', + ol.layer.Layer.prototype.getProperties); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'set', + ol.layer.Layer.prototype.set); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'setProperties', + ol.layer.Layer.prototype.setProperties); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'unset', + ol.layer.Layer.prototype.unset); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'changed', + ol.layer.Layer.prototype.changed); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'dispatchEvent', + ol.layer.Layer.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'getRevision', + ol.layer.Layer.prototype.getRevision); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'on', + ol.layer.Layer.prototype.on); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'once', + ol.layer.Layer.prototype.once); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'un', + ol.layer.Layer.prototype.un); + +goog.exportProperty( + ol.layer.Layer.prototype, + 'unByKey', + ol.layer.Layer.prototype.unByKey); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setMap', + ol.layer.Vector.prototype.setMap); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setSource', + ol.layer.Vector.prototype.setSource); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getExtent', + ol.layer.Vector.prototype.getExtent); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getMaxResolution', + ol.layer.Vector.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getMinResolution', + ol.layer.Vector.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getOpacity', + ol.layer.Vector.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getVisible', + ol.layer.Vector.prototype.getVisible); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getZIndex', + ol.layer.Vector.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setExtent', + ol.layer.Vector.prototype.setExtent); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setMaxResolution', + ol.layer.Vector.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setMinResolution', + ol.layer.Vector.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setOpacity', + ol.layer.Vector.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setVisible', + ol.layer.Vector.prototype.setVisible); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setZIndex', + ol.layer.Vector.prototype.setZIndex); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'get', + ol.layer.Vector.prototype.get); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getKeys', + ol.layer.Vector.prototype.getKeys); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getProperties', + ol.layer.Vector.prototype.getProperties); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'set', + ol.layer.Vector.prototype.set); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'setProperties', + ol.layer.Vector.prototype.setProperties); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'unset', + ol.layer.Vector.prototype.unset); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'changed', + ol.layer.Vector.prototype.changed); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'dispatchEvent', + ol.layer.Vector.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'getRevision', + ol.layer.Vector.prototype.getRevision); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'on', + ol.layer.Vector.prototype.on); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'once', + ol.layer.Vector.prototype.once); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'un', + ol.layer.Vector.prototype.un); + +goog.exportProperty( + ol.layer.Vector.prototype, + 'unByKey', + ol.layer.Vector.prototype.unByKey); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getSource', + ol.layer.Heatmap.prototype.getSource); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getStyle', + ol.layer.Heatmap.prototype.getStyle); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getStyleFunction', + ol.layer.Heatmap.prototype.getStyleFunction); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setStyle', + ol.layer.Heatmap.prototype.setStyle); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setMap', + ol.layer.Heatmap.prototype.setMap); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setSource', + ol.layer.Heatmap.prototype.setSource); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getExtent', + ol.layer.Heatmap.prototype.getExtent); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getMaxResolution', + ol.layer.Heatmap.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getMinResolution', + ol.layer.Heatmap.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getOpacity', + ol.layer.Heatmap.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getVisible', + ol.layer.Heatmap.prototype.getVisible); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getZIndex', + ol.layer.Heatmap.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setExtent', + ol.layer.Heatmap.prototype.setExtent); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setMaxResolution', + ol.layer.Heatmap.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setMinResolution', + ol.layer.Heatmap.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setOpacity', + ol.layer.Heatmap.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setVisible', + ol.layer.Heatmap.prototype.setVisible); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setZIndex', + ol.layer.Heatmap.prototype.setZIndex); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'get', + ol.layer.Heatmap.prototype.get); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getKeys', + ol.layer.Heatmap.prototype.getKeys); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getProperties', + ol.layer.Heatmap.prototype.getProperties); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'set', + ol.layer.Heatmap.prototype.set); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setProperties', + ol.layer.Heatmap.prototype.setProperties); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'unset', + ol.layer.Heatmap.prototype.unset); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'changed', + ol.layer.Heatmap.prototype.changed); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'dispatchEvent', + ol.layer.Heatmap.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getRevision', + ol.layer.Heatmap.prototype.getRevision); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'on', + ol.layer.Heatmap.prototype.on); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'once', + ol.layer.Heatmap.prototype.once); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'un', + ol.layer.Heatmap.prototype.un); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'unByKey', + ol.layer.Heatmap.prototype.unByKey); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setMap', + ol.layer.Image.prototype.setMap); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setSource', + ol.layer.Image.prototype.setSource); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getExtent', + ol.layer.Image.prototype.getExtent); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getMaxResolution', + ol.layer.Image.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getMinResolution', + ol.layer.Image.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getOpacity', + ol.layer.Image.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getVisible', + ol.layer.Image.prototype.getVisible); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getZIndex', + ol.layer.Image.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setExtent', + ol.layer.Image.prototype.setExtent); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setMaxResolution', + ol.layer.Image.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setMinResolution', + ol.layer.Image.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setOpacity', + ol.layer.Image.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setVisible', + ol.layer.Image.prototype.setVisible); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setZIndex', + ol.layer.Image.prototype.setZIndex); + +goog.exportProperty( + ol.layer.Image.prototype, + 'get', + ol.layer.Image.prototype.get); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getKeys', + ol.layer.Image.prototype.getKeys); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getProperties', + ol.layer.Image.prototype.getProperties); + +goog.exportProperty( + ol.layer.Image.prototype, + 'set', + ol.layer.Image.prototype.set); + +goog.exportProperty( + ol.layer.Image.prototype, + 'setProperties', + ol.layer.Image.prototype.setProperties); + +goog.exportProperty( + ol.layer.Image.prototype, + 'unset', + ol.layer.Image.prototype.unset); + +goog.exportProperty( + ol.layer.Image.prototype, + 'changed', + ol.layer.Image.prototype.changed); + +goog.exportProperty( + ol.layer.Image.prototype, + 'dispatchEvent', + ol.layer.Image.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Image.prototype, + 'getRevision', + ol.layer.Image.prototype.getRevision); + +goog.exportProperty( + ol.layer.Image.prototype, + 'on', + ol.layer.Image.prototype.on); + +goog.exportProperty( + ol.layer.Image.prototype, + 'once', + ol.layer.Image.prototype.once); + +goog.exportProperty( + ol.layer.Image.prototype, + 'un', + ol.layer.Image.prototype.un); + +goog.exportProperty( + ol.layer.Image.prototype, + 'unByKey', + ol.layer.Image.prototype.unByKey); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setMap', + ol.layer.Tile.prototype.setMap); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setSource', + ol.layer.Tile.prototype.setSource); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getExtent', + ol.layer.Tile.prototype.getExtent); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getMaxResolution', + ol.layer.Tile.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getMinResolution', + ol.layer.Tile.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getOpacity', + ol.layer.Tile.prototype.getOpacity); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getVisible', + ol.layer.Tile.prototype.getVisible); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getZIndex', + ol.layer.Tile.prototype.getZIndex); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setExtent', + ol.layer.Tile.prototype.setExtent); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setMaxResolution', + ol.layer.Tile.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setMinResolution', + ol.layer.Tile.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setOpacity', + ol.layer.Tile.prototype.setOpacity); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setVisible', + ol.layer.Tile.prototype.setVisible); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setZIndex', + ol.layer.Tile.prototype.setZIndex); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'get', + ol.layer.Tile.prototype.get); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getKeys', + ol.layer.Tile.prototype.getKeys); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getProperties', + ol.layer.Tile.prototype.getProperties); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'set', + ol.layer.Tile.prototype.set); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'setProperties', + ol.layer.Tile.prototype.setProperties); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'unset', + ol.layer.Tile.prototype.unset); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'changed', + ol.layer.Tile.prototype.changed); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'dispatchEvent', + ol.layer.Tile.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'getRevision', + ol.layer.Tile.prototype.getRevision); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'on', + ol.layer.Tile.prototype.on); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'once', + ol.layer.Tile.prototype.once); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'un', + ol.layer.Tile.prototype.un); + +goog.exportProperty( + ol.layer.Tile.prototype, + 'unByKey', + ol.layer.Tile.prototype.unByKey); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getSource', + ol.layer.VectorTile.prototype.getSource); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getStyle', + ol.layer.VectorTile.prototype.getStyle); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getStyleFunction', + ol.layer.VectorTile.prototype.getStyleFunction); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setStyle', + ol.layer.VectorTile.prototype.setStyle); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setMap', + ol.layer.VectorTile.prototype.setMap); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setSource', + ol.layer.VectorTile.prototype.setSource); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getExtent', + ol.layer.VectorTile.prototype.getExtent); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getMaxResolution', + ol.layer.VectorTile.prototype.getMaxResolution); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getMinResolution', + ol.layer.VectorTile.prototype.getMinResolution); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getOpacity', + ol.layer.VectorTile.prototype.getOpacity); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getVisible', + ol.layer.VectorTile.prototype.getVisible); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getZIndex', + ol.layer.VectorTile.prototype.getZIndex); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setExtent', + ol.layer.VectorTile.prototype.setExtent); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setMaxResolution', + ol.layer.VectorTile.prototype.setMaxResolution); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setMinResolution', + ol.layer.VectorTile.prototype.setMinResolution); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setOpacity', + ol.layer.VectorTile.prototype.setOpacity); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setVisible', + ol.layer.VectorTile.prototype.setVisible); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setZIndex', + ol.layer.VectorTile.prototype.setZIndex); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'get', + ol.layer.VectorTile.prototype.get); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getKeys', + ol.layer.VectorTile.prototype.getKeys); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getProperties', + ol.layer.VectorTile.prototype.getProperties); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'set', + ol.layer.VectorTile.prototype.set); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'setProperties', + ol.layer.VectorTile.prototype.setProperties); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'unset', + ol.layer.VectorTile.prototype.unset); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'changed', + ol.layer.VectorTile.prototype.changed); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'dispatchEvent', + ol.layer.VectorTile.prototype.dispatchEvent); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'getRevision', + ol.layer.VectorTile.prototype.getRevision); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'on', + ol.layer.VectorTile.prototype.on); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'once', + ol.layer.VectorTile.prototype.once); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'un', + ol.layer.VectorTile.prototype.un); + +goog.exportProperty( + ol.layer.VectorTile.prototype, + 'unByKey', + ol.layer.VectorTile.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'get', + ol.interaction.Interaction.prototype.get); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'getKeys', + ol.interaction.Interaction.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'getProperties', + ol.interaction.Interaction.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'set', + ol.interaction.Interaction.prototype.set); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'setProperties', + ol.interaction.Interaction.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'unset', + ol.interaction.Interaction.prototype.unset); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'changed', + ol.interaction.Interaction.prototype.changed); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'dispatchEvent', + ol.interaction.Interaction.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'getRevision', + ol.interaction.Interaction.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'on', + ol.interaction.Interaction.prototype.on); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'once', + ol.interaction.Interaction.prototype.once); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'un', + ol.interaction.Interaction.prototype.un); + +goog.exportProperty( + ol.interaction.Interaction.prototype, + 'unByKey', + ol.interaction.Interaction.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'getActive', + ol.interaction.DoubleClickZoom.prototype.getActive); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'getMap', + ol.interaction.DoubleClickZoom.prototype.getMap); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'setActive', + ol.interaction.DoubleClickZoom.prototype.setActive); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'get', + ol.interaction.DoubleClickZoom.prototype.get); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'getKeys', + ol.interaction.DoubleClickZoom.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'getProperties', + ol.interaction.DoubleClickZoom.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'set', + ol.interaction.DoubleClickZoom.prototype.set); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'setProperties', + ol.interaction.DoubleClickZoom.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'unset', + ol.interaction.DoubleClickZoom.prototype.unset); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'changed', + ol.interaction.DoubleClickZoom.prototype.changed); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'dispatchEvent', + ol.interaction.DoubleClickZoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'getRevision', + ol.interaction.DoubleClickZoom.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'on', + ol.interaction.DoubleClickZoom.prototype.on); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'once', + ol.interaction.DoubleClickZoom.prototype.once); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'un', + ol.interaction.DoubleClickZoom.prototype.un); + +goog.exportProperty( + ol.interaction.DoubleClickZoom.prototype, + 'unByKey', + ol.interaction.DoubleClickZoom.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'getActive', + ol.interaction.DragAndDrop.prototype.getActive); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'getMap', + ol.interaction.DragAndDrop.prototype.getMap); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'setActive', + ol.interaction.DragAndDrop.prototype.setActive); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'get', + ol.interaction.DragAndDrop.prototype.get); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'getKeys', + ol.interaction.DragAndDrop.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'getProperties', + ol.interaction.DragAndDrop.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'set', + ol.interaction.DragAndDrop.prototype.set); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'setProperties', + ol.interaction.DragAndDrop.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'unset', + ol.interaction.DragAndDrop.prototype.unset); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'changed', + ol.interaction.DragAndDrop.prototype.changed); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'dispatchEvent', + ol.interaction.DragAndDrop.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'getRevision', + ol.interaction.DragAndDrop.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'on', + ol.interaction.DragAndDrop.prototype.on); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'once', + ol.interaction.DragAndDrop.prototype.once); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'un', + ol.interaction.DragAndDrop.prototype.un); + +goog.exportProperty( + ol.interaction.DragAndDrop.prototype, + 'unByKey', + ol.interaction.DragAndDrop.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'type', + ol.interaction.DragAndDrop.Event.prototype.type); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'target', + ol.interaction.DragAndDrop.Event.prototype.target); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'preventDefault', + ol.interaction.DragAndDrop.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.DragAndDrop.Event.prototype, + 'stopPropagation', + ol.interaction.DragAndDrop.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'getActive', + ol.interaction.Pointer.prototype.getActive); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'getMap', + ol.interaction.Pointer.prototype.getMap); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'setActive', + ol.interaction.Pointer.prototype.setActive); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'get', + ol.interaction.Pointer.prototype.get); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'getKeys', + ol.interaction.Pointer.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'getProperties', + ol.interaction.Pointer.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'set', + ol.interaction.Pointer.prototype.set); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'setProperties', + ol.interaction.Pointer.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'unset', + ol.interaction.Pointer.prototype.unset); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'changed', + ol.interaction.Pointer.prototype.changed); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'dispatchEvent', + ol.interaction.Pointer.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'getRevision', + ol.interaction.Pointer.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'on', + ol.interaction.Pointer.prototype.on); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'once', + ol.interaction.Pointer.prototype.once); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'un', + ol.interaction.Pointer.prototype.un); + +goog.exportProperty( + ol.interaction.Pointer.prototype, + 'unByKey', + ol.interaction.Pointer.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'getActive', + ol.interaction.DragBox.prototype.getActive); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'getMap', + ol.interaction.DragBox.prototype.getMap); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'setActive', + ol.interaction.DragBox.prototype.setActive); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'get', + ol.interaction.DragBox.prototype.get); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'getKeys', + ol.interaction.DragBox.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'getProperties', + ol.interaction.DragBox.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'set', + ol.interaction.DragBox.prototype.set); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'setProperties', + ol.interaction.DragBox.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'unset', + ol.interaction.DragBox.prototype.unset); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'changed', + ol.interaction.DragBox.prototype.changed); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'dispatchEvent', + ol.interaction.DragBox.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'getRevision', + ol.interaction.DragBox.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'on', + ol.interaction.DragBox.prototype.on); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'once', + ol.interaction.DragBox.prototype.once); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'un', + ol.interaction.DragBox.prototype.un); + +goog.exportProperty( + ol.interaction.DragBox.prototype, + 'unByKey', + ol.interaction.DragBox.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragBox.Event.prototype, + 'type', + ol.interaction.DragBox.Event.prototype.type); + +goog.exportProperty( + ol.interaction.DragBox.Event.prototype, + 'target', + ol.interaction.DragBox.Event.prototype.target); + +goog.exportProperty( + ol.interaction.DragBox.Event.prototype, + 'preventDefault', + ol.interaction.DragBox.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.DragBox.Event.prototype, + 'stopPropagation', + ol.interaction.DragBox.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'getActive', + ol.interaction.DragPan.prototype.getActive); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'getMap', + ol.interaction.DragPan.prototype.getMap); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'setActive', + ol.interaction.DragPan.prototype.setActive); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'get', + ol.interaction.DragPan.prototype.get); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'getKeys', + ol.interaction.DragPan.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'getProperties', + ol.interaction.DragPan.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'set', + ol.interaction.DragPan.prototype.set); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'setProperties', + ol.interaction.DragPan.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'unset', + ol.interaction.DragPan.prototype.unset); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'changed', + ol.interaction.DragPan.prototype.changed); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'dispatchEvent', + ol.interaction.DragPan.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'getRevision', + ol.interaction.DragPan.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'on', + ol.interaction.DragPan.prototype.on); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'once', + ol.interaction.DragPan.prototype.once); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'un', + ol.interaction.DragPan.prototype.un); + +goog.exportProperty( + ol.interaction.DragPan.prototype, + 'unByKey', + ol.interaction.DragPan.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'getActive', + ol.interaction.DragRotate.prototype.getActive); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'getMap', + ol.interaction.DragRotate.prototype.getMap); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'setActive', + ol.interaction.DragRotate.prototype.setActive); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'get', + ol.interaction.DragRotate.prototype.get); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'getKeys', + ol.interaction.DragRotate.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'getProperties', + ol.interaction.DragRotate.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'set', + ol.interaction.DragRotate.prototype.set); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'setProperties', + ol.interaction.DragRotate.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'unset', + ol.interaction.DragRotate.prototype.unset); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'changed', + ol.interaction.DragRotate.prototype.changed); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'dispatchEvent', + ol.interaction.DragRotate.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'getRevision', + ol.interaction.DragRotate.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'on', + ol.interaction.DragRotate.prototype.on); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'once', + ol.interaction.DragRotate.prototype.once); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'un', + ol.interaction.DragRotate.prototype.un); + +goog.exportProperty( + ol.interaction.DragRotate.prototype, + 'unByKey', + ol.interaction.DragRotate.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'getActive', + ol.interaction.DragRotateAndZoom.prototype.getActive); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'getMap', + ol.interaction.DragRotateAndZoom.prototype.getMap); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'setActive', + ol.interaction.DragRotateAndZoom.prototype.setActive); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'get', + ol.interaction.DragRotateAndZoom.prototype.get); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'getKeys', + ol.interaction.DragRotateAndZoom.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'getProperties', + ol.interaction.DragRotateAndZoom.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'set', + ol.interaction.DragRotateAndZoom.prototype.set); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'setProperties', + ol.interaction.DragRotateAndZoom.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'unset', + ol.interaction.DragRotateAndZoom.prototype.unset); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'changed', + ol.interaction.DragRotateAndZoom.prototype.changed); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'dispatchEvent', + ol.interaction.DragRotateAndZoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'getRevision', + ol.interaction.DragRotateAndZoom.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'on', + ol.interaction.DragRotateAndZoom.prototype.on); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'once', + ol.interaction.DragRotateAndZoom.prototype.once); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'un', + ol.interaction.DragRotateAndZoom.prototype.un); + +goog.exportProperty( + ol.interaction.DragRotateAndZoom.prototype, + 'unByKey', + ol.interaction.DragRotateAndZoom.prototype.unByKey); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'getGeometry', + ol.interaction.DragZoom.prototype.getGeometry); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'getActive', + ol.interaction.DragZoom.prototype.getActive); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'getMap', + ol.interaction.DragZoom.prototype.getMap); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'setActive', + ol.interaction.DragZoom.prototype.setActive); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'get', + ol.interaction.DragZoom.prototype.get); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'getKeys', + ol.interaction.DragZoom.prototype.getKeys); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'getProperties', + ol.interaction.DragZoom.prototype.getProperties); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'set', + ol.interaction.DragZoom.prototype.set); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'setProperties', + ol.interaction.DragZoom.prototype.setProperties); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'unset', + ol.interaction.DragZoom.prototype.unset); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'changed', + ol.interaction.DragZoom.prototype.changed); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'dispatchEvent', + ol.interaction.DragZoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'getRevision', + ol.interaction.DragZoom.prototype.getRevision); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'on', + ol.interaction.DragZoom.prototype.on); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'once', + ol.interaction.DragZoom.prototype.once); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'un', + ol.interaction.DragZoom.prototype.un); + +goog.exportProperty( + ol.interaction.DragZoom.prototype, + 'unByKey', + ol.interaction.DragZoom.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'getActive', + ol.interaction.Draw.prototype.getActive); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'getMap', + ol.interaction.Draw.prototype.getMap); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'setActive', + ol.interaction.Draw.prototype.setActive); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'get', + ol.interaction.Draw.prototype.get); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'getKeys', + ol.interaction.Draw.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'getProperties', + ol.interaction.Draw.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'set', + ol.interaction.Draw.prototype.set); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'setProperties', + ol.interaction.Draw.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'unset', + ol.interaction.Draw.prototype.unset); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'changed', + ol.interaction.Draw.prototype.changed); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'dispatchEvent', + ol.interaction.Draw.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'getRevision', + ol.interaction.Draw.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'on', + ol.interaction.Draw.prototype.on); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'once', + ol.interaction.Draw.prototype.once); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'un', + ol.interaction.Draw.prototype.un); + +goog.exportProperty( + ol.interaction.Draw.prototype, + 'unByKey', + ol.interaction.Draw.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Draw.Event.prototype, + 'type', + ol.interaction.Draw.Event.prototype.type); + +goog.exportProperty( + ol.interaction.Draw.Event.prototype, + 'target', + ol.interaction.Draw.Event.prototype.target); + +goog.exportProperty( + ol.interaction.Draw.Event.prototype, + 'preventDefault', + ol.interaction.Draw.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.Draw.Event.prototype, + 'stopPropagation', + ol.interaction.Draw.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'getActive', + ol.interaction.Extent.prototype.getActive); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'getMap', + ol.interaction.Extent.prototype.getMap); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'setActive', + ol.interaction.Extent.prototype.setActive); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'get', + ol.interaction.Extent.prototype.get); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'getKeys', + ol.interaction.Extent.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'getProperties', + ol.interaction.Extent.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'set', + ol.interaction.Extent.prototype.set); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'setProperties', + ol.interaction.Extent.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'unset', + ol.interaction.Extent.prototype.unset); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'changed', + ol.interaction.Extent.prototype.changed); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'dispatchEvent', + ol.interaction.Extent.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'getRevision', + ol.interaction.Extent.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'on', + ol.interaction.Extent.prototype.on); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'once', + ol.interaction.Extent.prototype.once); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'un', + ol.interaction.Extent.prototype.un); + +goog.exportProperty( + ol.interaction.Extent.prototype, + 'unByKey', + ol.interaction.Extent.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Extent.Event.prototype, + 'type', + ol.interaction.Extent.Event.prototype.type); + +goog.exportProperty( + ol.interaction.Extent.Event.prototype, + 'target', + ol.interaction.Extent.Event.prototype.target); + +goog.exportProperty( + ol.interaction.Extent.Event.prototype, + 'preventDefault', + ol.interaction.Extent.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.Extent.Event.prototype, + 'stopPropagation', + ol.interaction.Extent.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'getActive', + ol.interaction.KeyboardPan.prototype.getActive); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'getMap', + ol.interaction.KeyboardPan.prototype.getMap); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'setActive', + ol.interaction.KeyboardPan.prototype.setActive); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'get', + ol.interaction.KeyboardPan.prototype.get); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'getKeys', + ol.interaction.KeyboardPan.prototype.getKeys); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'getProperties', + ol.interaction.KeyboardPan.prototype.getProperties); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'set', + ol.interaction.KeyboardPan.prototype.set); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'setProperties', + ol.interaction.KeyboardPan.prototype.setProperties); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'unset', + ol.interaction.KeyboardPan.prototype.unset); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'changed', + ol.interaction.KeyboardPan.prototype.changed); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'dispatchEvent', + ol.interaction.KeyboardPan.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'getRevision', + ol.interaction.KeyboardPan.prototype.getRevision); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'on', + ol.interaction.KeyboardPan.prototype.on); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'once', + ol.interaction.KeyboardPan.prototype.once); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'un', + ol.interaction.KeyboardPan.prototype.un); + +goog.exportProperty( + ol.interaction.KeyboardPan.prototype, + 'unByKey', + ol.interaction.KeyboardPan.prototype.unByKey); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'getActive', + ol.interaction.KeyboardZoom.prototype.getActive); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'getMap', + ol.interaction.KeyboardZoom.prototype.getMap); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'setActive', + ol.interaction.KeyboardZoom.prototype.setActive); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'get', + ol.interaction.KeyboardZoom.prototype.get); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'getKeys', + ol.interaction.KeyboardZoom.prototype.getKeys); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'getProperties', + ol.interaction.KeyboardZoom.prototype.getProperties); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'set', + ol.interaction.KeyboardZoom.prototype.set); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'setProperties', + ol.interaction.KeyboardZoom.prototype.setProperties); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'unset', + ol.interaction.KeyboardZoom.prototype.unset); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'changed', + ol.interaction.KeyboardZoom.prototype.changed); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'dispatchEvent', + ol.interaction.KeyboardZoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'getRevision', + ol.interaction.KeyboardZoom.prototype.getRevision); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'on', + ol.interaction.KeyboardZoom.prototype.on); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'once', + ol.interaction.KeyboardZoom.prototype.once); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'un', + ol.interaction.KeyboardZoom.prototype.un); + +goog.exportProperty( + ol.interaction.KeyboardZoom.prototype, + 'unByKey', + ol.interaction.KeyboardZoom.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'getActive', + ol.interaction.Modify.prototype.getActive); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'getMap', + ol.interaction.Modify.prototype.getMap); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'setActive', + ol.interaction.Modify.prototype.setActive); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'get', + ol.interaction.Modify.prototype.get); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'getKeys', + ol.interaction.Modify.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'getProperties', + ol.interaction.Modify.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'set', + ol.interaction.Modify.prototype.set); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'setProperties', + ol.interaction.Modify.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'unset', + ol.interaction.Modify.prototype.unset); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'changed', + ol.interaction.Modify.prototype.changed); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'dispatchEvent', + ol.interaction.Modify.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'getRevision', + ol.interaction.Modify.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'on', + ol.interaction.Modify.prototype.on); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'once', + ol.interaction.Modify.prototype.once); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'un', + ol.interaction.Modify.prototype.un); + +goog.exportProperty( + ol.interaction.Modify.prototype, + 'unByKey', + ol.interaction.Modify.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Modify.Event.prototype, + 'type', + ol.interaction.Modify.Event.prototype.type); + +goog.exportProperty( + ol.interaction.Modify.Event.prototype, + 'target', + ol.interaction.Modify.Event.prototype.target); + +goog.exportProperty( + ol.interaction.Modify.Event.prototype, + 'preventDefault', + ol.interaction.Modify.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.Modify.Event.prototype, + 'stopPropagation', + ol.interaction.Modify.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'getActive', + ol.interaction.MouseWheelZoom.prototype.getActive); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'getMap', + ol.interaction.MouseWheelZoom.prototype.getMap); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'setActive', + ol.interaction.MouseWheelZoom.prototype.setActive); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'get', + ol.interaction.MouseWheelZoom.prototype.get); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'getKeys', + ol.interaction.MouseWheelZoom.prototype.getKeys); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'getProperties', + ol.interaction.MouseWheelZoom.prototype.getProperties); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'set', + ol.interaction.MouseWheelZoom.prototype.set); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'setProperties', + ol.interaction.MouseWheelZoom.prototype.setProperties); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'unset', + ol.interaction.MouseWheelZoom.prototype.unset); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'changed', + ol.interaction.MouseWheelZoom.prototype.changed); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'dispatchEvent', + ol.interaction.MouseWheelZoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'getRevision', + ol.interaction.MouseWheelZoom.prototype.getRevision); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'on', + ol.interaction.MouseWheelZoom.prototype.on); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'once', + ol.interaction.MouseWheelZoom.prototype.once); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'un', + ol.interaction.MouseWheelZoom.prototype.un); + +goog.exportProperty( + ol.interaction.MouseWheelZoom.prototype, + 'unByKey', + ol.interaction.MouseWheelZoom.prototype.unByKey); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'getActive', + ol.interaction.PinchRotate.prototype.getActive); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'getMap', + ol.interaction.PinchRotate.prototype.getMap); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'setActive', + ol.interaction.PinchRotate.prototype.setActive); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'get', + ol.interaction.PinchRotate.prototype.get); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'getKeys', + ol.interaction.PinchRotate.prototype.getKeys); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'getProperties', + ol.interaction.PinchRotate.prototype.getProperties); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'set', + ol.interaction.PinchRotate.prototype.set); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'setProperties', + ol.interaction.PinchRotate.prototype.setProperties); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'unset', + ol.interaction.PinchRotate.prototype.unset); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'changed', + ol.interaction.PinchRotate.prototype.changed); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'dispatchEvent', + ol.interaction.PinchRotate.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'getRevision', + ol.interaction.PinchRotate.prototype.getRevision); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'on', + ol.interaction.PinchRotate.prototype.on); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'once', + ol.interaction.PinchRotate.prototype.once); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'un', + ol.interaction.PinchRotate.prototype.un); + +goog.exportProperty( + ol.interaction.PinchRotate.prototype, + 'unByKey', + ol.interaction.PinchRotate.prototype.unByKey); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'getActive', + ol.interaction.PinchZoom.prototype.getActive); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'getMap', + ol.interaction.PinchZoom.prototype.getMap); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'setActive', + ol.interaction.PinchZoom.prototype.setActive); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'get', + ol.interaction.PinchZoom.prototype.get); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'getKeys', + ol.interaction.PinchZoom.prototype.getKeys); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'getProperties', + ol.interaction.PinchZoom.prototype.getProperties); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'set', + ol.interaction.PinchZoom.prototype.set); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'setProperties', + ol.interaction.PinchZoom.prototype.setProperties); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'unset', + ol.interaction.PinchZoom.prototype.unset); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'changed', + ol.interaction.PinchZoom.prototype.changed); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'dispatchEvent', + ol.interaction.PinchZoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'getRevision', + ol.interaction.PinchZoom.prototype.getRevision); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'on', + ol.interaction.PinchZoom.prototype.on); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'once', + ol.interaction.PinchZoom.prototype.once); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'un', + ol.interaction.PinchZoom.prototype.un); + +goog.exportProperty( + ol.interaction.PinchZoom.prototype, + 'unByKey', + ol.interaction.PinchZoom.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getActive', + ol.interaction.Select.prototype.getActive); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getMap', + ol.interaction.Select.prototype.getMap); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'setActive', + ol.interaction.Select.prototype.setActive); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'get', + ol.interaction.Select.prototype.get); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getKeys', + ol.interaction.Select.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getProperties', + ol.interaction.Select.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'set', + ol.interaction.Select.prototype.set); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'setProperties', + ol.interaction.Select.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'unset', + ol.interaction.Select.prototype.unset); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'changed', + ol.interaction.Select.prototype.changed); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'dispatchEvent', + ol.interaction.Select.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'getRevision', + ol.interaction.Select.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'on', + ol.interaction.Select.prototype.on); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'once', + ol.interaction.Select.prototype.once); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'un', + ol.interaction.Select.prototype.un); + +goog.exportProperty( + ol.interaction.Select.prototype, + 'unByKey', + ol.interaction.Select.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'type', + ol.interaction.Select.Event.prototype.type); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'target', + ol.interaction.Select.Event.prototype.target); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'preventDefault', + ol.interaction.Select.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.Select.Event.prototype, + 'stopPropagation', + ol.interaction.Select.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getActive', + ol.interaction.Snap.prototype.getActive); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getMap', + ol.interaction.Snap.prototype.getMap); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'setActive', + ol.interaction.Snap.prototype.setActive); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'get', + ol.interaction.Snap.prototype.get); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getKeys', + ol.interaction.Snap.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getProperties', + ol.interaction.Snap.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'set', + ol.interaction.Snap.prototype.set); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'setProperties', + ol.interaction.Snap.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'unset', + ol.interaction.Snap.prototype.unset); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'changed', + ol.interaction.Snap.prototype.changed); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'dispatchEvent', + ol.interaction.Snap.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getRevision', + ol.interaction.Snap.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'on', + ol.interaction.Snap.prototype.on); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'once', + ol.interaction.Snap.prototype.once); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'un', + ol.interaction.Snap.prototype.un); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'unByKey', + ol.interaction.Snap.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'getActive', + ol.interaction.Translate.prototype.getActive); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'getMap', + ol.interaction.Translate.prototype.getMap); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'setActive', + ol.interaction.Translate.prototype.setActive); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'get', + ol.interaction.Translate.prototype.get); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'getKeys', + ol.interaction.Translate.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'getProperties', + ol.interaction.Translate.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'set', + ol.interaction.Translate.prototype.set); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'setProperties', + ol.interaction.Translate.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'unset', + ol.interaction.Translate.prototype.unset); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'changed', + ol.interaction.Translate.prototype.changed); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'dispatchEvent', + ol.interaction.Translate.prototype.dispatchEvent); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'getRevision', + ol.interaction.Translate.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'on', + ol.interaction.Translate.prototype.on); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'once', + ol.interaction.Translate.prototype.once); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'un', + ol.interaction.Translate.prototype.un); + +goog.exportProperty( + ol.interaction.Translate.prototype, + 'unByKey', + ol.interaction.Translate.prototype.unByKey); + +goog.exportProperty( + ol.interaction.Translate.Event.prototype, + 'type', + ol.interaction.Translate.Event.prototype.type); + +goog.exportProperty( + ol.interaction.Translate.Event.prototype, + 'target', + ol.interaction.Translate.Event.prototype.target); + +goog.exportProperty( + ol.interaction.Translate.Event.prototype, + 'preventDefault', + ol.interaction.Translate.Event.prototype.preventDefault); + +goog.exportProperty( + ol.interaction.Translate.Event.prototype, + 'stopPropagation', + ol.interaction.Translate.Event.prototype.stopPropagation); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'get', + ol.geom.Geometry.prototype.get); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'getKeys', + ol.geom.Geometry.prototype.getKeys); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'getProperties', + ol.geom.Geometry.prototype.getProperties); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'set', + ol.geom.Geometry.prototype.set); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'setProperties', + ol.geom.Geometry.prototype.setProperties); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'unset', + ol.geom.Geometry.prototype.unset); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'changed', + ol.geom.Geometry.prototype.changed); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'dispatchEvent', + ol.geom.Geometry.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'getRevision', + ol.geom.Geometry.prototype.getRevision); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'on', + ol.geom.Geometry.prototype.on); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'once', + ol.geom.Geometry.prototype.once); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'un', + ol.geom.Geometry.prototype.un); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'unByKey', + ol.geom.Geometry.prototype.unByKey); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getClosestPoint', + ol.geom.SimpleGeometry.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'intersectsCoordinate', + ol.geom.SimpleGeometry.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getExtent', + ol.geom.SimpleGeometry.prototype.getExtent); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'rotate', + ol.geom.SimpleGeometry.prototype.rotate); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'scale', + ol.geom.SimpleGeometry.prototype.scale); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'simplify', + ol.geom.SimpleGeometry.prototype.simplify); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'transform', + ol.geom.SimpleGeometry.prototype.transform); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'get', + ol.geom.SimpleGeometry.prototype.get); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getKeys', + ol.geom.SimpleGeometry.prototype.getKeys); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getProperties', + ol.geom.SimpleGeometry.prototype.getProperties); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'set', + ol.geom.SimpleGeometry.prototype.set); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'setProperties', + ol.geom.SimpleGeometry.prototype.setProperties); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'unset', + ol.geom.SimpleGeometry.prototype.unset); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'changed', + ol.geom.SimpleGeometry.prototype.changed); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'dispatchEvent', + ol.geom.SimpleGeometry.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getRevision', + ol.geom.SimpleGeometry.prototype.getRevision); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'on', + ol.geom.SimpleGeometry.prototype.on); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'once', + ol.geom.SimpleGeometry.prototype.once); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'un', + ol.geom.SimpleGeometry.prototype.un); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'unByKey', + ol.geom.SimpleGeometry.prototype.unByKey); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getFirstCoordinate', + ol.geom.Circle.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getLastCoordinate', + ol.geom.Circle.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getLayout', + ol.geom.Circle.prototype.getLayout); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'rotate', + ol.geom.Circle.prototype.rotate); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'scale', + ol.geom.Circle.prototype.scale); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getClosestPoint', + ol.geom.Circle.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'intersectsCoordinate', + ol.geom.Circle.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getExtent', + ol.geom.Circle.prototype.getExtent); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'simplify', + ol.geom.Circle.prototype.simplify); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'get', + ol.geom.Circle.prototype.get); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getKeys', + ol.geom.Circle.prototype.getKeys); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getProperties', + ol.geom.Circle.prototype.getProperties); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'set', + ol.geom.Circle.prototype.set); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'setProperties', + ol.geom.Circle.prototype.setProperties); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'unset', + ol.geom.Circle.prototype.unset); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'changed', + ol.geom.Circle.prototype.changed); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'dispatchEvent', + ol.geom.Circle.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getRevision', + ol.geom.Circle.prototype.getRevision); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'on', + ol.geom.Circle.prototype.on); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'once', + ol.geom.Circle.prototype.once); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'un', + ol.geom.Circle.prototype.un); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'unByKey', + ol.geom.Circle.prototype.unByKey); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getClosestPoint', + ol.geom.GeometryCollection.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'intersectsCoordinate', + ol.geom.GeometryCollection.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getExtent', + ol.geom.GeometryCollection.prototype.getExtent); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'rotate', + ol.geom.GeometryCollection.prototype.rotate); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'scale', + ol.geom.GeometryCollection.prototype.scale); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'simplify', + ol.geom.GeometryCollection.prototype.simplify); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'transform', + ol.geom.GeometryCollection.prototype.transform); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'get', + ol.geom.GeometryCollection.prototype.get); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getKeys', + ol.geom.GeometryCollection.prototype.getKeys); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getProperties', + ol.geom.GeometryCollection.prototype.getProperties); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'set', + ol.geom.GeometryCollection.prototype.set); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'setProperties', + ol.geom.GeometryCollection.prototype.setProperties); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'unset', + ol.geom.GeometryCollection.prototype.unset); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'changed', + ol.geom.GeometryCollection.prototype.changed); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'dispatchEvent', + ol.geom.GeometryCollection.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getRevision', + ol.geom.GeometryCollection.prototype.getRevision); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'on', + ol.geom.GeometryCollection.prototype.on); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'once', + ol.geom.GeometryCollection.prototype.once); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'un', + ol.geom.GeometryCollection.prototype.un); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'unByKey', + ol.geom.GeometryCollection.prototype.unByKey); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getFirstCoordinate', + ol.geom.LinearRing.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getLastCoordinate', + ol.geom.LinearRing.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getLayout', + ol.geom.LinearRing.prototype.getLayout); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'rotate', + ol.geom.LinearRing.prototype.rotate); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'scale', + ol.geom.LinearRing.prototype.scale); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getClosestPoint', + ol.geom.LinearRing.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'intersectsCoordinate', + ol.geom.LinearRing.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getExtent', + ol.geom.LinearRing.prototype.getExtent); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'simplify', + ol.geom.LinearRing.prototype.simplify); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'transform', + ol.geom.LinearRing.prototype.transform); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'get', + ol.geom.LinearRing.prototype.get); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getKeys', + ol.geom.LinearRing.prototype.getKeys); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getProperties', + ol.geom.LinearRing.prototype.getProperties); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'set', + ol.geom.LinearRing.prototype.set); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'setProperties', + ol.geom.LinearRing.prototype.setProperties); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'unset', + ol.geom.LinearRing.prototype.unset); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'changed', + ol.geom.LinearRing.prototype.changed); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'dispatchEvent', + ol.geom.LinearRing.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getRevision', + ol.geom.LinearRing.prototype.getRevision); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'on', + ol.geom.LinearRing.prototype.on); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'once', + ol.geom.LinearRing.prototype.once); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'un', + ol.geom.LinearRing.prototype.un); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'unByKey', + ol.geom.LinearRing.prototype.unByKey); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getFirstCoordinate', + ol.geom.LineString.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getLastCoordinate', + ol.geom.LineString.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getLayout', + ol.geom.LineString.prototype.getLayout); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'rotate', + ol.geom.LineString.prototype.rotate); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'scale', + ol.geom.LineString.prototype.scale); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getClosestPoint', + ol.geom.LineString.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'intersectsCoordinate', + ol.geom.LineString.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getExtent', + ol.geom.LineString.prototype.getExtent); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'simplify', + ol.geom.LineString.prototype.simplify); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'transform', + ol.geom.LineString.prototype.transform); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'get', + ol.geom.LineString.prototype.get); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getKeys', + ol.geom.LineString.prototype.getKeys); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getProperties', + ol.geom.LineString.prototype.getProperties); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'set', + ol.geom.LineString.prototype.set); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'setProperties', + ol.geom.LineString.prototype.setProperties); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'unset', + ol.geom.LineString.prototype.unset); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'changed', + ol.geom.LineString.prototype.changed); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'dispatchEvent', + ol.geom.LineString.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getRevision', + ol.geom.LineString.prototype.getRevision); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'on', + ol.geom.LineString.prototype.on); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'once', + ol.geom.LineString.prototype.once); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'un', + ol.geom.LineString.prototype.un); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'unByKey', + ol.geom.LineString.prototype.unByKey); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getFirstCoordinate', + ol.geom.MultiLineString.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getLastCoordinate', + ol.geom.MultiLineString.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getLayout', + ol.geom.MultiLineString.prototype.getLayout); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'rotate', + ol.geom.MultiLineString.prototype.rotate); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'scale', + ol.geom.MultiLineString.prototype.scale); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getClosestPoint', + ol.geom.MultiLineString.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'intersectsCoordinate', + ol.geom.MultiLineString.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getExtent', + ol.geom.MultiLineString.prototype.getExtent); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'simplify', + ol.geom.MultiLineString.prototype.simplify); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'transform', + ol.geom.MultiLineString.prototype.transform); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'get', + ol.geom.MultiLineString.prototype.get); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getKeys', + ol.geom.MultiLineString.prototype.getKeys); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getProperties', + ol.geom.MultiLineString.prototype.getProperties); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'set', + ol.geom.MultiLineString.prototype.set); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'setProperties', + ol.geom.MultiLineString.prototype.setProperties); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'unset', + ol.geom.MultiLineString.prototype.unset); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'changed', + ol.geom.MultiLineString.prototype.changed); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'dispatchEvent', + ol.geom.MultiLineString.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getRevision', + ol.geom.MultiLineString.prototype.getRevision); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'on', + ol.geom.MultiLineString.prototype.on); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'once', + ol.geom.MultiLineString.prototype.once); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'un', + ol.geom.MultiLineString.prototype.un); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'unByKey', + ol.geom.MultiLineString.prototype.unByKey); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getFirstCoordinate', + ol.geom.MultiPoint.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getLastCoordinate', + ol.geom.MultiPoint.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getLayout', + ol.geom.MultiPoint.prototype.getLayout); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'rotate', + ol.geom.MultiPoint.prototype.rotate); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'scale', + ol.geom.MultiPoint.prototype.scale); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getClosestPoint', + ol.geom.MultiPoint.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'intersectsCoordinate', + ol.geom.MultiPoint.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getExtent', + ol.geom.MultiPoint.prototype.getExtent); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'simplify', + ol.geom.MultiPoint.prototype.simplify); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'transform', + ol.geom.MultiPoint.prototype.transform); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'get', + ol.geom.MultiPoint.prototype.get); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getKeys', + ol.geom.MultiPoint.prototype.getKeys); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getProperties', + ol.geom.MultiPoint.prototype.getProperties); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'set', + ol.geom.MultiPoint.prototype.set); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'setProperties', + ol.geom.MultiPoint.prototype.setProperties); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'unset', + ol.geom.MultiPoint.prototype.unset); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'changed', + ol.geom.MultiPoint.prototype.changed); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'dispatchEvent', + ol.geom.MultiPoint.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getRevision', + ol.geom.MultiPoint.prototype.getRevision); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'on', + ol.geom.MultiPoint.prototype.on); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'once', + ol.geom.MultiPoint.prototype.once); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'un', + ol.geom.MultiPoint.prototype.un); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'unByKey', + ol.geom.MultiPoint.prototype.unByKey); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getFirstCoordinate', + ol.geom.MultiPolygon.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getLastCoordinate', + ol.geom.MultiPolygon.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getLayout', + ol.geom.MultiPolygon.prototype.getLayout); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'rotate', + ol.geom.MultiPolygon.prototype.rotate); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'scale', + ol.geom.MultiPolygon.prototype.scale); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getClosestPoint', + ol.geom.MultiPolygon.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'intersectsCoordinate', + ol.geom.MultiPolygon.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getExtent', + ol.geom.MultiPolygon.prototype.getExtent); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'simplify', + ol.geom.MultiPolygon.prototype.simplify); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'transform', + ol.geom.MultiPolygon.prototype.transform); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'get', + ol.geom.MultiPolygon.prototype.get); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getKeys', + ol.geom.MultiPolygon.prototype.getKeys); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getProperties', + ol.geom.MultiPolygon.prototype.getProperties); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'set', + ol.geom.MultiPolygon.prototype.set); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'setProperties', + ol.geom.MultiPolygon.prototype.setProperties); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'unset', + ol.geom.MultiPolygon.prototype.unset); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'changed', + ol.geom.MultiPolygon.prototype.changed); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'dispatchEvent', + ol.geom.MultiPolygon.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getRevision', + ol.geom.MultiPolygon.prototype.getRevision); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'on', + ol.geom.MultiPolygon.prototype.on); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'once', + ol.geom.MultiPolygon.prototype.once); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'un', + ol.geom.MultiPolygon.prototype.un); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'unByKey', + ol.geom.MultiPolygon.prototype.unByKey); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getFirstCoordinate', + ol.geom.Point.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getLastCoordinate', + ol.geom.Point.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getLayout', + ol.geom.Point.prototype.getLayout); + +goog.exportProperty( + ol.geom.Point.prototype, + 'rotate', + ol.geom.Point.prototype.rotate); + +goog.exportProperty( + ol.geom.Point.prototype, + 'scale', + ol.geom.Point.prototype.scale); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getClosestPoint', + ol.geom.Point.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.Point.prototype, + 'intersectsCoordinate', + ol.geom.Point.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getExtent', + ol.geom.Point.prototype.getExtent); + +goog.exportProperty( + ol.geom.Point.prototype, + 'simplify', + ol.geom.Point.prototype.simplify); + +goog.exportProperty( + ol.geom.Point.prototype, + 'transform', + ol.geom.Point.prototype.transform); + +goog.exportProperty( + ol.geom.Point.prototype, + 'get', + ol.geom.Point.prototype.get); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getKeys', + ol.geom.Point.prototype.getKeys); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getProperties', + ol.geom.Point.prototype.getProperties); + +goog.exportProperty( + ol.geom.Point.prototype, + 'set', + ol.geom.Point.prototype.set); + +goog.exportProperty( + ol.geom.Point.prototype, + 'setProperties', + ol.geom.Point.prototype.setProperties); + +goog.exportProperty( + ol.geom.Point.prototype, + 'unset', + ol.geom.Point.prototype.unset); + +goog.exportProperty( + ol.geom.Point.prototype, + 'changed', + ol.geom.Point.prototype.changed); + +goog.exportProperty( + ol.geom.Point.prototype, + 'dispatchEvent', + ol.geom.Point.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getRevision', + ol.geom.Point.prototype.getRevision); + +goog.exportProperty( + ol.geom.Point.prototype, + 'on', + ol.geom.Point.prototype.on); + +goog.exportProperty( + ol.geom.Point.prototype, + 'once', + ol.geom.Point.prototype.once); + +goog.exportProperty( + ol.geom.Point.prototype, + 'un', + ol.geom.Point.prototype.un); + +goog.exportProperty( + ol.geom.Point.prototype, + 'unByKey', + ol.geom.Point.prototype.unByKey); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getFirstCoordinate', + ol.geom.Polygon.prototype.getFirstCoordinate); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getLastCoordinate', + ol.geom.Polygon.prototype.getLastCoordinate); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getLayout', + ol.geom.Polygon.prototype.getLayout); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'rotate', + ol.geom.Polygon.prototype.rotate); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'scale', + ol.geom.Polygon.prototype.scale); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getClosestPoint', + ol.geom.Polygon.prototype.getClosestPoint); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'intersectsCoordinate', + ol.geom.Polygon.prototype.intersectsCoordinate); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getExtent', + ol.geom.Polygon.prototype.getExtent); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'simplify', + ol.geom.Polygon.prototype.simplify); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'transform', + ol.geom.Polygon.prototype.transform); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'get', + ol.geom.Polygon.prototype.get); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getKeys', + ol.geom.Polygon.prototype.getKeys); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getProperties', + ol.geom.Polygon.prototype.getProperties); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'set', + ol.geom.Polygon.prototype.set); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'setProperties', + ol.geom.Polygon.prototype.setProperties); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'unset', + ol.geom.Polygon.prototype.unset); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'changed', + ol.geom.Polygon.prototype.changed); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'dispatchEvent', + ol.geom.Polygon.prototype.dispatchEvent); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getRevision', + ol.geom.Polygon.prototype.getRevision); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'on', + ol.geom.Polygon.prototype.on); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'once', + ol.geom.Polygon.prototype.once); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'un', + ol.geom.Polygon.prototype.un); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'unByKey', + ol.geom.Polygon.prototype.unByKey); + +goog.exportProperty( + ol.format.GML.prototype, + 'readFeatures', + ol.format.GML.prototype.readFeatures); + +goog.exportProperty( + ol.format.GML2.prototype, + 'readFeatures', + ol.format.GML2.prototype.readFeatures); + +goog.exportProperty( + ol.format.GML3.prototype, + 'readFeatures', + ol.format.GML3.prototype.readFeatures); + +goog.exportProperty( + ol.control.Control.prototype, + 'get', + ol.control.Control.prototype.get); + +goog.exportProperty( + ol.control.Control.prototype, + 'getKeys', + ol.control.Control.prototype.getKeys); + +goog.exportProperty( + ol.control.Control.prototype, + 'getProperties', + ol.control.Control.prototype.getProperties); + +goog.exportProperty( + ol.control.Control.prototype, + 'set', + ol.control.Control.prototype.set); + +goog.exportProperty( + ol.control.Control.prototype, + 'setProperties', + ol.control.Control.prototype.setProperties); + +goog.exportProperty( + ol.control.Control.prototype, + 'unset', + ol.control.Control.prototype.unset); + +goog.exportProperty( + ol.control.Control.prototype, + 'changed', + ol.control.Control.prototype.changed); + +goog.exportProperty( + ol.control.Control.prototype, + 'dispatchEvent', + ol.control.Control.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.Control.prototype, + 'getRevision', + ol.control.Control.prototype.getRevision); + +goog.exportProperty( + ol.control.Control.prototype, + 'on', + ol.control.Control.prototype.on); + +goog.exportProperty( + ol.control.Control.prototype, + 'once', + ol.control.Control.prototype.once); + +goog.exportProperty( + ol.control.Control.prototype, + 'un', + ol.control.Control.prototype.un); + +goog.exportProperty( + ol.control.Control.prototype, + 'unByKey', + ol.control.Control.prototype.unByKey); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'getMap', + ol.control.Attribution.prototype.getMap); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'setMap', + ol.control.Attribution.prototype.setMap); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'setTarget', + ol.control.Attribution.prototype.setTarget); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'get', + ol.control.Attribution.prototype.get); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'getKeys', + ol.control.Attribution.prototype.getKeys); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'getProperties', + ol.control.Attribution.prototype.getProperties); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'set', + ol.control.Attribution.prototype.set); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'setProperties', + ol.control.Attribution.prototype.setProperties); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'unset', + ol.control.Attribution.prototype.unset); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'changed', + ol.control.Attribution.prototype.changed); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'dispatchEvent', + ol.control.Attribution.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'getRevision', + ol.control.Attribution.prototype.getRevision); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'on', + ol.control.Attribution.prototype.on); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'once', + ol.control.Attribution.prototype.once); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'un', + ol.control.Attribution.prototype.un); + +goog.exportProperty( + ol.control.Attribution.prototype, + 'unByKey', + ol.control.Attribution.prototype.unByKey); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'getMap', + ol.control.FullScreen.prototype.getMap); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'setMap', + ol.control.FullScreen.prototype.setMap); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'setTarget', + ol.control.FullScreen.prototype.setTarget); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'get', + ol.control.FullScreen.prototype.get); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'getKeys', + ol.control.FullScreen.prototype.getKeys); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'getProperties', + ol.control.FullScreen.prototype.getProperties); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'set', + ol.control.FullScreen.prototype.set); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'setProperties', + ol.control.FullScreen.prototype.setProperties); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'unset', + ol.control.FullScreen.prototype.unset); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'changed', + ol.control.FullScreen.prototype.changed); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'dispatchEvent', + ol.control.FullScreen.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'getRevision', + ol.control.FullScreen.prototype.getRevision); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'on', + ol.control.FullScreen.prototype.on); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'once', + ol.control.FullScreen.prototype.once); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'un', + ol.control.FullScreen.prototype.un); + +goog.exportProperty( + ol.control.FullScreen.prototype, + 'unByKey', + ol.control.FullScreen.prototype.unByKey); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'getMap', + ol.control.MousePosition.prototype.getMap); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'setMap', + ol.control.MousePosition.prototype.setMap); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'setTarget', + ol.control.MousePosition.prototype.setTarget); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'get', + ol.control.MousePosition.prototype.get); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'getKeys', + ol.control.MousePosition.prototype.getKeys); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'getProperties', + ol.control.MousePosition.prototype.getProperties); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'set', + ol.control.MousePosition.prototype.set); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'setProperties', + ol.control.MousePosition.prototype.setProperties); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'unset', + ol.control.MousePosition.prototype.unset); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'changed', + ol.control.MousePosition.prototype.changed); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'dispatchEvent', + ol.control.MousePosition.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'getRevision', + ol.control.MousePosition.prototype.getRevision); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'on', + ol.control.MousePosition.prototype.on); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'once', + ol.control.MousePosition.prototype.once); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'un', + ol.control.MousePosition.prototype.un); + +goog.exportProperty( + ol.control.MousePosition.prototype, + 'unByKey', + ol.control.MousePosition.prototype.unByKey); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getMap', + ol.control.OverviewMap.prototype.getMap); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'setMap', + ol.control.OverviewMap.prototype.setMap); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'setTarget', + ol.control.OverviewMap.prototype.setTarget); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'get', + ol.control.OverviewMap.prototype.get); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getKeys', + ol.control.OverviewMap.prototype.getKeys); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getProperties', + ol.control.OverviewMap.prototype.getProperties); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'set', + ol.control.OverviewMap.prototype.set); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'setProperties', + ol.control.OverviewMap.prototype.setProperties); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'unset', + ol.control.OverviewMap.prototype.unset); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'changed', + ol.control.OverviewMap.prototype.changed); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'dispatchEvent', + ol.control.OverviewMap.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'getRevision', + ol.control.OverviewMap.prototype.getRevision); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'on', + ol.control.OverviewMap.prototype.on); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'once', + ol.control.OverviewMap.prototype.once); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'un', + ol.control.OverviewMap.prototype.un); + +goog.exportProperty( + ol.control.OverviewMap.prototype, + 'unByKey', + ol.control.OverviewMap.prototype.unByKey); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'getMap', + ol.control.Rotate.prototype.getMap); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'setMap', + ol.control.Rotate.prototype.setMap); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'setTarget', + ol.control.Rotate.prototype.setTarget); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'get', + ol.control.Rotate.prototype.get); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'getKeys', + ol.control.Rotate.prototype.getKeys); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'getProperties', + ol.control.Rotate.prototype.getProperties); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'set', + ol.control.Rotate.prototype.set); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'setProperties', + ol.control.Rotate.prototype.setProperties); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'unset', + ol.control.Rotate.prototype.unset); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'changed', + ol.control.Rotate.prototype.changed); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'dispatchEvent', + ol.control.Rotate.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'getRevision', + ol.control.Rotate.prototype.getRevision); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'on', + ol.control.Rotate.prototype.on); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'once', + ol.control.Rotate.prototype.once); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'un', + ol.control.Rotate.prototype.un); + +goog.exportProperty( + ol.control.Rotate.prototype, + 'unByKey', + ol.control.Rotate.prototype.unByKey); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'getMap', + ol.control.ScaleLine.prototype.getMap); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'setMap', + ol.control.ScaleLine.prototype.setMap); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'setTarget', + ol.control.ScaleLine.prototype.setTarget); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'get', + ol.control.ScaleLine.prototype.get); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'getKeys', + ol.control.ScaleLine.prototype.getKeys); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'getProperties', + ol.control.ScaleLine.prototype.getProperties); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'set', + ol.control.ScaleLine.prototype.set); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'setProperties', + ol.control.ScaleLine.prototype.setProperties); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'unset', + ol.control.ScaleLine.prototype.unset); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'changed', + ol.control.ScaleLine.prototype.changed); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'dispatchEvent', + ol.control.ScaleLine.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'getRevision', + ol.control.ScaleLine.prototype.getRevision); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'on', + ol.control.ScaleLine.prototype.on); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'once', + ol.control.ScaleLine.prototype.once); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'un', + ol.control.ScaleLine.prototype.un); + +goog.exportProperty( + ol.control.ScaleLine.prototype, + 'unByKey', + ol.control.ScaleLine.prototype.unByKey); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'getMap', + ol.control.Zoom.prototype.getMap); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'setMap', + ol.control.Zoom.prototype.setMap); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'setTarget', + ol.control.Zoom.prototype.setTarget); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'get', + ol.control.Zoom.prototype.get); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'getKeys', + ol.control.Zoom.prototype.getKeys); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'getProperties', + ol.control.Zoom.prototype.getProperties); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'set', + ol.control.Zoom.prototype.set); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'setProperties', + ol.control.Zoom.prototype.setProperties); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'unset', + ol.control.Zoom.prototype.unset); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'changed', + ol.control.Zoom.prototype.changed); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'dispatchEvent', + ol.control.Zoom.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'getRevision', + ol.control.Zoom.prototype.getRevision); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'on', + ol.control.Zoom.prototype.on); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'once', + ol.control.Zoom.prototype.once); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'un', + ol.control.Zoom.prototype.un); + +goog.exportProperty( + ol.control.Zoom.prototype, + 'unByKey', + ol.control.Zoom.prototype.unByKey); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'getMap', + ol.control.ZoomSlider.prototype.getMap); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'setMap', + ol.control.ZoomSlider.prototype.setMap); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'setTarget', + ol.control.ZoomSlider.prototype.setTarget); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'get', + ol.control.ZoomSlider.prototype.get); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'getKeys', + ol.control.ZoomSlider.prototype.getKeys); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'getProperties', + ol.control.ZoomSlider.prototype.getProperties); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'set', + ol.control.ZoomSlider.prototype.set); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'setProperties', + ol.control.ZoomSlider.prototype.setProperties); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'unset', + ol.control.ZoomSlider.prototype.unset); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'changed', + ol.control.ZoomSlider.prototype.changed); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'dispatchEvent', + ol.control.ZoomSlider.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'getRevision', + ol.control.ZoomSlider.prototype.getRevision); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'on', + ol.control.ZoomSlider.prototype.on); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'once', + ol.control.ZoomSlider.prototype.once); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'un', + ol.control.ZoomSlider.prototype.un); + +goog.exportProperty( + ol.control.ZoomSlider.prototype, + 'unByKey', + ol.control.ZoomSlider.prototype.unByKey); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'getMap', + ol.control.ZoomToExtent.prototype.getMap); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'setMap', + ol.control.ZoomToExtent.prototype.setMap); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'setTarget', + ol.control.ZoomToExtent.prototype.setTarget); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'get', + ol.control.ZoomToExtent.prototype.get); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'getKeys', + ol.control.ZoomToExtent.prototype.getKeys); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'getProperties', + ol.control.ZoomToExtent.prototype.getProperties); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'set', + ol.control.ZoomToExtent.prototype.set); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'setProperties', + ol.control.ZoomToExtent.prototype.setProperties); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'unset', + ol.control.ZoomToExtent.prototype.unset); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'changed', + ol.control.ZoomToExtent.prototype.changed); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'dispatchEvent', + ol.control.ZoomToExtent.prototype.dispatchEvent); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'getRevision', + ol.control.ZoomToExtent.prototype.getRevision); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'on', + ol.control.ZoomToExtent.prototype.on); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'once', + ol.control.ZoomToExtent.prototype.once); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'un', + ol.control.ZoomToExtent.prototype.un); + +goog.exportProperty( + ol.control.ZoomToExtent.prototype, + 'unByKey', + ol.control.ZoomToExtent.prototype.unByKey); +ol.VERSION = 'v3.20.1'; +OPENLAYERS.ol = ol; + + return OPENLAYERS.ol; +})); + +var map; +var extent; +var overlays = []; +var vectorSource = new ol.source.Vector(); +var lastClick; +var popupOverlay; +var vectorLayer; +var id = null; +var userPositionMarker = null; +var lockViewToPosition = true; +var gps = false; +var gpsLocation = null; +var featureStyle = new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: 'rgb(153,39,208)', + lineDash: [4, 8] + }), + fill: new ol.style.Fill({ + color: 'rgba(153,39,208,.03)' + }) +}); +var mapClickFunction = function(evt) { + var pos = ol.proj.transform(evt["coordinate"], 'EPSG:3857', 'EPSG:4326'); + lastClick = pos; + getNearest(pos[0], pos[1]); +}; +var moveFunction = function() { + var q = $("#search input[name=q]").val(); + if (q !== "" && $("#search input[name=q]").attr("data-move-search") === "") { + updateMapExtent(); + var q = $("#search input[name=q]").val(); + q = encodeURI(q); + $("#clearInput").html("<img src=\"/img/ajax-loader.gif\" />"); + var url = '/' + q + '/' + encodeURI(extent[0]) + '/' + encodeURI(extent[1]) + '/' + encodeURI(extent[2]) + '/' + encodeURI(extent[3] + '/' + false + '/50'); + $.getScript(url); + } +}; +var clearInputFunction = function() { + $("#search input[name=q]").val(''); + $("#search input[name=q]").focus(); + clearPOIS(); + $.each(overlays, function(index, value) { + map.removeOverlay(value); + $("#popup-closer").click(); + }); + deinitResults(); + $("#clearInput").off(); +}; +$(document).ready(function() { + // Initialize the Map + initMap(); + checkGPS(startApplication); + $("#closer").click(function() { + toggleResults(); + }); + map.on('singleclick', mapClickFunction); + $(window).resize(function() { + updateResultsPosition(); + updateCloserPosition(); + }); + $("#follow-location > span.button").click(function() { + followLocation(); + }); + $("#lock-location > span.button").click(function() { + toggleViewLock(); + }); +}); +var options = { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0 +}; + +function success(pos) { + var crd = pos.coords; + map.getView().setCenter(ol.proj.transform([crd.longitude, crd.latitude], 'EPSG:4326', 'EPSG:3857')); + map.getView().setZoom(12); + updateMapExtent(); +} + +function error(err) { + console.warn('ERROR(' + err.code + '): ' + err.message); +} + +function receiveLocation() { + navigator.geolocation.getCurrentPosition(success, error, options); +} + +function updateMapExtent() { + var tmpExtent = map.getView().calculateExtent([$("#map").width(), $("#map").height()]); + extent = ol.proj.transform([tmpExtent[0], tmpExtent[1]], 'EPSG:3857', 'EPSG:4326').concat(ol.proj.transform([tmpExtent[2], tmpExtent[3]], 'EPSG:3857', 'EPSG:4326')); +} + +function numberWithPoints(x) { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, "."); +} + +function deinitSearchBox() { + $("#search").addClass("hidden"); +} + +function initResults() { + if ($("#results").hasClass("hidden")) { + $('#results').removeClass("hidden"); + } + if ($("#closer").hasClass("hidden")) { + $("#closer").removeClass("hidden"); + updateCloserPosition(); + } + updateMapSize(); + toggleResults("out"); +} + +function initStartNavigation() { + $("#clearInput").html('<a href="/route/start/foot" target="_self"><img src="/img/navigation-arrow.svg" height="20px"></a>'); + $("#clearInput").off(); + $("#clearInput").attr("title", "Routenplaner starten"); +} + +function initClearInput() { + $("#clearInput").html('<span class="font-bold">X</span>'); + $("#clearInput").off(); + $("#clearInput").click(clearInputFunction); + $("#clearInput").attr("title", "Sucheingabe löschen"); +} + +function toggleResults(status) { + if (status === undefined) { + status = $("#results").attr("data-status"); + } else if (status !== "in" && status !== "out") { + status = "in"; + } + if (status === "in") { + $("#closer").html("<"); + $("#results").attr("data-status", "out"); + $("#closer").attr("title", "Ergebnisse ausklappen"); + updateResultsPosition(); + updateCloserPosition(); + } else { + $("#closer").html(">"); + $("#results").attr("data-status", "in"); + $("#closer").attr("title", "Ergebnisse einklappen"); + updateResultsPosition(); + updateCloserPosition(); + } + updateMapSize(); +} + +function updateMapSize() { + var resultsWidth = parseInt($("#results").width()); + if ($("#results").hasClass("hidden")) { + resultsWidth = 0; + } + $("#search input[name=q]").attr("data-move-search", "false"); + var displayWidth = $(window).width(); + // Change Map Width + $("#map").width(displayWidth - resultsWidth); + var navBarHeight = $("nav").height(); + if($("nav").hasClass("hidden")){ + navBarHeight = 0; + } + var displayHeight = $(window).height(); + // Change The Map Height + $("#map").height(displayHeight - navBarHeight); + $("#map").css("margin-top", navBarHeight); + map.updateSize(); + setTimeout(function() { + $("#search input[name=q]").attr("data-move-search", ""); + }, 1500); +} + +function updateResultsPosition() { + if ($("#results").attr("data-status") === "out") { + $("#results").addClass("hidden"); + } else { + $("#results").removeClass("hidden"); + } +} + +function updateCloserPosition() { + if ($("#results").attr("data-status") === "out") { + $("#closer").css("right", "0px"); + } else { + var screenWidth = $(window).width(); + var resultsWidth = $("#results").width() - 1; + var closerWidth = $("#closer").width(); + if (screenWidth > (resultsWidth + closerWidth)) { + $("#closer").css("right", resultsWidth + "px"); + } else { + $("#closer").css("right", (resultsWidth - closerWidth) + "px"); + } + } +} + +function adjustView(results) { + if (results.length <= 0) return; + var minPosition = []; + var maxPosition = []; + for (var i = 0; i < results.length; i++) { + if (typeof minPosition[0] === 'undefined' || minPosition[0] > parseFloat(results[i]["lon"])) { + minPosition[0] = parseFloat(results[i]["lon"]); + } + if (typeof minPosition[0] === 'undefined' || (typeof results[i]["boundingbox"] !== 'undefined' && minPosition[0] > parseFloat(results[i]["boundingbox"][2]))) { + minPosition[0] = parseFloat(results[i]["boundingbox"][2]); + } + if (typeof minPosition[1] === 'undefined' || minPosition[1] > parseFloat(results[i]["lat"])) { + minPosition[1] = parseFloat(results[i]["lat"]); + } + if (typeof minPosition[1] === 'undefined' || (typeof results[i]["boundingbox"] !== 'undefined' && minPosition[1] > parseFloat(results[i]["boundingbox"][0]))) { + minPosition[1] = parseFloat(results[i]["boundingbox"][0]); + } + if (typeof maxPosition[0] === 'undefined' || maxPosition[0] < parseFloat(results[i]["lon"])) { + maxPosition[0] = parseFloat(results[i]["lon"]); + } + if (typeof maxPosition[0] === 'undefined' || (typeof results[i]["boundingbox"] !== 'undefined' && maxPosition[0] < parseFloat(results[i]["boundingbox"][3]))) { + maxPosition[0] = parseFloat(results[i]["boundingbox"][3]); + } + if (typeof maxPosition[1] === 'undefined' || maxPosition[1] < parseFloat(results[i]["lat"])) { + maxPosition[1] = parseFloat(results[i]["lat"]); + } + if (typeof maxPosition[1] === 'undefined' || (typeof results[i]["boundingbox"] !== 'undefined' && maxPosition[1] < parseFloat(results[i]["boundingbox"][1]))) { + maxPosition[1] = parseFloat(results[i]["boundingbox"][1]); + } + if (typeof results[i]["type"] !== 'undefined' && (results[i]["type"] === 'city' || results[i]["type"] === 'administrative' || results[i]["type"] === 'river')) { + break; + } + } + minPosition = ol.proj.transform(minPosition, 'EPSG:4326', 'EPSG:3857'); + maxPosition = ol.proj.transform(maxPosition, 'EPSG:4326', 'EPSG:3857'); + map.getView().fit([minPosition[0], minPosition[1], maxPosition[0], maxPosition[1]], map.getSize()); + updateMapExtent(); +} +/** + * Parsesan OSM-Address-Object for the Road-Name + * @param {Array} address + * @return {String} roadname + */ +function getRoad(address) { + var road = ""; + if (typeof address["road"] !== 'undefined') { + road = address["road"]; + } else if (typeof address["pedestrian"] !== 'undefined') { + road = address["pedestrian"]; + } else if (typeof address["path"] !== 'undefined') { + road = address["path"]; + } else if (typeof address["footway"] !== 'undefined') { + road = address["footway"]; + } + return road; +} +/** + * Parse an OSM-Address-Object for the House Number + * @param {Array} address + * @return {String} Housenumber + */ +function getHouseNumber(address) { + var house_number = typeof address["house_number"] !== 'undefined' ? address["house_number"] : ""; + return house_number; +} +/** + * Parse an OSM-Address-Object for the City (including Zip-Code) + * @param {Array} address + * @return {String} City + */ +function getCity(address) { + var city = typeof address["postcode"] !== 'undefined' ? address["postcode"] + " " : ""; + if (typeof address["city"] !== "undefined") { + city += address["city"]; + } else if (typeof address["town"] !== "undefined") { + city += address["town"]; + } else if (typeof address["village"] !== "undefined") { + city += address["village"]; + } + return city; +} + +function adjustViewBoundingBox(minpos, maxpos) { + minPosition = ol.proj.transform(minpos, 'EPSG:4326', 'EPSG:3857'); + maxPosition = ol.proj.transform(maxpos, 'EPSG:4326', 'EPSG:3857'); + map.getView().fit([minPosition[0], minPosition[1], maxPosition[0], maxPosition[1]], map.getSize()); + updateMapExtent(); +} +/* + * This Function takes an array of Positions and adjusts the view of the map so everything is visible + * @param positions{Array} - Array with Position Objects ([lon,lat]) + */ +function adjustViewPosList(positions) { + var minpos = [null, null]; + var maxpos = [null, null]; + $.each(positions, function(index, value) { + if(value === ""){ + return; + } + if (minpos[0] === null || value[0] < minpos[0]) { + minpos[0] = value[0]; + } + if (maxpos[0] === null || value[0] > maxpos[0]) { + maxpos[0] = value[0]; + } + if (minpos[1] === null || value[1] < minpos[1]) { + minpos[1] = value[1]; + } + if (maxpos[1] === null || value[1] > maxpos[1]) { + maxpos[1] = value[1]; + } + }); + adjustViewBoundingBox(minpos, maxpos); +} + +function clearPOIS() { + // Remove All Existing Overlays + $.each(overlays, function(index, value) { + map.removeOverlay(value); + }); + map.removeLayer(vectorLayer); + vectorSource = new ol.source.Vector(); + // Remove Existing Results + $("#results > .result").remove(); + $("#results > h4").remove(); + overlays = []; +} + +function centerMap(longitude, latitude) { + var point = ol.proj.transform([longitude, latitude], 'EPSG:4326', 'EPSG:3857') + map.getView().setCenter(point); +} +/** + * Fügt einen Marker auf die Karte hinzu + * Parameter: + * el: HTML-Code für das Element, welches den Marker definiert + * pos: Position, auf der sich der Marker befinden soll int[2] (Lat, Long) + **/ +function addMarker(el, pos) { + var overlay = new ol.Overlay({ + position: pos, + element: el.get(0), + offset: [-12, -45], + stopEvent: false, + }); + map.addOverlay(overlay); + return overlay; +} + +function checkGPS(callback) { + + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition(function(position){ + if(position.coords.accuracy > 500){ + gps = false; + toggleGPSLocator(false); + }else{ + gps = gps = true; + lon = parseFloat(position.coords.longitude); + lat = parseFloat(position.coords.latitude); + gpsLocation = [lon, lat]; + toggleGPSLocator(true); + } + if(typeof callback === "function"){ + callback(); + } + }, function(error){ + gps = false; + toggleGPSLocator(false); + if(typeof callback === "function"){ + callback(); + } + }); + } else { + gps = false; + toggleGPSLocator(false); + if(typeof callback === "function"){ + callback(); + } + } +} + +function startApplication(){ + if(typeof start === "function"){ + start(); + } +} + +function toggleGPSLocator(visible){ + if(visible){ + $("#location-tool").removeClass("hidden"); + }else{ + $("#location-tool").addClass("hidden"); + } +} + +function followLocation() { + // Element to be displayed at the user-location + var el = $('<span id="user-position" class="glyphicon glyphicon-record" style="color: #2881cc;"></span>'); + if (lockViewToPosition) $("#lock-location").addClass("active"); + else $("#lock-location").removeClass("active"); + if (id === null) { + id = navigator.geolocation.watchPosition(function(position) { + // Remove possibly existing User-Location Marker: + if (userPositionMarker !== null) { + map.removeLayer(userPositionMarker); + userPositionMarker = null; + } + // Create User Position + var point_geom = new ol.geom.Point(ol.proj.transform([position.coords.longitude, position.coords.latitude], 'EPSG:4326', 'EPSG:3857')); + var point_feature = new ol.Feature({ + name: "Position", + geometry: point_geom + }); + // Create the accuracy Circle: + var circle = new ol.geom.Circle(ol.proj.transform([position.coords.longitude, position.coords.latitude], 'EPSG:4326', 'EPSG:3857'), position.coords.accuracy); + var accuracy_feature = new ol.Feature({ + name: "Accuracy", + geometry: circle + }); + userPositionMarker = new ol.layer.Vector({ + source: new ol.source.Vector({ + features: [point_feature, accuracy_feature] + }) + }); + map.addLayer(userPositionMarker); + if (lockViewToPosition) { + // Fit the Extent of the Map to Fit the new Features Exactly + map.getView().fit(userPositionMarker.getSource().getExtent(), map.getSize()); + } + // Change the color of the Icon so the user knows that the position is tracked: + $("#follow-location").addClass("active"); + }, function(error) {}, options); + // Show the Lock View to Position Button + $("#lock-location").removeClass("hidden"); + $("#lock-location > span.info").fadeOut(2000); + } else { + map.removeLayer(userPositionMarker); + userPositionMarker = null; + navigator.geolocation.clearWatch(id); + id = null; + // Clear the color of the Icon so the user knows that the position is no longer tracked + $("#follow-location").removeClass("active"); + // Hide the lock View to Position Button + $("#lock-location").addClass("hidden"); + $("#lock-location > span.info").css("display", ""); + } +} + +function updateCurrentLocation(callback) { + var lon = ""; + var lat = ""; + if (gps) { + navigator.geolocation.getCurrentPosition(function(position) { + lon = parseFloat(position.coords.longitude); + lat = parseFloat(position.coords.latitude); + gpsLocation = [lon, lat]; + if(typeof callback === "function"){ + callback(); + } + }, function(error) { + checkGPS(callback); + }); + + + } else { + return null; + } +} + +function toggleViewLock() { + if (lockViewToPosition) { + lockViewToPosition = false; + $("#lock-location").removeClass("active"); + $("#lock-location > span.info").html("Ansicht freigegeben"); + $("#lock-location > span.info").css("display", ""); + $("#lock-location > span.info").fadeOut(2000); + } else { + lockViewToPosition = true; + $("#lock-location").addClass("active"); + $("#lock-location > span.info").html("Ansicht zentriert"); + $("#lock-location > span.info").css("display", ""); + $("#lock-location > span.info").fadeOut(2000); + } +} + +function createPopup(lon, lat, html) { + var pos = ol.proj.transform([parseFloat(lon), parseFloat(lat)], 'EPSG:4326', 'EPSG:3857'); + $("#popup-content").html(html); + popupOverlay.setPosition(pos); +} + + +$(document).ready(function() { + initStartNavigation(); + if (!boundings && getPosition) receiveLocation(); + if (boundings) { + adjustViewBoundingBox(minPos, maxPos); + } + $("#search input[name=q]").on("keydown", function(event) { + if (event.which == 13) $("#doSearch").click(); + }); + $("#doSearch").click(function() { + updateMapExtent(); + var q = $("#search input[name=q]").val(); + q = encodeURI(q); + $("#clearInput").html("<img src=\"/img/ajax-loader.gif\" />"); + var url = '/' + q + '/' + encodeURI(extent[0]) + '/' + encodeURI(extent[1]) + '/' + encodeURI(extent[2]) + '/' + encodeURI(extent[3]); + $.getScript(url).fail(function(jqxhr, settings, exception) { + console.log(exception); + }); + $("#search input[name=q]").blur(); + }); +}); + +function initMap() { + popupOverlay = new ol.Overlay( /** @type {olx.OverlayOptions} */ ({ + element: document.getElementById("popup"), + autoPan: true, + autoPanAnimation: { + duration: 250 + } + })); + map = new ol.Map({ + layers: [ + new ol.layer.Tile({ + preload: Infinity, + source: new ol.source.OSM({ + attributions: [ + new ol.Attribution({ + html: '<a href="https://metager.de/impressum">Impressum</a>' + }), + new ol.Attribution({ + html: 'All search results © ' + '<a href="http://nominatim.openstreetmap.org/">Nominatim</a>' + }), + ol.source.OSM.ATTRIBUTION, + ], + url: 'https://maps.metager.de/osm_tiles/{z}/{x}/{y}.png' + }) + }) + ], + target: 'map', + controls: ol.control.defaults({ + attributionOptions: /** @type {olx.control.AttributionOptions} */ ({ + collapsible: true + }) + }), + overlays: [popupOverlay], + view: new ol.View({ + maxZoom: 18, + minZoom: 6, + center: ol.proj.transform( + [10.06897, 51.37247], 'EPSG:4326', 'EPSG:3857'), + zoom: 6 + }), + loadTilesWhileAnimating: true, + loadTilesWhileInteracting: true + }); + map.addControl(new ol.control.ZoomSlider()); + $("#popup-closer").click(function() { + popupOverlay.setPosition(undefined); + $(this).blur(); + return false; + }); +} +/** + * This function sends a request to our Nominatim instance and evaluates the given coordinates to an adress + * @param {Float} lon + * @param {Float} lat + * @return {Array} adress + */ +function getNearest(lon, lat) { + var url = "https://maps.metager.de/nominatim/reverse.php?format=json&lat=" + lat + "&lon=" + lon + "&zoom=18"; + // Send the Request + $.get(url, function(data) { + if (typeof data !== "undefined" && typeof data["address"] !== "undefined") { + // Success we have an address + var address = data["address"]; + + var road = getRoad(address); + var house_number = getHouseNumber(address); + var city = getCity(address); + var id = data["place_id"]; + + var url = "/route/start/foot/"+lon+","+lat; + + var popup = $("\ + <div class=\"result col-xs-12\">\ + <p class=\"address\">" + road + " " + house_number + "</p>\ + <p class=\"city\">" + city + "</p>\ + <p class=\"address\">Longitude: " + lon + "</p>\ + <p class=\"address\">Latitude: " + lat + "</p>\ + <a href=\"https://maps.metager.de/nominatim/details.php?place_id=" + id + "\" target=\"_blank\" class=\"btn btn-default btn-xs\">Details</a>\ + <a href=\""+url+"\" class=\"btn btn-default btn-xs\">Route berechnen</a>\ + </div>"); + + // And now we can show the Popup where the user clicked + createPopup(lon, lat, popup); + } + }); +} + +function deinitResults() { + toggleResults("out"); + $("#results").addClass("hidden"); + $("#closer").addClass("hidden"); + $("#results").html(""); + updateMapSize(); + initStartNavigation(); +} +var routeLineStyle = new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: 'rgb(255,0,0)', + width: 5 + }), + fill: new ol.style.Fill({ + color: 'rgba(255,0,0,.03)' + }) +}); +var route = {}; +var routeLayer = null; +var routeMarkers = []; +function start(){ + var pointString = points; + if(points.match(/gps/) !== null){ + var pos = gpsLocation; + pointString = points; + if(pos !== null){ + pointString = pointString.replace(/gps/, pos.toString()); + }else{ + pointString = pointString.replace(/;{0,1}gps;{0,1}/, ';'); + } + } + var url = '/route/find/' + vehicle + '/' + pointString; + $.getJSON(url, function(response) { + route = response; + }).success(function() { + // If the Route could be loaded and there is a route between the points we can show it: + if (typeof route["code"] !== 'undefined' && route["code"] === "Ok" && route["routes"].length >= 1) { + deinitSearchBox(); + addGraphics(); + addResults(); + initResults(); + } + }); +}; + +function addResults() { + $("#results").html(""); + var vehicleChooser = $("<div id=\"vehicle-chooser\">\ + <label class=\"radio-inline\">\ + <input type=\"radio\" name=\"vehicle\" value=\"foot\"> <div><img src=\"/img/silhouette-walk.png\" height=\"20px\" /></div>\ + </label>\ + <label class=\"radio-inline\" >\ + <input type=\"radio\" name=\"vehicle\" value=\"bicycle\"> <div><img src=\"/img/bike.png\" height=\"20px\" /></div>\ + </label>\ + <label class=\"radio-inline\">\ + <input type=\"radio\" name=\"vehicle\" value=\"car\"> <div><img src=\"/img/car.png\" height=\"20px\" /></div>\ + </label>\ + </div>\ + <div id=\"route-content\">\ + </div>\ + "); + $("#results").append(vehicleChooser); + $(vehicleChooser).find("input[value=" + vehicle + "]").prop("checked", true); + // Add the changed Listener to the vehicle Chooser: + $(vehicleChooser).find("input[type=radio]").change(function() { + vehicle = $(vehicleChooser).find("input[type=radio]:checked").val(); + var url = '/route/start/' + vehicle + '/'; + $.each(route["waypoints"], function(index, value) { + url += value["location"].toString() + ";"; + }); + url = url.replace(/;+$/, ''); + document.location.href = url; + }); + // We should add a Place to display Informations About the Route + var routeInformation = $('<div id="route-information" class="row"><div id="length" class="col-md-6"></div><div id="duration" class="col-md-6"></div></div>') + $("#route-content").prepend(routeInformation); + // Add Button for starting the route assistent + if(points.match(/^gps/) !== null){ + var routeAssistent = $('<div class="container-fluid"><div class="row"><div class="col-xs-12"><a id="route-assistent" href="javascript:updateCurrentLocation(startAssistent);">Routenführung starten</a></div></div></div>'); + $("#route-content").prepend(routeAssistent); + } + addRouteMetaData(); + insertSteps(); +} + +function insertSteps() { + var takenRoute = route["routes"][0]; + var stepList = $("<table id=\"routing-steps\" class=\"table\"></table>"); + // Parse Each Leg + $.each(takenRoute["legs"], function(legIndex, leg) { + // Parse all steps for the Leg + $.each(leg["steps"], function(stepIndex, step) { + var lon = step["maneuver"]["location"][0]; + var lat = step["maneuver"]["location"][1]; + var maneuver = $("<tr class=\"step\" data-lon=\"" + lon + "\" data-lat=\"" + lat + "\"></tr>"); + var directionImg = $("<td class=\"step-image\"></td>"); + var img = parseImg(step); + if (img !== "") { + $(directionImg).append("<img height=\"35px\" src=\"" + img + "\" />"); + } + $(maneuver).append(directionImg); + var stepString = parseManeuver(step["maneuver"], takenRoute, legIndex, stepIndex); + //$(maneuver).append($("<div class=\"col-xs-8\">" + stepString + "</p>")); + $(maneuver).append($("<td>" + stepString + "</td>")); + var distance = parseFloat(step["distance"]); + distance = Math.ceil(distance); + if (distance > 1000) { + distance /= 1000; + distance = Math.round(distance * 10) / 10; + distance = distance + " km"; + } else { + distance = distance + " m"; + } + if (step["maneuver"]["type"] === "arrive") { + distance = ""; + } + $(maneuver).append($("<td>" + distance + "</td>")); + $(maneuver).mouseover(function() { + var lon = parseFloat($(this).attr("data-lon")); + var lat = parseFloat($(this).attr("data-lat")); + var layer = addPoint(lon, lat); + $(maneuver).mouseout(function() { + map.removeLayer(layer); + }); + $(maneuver).click(function() { + var point = ol.proj.transform([lon, lat], 'EPSG:4326', 'EPSG:3857'); + map.getView().setCenter(point); + map.getView().setZoom(16); + }); + }); + $(stepList).append(maneuver); + }); + }); + $("#route-content").append(stepList); +} + +function parseImg(step) { + switch (step["maneuver"]["type"]) { + case "depart": + case "new name": + return "/img/straight.png"; + case "roundabout turn": + case "continue": + case "end of road": + case "turn": + switch (step["maneuver"]["modifier"]) { + case "left": + return "/img/turn-left.png"; + case "sharp left": + return "/img/turn-sharp-left.png"; + case "right": + return "/img/turn-right.png"; + case "sharp right": + return "/img/turn-sharp-right.png"; + case "uturn": + return "/img/uturn.png"; + case "slight right": + return "/img/fork-slight-right.png"; + case "slight left": + return "/img/fork-slight-left.png"; + case "straight": + return "/img/straight.png"; + default: + // console.log(step); + } + break; + case "roundabout": + case "rotary": + return "/img/roundabout.png"; + case "merge": + case "off ramp": + case "fork": + switch (step["maneuver"]["modifier"]) { + case "left": + return "/img/fork-left.png"; + case "right": + return "/img/fork-right.png"; + case "slight right": + return "/img/fork-slight-right.png"; + case "slight left": + return "/img/fork-slight-left.png"; + case "straight": + return "/img/straight.png"; + default: + // console.log(step); + } + default: + // console.log(step); + } + return ""; +} + +function addPoint(lon, lat) { + var layer = new ol.layer.Vector({ + source: new ol.source.Vector({ + features: [new ol.Feature({ + geometry: new ol.geom.Point(ol.proj.transform([lon, lat], 'EPSG:4326', 'EPSG:3857')) + })] + }) + }); + map.addLayer(layer); + return layer; +} + +function addRouteMetaData() { + var distance = route["routes"][0]["distance"]; + var duration = route["routes"][0]["duration"]; + $("#route-information #length").html(parseDistance(distance)); + $("#route-information #duration").html(parseDuration(duration)); +} + +function parseManeuver(maneuver, takenRoute, legIndex, stepIndex) { + //console.log(maneuver); + var stepString = ""; + var type = maneuver["type"]; + var modifier = maneuver["modifier"]; + var targetStreet = takenRoute["legs"][legIndex]["steps"][stepIndex]["name"]; + var destinations = takenRoute["legs"][legIndex]["steps"][stepIndex]["destinations"]; + if (!destinations) { + destinations = takenRoute["legs"][legIndex]["steps"][stepIndex]["ref"] + } + switch (type) { + case "depart": + var direction = parseBearing(maneuver["bearing_after"]); + var start = takenRoute["legs"][legIndex]["steps"][stepIndex]["name"]; + stepString = "Auf " + start + " nach " + direction; + var nextStreet = takenRoute["legs"][legIndex]["steps"][stepIndex + 1]["name"]; + if (nextStreet !== start) { + stepString += " Richtung " + nextStreet; + } + break; + case "roundabout turn": + case "continue": + case "end of road": + case "turn": + var direction = ""; + if (maneuver["modifier"] === "uturn") { + stepString = "Bei " + targetStreet + " wenden"; + } else { + var modifier = parseModifier(maneuver["modifier"]); + if (modifier !== "Weiter") { + modifier += " abbiegen"; + } + stepString = modifier; + } + if (targetStreet !== "") { + stepString += " auf " + targetStreet; + } + break; + case "roundabout": + case "rotary": + stepString = "Im Kreisverkehr "; + if (maneuver["exit"] !== null) { + stepString += "die "; + switch (parseInt(maneuver["exit"])) { + case 1: + stepString += "erste "; + break; + case 2: + stepString += "zweite "; + break; + case 3: + stepString += "dritte "; + break; + case 4: + stepString += "vierte "; + break; + case 5: + stepString += "fünfte "; + break; + case 6: + stepString += "sechste "; + break; + case 7: + stepString += "siebte "; + break; + case 8: + stepString += "achte "; + break; + case 9: + stepString += "neunte "; + break; + } + stepString += "Ausfahrt nehmen." + if (destinations) { + stepString += " <nobr>(" + destinations + ")</nobr>"; + } + } + break; + case "arrive": + var mod = parseModifier(modifier); + if (mod === undefined) { + stepString = "Sie haben das Ziel erreicht."; + } else { + stepString = "Das Ziel befindet sich " + mod; + } + break; + case "new name": + stepString = "Weiter auf " + targetStreet; + break; + case "merge": + var mod = parseModifier(modifier); + stepString = mod + " auffahren."; + if (targetStreet && !destinations) { + stepString = mod + " auffahren auf " + targetStreet + "."; + } else if (targetStreet && destinations) { + stepString = mod + " halten um auf " + targetStreet + " (" + destinations + ") aufzufahren."; + } + break; + case "off ramp": + case "fork": + var mod = parseModifier(modifier); + stepString = "An der Gabelung " + mod + " halten."; + if (targetStreet && !destinations) { + stepString = "An der Gabelung " + mod + " halten. Richtung " + targetStreet; + } else if (!targetStreet && destinations) { + stepString = "An der Gabelung " + mod + " halten. (" + destinations + ")"; + } else if (targetStreet && destinations) { + stepString = "An der Gabelung " + mod + " halten. Richtung " + targetStreet + " (" + destinations + ")"; + } + break; + default: + console.log(takenRoute["legs"][legIndex]["steps"][stepIndex]); + stepString = "Konnte diesen Schritt nicht zu einem String auswerten"; + } + return stepString; +} + +function parseModifier(modifier) { + var direction = ""; + switch (modifier) { + case undefined: + direction = undefined; + break; + case "sharp right": + direction = "Scharf rechts"; + break; + case "right": + direction = "Rechts"; + break; + case "slight right": + direction = "Leicht Rechts"; + break; + case "straight": + direction = "Weiter"; + break; + case "slight left": + direction = "Leicht Links"; + break; + case "left": + direction = "Links"; + break; + case "sharp left": + direction = "Scharf Links"; + break; + default: + direction = "Konnte Richtungs-Modifizierer nicht auswerten: " + modifier; + } + return direction; +} + +function parseBearing(bearing) { + bearing = parseFloat(bearing); + if ((bearing >= 0 && bearing < 22.5) || bearing >= 337.5) { + return "Norden"; + } else if (bearing >= 22.5 && bearing < 67.5) { + return "Nordosten"; + } else if (bearing >= 67.5 && bearing < 112.5) { + return "Osten"; + } else if (bearing >= 112.5 && bearing < 157.5) { + return "Südosten"; + } else if (bearing >= 157.5 && bearing < 202.5) { + return "Süden"; + } else if (bearing >= 202.5 && bearing < 247.5) { + return "Südwesten"; + } else if (bearing >= 247.5 && bearing < 292.5) { + return "Westen"; + } else if (bearing >= 292.5 && bearing < 337.5) { + return "Nordwesten"; + } +} + +function parseDistance(distance) { + distance = parseFloat(distance); + distance /= 1000; + var km = Math.round(distance * 10) / 10; + return km + " km"; +} + +function parseDuration(duration) { + duration = Math.floor(parseFloat(duration)); + var hours = 0; + if (duration > 3600) { + hours = Math.floor(duration / 3600); + duration = duration % 3600; + } + var minute = 0; + if (duration > 60) { + minute = Math.round(duration / 60); + duration = duration % 60; + } + var result = ""; + if (hours > 0) { + result += hours + " Std."; + } + if (minute > 0) { + result += " " + minute + " Min."; + } + return result; +} + +function addGraphics() { // We collect the minimal Position and the maximum Position within the route so we can Adjust the View: + var minPos = []; + var maxPos = []; + var vectorS = new ol.source.Vector(); + // We will show the first Route: + $.each(route["routes"][0]["legs"], function(index, value) { + // For each leg we collect all the steps: + $.each(value["steps"], function(index, value) { + var geojson = value["geometry"]; + // Let's look through the Points to find the minimal + $.each(geojson["coordinates"], function(index, value) { + if (typeof minPos[0] === "undefined" || parseFloat(value[0]) < minPos[0]) { + minPos[0] = value[0]; + } else if (typeof maxPos[0] === "undefined" || parseFloat(value[0]) > maxPos[0]) { + maxPos[0] = value[0]; + } + if (typeof minPos[1] === "undefined" || parseFloat(value[1]) < minPos[1]) { + minPos[1] = value[1]; + } else if (typeof maxPos[1] === "undefined" || parseFloat(value[1]) > maxPos[1]) { + maxPos[1] = value[1]; + } + }); + var geom = (new ol.format.GeoJSON()).readGeometry(geojson, { + 'dataProjection': 'EPSG:4326', + 'featureProjection': 'EPSG:3857' + }); + var feature = new ol.Feature({ + 'geometry': geom + }); + feature.setStyle(routeLineStyle); + // feature.setId(index); + vectorS.addFeature(feature); + }); + }); + adjustViewBoundingBox(minPos, maxPos); + if(routeLayer !== null){ + // Remove old Features + map.removeLayer(routeLayer); + } + // Remove old Markers + $.each(routeMarkers, function(index, value){ + map.removeOverlay(value); + }); + // add Features + + routeLayer = new ol.layer.Vector({ + source: vectorS + }); + map.addLayer(routeLayer); + // We should add some Pins to the Waypoint Locations + $.each(route["waypoints"], function(index, value) { + // This will work upto an index of 25 + // Caharacter Representation of the index: + var chr = String.fromCharCode(65 + index); + // So now the Pin + var el = $('<span id="' + chr + '" class="marker">' + chr + '</span>'); + var pos = ol.proj.transform([parseFloat(value["location"][0]), parseFloat(value["location"][1])], 'EPSG:4326', 'EPSG:3857'); + routeMarkers.push(addMarker(el, pos)); + }); +} + +function startAssistent() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/js/routeAssistent.js', { + scope: '/' + }).then(function(reg) { + // registration worked + console.log('Registration succeeded. Scope is ' + reg.scope); + }).catch(function(error) { + // registration failed + console.error('Registration failed with ' + error); + }); + }; +} +var followingId = null; +var positions = []; +var drivenRoute = null; +var routeAssistentVectorSource = new ol.source.Vector(); +var waypoints = []; + +function startAssistent() { + if (gps && points.match(/^gps;/) !== null) { + positions = []; + initWaypoints(); + prepareInterface(); + initAssistentGraphics(); + reloadRoute(); + startLocationFollowing(); + } +} + +function initWaypoints() { + var result = points.split(';'); + $.each(result, function(index, value) { + if (value === 'gps') { + waypoints.push('gps'); + } else { + waypoints.push({ + hint: '', + lon: parseFloat(value.split(',')[0]), + lat: parseFloat(value.split(',')[1]) + }); + } + }); +} + +function initAssistentGraphics() { + // Set The Route Layer to the new one: + routeLayer.setSource(routeAssistentVectorSource); + // Remove old Markers + $.each(routeMarkers, function(index, value) { + map.removeOverlay(value); + }); +} + +function prepareInterface() { + // Change Navigation-Bar to make it show the next Steps + // Hide Navigation Bar + $("nav").removeClass("navbar"); + $("nav").removeClass("navbar-default"); + // Create the Layout for the next Step: + var nextStep = $('\ + <div id="route-content">\ + <div id="route-information" class="row">\ + <div id="length" class="col-xs-6"></div>\ + <div id="duration" class="col-xs-6"></div>\ + </div>\ + <table id="routing-steps" class="table">\ + <tbody>\ + <tr class="step">\ + <td class="step-image"><img height="35px" src=""></td>\ + <td class="step-string"></td>\ + <td class="step-length"></td>\ + </tr>\ + </tbody>\ + </table>\ + </div>\ + '); + $("nav").html(nextStep); + //Hide Results + deinitResults(); + // Remove Zoom Bar + $(".ol-zoom, .ol-zoomslider, #location-tool").addClass("hidden"); + //Update Map Size + updateMapSize(); +} + +function deinitAssistent() { + window.location.reload(); +} +var currentPosition = {}; +var calculating = false; + +function startLocationFollowing() { + if (followingId === null) { + options = { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0 + }; + followingId = navigator.geolocation.watchPosition(function(position) { + var timestamp = Math.floor(position.timestamp / 1000); + var lon = parseFloat(position.coords.longitude); + var lat = parseFloat(position.coords.latitude); + var accuracy = Math.max(position.coords.accuracy, 0.3); + var newPosition = { + timestamp: timestamp, + lon: lon, + lat: lat, + accuracy: accuracy + }; + var dist = getDistance(currentPosition, newPosition); + var minDist = 0; + switch (vehicle) { + case "car": + minDist = 20; + break; + case "bicycle": + minDist = 10; + break; + case "foot": + minDist = 5; + break; + } + if (dist <= minDist || calculating) { + return; + } else { + calculating = true; + } + currentPosition = { + timestamp: timestamp, + lon: lon, + lat: lat, + accuracy: accuracy + }; + positions.push({ + timestamp: timestamp, + lon: lon, + lat: lat, + accuracy: accuracy + }); + if (positions.length >= 2) { + // Generate url to retrieve + var positionstring = ""; + var timestampstring = ""; + var radiusstring = ""; + $.each(positions, function(index, value) { + positionstring += value.lon + "," + value.lat + ";"; + timestampstring += value.timestamp + ";"; + radiusstring += value.accuracy + ";"; + }); + positionstring = positionstring.replace(/;+$/, ''); + timestampstring = timestampstring.replace(/;+$/, ''); + radiusstring = radiusstring.replace(/;+$/, ''); + var url = '/route/match/' + vehicle + '/' + positionstring + '/' + timestampstring + '/' + radiusstring; + $.getJSON(url, function(r) { + // Aus den "gesnappten" Koordinaten berechnen wir nun die gefahrene Route + if (r.code === "Ok" && r.tracepoints !== null && r.tracepoints[r.tracepoints.length - 1] !== null) { + var position = { + hint: r.tracepoints[r.tracepoints.length - 1].hint, + lon: parseFloat(r.tracepoints[r.tracepoints.length - 1].location[0]), + lat: parseFloat(r.tracepoints[r.tracepoints.length - 1].location[1]) + } + // Wir nehmen vorerst immer das Beste Matching für die gefahrene Route. + // Vielleicht fällt mir später etwas besseres ein + if (r.matchings !== null && r.matchings[0] !== undefined) { + // Clear the shown Route + routeAssistentVectorSource.clear(); + // Add the driven Route + drawGeojson(r.matchings[0].geometry, 'grey'); + // Get the Route until the target + var hintsComplete = true; + var pointString = ""; + var hintString = ""; + $.each(waypoints, function(index, value) { + if (value === 'gps') { + pointString += position.lon + "," + position.lat + ";"; + hintString += position.hint + ";"; + } else { + pointString += value.lon + "," + value.lat + ";"; + if (value.hint !== "") { + hintString += value.hint + ";"; + } else { + hintsComplete = false; + } + } + }); + pointString = pointString.replace(/;$/, ''); + hintString = hintString.replace(/;$/, ''); + var url = '/route/find/' + vehicle + '/' + pointString; + if (hintsComplete) { + url += "/" + hintString; + } + $.getJSON(url, function(data) { + if (data.code === "Ok") { + drawGeojson(data.routes[0].geometry, 'red'); + // Add the information for the next steps and route meta data + // Route Metadata + var duration = parseFloat(data.routes[0].duration); + duration = parseDuration(duration) + $("nav #route-information #duration").html(duration); + var distance = parseFloat(data.routes[0].distance) + distance = parseDistance(distance); + $("nav #route-information #length").html(distance); + // Next Step + var step = data.routes[0].legs[0].steps[1]; + var stepString = parseManeuver(step["maneuver"], data.routes[0], 0, 1); + $("nav #routing-steps .step-string").html(stepString); + var img = parseImg(step); + $("nav #routing-steps .step-image img").attr('src', img); + var stepDistance = parseDistance(parseFloat(data.routes[0].legs[0].steps[0].distance)); + $("nav #routing-steps .step-length").html(stepDistance); + // So now that everything is Drawn we adjust the Map View + // The new Rotation is The bearing of the first step of the route: + var rotation = parseInt(data.routes[0].legs[0].steps[0].maneuver.bearing_after); + rotation = 360 - rotation; + // The Value needs to be in Radians + rotation *= Math.PI; + rotation /= 180; + map.getView().setRotation(rotation); + map.getView().setZoom(18); + map.getView().setCenter(ol.proj.transform([position.lon, position.lat], 'EPSG:4326', 'EPSG:3857')); + } + }).always(function() { + calculating = false; + }); + }else{ + calculating = false; + } + }else{ + calculating = false; + } + }); + }else{ + calculating = false; + } + }, function(error) { + // Follow Location couldn't be started. Abort now + deinitAssistent(); + }, options); + } +} +var rad = function(x) { + return x * Math.PI / 180; +}; +var getDistance = function(p1, p2) { + var R = 6378137; // Earth’s mean radius in meter + var dLat = rad(p2.lat - p1.lat); + var dLong = rad(p2.lon - p1.lon); + var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(rad(p1.lat)) * Math.cos(rad(p2.lat)) * Math.sin(dLong / 2) * Math.sin(dLong / 2); + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + var d = R * c; + return d; // returns the distance in meter +}; + +function removeRoute() { + if (routeLayer !== null) { + // Remove old Features + map.removeLayer(routeLayer); + } + // Remove old Markers + $.each(routeMarkers, function(index, value) { + map.removeOverlay(value); + }); + vectorS = new ol.source.Vector(); +} + +function drawGeojson(geojson, lineColor) { + var drivenRouteLineStyle = new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: lineColor, + width: 5 + }), + fill: new ol.style.Fill({ + color: lineColor + }) + }); + var geom = (new ol.format.GeoJSON()).readGeometry(geojson, { + 'dataProjection': 'EPSG:4326', + 'featureProjection': 'EPSG:3857' + }); + var feature = new ol.Feature({ + 'geometry': geom + }); + feature.setStyle(drivenRouteLineStyle); + routeAssistentVectorSource.addFeature(feature); +} + +function reloadRoute() { + if (points.match(/gps/) !== null) { + var pos = gpsLocation; + var pointString = points; + if (pos !== null) { + pointString = pointString.replace(/gps/, pos.toString()); + } else { + pointString = pointString.replace(/;{0,1}gps;{0,1}/, ';'); + } + } + var url = '/route/find/' + vehicle + '/' + pointString; + $.getJSON(url, function(response) { + route = response; + // Add The Hints to the waypoints: + $.each(route.waypoints, function(index, value) { + if (waypoints[index] !== 'gps') { + waypoints[index].hint = value.hint; + } + }); + }).success(function() { + //addGraphics(); + }); +} +//# sourceMappingURL=routing.js.map -- GitLab