1 /*! jQuery Geo - vtest - 2012-11-02
3 * Copyright (c) 2012 Ryan Westphal/Applied Geographics, Inc.; Licensed MIT, GPL */
5 // Copyright 2006 Google Inc.
7 // Licensed under the Apache License, Version 2.0 (the "License");
8 // you may not use this file except in compliance with the License.
9 // You may obtain a copy of the License at
11 // http://www.apache.org/licenses/LICENSE-2.0
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
22 // * Patterns only support repeat.
23 // * Radial gradient are not implemented. The VML version of these look very
24 // different from the canvas one.
25 // * Clipping paths are not implemented.
26 // * Coordsize. The width and height attribute have higher priority than the
27 // width and height style values which isn't correct.
28 // * Painting mode isn't implemented.
29 // * Canvas width/height should is using content-box by default. IE in
30 // Quirks mode will draw the canvas using border-box. Either change your
32 // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
33 // or use Box Sizing Behavior from WebFX
34 // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
35 // * Non uniform scaling does not correctly scale strokes.
36 // * Optimize. There is always room for speed improvements.
38 // Only add this code if we do not already have a canvas implementation
39 if (!document.createElement('canvas').getContext) {
43 // alias some functions to make (compiled) code shorter
51 // this is used for sub pixel precision
55 var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
58 * This funtion is assigned to the <canvas> elements as element.getContext().
60 * @return {CanvasRenderingContext2D_}
62 function getContext() {
63 return this.context_ ||
64 (this.context_ = new CanvasRenderingContext2D_(this));
67 var slice = Array.prototype.slice;
70 * Binds a function to an object. The returned function will always use the
71 * passed in {@code obj} as {@code this}.
75 * g = bind(f, obj, a, b)
76 * g(c, d) // will do f.call(obj, a, b, c, d)
78 * @param {Function} f The function to bind the object to
79 * @param {Object} obj The object that should act as this when the function
81 * @param {*} var_args Rest arguments that will be used as the initial
82 * arguments when the function is called
83 * @return {Function} A new function that has bound this
85 function bind(f, obj, var_args) {
86 var a = slice.call(arguments, 2);
88 return f.apply(obj, a.concat(slice.call(arguments)));
92 function encodeHtmlAttribute(s) {
93 return String(s).replace(/&/g, '&').replace(/"/g, '"');
96 function addNamespace(doc, prefix, urn) {
97 if (!doc.namespaces[prefix]) {
98 doc.namespaces.add(prefix, urn, '#default#VML');
102 function addNamespacesAndStylesheet(doc) {
103 addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
104 addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
106 // Setup default CSS. Only add one style sheet per document
107 if (!doc.styleSheets['ex_canvas_']) {
108 var ss = doc.createStyleSheet();
109 ss.owningElement.id = 'ex_canvas_';
110 ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
111 // default size is 300x150 in Gecko and Opera
112 'text-align:left;width:300px;height:150px}';
116 // Add namespaces and stylesheet at startup.
117 addNamespacesAndStylesheet(document);
119 var G_vmlCanvasManager_ = {
120 init: function (opt_doc) {
121 var doc = opt_doc || document;
122 // Create a dummy element so that IE will allow canvas elements to be
124 doc.createElement('canvas');
125 doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
128 init_: function (doc) {
129 // find all canvas elements
130 var els = doc.getElementsByTagName('canvas');
131 for (var i = 0; i < els.length; i++) {
132 this.initElement(els[i]);
137 * Public initializes a canvas element so that it can be used as canvas
138 * element from now on. This is called automatically before the page is
139 * loaded but if you are creating elements using createElement you need to
140 * make sure this is called on the element.
141 * @param {HTMLElement} el The canvas element to initialize.
142 * @return {HTMLElement} the element that was created.
144 initElement: function (el) {
145 if (!el.getContext) {
146 el.getContext = getContext;
148 // Add namespaces and stylesheet to document of the element.
149 addNamespacesAndStylesheet(el.ownerDocument);
151 // Remove fallback content. There is no way to hide text nodes so we
152 // just remove all childNodes. We could hide all elements and remove
153 // text nodes but who really cares about the fallback content.
156 // do not use inline function because that will leak memory
157 el.attachEvent('onpropertychange', onPropertyChange);
158 el.attachEvent('onresize', onResize);
160 var attrs = el.attributes;
161 if (attrs.width && attrs.width.specified) {
162 // TODO: use runtimeStyle and coordsize
163 // el.getContext().setWidth_(attrs.width.nodeValue);
164 el.style.width = attrs.width.nodeValue + 'px';
166 el.width = el.clientWidth;
168 if (attrs.height && attrs.height.specified) {
169 // TODO: use runtimeStyle and coordsize
170 // el.getContext().setHeight_(attrs.height.nodeValue);
171 el.style.height = attrs.height.nodeValue + 'px';
173 el.height = el.clientHeight;
175 //el.getContext().setCoordsize_()
181 function onPropertyChange(e) {
182 var el = e.srcElement;
184 switch (e.propertyName) {
186 el.getContext().clearRect();
187 el.style.width = el.attributes.width.nodeValue + 'px';
188 // In IE8 this does not trigger onresize.
189 el.firstChild.style.width = el.clientWidth + 'px';
192 el.getContext().clearRect();
193 el.style.height = el.attributes.height.nodeValue + 'px';
194 el.firstChild.style.height = el.clientHeight + 'px';
199 function onResize(e) {
200 var el = e.srcElement;
202 el.firstChild.style.width = el.clientWidth + 'px';
203 el.firstChild.style.height = el.clientHeight + 'px';
207 G_vmlCanvasManager_.init();
209 // precompute "00" to "FF"
211 for (var i = 0; i < 16; i++) {
212 for (var j = 0; j < 16; j++) {
213 decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
217 function createMatrixIdentity() {
225 function matrixMultiply(m1, m2) {
226 var result = createMatrixIdentity();
228 for (var x = 0; x < 3; x++) {
229 for (var y = 0; y < 3; y++) {
232 for (var z = 0; z < 3; z++) {
233 sum += m1[x][z] * m2[z][y];
242 function copyState(o1, o2) {
243 o2.fillStyle = o1.fillStyle;
244 o2.lineCap = o1.lineCap;
245 o2.lineJoin = o1.lineJoin;
246 o2.lineWidth = o1.lineWidth;
247 o2.miterLimit = o1.miterLimit;
248 o2.shadowBlur = o1.shadowBlur;
249 o2.shadowColor = o1.shadowColor;
250 o2.shadowOffsetX = o1.shadowOffsetX;
251 o2.shadowOffsetY = o1.shadowOffsetY;
252 o2.strokeStyle = o1.strokeStyle;
253 o2.globalAlpha = o1.globalAlpha;
255 o2.textAlign = o1.textAlign;
256 o2.textBaseline = o1.textBaseline;
257 o2.arcScaleX_ = o1.arcScaleX_;
258 o2.arcScaleY_ = o1.arcScaleY_;
259 o2.lineScale_ = o1.lineScale_;
263 // aliceblue: '#F0F8FF',
264 // antiquewhite: '#FAEBD7',
265 // aquamarine: '#7FFFD4',
268 // bisque: '#FFE4C4',
270 // blanchedalmond: '#FFEBCD',
271 // blueviolet: '#8A2BE2',
273 // burlywood: '#DEB887',
274 // cadetblue: '#5F9EA0',
275 // chartreuse: '#7FFF00',
276 // chocolate: '#D2691E',
278 // cornflowerblue: '#6495ED',
279 // cornsilk: '#FFF8DC',
280 // crimson: '#DC143C',
282 // darkblue: '#00008B',
283 // darkcyan: '#008B8B',
284 // darkgoldenrod: '#B8860B',
285 // darkgray: '#A9A9A9',
286 // darkgreen: '#006400',
287 // darkgrey: '#A9A9A9',
288 // darkkhaki: '#BDB76B',
289 // darkmagenta: '#8B008B',
290 // darkolivegreen: '#556B2F',
291 // darkorange: '#FF8C00',
292 // darkorchid: '#9932CC',
293 // darkred: '#8B0000',
294 // darksalmon: '#E9967A',
295 // darkseagreen: '#8FBC8F',
296 // darkslateblue: '#483D8B',
297 // darkslategray: '#2F4F4F',
298 // darkslategrey: '#2F4F4F',
299 // darkturquoise: '#00CED1',
300 // darkviolet: '#9400D3',
301 // deeppink: '#FF1493',
302 // deepskyblue: '#00BFFF',
303 // dimgray: '#696969',
304 // dimgrey: '#696969',
305 // dodgerblue: '#1E90FF',
306 // firebrick: '#B22222',
307 // floralwhite: '#FFFAF0',
308 // forestgreen: '#228B22',
309 // gainsboro: '#DCDCDC',
310 // ghostwhite: '#F8F8FF',
312 // goldenrod: '#DAA520',
314 // greenyellow: '#ADFF2F',
315 // honeydew: '#F0FFF0',
316 // hotpink: '#FF69B4',
317 // indianred: '#CD5C5C',
318 // indigo: '#4B0082',
321 // lavender: '#E6E6FA',
322 // lavenderblush: '#FFF0F5',
323 // lawngreen: '#7CFC00',
324 // lemonchiffon: '#FFFACD',
325 // lightblue: '#ADD8E6',
326 // lightcoral: '#F08080',
327 // lightcyan: '#E0FFFF',
328 // lightgoldenrodyellow: '#FAFAD2',
329 // lightgreen: '#90EE90',
330 // lightgrey: '#D3D3D3',
331 // lightpink: '#FFB6C1',
332 // lightsalmon: '#FFA07A',
333 // lightseagreen: '#20B2AA',
334 // lightskyblue: '#87CEFA',
335 // lightslategray: '#778899',
336 // lightslategrey: '#778899',
337 // lightsteelblue: '#B0C4DE',
338 // lightyellow: '#FFFFE0',
339 // limegreen: '#32CD32',
341 // magenta: '#FF00FF',
342 // mediumaquamarine: '#66CDAA',
343 // mediumblue: '#0000CD',
344 // mediumorchid: '#BA55D3',
345 // mediumpurple: '#9370DB',
346 // mediumseagreen: '#3CB371',
347 // mediumslateblue: '#7B68EE',
348 // mediumspringgreen: '#00FA9A',
349 // mediumturquoise: '#48D1CC',
350 // mediumvioletred: '#C71585',
351 // midnightblue: '#191970',
352 // mintcream: '#F5FFFA',
353 // mistyrose: '#FFE4E1',
354 // moccasin: '#FFE4B5',
355 // navajowhite: '#FFDEAD',
356 // oldlace: '#FDF5E6',
357 // olivedrab: '#6B8E23',
358 // orange: '#FFA500',
359 // orangered: '#FF4500',
360 // orchid: '#DA70D6',
361 // palegoldenrod: '#EEE8AA',
362 // palegreen: '#98FB98',
363 // paleturquoise: '#AFEEEE',
364 // palevioletred: '#DB7093',
365 // papayawhip: '#FFEFD5',
366 // peachpuff: '#FFDAB9',
370 // powderblue: '#B0E0E6',
371 // rosybrown: '#BC8F8F',
372 // royalblue: '#4169E1',
373 // saddlebrown: '#8B4513',
374 // salmon: '#FA8072',
375 // sandybrown: '#F4A460',
376 // seagreen: '#2E8B57',
377 // seashell: '#FFF5EE',
378 // sienna: '#A0522D',
379 // skyblue: '#87CEEB',
380 // slateblue: '#6A5ACD',
381 // slategray: '#708090',
382 // slategrey: '#708090',
384 // springgreen: '#00FF7F',
385 // steelblue: '#4682B4',
387 // thistle: '#D8BFD8',
388 // tomato: '#FF6347',
389 // turquoise: '#40E0D0',
390 // violet: '#EE82EE',
392 // whitesmoke: '#F5F5F5',
393 // yellowgreen: '#9ACD32'
397 function getRgbHslContent(styleString) {
398 var start = styleString.indexOf('(', 3);
399 var end = styleString.indexOf(')', start + 1);
400 var parts = styleString.substring(start + 1, end).split(',');
401 // add alpha if needed
402 if (parts.length != 4 || styleString.charAt(3) != 'a') {
408 function percent(s) {
409 return parseFloat(s) / 100;
412 function clamp(v, min, max) {
413 return Math.min(max, Math.max(min, v));
416 function hslToRgb(parts) {
417 var r, g, b, h, s, l;
418 h = parseFloat(parts[0]) / 360 % 360;
421 s = clamp(percent(parts[1]), 0, 1);
422 l = clamp(percent(parts[2]), 0, 1);
424 r = g = b = l; // achromatic
426 var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
428 r = hueToRgb(p, q, h + 1 / 3);
429 g = hueToRgb(p, q, h);
430 b = hueToRgb(p, q, h - 1 / 3);
433 return '#' + decToHex[Math.floor(r * 255)] +
434 decToHex[Math.floor(g * 255)] +
435 decToHex[Math.floor(b * 255)];
438 function hueToRgb(m1, m2, h) {
445 return m1 + (m2 - m1) * 6 * h;
449 return m1 + (m2 - m1) * (2 / 3 - h) * 6;
454 var processStyleCache = {};
456 function processStyle(styleString) {
457 if (styleString in processStyleCache) {
458 return processStyleCache[styleString];
463 styleString = String(styleString);
464 if (styleString.charAt(0) == '#') {
466 } else if (/^rgb/.test(styleString)) {
467 var parts = getRgbHslContent(styleString);
469 for (var i = 0; i < 3; i++) {
470 if (parts[i].indexOf('%') != -1) {
471 n = Math.floor(percent(parts[i]) * 255);
475 str += decToHex[clamp(n, 0, 255)];
478 } else if (/^hsl/.test(styleString)) {
479 var parts = getRgbHslContent(styleString);
480 str = hslToRgb(parts);
483 str = /*colorData[styleString] ||*/styleString;
485 return processStyleCache[styleString] = { color: str, alpha: alpha };
488 var DEFAULT_STYLE = {
496 // Internal text style cache
497 // var fontStyleCache = {};
499 // function processFontStyle(styleString) {
500 // if (fontStyleCache[styleString]) {
501 // return fontStyleCache[styleString];
504 // var el = document.createElement('div');
505 // var style = el.style;
507 // style.font = styleString;
509 // // Ignore failures to set to invalid font.
512 // return fontStyleCache[styleString] = {
513 // style: style.fontStyle || DEFAULT_STYLE.style,
514 // variant: style.fontVariant || DEFAULT_STYLE.variant,
515 // weight: style.fontWeight || DEFAULT_STYLE.weight,
516 // size: style.fontSize || DEFAULT_STYLE.size,
517 // family: style.fontFamily || DEFAULT_STYLE.family
521 // function getComputedStyle(style, element) {
522 // var computedStyle = {};
524 // for (var p in style) {
525 // computedStyle[p] = style[p];
528 // // Compute the size
529 // var canvasFontSize = parseFloat(element.currentStyle.fontSize),
530 // fontSize = parseFloat(style.size);
532 // if (typeof style.size == 'number') {
533 // computedStyle.size = style.size;
534 // } else if (style.size.indexOf('px') != -1) {
535 // computedStyle.size = fontSize;
536 // } else if (style.size.indexOf('em') != -1) {
537 // computedStyle.size = canvasFontSize * fontSize;
538 // } else if(style.size.indexOf('%') != -1) {
539 // computedStyle.size = (canvasFontSize / 100) * fontSize;
540 // } else if (style.size.indexOf('pt') != -1) {
541 // computedStyle.size = fontSize / .75;
543 // computedStyle.size = canvasFontSize;
546 // // Different scaling between normal text and VML text. This was found using
547 // // trial and error to get the same size as non VML text.
548 // computedStyle.size *= 0.981;
550 // return computedStyle;
553 // function buildStyle(style) {
554 // return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
555 // style.size + 'px ' + style.family;
563 function processLineCap(lineCap) {
564 return lineCapMap[lineCap] || 'square';
568 * This class implements CanvasRenderingContext2D interface as described by
570 * @param {HTMLElement} canvasElement The element that the 2D context should
573 function CanvasRenderingContext2D_(canvasElement) {
574 this.m_ = createMatrixIdentity();
578 this.currentPath_ = [];
580 // Canvas context properties
581 this.strokeStyle = '#000';
582 this.fillStyle = '#000';
585 this.lineJoin = 'miter';
586 this.lineCap = 'butt';
587 this.miterLimit = Z * 1;
588 this.globalAlpha = 1;
589 //this.font = '10px sans-serif';
590 //this.textAlign = 'left';
591 //this.textBaseline = 'alphabetic';
592 this.canvas = canvasElement;
594 var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' +
595 canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
596 var el = canvasElement.ownerDocument.createElement('div');
597 el.style.cssText = cssText;
598 canvasElement.appendChild(el);
600 var overlayEl = el.cloneNode(false);
601 // Use a non transparent background.
602 overlayEl.style.backgroundColor = 'red';
603 overlayEl.style.filter = 'alpha(opacity=0)';
604 canvasElement.appendChild(overlayEl);
612 var contextPrototype = CanvasRenderingContext2D_.prototype;
613 contextPrototype.clearRect = function () {
614 if (this.textMeasureEl_) {
615 this.textMeasureEl_.removeNode(true);
616 this.textMeasureEl_ = null;
618 this.element_.innerHTML = '';
621 contextPrototype.beginPath = function () {
622 // TODO: Branch current matrix so that save/restore has no effect
623 // as per safari docs.
624 this.currentPath_ = [];
627 contextPrototype.moveTo = function (aX, aY) {
628 var p = getCoords(this, aX, aY);
629 this.currentPath_.push({ type: 'moveTo', x: p.x, y: p.y });
630 this.currentX_ = p.x;
631 this.currentY_ = p.y;
634 contextPrototype.lineTo = function (aX, aY) {
635 var p = getCoords(this, aX, aY);
636 this.currentPath_.push({ type: 'lineTo', x: p.x, y: p.y });
638 this.currentX_ = p.x;
639 this.currentY_ = p.y;
642 contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
645 var p = getCoords(this, aX, aY);
646 var cp1 = getCoords(this, aCP1x, aCP1y);
647 var cp2 = getCoords(this, aCP2x, aCP2y);
648 bezierCurveTo(this, cp1, cp2, p);
651 // Helper function that takes the already fixed cordinates.
652 function bezierCurveTo(self, cp1, cp2, p) {
653 self.currentPath_.push({
654 type: 'bezierCurveTo',
662 self.currentX_ = p.x;
663 self.currentY_ = p.y;
666 contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
667 // the following is lifted almost directly from
668 // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
670 var cp = getCoords(this, aCPx, aCPy);
671 var p = getCoords(this, aX, aY);
674 x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
675 y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
678 x: cp1.x + (p.x - this.currentX_) / 3.0,
679 y: cp1.y + (p.y - this.currentY_) / 3.0
682 bezierCurveTo(this, cp1, cp2, p);
685 contextPrototype.arc = function (aX, aY, aRadius,
686 aStartAngle, aEndAngle, aClockwise) {
688 var arcType = aClockwise ? 'at' : 'wa';
690 var xStart = aX + mc(aStartAngle) * aRadius - Z2;
691 var yStart = aY + ms(aStartAngle) * aRadius - Z2;
693 var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
694 var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
696 // IE won't render arches drawn counter clockwise if xStart == xEnd.
697 if (xStart == xEnd && !aClockwise) {
698 xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
699 // that can be represented in binary
702 var p = getCoords(this, aX, aY);
703 var pStart = getCoords(this, xStart, yStart);
704 var pEnd = getCoords(this, xEnd, yEnd);
706 this.currentPath_.push({ type: arcType,
718 // contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
719 // this.moveTo(aX, aY);
720 // this.lineTo(aX + aWidth, aY);
721 // this.lineTo(aX + aWidth, aY + aHeight);
722 // this.lineTo(aX, aY + aHeight);
726 // contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
727 // var oldPath = this.currentPath_;
730 // this.moveTo(aX, aY);
731 // this.lineTo(aX + aWidth, aY);
732 // this.lineTo(aX + aWidth, aY + aHeight);
733 // this.lineTo(aX, aY + aHeight);
737 // this.currentPath_ = oldPath;
740 // contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
741 // var oldPath = this.currentPath_;
744 // this.moveTo(aX, aY);
745 // this.lineTo(aX + aWidth, aY);
746 // this.lineTo(aX + aWidth, aY + aHeight);
747 // this.lineTo(aX, aY + aHeight);
751 // this.currentPath_ = oldPath;
754 // contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
755 // var gradient = new CanvasGradient_('gradient');
756 // gradient.x0_ = aX0;
757 // gradient.y0_ = aY0;
758 // gradient.x1_ = aX1;
759 // gradient.y1_ = aY1;
763 // contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
765 // var gradient = new CanvasGradient_('gradientradial');
766 // gradient.x0_ = aX0;
767 // gradient.y0_ = aY0;
768 // gradient.r0_ = aR0;
769 // gradient.x1_ = aX1;
770 // gradient.y1_ = aY1;
771 // gradient.r1_ = aR1;
775 // contextPrototype.drawImage = function(image, var_args) {
776 // var dx, dy, dw, dh, sx, sy, sw, sh;
778 // // to find the original width we overide the width and height
779 // var oldRuntimeWidth = image.runtimeStyle.width;
780 // var oldRuntimeHeight = image.runtimeStyle.height;
781 // image.runtimeStyle.width = 'auto';
782 // image.runtimeStyle.height = 'auto';
784 // // get the original size
785 // var w = image.width;
786 // var h = image.height;
788 // // and remove overides
789 // image.runtimeStyle.width = oldRuntimeWidth;
790 // image.runtimeStyle.height = oldRuntimeHeight;
792 // if (arguments.length == 3) {
793 // dx = arguments[1];
794 // dy = arguments[2];
798 // } else if (arguments.length == 5) {
799 // dx = arguments[1];
800 // dy = arguments[2];
801 // dw = arguments[3];
802 // dh = arguments[4];
806 // } else if (arguments.length == 9) {
807 // sx = arguments[1];
808 // sy = arguments[2];
809 // sw = arguments[3];
810 // sh = arguments[4];
811 // dx = arguments[5];
812 // dy = arguments[6];
813 // dw = arguments[7];
814 // dh = arguments[8];
816 // throw Error('Invalid number of arguments');
819 // var d = getCoords(this, dx, dy);
829 // // For some reason that I've now forgotten, using divs didn't work
830 // vmlStr.push(' <g_vml_:group',
831 // ' coordsize="', Z * W, ',', Z * H, '"',
832 // ' coordorigin="0,0"' ,
833 // ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
835 // // If filters are necessary (rotation exists), create them
836 // // filters are bog-slow, so only create them if abbsolutely necessary
837 // // The following check doesn't account for skews (which don't exist
838 // // in the canvas spec (yet) anyway.
840 // if (this.m_[0][0] != 1 || this.m_[0][1] ||
841 // this.m_[1][1] != 1 || this.m_[1][0]) {
844 // // Note the 12/21 reversal
845 // filter.push('M11=', this.m_[0][0], ',',
846 // 'M12=', this.m_[1][0], ',',
847 // 'M21=', this.m_[0][1], ',',
848 // 'M22=', this.m_[1][1], ',',
849 // 'Dx=', mr(d.x / Z), ',',
850 // 'Dy=', mr(d.y / Z), '');
852 // // Bounding box calculation (need to minimize displayed area so that
853 // // filters don't waste time on unused pixels.
855 // var c2 = getCoords(this, dx + dw, dy);
856 // var c3 = getCoords(this, dx, dy + dh);
857 // var c4 = getCoords(this, dx + dw, dy + dh);
859 // max.x = m.max(max.x, c2.x, c3.x, c4.x);
860 // max.y = m.max(max.y, c2.y, c3.y, c4.y);
862 // vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
863 // 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
864 // filter.join(''), ", sizingmethod='clip');");
867 // vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
870 // vmlStr.push(' ">' ,
871 // '<g_vml_:image src="', image.src, '"',
872 // ' style="width:', Z * dw, 'px;',
873 // ' height:', Z * dh, 'px"',
874 // ' cropleft="', sx / w, '"',
875 // ' croptop="', sy / h, '"',
876 // ' cropright="', (w - sx - sw) / w, '"',
877 // ' cropbottom="', (h - sy - sh) / h, '"',
879 // '</g_vml_:group>');
881 // this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
884 contextPrototype.stroke = function (aFill) {
886 var lineOpen = false;
891 lineStr.push('<g_vml_:shape',
892 ' filled="', !!aFill, '"',
893 ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
894 ' coordorigin="0,0"',
895 ' coordsize="', Z * W, ',', Z * H, '"',
896 ' stroked="', !aFill, '"',
900 var min = { x: null, y: null };
901 var max = { x: null, y: null };
903 for (var i = 0; i < this.currentPath_.length; i++) {
904 var p = this.currentPath_[i];
910 lineStr.push(' m ', mr(p.x), ',', mr(p.y));
913 lineStr.push(' l ', mr(p.x), ',', mr(p.y));
919 case 'bezierCurveTo':
921 mr(p.cp1x), ',', mr(p.cp1y), ',',
922 mr(p.cp2x), ',', mr(p.cp2y), ',',
923 mr(p.x), ',', mr(p.y));
927 lineStr.push(' ', p.type, ' ',
928 mr(p.x - this.arcScaleX_ * p.radius), ',',
929 mr(p.y - this.arcScaleY_ * p.radius), ' ',
930 mr(p.x + this.arcScaleX_ * p.radius), ',',
931 mr(p.y + this.arcScaleY_ * p.radius), ' ',
932 mr(p.xStart), ',', mr(p.yStart), ' ',
933 mr(p.xEnd), ',', mr(p.yEnd));
938 // TODO: Following is broken for curves due to
939 // move to proper paths.
941 // Figure out dimensions so we can do gradient fills
944 if (min.x == null || p.x < min.x) {
947 if (max.x == null || p.x > max.x) {
950 if (min.y == null || p.y < min.y) {
953 if (max.y == null || p.y > max.y) {
961 appendStroke(this, lineStr);
963 appendFill(this, lineStr, min, max);
966 lineStr.push('</g_vml_:shape>');
968 this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
971 function appendStroke(ctx, lineStr) {
972 var a = processStyle(ctx.strokeStyle);
974 var opacity = a.alpha * ctx.globalAlpha;
975 var lineWidth = ctx.lineScale_ * ctx.lineWidth;
977 // VML cannot correctly render a line if the width is less than 1px.
978 // In that case, we dilute the color to make the line look thinner.
980 opacity *= lineWidth;
985 ' opacity="', opacity, '"',
986 ' joinstyle="', ctx.lineJoin, '"',
987 ' miterlimit="', ctx.miterLimit, '"',
988 ' endcap="', processLineCap(ctx.lineCap), '"',
989 ' weight="', lineWidth, 'px"',
990 ' color="', color, '" />'
994 function appendFill(ctx, lineStr, min, max) {
995 var fillStyle = ctx.fillStyle;
996 var arcScaleX = ctx.arcScaleX_;
997 var arcScaleY = ctx.arcScaleY_;
998 var width = max.x - min.x;
999 var height = max.y - min.y;
1000 // if (fillStyle instanceof CanvasGradient_) {
1001 // // TODO: Gradients transformed with the transformation matrix.
1003 // var focus = {x: 0, y: 0};
1005 // // additional offset
1007 // // scale factor for offset
1008 // var expansion = 1;
1010 // if (fillStyle.type_ == 'gradient') {
1011 // var x0 = fillStyle.x0_ / arcScaleX;
1012 // var y0 = fillStyle.y0_ / arcScaleY;
1013 // var x1 = fillStyle.x1_ / arcScaleX;
1014 // var y1 = fillStyle.y1_ / arcScaleY;
1015 // var p0 = getCoords(ctx, x0, y0);
1016 // var p1 = getCoords(ctx, x1, y1);
1017 // var dx = p1.x - p0.x;
1018 // var dy = p1.y - p0.y;
1019 // angle = Math.atan2(dx, dy) * 180 / Math.PI;
1021 // // The angle should be a non-negative number.
1026 // // Very small angles produce an unexpected result because they are
1027 // // converted to a scientific notation string.
1028 // if (angle < 1e-6) {
1032 // var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
1034 // x: (p0.x - min.x) / width,
1035 // y: (p0.y - min.y) / height
1038 // width /= arcScaleX * Z;
1039 // height /= arcScaleY * Z;
1040 // var dimension = m.max(width, height);
1041 // shift = 2 * fillStyle.r0_ / dimension;
1042 // expansion = 2 * fillStyle.r1_ / dimension - shift;
1045 // // We need to sort the color stops in ascending order by offset,
1046 // // otherwise IE won't interpret it correctly.
1047 // var stops = fillStyle.colors_;
1048 // stops.sort(function(cs1, cs2) {
1049 // return cs1.offset - cs2.offset;
1052 // var length = stops.length;
1053 // var color1 = stops[0].color;
1054 // var color2 = stops[length - 1].color;
1055 // var opacity1 = stops[0].alpha * ctx.globalAlpha;
1056 // var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
1059 // for (var i = 0; i < length; i++) {
1060 // var stop = stops[i];
1061 // colors.push(stop.offset * expansion + shift + ' ' + stop.color);
1064 // // When colors attribute is used, the meanings of opacity and o:opacity2
1066 // lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
1067 // ' method="none" focus="100%"',
1068 // ' color="', color1, '"',
1069 // ' color2="', color2, '"',
1070 // ' colors="', colors.join(','), '"',
1071 // ' opacity="', opacity2, '"',
1072 // ' g_o_:opacity2="', opacity1, '"',
1073 // ' angle="', angle, '"',
1074 // ' focusposition="', focus.x, ',', focus.y, '" />');
1075 // } else if (fillStyle instanceof CanvasPattern_) {
1076 // if (width && height) {
1077 // var deltaLeft = -min.x;
1078 // var deltaTop = -min.y;
1079 // lineStr.push('<g_vml_:fill',
1081 // deltaLeft / width * arcScaleX * arcScaleX, ',',
1082 // deltaTop / height * arcScaleY * arcScaleY, '"',
1084 // // TODO: Figure out the correct size to fit the scale.
1085 // //' size="', w, 'px ', h, 'px"',
1086 // ' src="', fillStyle.src_, '" />');
1089 var a = processStyle(ctx.fillStyle);
1090 var color = a.color;
1091 var opacity = a.alpha * ctx.globalAlpha;
1092 lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
1097 contextPrototype.fill = function () {
1101 contextPrototype.closePath = function () {
1102 this.currentPath_.push({ type: 'close' });
1105 function getCoords(ctx, aX, aY) {
1108 x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
1109 y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
1113 contextPrototype.save = function () {
1116 this.aStack_.push(o);
1117 this.mStack_.push(this.m_);
1118 this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
1121 contextPrototype.restore = function () {
1122 if (this.aStack_.length) {
1123 copyState(this.aStack_.pop(), this);
1124 this.m_ = this.mStack_.pop();
1128 function matrixIsFinite(m) {
1129 return isFinite(m[0][0]) && isFinite(m[0][1]) &&
1130 isFinite(m[1][0]) && isFinite(m[1][1]) &&
1131 isFinite(m[2][0]) && isFinite(m[2][1]);
1134 function setM(ctx, m, updateLineScale) {
1135 if (!matrixIsFinite(m)) {
1140 if (updateLineScale) {
1141 // Get the line scale.
1142 // Determinant of this.m_ means how much the area is enlarged by the
1143 // transformation. So its square root can be used as a scale factor
1145 var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
1146 ctx.lineScale_ = sqrt(abs(det));
1150 contextPrototype.translate = function (aX, aY) {
1157 setM(this, matrixMultiply(m1, this.m_), false);
1160 // contextPrototype.rotate = function(aRot) {
1161 // var c = mc(aRot);
1162 // var s = ms(aRot);
1170 // setM(this, matrixMultiply(m1, this.m_), false);
1173 contextPrototype.scale = function (aX, aY) {
1174 this.arcScaleX_ *= aX;
1175 this.arcScaleY_ *= aY;
1182 setM(this, matrixMultiply(m1, this.m_), true);
1185 // contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
1192 // setM(this, matrixMultiply(m1, this.m_), true);
1195 // contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
1202 // setM(this, m, true);
1206 * The text drawing function.
1207 * The maxWidth argument isn't taken in account, since no browser supports
1210 // contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
1215 // offset = {x: 0, y: 0},
1218 // var fontStyle = getComputedStyle(processFontStyle(this.font),
1221 // var fontStyleString = buildStyle(fontStyle);
1223 // var elementStyle = this.element_.currentStyle;
1224 // var textAlign = this.textAlign.toLowerCase();
1225 // switch (textAlign) {
1231 // textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
1234 // textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
1237 // textAlign = 'left';
1240 // // 1.75 is an arbitrary number, as there is no info about the text baseline
1241 // switch (this.textBaseline) {
1244 // offset.y = fontStyle.size / 1.75;
1250 // case 'alphabetic':
1251 // case 'ideographic':
1253 // offset.y = -fontStyle.size / 2.25;
1257 // switch(textAlign) {
1263 // left = right = delta / 2;
1267 // var d = getCoords(this, x + offset.x, y + offset.y);
1269 // lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ',
1270 // ' coordsize="100 100" coordorigin="0 0"',
1271 // ' filled="', !stroke, '" stroked="', !!stroke,
1272 // '" style="position:absolute;width:1px;height:1px;">');
1275 // appendStroke(this, lineStr);
1277 // // TODO: Fix the min and max params.
1278 // appendFill(this, lineStr, {x: -left, y: 0},
1279 // {x: right, y: fontStyle.size});
1282 // var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
1283 // m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
1285 // var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
1287 // lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ',
1288 // ' offset="', skewOffset, '" origin="', left ,' 0" />',
1289 // '<g_vml_:path textpathok="true" />',
1290 // '<g_vml_:textpath on="true" string="',
1291 // encodeHtmlAttribute(text),
1292 // '" style="v-text-align:', textAlign,
1293 // ';font:', encodeHtmlAttribute(fontStyleString),
1294 // '" /></g_vml_:line>');
1296 // this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
1299 // contextPrototype.fillText = function(text, x, y, maxWidth) {
1300 // this.drawText_(text, x, y, maxWidth, false);
1303 // contextPrototype.strokeText = function(text, x, y, maxWidth) {
1304 // this.drawText_(text, x, y, maxWidth, true);
1307 // contextPrototype.measureText = function(text) {
1308 // if (!this.textMeasureEl_) {
1309 // var s = '<span style="position:absolute;' +
1310 // 'top:-20000px;left:0;padding:0;margin:0;border:none;' +
1311 // 'white-space:pre;"></span>';
1312 // this.element_.insertAdjacentHTML('beforeEnd', s);
1313 // this.textMeasureEl_ = this.element_.lastChild;
1315 // var doc = this.element_.ownerDocument;
1316 // this.textMeasureEl_.innerHTML = '';
1317 // this.textMeasureEl_.style.font = this.font;
1318 // // Don't use innerHTML or innerText because they allow markup/whitespace.
1319 // this.textMeasureEl_.appendChild(doc.createTextNode(text));
1320 // return {width: this.textMeasureEl_.offsetWidth};
1323 /******** STUBS ********/
1324 // contextPrototype.clip = function() {
1325 // // TODO: Implement
1328 // contextPrototype.arcTo = function() {
1329 // // TODO: Implement
1332 // contextPrototype.createPattern = function(image, repetition) {
1333 // return new CanvasPattern_(image, repetition);
1336 // // Gradient / Pattern Stubs
1337 // function CanvasGradient_(aType) {
1338 // this.type_ = aType;
1345 // this.colors_ = [];
1348 // CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
1349 // aColor = processStyle(aColor);
1350 // this.colors_.push({offset: aOffset,
1351 // color: aColor.color,
1352 // alpha: aColor.alpha});
1355 // function CanvasPattern_(image, repetition) {
1356 // assertImageIsValid(image);
1357 // switch (repetition) {
1361 // this.repetition_ = 'repeat';
1365 // case 'no-repeat':
1366 // this.repetition_ = repetition;
1369 // throwException('SYNTAX_ERR');
1372 // this.src_ = image.src;
1373 // this.width_ = image.width;
1374 // this.height_ = image.height;
1377 function throwException(s) {
1378 throw new DOMException_(s);
1381 // function assertImageIsValid(img) {
1382 // if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
1383 // throwException('TYPE_MISMATCH_ERR');
1385 // if (img.readyState != 'complete') {
1386 // throwException('INVALID_STATE_ERR');
1390 function DOMException_(s) {
1391 this.code = this[s];
1392 this.message = s + ': DOM Exception ' + this.code;
1394 var p = DOMException_.prototype = new Error;
1395 p.INDEX_SIZE_ERR = 1;
1396 p.DOMSTRING_SIZE_ERR = 2;
1397 p.HIERARCHY_REQUEST_ERR = 3;
1398 p.WRONG_DOCUMENT_ERR = 4;
1399 p.INVALID_CHARACTER_ERR = 5;
1400 p.NO_DATA_ALLOWED_ERR = 6;
1401 p.NO_MODIFICATION_ALLOWED_ERR = 7;
1402 p.NOT_FOUND_ERR = 8;
1403 p.NOT_SUPPORTED_ERR = 9;
1404 p.INUSE_ATTRIBUTE_ERR = 10;
1405 p.INVALID_STATE_ERR = 11;
1407 p.INVALID_MODIFICATION_ERR = 13;
1408 p.NAMESPACE_ERR = 14;
1409 p.INVALID_ACCESS_ERR = 15;
1410 p.VALIDATION_ERR = 16;
1411 p.TYPE_MISMATCH_ERR = 17;
1414 G_vmlCanvasManager = G_vmlCanvasManager_;
1415 CanvasRenderingContext2D = CanvasRenderingContext2D_;
1416 //CanvasGradient = CanvasGradient_;
1417 //CanvasPattern = CanvasPattern_;
1418 DOMException = DOMException_;
1423 /*! JsRender v1.0pre: http://github.com/BorisMoore/jsrender */
1425 * Optimized version of jQuery Templates, for rendering to string.
1426 * Does not require jQuery, or HTML DOM
1427 * Integrates with JsViews (http://github.com/BorisMoore/jsviews)
1428 * Copyright 2012, Boris Moore
1429 * Released under the MIT License.
1431 // informal pre beta commit counter: 3
1433 this.jsviews || this.jQuery && jQuery.views || (function( window, undefined ) {
1435 //========================== Top-level vars ==========================
1437 var versionNumber = "v1.0pre",
1439 $, rTag, rTmplString, extend,
1441 FALSE = false, TRUE = true,
1442 jQuery = window.jQuery,
1444 rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|~([\w$]+)|#(view|([\w$]+))?)([\w$.]*?)(?:[.[]([\w$]+)\]?)?|(['"]).*\8)$/g,
1445 // nil object helper view viewProperty pathTokens leafToken string
1447 rParams = /(\()(?=|\s*\()|(?:([([])\s*)?(?:([#~]?[\w$.]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$.]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*([)\]])([([]?))|(\s+)/g,
1448 // lftPrn lftPrn2 path operator err eq path2 prn comma lftPrn2 apos quot rtPrn prn2 space
1449 // (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space
1451 rNewLine = /\r?\n/g,
1452 rUnescapeQuotes = /\\(['"])/g,
1453 rEscapeQuotes = /\\?(['"])/g,
1454 rBuildHash = /\x08(~)?([^\x08]+)\x08/g,
1458 escapeMapForHtml = {
1463 tmplAttr = "data-jsv-tmpl",
1464 fnDeclStr = "var j=j||" + (jQuery ? "jQuery." : "js") + "views,",
1465 htmlSpecialChar = /[\x00"&'<>]/g,
1466 slice = Array.prototype.slice,
1470 // jsviews object ($.views if jQuery is loaded)
1472 jsviews: versionNumber,
1473 sub: sub, // subscription, e.g. JsViews integration
1475 err: function( e ) {
1476 return jsv.debugMode ? ("<br/><b>Error:</b> <em> " + (e.message || e) + ". </em>") : '""';
1480 templates: templates,
1483 converters: converters,
1486 delimiters: setDelimiters,
1490 //========================== Top-level functions ==========================
1492 //===================
1493 // jsviews.delimiters
1494 //===================
1496 function setDelimiters( openChars, closeChars ) {
1497 // Set the tag opening and closing delimiters. Default is "{{" and "}}"
1498 // openChar, closeChars: opening and closing strings, each with two characters
1499 var firstOpenChar = "\\" + openChars.charAt( 0 ), // Escape the characters - since they could be regex special characters
1500 secondOpenChar = "\\" + openChars.charAt( 1 ),
1501 firstCloseChar = "\\" + closeChars.charAt( 0 ),
1502 secondCloseChar = "\\" + closeChars.charAt( 1 );
1503 // Build regex with new delimiters
1504 jsv.rTag = rTag // make rTag available to JsViews (or other components) for parsing binding expressions
1506 // tag (followed by / space or }) or colon or html or code
1507 + "(?:(?:(\\w+(?=[\\/\\s" + firstCloseChar + "]))|(?:(\\w+)?(:)|(>)|(\\*)))"
1509 + "\\s*((?:[^" + firstCloseChar + "]|" + firstCloseChar + "(?!" + secondCloseChar + "))*?)"
1510 // slash or closeBlock
1511 + "(\\/)?|(?:\\/(\\w+)))"
1515 // Default rTag: tag converter colon html code params slash closeBlock
1516 // /{{(?:(?:(\w+(?=[\/\s\}]))|(?:(\w+)?(:)|(>)|(\*)))\s*((?:[^}]|}(?!\}))*?)(\/)?|(?:\/(\w+)))}}
1518 // /{{(?:(?:(\w+(?=[\/!\s\}!]))|(?:(\w+)?(:)|(>)|(\*)))((?:[^\}]|}(?!}))*?)(\/)?|(?:\/(\w+)))}}/g;
1519 rTag = new RegExp( firstOpenChar + rTag + secondCloseChar, "g" );
1520 rTmplString = new RegExp( "<.*>|" + openChars + ".*" + closeChars );
1528 function getHelper( helper ) {
1529 // Helper method called as view.hlp() from compiled template, for helper functions or template parameters ~foo
1531 tmplHelpers = view.tmpl.helpers || {};
1532 helper = (view.ctx[ helper ] !== undefined ? view.ctx : tmplHelpers[ helper ] !== undefined ? tmplHelpers : helpers[ helper ] !== undefined ? helpers : {})[ helper ];
1533 return typeof helper !== "function" ? helper : function() {
1534 return helper.apply(view, arguments);
1542 function convert( converter, view, text ) {
1543 var tmplConverters = view.tmpl.converters;
1544 converter = tmplConverters && tmplConverters[ converter ] || converters[ converter ];
1545 return converter ? converter.call( view, text ) : text;
1552 function renderTag( tag, parentView, converter, content, tagObject ) {
1553 // Called from within compiled template function, to render a nested tag
1554 // Returns the rendered tag
1555 tagObject.props = tagObject.props || {};
1557 tmpl = tagObject.props.tmpl,
1558 tmplTags = parentView.tmpl.tags,
1559 nestedTemplates = parentView.tmpl.templates,
1561 tagFn = tmplTags && tmplTags[ tag ] || tags[ tag ];
1566 // Set the tmpl property to the content of the block tag, unless set as an override property on the tag
1567 content = content && parentView.tmpl.tmpls[ content - 1 ];
1568 tmpl = tmpl || content || undefined;
1570 "" + tmpl === tmpl // if a string
1571 ? nestedTemplates && nestedTemplates[ tmpl ] || templates[ tmpl ] || templates( tmpl )
1574 tagObject.isTag = TRUE;
1575 tagObject.converter = converter;
1576 tagObject.view = parentView;
1577 tagObject.renderContent = renderContent;
1578 if ( parentView.ctx ) {
1579 extend( tagObject.ctx, parentView.ctx);
1582 ret = tagFn.apply( tagObject, args.length > 5 ? slice.call( args, 5 ) : [] );
1583 return ret || ( ret == undefined ? "" : ret.toString()); // (If ret is the value 0 or false, will render to string)
1590 function View( context, path, parentView, data, template, index ) {
1591 // Constructor for view object in view hierarchy. (Augmented by JsViews if JsViews is loaded)
1592 var views = parentView.views,
1593 // TODO: add this, as part of smart re-linking on existing content ($.link method), or remove completely
1594 // self = parentView.views[ index ];
1595 // if ( !self ) { ... }
1602 views: $.isArray( data ) ? [] : {},
1606 if ( $.isArray( views )) {
1608 self.index = index !== undefined
1610 : views.length, 0, self
1613 views[ self.index = "_" + autoViewKey++ ] = self;
1622 function addToStore( self, store, name, item, process ) {
1623 // Add item to named store such as templates, helpers, converters...
1625 if ( name && typeof name === "object" && !name.nodeType ) {
1626 // If name is a map, iterate over map and call store for key
1627 for ( key in name ) {
1628 store( key, name[ key ]);
1632 if ( !name || item === undefined ) {
1634 item = process( undefined, item || name );
1636 } else if ( "" + name === name ) { // name must be a string
1637 if ( item === null ) {
1638 // If item is null, delete this entry
1640 } else if ( item = process ? process( name, item ) : item ) {
1641 store[ name ] = item;
1644 if ( onStore = sub.onStoreItem ) {
1645 // e.g. JsViews integration
1646 onStore( store, name, item, process );
1651 function templates( name, tmpl ) {
1652 // Register templates
1653 // Setter: Use $.view.tags( name, tagFn ) or $.view.tags({ name: tagFn, ... }) to add additional tags to the registered tags collection.
1654 // Getter: Use var tagFn = $.views.tags( name ) or $.views.tags[name] or $.views.tags.name to return the function for the registered tag.
1655 // Remove: Use $.view.tags( name, null ) to remove a registered tag from $.view.tags.
1657 // When registering for {{foo a b c==d e=f}}, tagFn should be a function with the signature:
1658 // function(a,b). The 'this' pointer will be a hash with properties c and e.
1659 return addToStore( this, templates, name, tmpl, compile );
1662 function tags( name, tagFn ) {
1663 // Register template tags
1664 // Setter: Use $.view.tags( name, tagFn ) or $.view.tags({ name: tagFn, ... }) to add additional tags to the registered tags collection.
1665 // Getter: Use var tagFn = $.views.tags( name ) or $.views.tags[name] or $.views.tags.name to return the function for the registered tag.
1666 // Remove: Use $.view.tags( name, null ) to remove a registered tag from $.view.tags.
1668 // When registering for {{foo a b c==d e=f}}, tagFn should be a function with the signature:
1669 // function(a,b). The 'this' pointer will be a hash with properties c and e.
1670 return addToStore( this, tags, name, tagFn );
1673 function helpers( name, helperFn ) {
1674 // Register helper functions for use in templates (or in data-link expressions if JsViews is loaded)
1675 // Setter: Use $.view.helpers( name, helperFn ) or $.view.helpers({ name: helperFn, ... }) to add additional helpers to the registered helpers collection.
1676 // Getter: Use var helperFn = $.views.helpers( name ) or $.views.helpers[name] or $.views.helpers.name to return the function.
1677 // Remove: Use $.view.helpers( name, null ) to remove a registered helper function from $.view.helpers.
1678 // Within a template, access the helper using the syntax: {{... ~myHelper(...) ...}}.
1679 return addToStore( this, helpers, name, helperFn );
1682 function converters( name, converterFn ) {
1683 // Register converter functions for use in templates (or in data-link expressions if JsViews is loaded)
1684 // Setter: Use $.view.converters( name, converterFn ) or $.view.converters({ name: converterFn, ... }) to add additional converters to the registered converters collection.
1685 // Getter: Use var converterFn = $.views.converters( name ) or $.views.converters[name] or $.views.converters.name to return the converter function.
1686 // Remove: Use $.view.converters( name, null ) to remove a registered converter from $.view.converters.
1687 // Within a template, access the converter using the syntax: {{myConverter:...}}.
1688 return addToStore( this, converters, name, converterFn );
1695 function renderContent( data, context, parentView, path, index ) {
1696 // Render template against data as a tree of subviews (nested template), or as a string (top-level template).
1697 // tagName parameter for internal use only. Used for rendering templates registered as tags (which may have associated presenter objects)
1698 var i, l, dataItem, newView, itemWrap, itemsWrap, itemResult, parentContext, tmpl, layout,
1700 swapContent = index === TRUE,
1705 // This is a call from renderTag
1707 context = context || self.ctx;
1708 parentView = parentView || self.view;
1709 path = path || self.path;
1710 index = index || self.index;
1713 tmpl = self.jquery && self[0] // This is a call $.fn.render
1714 || self; // This is a call from tmpl.render
1716 parentView = parentView || jsv.topView;
1717 parentContext = parentView.ctx;
1718 layout = tmpl.layout;
1719 if ( data === parentView ) {
1720 // Inherit the data from the parent view.
1721 // This may be the contents of an {{if}} block
1722 data = parentView.data;
1726 // Set additional context on views created here, (as modified context inherited from the parent, and be inherited by child views)
1727 // Note: If no jQuery, extend does not support chained copies - so limit extend() to two parameters
1728 context = (context && context === parentContext)
1731 // if parentContext, make copy
1732 ? ((parentContext = extend( {}, parentContext ), context)
1733 // If context, merge context with copied parentContext
1734 ? extend( parentContext, context )
1736 // if no parentContext, use context, or default to {}
1739 if ( props.link === FALSE ) {
1740 // Override inherited value of link by an explicit setting in props: link=false
1741 // The child views of an unlinked view are also unlinked. So setting child back to true will not have any effect.
1742 context.link = FALSE;
1745 tmpl = templates[ tmpl ] || templates( tmpl );
1747 itemWrap = context.link && sub.onRenderItem;
1748 itemsWrap = context.link && sub.onRenderItems;
1751 if ( $.isArray( data ) && !layout ) {
1752 // Create a view for the array, whose child views correspond to each data item.
1753 // (Note: if index and parentView are passed in along with parent view, treat as
1754 // insert -e.g. from view.addViews - so parentView is already the view item for array)
1755 newView = swapContent ? parentView : (index !== undefined && parentView) || View( context, path, parentView, data, tmpl, index );
1757 for ( i = 0, l = data.length; i < l; i++ ) {
1758 // Create a view for each data item.
1759 dataItem = data[ i ];
1760 itemResult = tmpl.fn( dataItem, View( context, path, newView, dataItem, tmpl, (index||0) + i ), jsv );
1761 result += itemWrap ? itemWrap( itemResult, props ) : itemResult;
1764 // Create a view for singleton data object.
1765 newView = swapContent ? parentView : View( context, path, parentView, data, tmpl, index );
1766 result += (data || layout) ? tmpl.fn( data, newView, jsv ) : "";
1768 parentView.topKey = newView.index;
1769 return itemsWrap ? itemsWrap( result, path, newView.index, tmpl, props ) : result;
1771 return ""; // No tmpl. Could throw...
1774 //===========================
1775 // Build and compile template
1776 //===========================
1778 // Generate a reusable function that will serve to render a template against data
1779 // (Compile AST then build template function)
1781 function syntaxError() {
1782 throw "Syntax error";
1785 function tmplFn( markup, tmpl, bind ) {
1786 // Compile markup to AST (abtract syntax tree) then build the template function code from the AST nodes
1787 // Used for compiling templates, and also by JsViews to build functions for data link expressions
1788 var newNode, node, i, l, code, hasTag, hasEncoder, getsValue, hasConverter, hasViewPath, tag, converter, params, hash, nestedTmpl, allowCode,
1789 tmplOptions = tmpl ? {
1790 allowCode: allowCode = tmpl.allowCode,
1793 nested = tmpl && tmpl.tmpls,
1798 current = [,,,astTop],
1801 //==== nested functions ====
1802 function pushPreceedingContent( shift ) {
1805 content.push( markup.substr( loc, shift ).replace( rNewLine, "\\n" ));
1809 function parseTag( all, tagName, converter, colon, html, code, params, slash, closeBlock, index ) {
1810 // tag converter colon html code params slash closeBlock
1811 // /{{(?:(?:(\w+(?=[\/!\s\}!]))|(?:(\w+)?(:)|(?:(>)|(\*)))((?:[^\}]|}(?!}))*?)(\/)?|(?:\/(\w+)))}}/g;
1812 // Build abstract syntax tree (AST): [ tagName, converter, params, content, hash, contentMarkup ]
1819 block = !slash && !colon; // Block tag if not self-closing and not {{:}} or {{>}} (special case)
1821 //==== nested helper function ====
1823 tagName = tagName || colon;
1824 pushPreceedingContent( index );
1825 loc = index + all.length; // location marker - parsed up to here
1828 content.push([ "*", params.replace( rUnescapeQuotes, "$1" ) ]);
1830 } else if ( tagName ) {
1831 if ( tagName === "else" ) {
1832 current[ 5 ] = markup.substring( current[ 5 ], index ); // contentMarkup for block tag
1833 current = stack.pop();
1834 content = current[ 3 ];
1838 ? parseParams( params, bind )
1839 .replace( rBuildHash, function( all, isCtx, keyValue ) {
1841 passedCtx += keyValue + ",";
1843 hash += keyValue + ",";
1848 hash = hash.slice( 0, -1 );
1849 params = params.slice( 0, -1 );
1855 "{" + (hash ? ("props:{" + hash + "},"): "") + "path:'" + params + "'" + (passedCtx ? ",ctx:{" + passedCtx.slice( 0, -1 ) + "}" : "") + "}"
1858 stack.push( current );
1860 current[ 5 ] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag
1862 content.push( newNode );
1863 } else if ( closeBlock ) {
1864 //if ( closeBlock !== current[ 0 ]) {
1865 // throw "unmatched close tag: /" + closeBlock + ". Expected /" + current[ 0 ];
1867 current[ 5 ] = markup.substring( current[ 5 ], index ); // contentMarkup for block tag
1868 current = stack.pop();
1871 throw "Expected block tag";
1873 content = current[ 3 ];
1875 //==== /end of nested functions ====
1877 markup = markup.replace( rEscapeQuotes, "\\$1" );
1879 // Build the AST (abstract syntax tree) under astTop
1880 markup.replace( rTag, parseTag );
1882 pushPreceedingContent( markup.length );
1884 // Use the AST (astTop) to build the template function
1886 code = (l ? "" : '"";');
1888 for ( i = 0; i < l; i++ ) {
1889 // AST nodes: [ tagName, converter, params, content, hash, contentMarkup ]
1891 if ( node[ 0 ] === "*" ) {
1892 code = code.slice( 0, i ? -1 : -3 ) + ";" + node[ 1 ] + (i + 1 < l ? "ret+=" : "");
1893 } else if ( "" + node === node ) { // type string
1894 code += '"' + node + '"+';
1897 converter = node[ 1 ];
1899 content = node[ 3 ];
1903 // Create template object for nested template
1904 nestedTmpl = TmplObject( markup, tmplOptions, tmpl, nestedIndex++ );
1905 // Compile to AST and then to compiled function
1906 tmplFn( markup, nestedTmpl);
1907 nested.push( nestedTmpl );
1909 hasViewPath = hasViewPath || hash.indexOf( "view" ) > -1;
1910 code += (tag === ":"
1911 ? (converter === "html"
1912 ? (hasEncoder = TRUE, "e(" + params)
1914 ? (hasConverter = TRUE, 'c("' + converter + '",view,' + params)
1915 : (getsValue = TRUE, "((v=" + params + ')!=u?v:""')
1917 : (hasTag = TRUE, 't("' + tag + '",view,"' + (converter || "") + '",'
1918 + (content ? nested.length : '""') // For block tags, pass in the key (nested.length) to the nested content template
1919 + "," + hash + (params ? "," : "") + params))
1923 code = new Function( "data, view, j, b, u", fnDeclStr
1924 + (getsValue ? "v," : "")
1925 + (hasTag ? "t=j.tag," : "")
1926 + (hasConverter ? "c=j.convert," : "")
1927 + (hasEncoder ? "e=j.converters.html," : "")
1929 + (tmplOptions.debug ? "debugger;" : "")
1930 + (allowCode ? 'ret=' : 'return ')
1931 + code.slice( 0, -1 ) + ";\n\n"
1932 + (allowCode ? "return ret;" : "")
1933 + "}catch(e){return j.err(e);}"
1936 // Include only the var references that are needed in the code
1939 tmpl.useVw = hasConverter || hasViewPath || hasTag;
1944 function parseParams( params, bind ) {
1948 quoted = FALSE, // boolean for string content in double quotes
1949 aposed = FALSE; // or in single quotes
1951 function parseTokens( all, lftPrn0, lftPrn, path, operator, err, eq, path2, prn, comma, lftPrn2, apos, quot, rtPrn, prn2, space ) {
1952 // rParams = /(?:([([])\s*)?(?:([#~]?[\w$.]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*([)\]])([([]?))|(\s+)/g,
1953 // lftPrn path operator err eq path2 prn comma lftPrn3 apos quot rtPrn prn2 space
1954 // (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space
1955 operator = operator || "";
1956 lftPrn = lftPrn || lftPrn0 || lftPrn2;
1957 path = path || path2;
1958 prn = prn || prn2 || "";
1959 operator = operator || "";
1961 function parsePath( all, object, helper, view, viewProperty, pathTokens, leafToken ) {
1962 // rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|~([\w$]+)|#(view|([\w$]+))?)([\w$.]*?)(?:[.[]([\w$]+)\]?)?|(['"]).*\8)$/g,
1963 // object helper view viewProperty pathTokens leafToken string
1966 ? 'view.hlp("' + helper + '")'
1972 ? "." + viewProperty
1975 : (view ? "" : "." + object)
1976 ) + (pathTokens || "")
1977 : (leafToken = helper ? "" : view ? viewProperty || "" : object, ""));
1979 if ( bind && prn !== "(" ) {
1980 ret = "b(" + ret + ',"' + leafToken + '")';
1982 return ret + (leafToken ? "." + leafToken : "");
1991 // within single-quoted string
1992 ? (aposed = !apos, (aposed ? all : '"'))
1994 // within double-quoted string
1995 ? (quoted = !quot, (quoted ? all : '"'))
1999 ? (parenDepth++, lftPrn)
2005 ? (named = FALSE, "\b")
2010 ? (parenDepth && syntaxError(), named = TRUE, '\b' + path + ':')
2013 ? (path.replace( rPath, parsePath )
2015 ? (fnCall[ ++parenDepth ] = TRUE, prn)
2022 ? ((fnCall[ parenDepth-- ] = FALSE, rtPrn)
2024 ? (fnCall[ ++parenDepth ] = TRUE, prn)
2028 ? (fnCall[ parenDepth ] || syntaxError(), ",") // We don't allow top-level literal arrays or objects
2031 : (aposed = apos, quoted = quot, '"')
2036 params = (params + " " ).replace( rParams, parseTokens );
2040 function compile( name, tmpl, parent, options ) {
2041 // tmpl is either a template object, a selector for a template script block, the name of a compiled template, or a template object
2042 // options is the set of template properties, c
2043 var tmplOrMarkup, elem, key, nested, nestedItem;
2045 //==== nested functions ====
2046 function tmplOrMarkupFromStr( value ) {
2047 // If value is of type string - treat as selector, or name of compiled template
2048 // Return the template object, if already compiled, or the markup string
2050 if ( ("" + value === value) || value.nodeType > 0 ) {
2051 // If selector is valid and returns at least one element, get first element
2052 elem = value.nodeType > 0 ? value : !rTmplString.test( value ) && jQuery && jQuery( value )[0];
2053 if ( elem && elem.type ) {
2054 // It is a script element
2055 // Create a name for data linking if none provided
2056 value = templates[ elem.getAttribute( tmplAttr )];
2058 // Not already compiled and cached, so compile and cache the name
2059 name = name || "_" + autoTmplName++;
2060 elem.setAttribute( tmplAttr, name );
2061 value = compile( name, elem.innerHTML, parent, options ); // Use tmpl as options
2062 templates[ name ] = value;
2067 // If value is not a string or dom element, return undefined
2070 //==== Compile the template ====
2071 tmplOrMarkup = tmplOrMarkupFromStr( tmpl );
2073 // If tmpl is a template object, use it for options
2074 options = options || (tmpl.markup ? tmpl : {});
2075 options.name = name;
2076 nested = options.templates;
2078 // If tmpl is not a markup string or a selector string, then it must be a template object
2079 // In that case, get it from the markup property of the object
2080 if ( !tmplOrMarkup && tmpl.markup && (tmplOrMarkup = tmplOrMarkupFromStr( tmpl.markup ))) {
2081 if ( tmplOrMarkup.fn && (tmplOrMarkup.debug !== tmpl.debug || tmplOrMarkup.allowCode !== tmpl.allowCode )) {
2082 // if the string references a compiled template object, but the debug or allowCode props are different, need to recompile
2083 tmplOrMarkup = tmplOrMarkup.markup;
2086 if ( tmplOrMarkup !== undefined ) {
2087 if ( name && !parent ) {
2088 render[ name ] = function() {
2089 return tmpl.render.apply( tmpl, arguments );
2092 if ( tmplOrMarkup.fn || tmpl.fn ) {
2093 // tmpl is already compiled, so use it, or if different name is provided, clone it
2094 if ( tmplOrMarkup.fn ) {
2095 if ( name && name !== tmplOrMarkup.name ) {
2096 tmpl = extend( extend( {}, tmplOrMarkup ), options);
2098 tmpl = tmplOrMarkup;
2102 // tmplOrMarkup is a markup string, not a compiled template
2103 // Create template object
2104 tmpl = TmplObject( tmplOrMarkup, options, parent, 0 );
2105 // Compile to AST and then to compiled function
2106 tmplFn( tmplOrMarkup, tmpl );
2108 for ( key in nested ) {
2109 // compile nested template declarations
2110 nestedItem = nested[ key ];
2111 if ( nestedItem.name !== key ) {
2112 nested[ key ] = compile( key, nestedItem, tmpl );
2118 //==== /end of function compile ====
2120 function TmplObject( markup, options, parent, index ) {
2121 // Template object constructor
2123 // nested helper function
2124 function extendStore( storeName ) {
2125 if ( parent[ storeName ]) {
2126 // Include parent items except if overridden by item of same name in options
2127 tmpl[ storeName ] = extend( extend( {}, parent[ storeName ] ), options[ storeName ] );
2131 options = options || {};
2136 render: renderContent
2139 if ( parent.templates ) {
2140 tmpl.templates = extend( extend( {}, parent.templates ), options.templates );
2142 tmpl.parent = parent;
2143 tmpl.name = parent.name + "[" + index + "]";
2147 extend( tmpl, options );
2149 extendStore( "templates" );
2150 extendStore( "tags" );
2151 extendStore( "helpers" );
2152 extendStore( "converters" );
2157 //========================== Initialize ==========================
2160 ////////////////////////////////////////////////////////////////////////////////////////////////
2161 // jQuery is loaded, so make $ the jQuery object
2163 $.templates = templates;
2166 $.fn.render = renderContent;
2169 ////////////////////////////////////////////////////////////////////////////////////////////////
2170 // jQuery is not loaded.
2172 $ = window.jsviews = jsv;
2173 $.extend = function( target, source ) {
2175 target = target || {};
2176 for ( name in source ) {
2177 target[ name ] = source[ name ];
2182 $.isArray = Array && Array.isArray || function( obj ) {
2183 return Object.prototype.toString.call( obj ) === "[object Array]";
2189 jsv.topView = { views: {}, tmpl: {}, hlp: getHelper, ctx: jsv.helpers };
2191 function replacerForHtml( ch ) {
2192 // Original code from Mike Samuel <msamuel@google.com>
2193 return escapeMapForHtml[ ch ]
2194 // Intentional assignment that caches the result of encoding ch.
2195 || (escapeMapForHtml[ ch ] = "&#" + ch.charCodeAt( 0 ) + ";");
2198 //========================== Register tags ==========================
2205 view.onElse = function( tagObject, args ) {
2209 while ( l && !args[ i++ ]) {
2210 // Only render content if args.length === 0 (i.e. this is an else with no condition) or if a condition argument is truey
2215 view.onElse = undefined; // If condition satisfied, so won't run 'else'.
2216 tagObject.path = "";
2217 return tagObject.renderContent( view );
2218 // Test is satisfied, so render content, while remaining in current data context
2219 // By passing the view, we inherit data context from the parent view, and the content is treated as a layout template
2220 // (so if the data is an array, it will not iterate over the data
2222 return view.onElse( this, arguments );
2224 "else": function() {
2225 var view = this.view;
2226 return view.onElse ? view.onElse( this, arguments ) : "";
2234 if ( self.props.layout ) {
2235 self.tmpl.layout = TRUE;
2237 for ( i = 0; i < l; i++ ) {
2238 result += self.renderContent( args[ i ]);
2242 "=": function( value ) {
2245 "*": function( value ) {
2250 //========================== Register global helpers ==========================
2252 // helpers({ // Global helper functions
2253 // // TODO add any useful built-in helper functions
2256 //========================== Register converters ==========================
2259 html: function( text ) {
2260 // HTML encoding helper: Replace < > & and ' and " by corresponding entities.
2261 // inspired by Mike Samuel <msamuel@google.com>
2262 return text != undefined ? String( text ).replace( htmlSpecialChar, replacerForHtml ) : "";
2266 //========================== Define default delimiters ==========================
2267 setDelimiters( "{{", "}}" );
2271 /*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
2272 * Licensed under the MIT License (LICENSE.txt).
2274 * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
2275 * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
2276 * Thanks to: Seamus Leahy for adding deltaX and deltaY
2285 var types = ['DOMMouseScroll', 'mousewheel'];
2287 if ($.event.fixHooks) {
2288 for ( var i=types.length; i; ) {
2289 $.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
2293 $.event.special.mousewheel = {
2295 if ( this.addEventListener ) {
2296 for ( var i=types.length; i; ) {
2297 this.addEventListener( types[--i], handler, false );
2300 this.onmousewheel = handler;
2304 teardown: function() {
2305 if ( this.removeEventListener ) {
2306 for ( var i=types.length; i; ) {
2307 this.removeEventListener( types[--i], handler, false );
2310 this.onmousewheel = null;
2316 mousewheel: function(fn) {
2317 return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
2320 unmousewheel: function(fn) {
2321 return this.unbind("mousewheel", fn);
2326 function handler(event) {
2327 var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
2328 event = $.event.fix(orgEvent);
2329 event.type = "mousewheel";
2331 // Old school scrollwheel delta
2332 if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; }
2333 if ( orgEvent.detail ) { delta = -orgEvent.detail/3; }
2335 // New school multidimensional scroll (touchpads) deltas
2339 if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
2345 if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
2346 if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
2348 // Add event and delta to the front of the arguments
2349 args.unshift(event, delta, deltaX, deltaY);
2351 return ($.event.dispatch || $.event.handle).apply(this, args);
2357 * jQuery UI Widget 1.8.18
2359 * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
2360 * Dual licensed under the MIT or GPL Version 2 licenses.
2361 * http://jquery.org/license
2363 * http://docs.jquery.com/UI/Widget
2368 (function( $, undefined ) {
2371 if ( $.cleanData ) {
2372 var _cleanData = $.cleanData;
2373 $.cleanData = function( elems ) {
2374 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
2376 $( elem ).triggerHandler( "remove" );
2377 // http://bugs.jquery.com/ticket/8235
2380 _cleanData( elems );
2383 var _remove = $.fn.remove;
2384 $.fn.remove = function( selector, keepData ) {
2385 return this.each(function() {
2387 if ( !selector || $.filter( selector, [ this ] ).length ) {
2388 $( "*", this ).add( [ this ] ).each(function() {
2390 $( this ).triggerHandler( "remove" );
2391 // http://bugs.jquery.com/ticket/8235
2396 return _remove.call( $(this), selector, keepData );
2401 $.widget = function( name, base, prototype ) {
2402 var namespace = name.split( "." )[ 0 ],
2404 name = name.split( "." )[ 1 ];
2405 fullName = namespace + "-" + name;
2412 // create selector for plugin
2413 $.expr[ ":" ][ fullName ] = function( elem ) {
2414 return !!$.data( elem, name );
2417 $[ namespace ] = $[ namespace ] || {};
2418 $[ namespace ][ name ] = function( options, element ) {
2419 // allow instantiation without initializing for simple inheritance
2420 if ( arguments.length ) {
2421 this._createWidget( options, element );
2425 var basePrototype = new base();
2426 // we need to make the options hash a property directly on the new instance
2427 // otherwise we'll modify the options hash on the prototype that we're
2429 // $.each( basePrototype, function( key, val ) {
2430 // if ( $.isPlainObject(val) ) {
2431 // basePrototype[ key ] = $.extend( {}, val );
2434 basePrototype.options = $.extend( true, {}, basePrototype.options );
2435 $[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
2436 namespace: namespace,
2438 widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
2439 widgetBaseClass: fullName
2442 $.widget.bridge( name, $[ namespace ][ name ] );
2445 $.widget.bridge = function( name, object ) {
2446 $.fn[ name ] = function( options ) {
2447 var isMethodCall = typeof options === "string",
2448 args = Array.prototype.slice.call( arguments, 1 ),
2451 // allow multiple hashes to be passed on init
2452 options = !isMethodCall && args.length ?
2453 $.extend.apply( null, [ true, options ].concat(args) ) :
2456 // prevent calls to internal methods
2457 if ( isMethodCall && options.charAt( 0 ) === "_" ) {
2461 if ( isMethodCall ) {
2462 this.each(function() {
2463 var instance = $.data( this, name ),
2464 methodValue = instance && $.isFunction( instance[options] ) ?
2465 instance[ options ].apply( instance, args ) :
2467 // TODO: add this back in 1.9 and use $.error() (see #5972)
2468 // if ( !instance ) {
2469 // throw "cannot call methods on " + name + " prior to initialization; " +
2470 // "attempted to call method '" + options + "'";
2472 // if ( !$.isFunction( instance[options] ) ) {
2473 // throw "no such method '" + options + "' for " + name + " widget instance";
2475 // var methodValue = instance[ options ].apply( instance, args );
2476 if ( methodValue !== instance && methodValue !== undefined ) {
2477 returnValue = methodValue;
2482 this.each(function() {
2483 var instance = $.data( this, name );
2485 instance.option( options || {} )._init();
2487 $.data( this, name, new object( options, this ) );
2496 $.Widget = function( options, element ) {
2497 // allow instantiation without initializing for simple inheritance
2498 if ( arguments.length ) {
2499 this._createWidget( options, element );
2503 $.Widget.prototype = {
2504 widgetName: "widget",
2505 widgetEventPrefix: "",
2509 _createWidget: function( options, element ) {
2510 // $.widget.bridge stores the plugin instance, but we do it anyway
2511 // so that it's stored even before the _create function runs
2512 $.data( element, this.widgetName, this );
2513 this.element = $( element );
2514 this.options = $.extend( true, {},
2516 this._getCreateOptions(),
2520 this.element.bind( "remove." + this.widgetName, function() {
2525 this._trigger( "create" );
2528 _getCreateOptions: function() {
2529 return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
2531 _create: function() {},
2532 _init: function() {},
2534 destroy: function() {
2536 .unbind( "." + this.widgetName )
2537 .removeData( this.widgetName );
2539 .unbind( "." + this.widgetName )
2540 .removeAttr( "aria-disabled" )
2542 this.widgetBaseClass + "-disabled " +
2543 "ui-state-disabled" );
2546 widget: function() {
2547 return this.element;
2550 option: function( key, value ) {
2553 if ( arguments.length === 0 ) {
2554 // don't return a reference to the internal hash
2555 return $.extend( {}, this.options );
2558 if (typeof key === "string" ) {
2559 if ( value === undefined ) {
2560 return this.options[ key ];
2563 options[ key ] = value;
2566 this._setOptions( options );
2570 _setOptions: function( options ) {
2572 $.each( options, function( key, value ) {
2573 self._setOption( key, value );
2578 _setOption: function( key, value ) {
2579 this.options[ key ] = value;
2581 if ( key === "disabled" ) {
2583 [ value ? "addClass" : "removeClass"](
2584 this.widgetBaseClass + "-disabled" + " " +
2585 "ui-state-disabled" )
2586 .attr( "aria-disabled", value );
2592 enable: function() {
2593 return this._setOption( "disabled", false );
2595 disable: function() {
2596 return this._setOption( "disabled", true );
2599 _trigger: function( type, event, data ) {
2601 callback = this.options[ type ];
2604 event = $.Event( event );
2605 event.type = ( type === this.widgetEventPrefix ?
2607 this.widgetEventPrefix + type ).toLowerCase();
2608 // the original event may come from any element
2609 // so we need to reset the target on the new event
2610 event.target = this.element[ 0 ];
2612 // copy original event properties over to the new event
2613 orig = event.originalEvent;
2615 for ( prop in orig ) {
2616 if ( !( prop in event ) ) {
2617 event[ prop ] = orig[ prop ];
2622 this.element.trigger( event, data );
2624 return !( $.isFunction(callback) &&
2625 callback.call( this.element[0], event, data ) === false ||
2626 event.isDefaultPrevented() );
2635 (function ($, window, undefined) {
2636 var pos_oo = Number.POSITIVE_INFINITY,
2637 neg_oo = Number.NEGATIVE_INFINITY;
2641 // utility functions
2644 _allCoordinates: function (geom) {
2645 // return array of all positions in all geometries of geom
2647 var geometries = this._flatten(geom),
2651 for (; curGeom < geometries.length; curGeom++) {
2652 var coordinates = geometries[curGeom].coordinates,
2653 isArray = coordinates && $.isArray(coordinates[0]),
2654 isDblArray = isArray && $.isArray(coordinates[0][0]),
2655 isTriArray = isDblArray && $.isArray(coordinates[0][0][0]),
2661 coordinates = [coordinates];
2663 coordinates = [coordinates];
2665 coordinates = [coordinates];
2668 for (i = 0; i < coordinates.length; i++) {
2669 for (j = 0; j < coordinates[i].length; j++) {
2670 for (k = 0; k < coordinates[i][j].length; k++) {
2671 result.push(coordinates[i][j][k]);
2679 _isGeodetic: function( coords ) {
2680 // returns true if the first coordinate it can find is geodetic
2682 while ( $.isArray( coords ) ) {
2683 if ( coords.length > 1 && ! $.isArray( coords[ 0 ] ) ) {
2684 return ( coords[ 0 ] >= -180 && coords[ 0 ] <= 180 && coords[ 1 ] >= -85 && coords[ 1 ] <= 85 );
2686 coords = coords[ 0 ];
2697 center: function (bbox, _ignoreGeo /* Internal Use Only */) {
2698 // Envelope.centre in JTS
2699 // bbox only, use centroid for geom
2700 var wasGeodetic = false;
2701 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2703 bbox = $.geo.proj.fromGeodetic(bbox);
2706 var center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
2707 return wasGeodetic ? $.geo.proj.toGeodetic(center) : center;
2710 expandBy: function (bbox, dx, dy, _ignoreGeo /* Internal Use Only */) {
2711 var wasGeodetic = false;
2712 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2714 bbox = $.geo.proj.fromGeodetic(bbox);
2717 bbox = [bbox[0] - dx, bbox[1] - dy, bbox[2] + dx, bbox[3] + dy];
2718 return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2721 height: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
2722 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2723 bbox = $.geo.proj.fromGeodetic(bbox);
2726 return bbox[3] - bbox[1];
2729 _in: function(bbox1, bbox2) {
2730 return bbox1[0] <= bbox2[0] &&
2731 bbox1[1] <= bbox2[1] &&
2732 bbox1[2] >= bbox2[2] &&
2733 bbox1[3] >= bbox2[3];
2736 _bboxDisjoint: function( bbox1, bbox2 ) {
2737 return bbox2[ 0 ] > bbox1[ 2 ] ||
2738 bbox2[ 2 ] < bbox1[ 0 ] ||
2739 bbox2[ 1 ] > bbox1[ 3 ] ||
2740 bbox2[ 3 ] < bbox1[ 1 ];
2743 include: function( bbox, value, _ignoreGeo /* Internal Use Only */ ) {
2744 // similar to Envelope.expandToInclude in JTS
2745 if ( !value || !$.isArray( value ) ) {
2749 var wasGeodetic = false;
2750 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox || value ) ) {
2755 bbox = [ pos_oo, pos_oo, neg_oo, neg_oo ];
2756 } else if ( wasGeodetic ) {
2757 bbox = $.geo.proj.fromGeodetic( bbox );
2760 if ( value.length === 2 ) {
2761 value = [ value[ 0 ], value[ 1 ], value[ 0 ], value[ 1 ] ];
2764 value = $.geo.proj.fromGeodetic( value );
2766 bbox[0] = Math.min( value[ 0 ], bbox[ 0 ] );
2767 bbox[1] = Math.min( value[ 1 ], bbox[ 1 ] );
2768 bbox[2] = Math.max( value[ 2 ], bbox[ 2 ] );
2769 bbox[3] = Math.max( value[ 3 ], bbox[ 3 ] );
2771 return wasGeodetic ? $.geo.proj.toGeodetic( bbox ) : bbox;
2774 polygonize: function( bbox, _ignoreGeo /* Internal Use Only */ ) {
2775 // adaptation of Polygonizer class in JTS for use with bboxes
2776 var wasGeodetic = false;
2777 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2779 bbox = $.geo.proj.fromGeodetic(bbox);
2785 [ bbox[ 0 ], bbox[ 1 ] ],
2786 [ bbox[ 0 ], bbox[ 3 ] ],
2787 [ bbox[ 2 ], bbox[ 3 ] ],
2788 [ bbox[ 2 ], bbox[ 1 ] ],
2789 [ bbox[ 0 ], bbox[ 1 ] ]
2793 if ( wasGeodetic ) {
2794 polygon.coordinates = $.geo.proj.toGeodetic( polygon.coordinates );
2800 reaspect: function (bbox, ratio, _ignoreGeo /* Internal Use Only */ ) {
2802 var wasGeodetic = false;
2803 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2805 bbox = $.geo.proj.fromGeodetic(bbox);
2808 var width = this.width(bbox, true),
2809 height = this.height(bbox, true),
2810 center = this.center(bbox, true),
2813 if (width !== 0 && height !== 0 && ratio > 0) {
2814 if (width / height > ratio) {
2822 bbox = [center[0] - dx, center[1] - dy, center[0] + dx, center[1] + dy];
2825 return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2828 recenter: function( bbox, center, _ignoreGeo /* Internal Use Only */ ) {
2830 var wasGeodetic = false;
2831 if ( !_ignoreGeo && $.geo.proj ) {
2832 if ( this._isGeodetic( bbox ) ) {
2834 bbox = $.geo.proj.fromGeodetic(bbox);
2837 if ( this._isGeodetic( center ) ) {
2838 center = $.geo.proj.fromGeodetic(center);
2842 var halfWidth = ( bbox[ 2 ] - bbox[ 0 ] ) / 2,
2843 halfHeight = ( bbox[ 3 ] - bbox[ 1 ] ) / 2;
2846 center[ 0 ] - halfWidth,
2847 center[ 1 ] - halfHeight,
2848 center[ 0 ] + halfWidth,
2849 center[ 1 ] + halfHeight
2852 return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2855 scaleBy: function ( bbox, scale, _ignoreGeo /* Internal Use Only */ ) {
2857 var wasGeodetic = false;
2858 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2860 bbox = $.geo.proj.fromGeodetic(bbox);
2863 var c = this.center(bbox, true),
2864 dx = (bbox[2] - bbox[0]) * scale / 2,
2865 dy = (bbox[3] - bbox[1]) * scale / 2;
2867 bbox = [c[0] - dx, c[1] - dy, c[0] + dx, c[1] + dy];
2869 return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2872 width: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
2873 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2874 bbox = $.geo.proj.fromGeodetic(bbox);
2877 return bbox[2] - bbox[0];
2881 // geometry functions
2884 // bbox (Geometry.getEnvelope in JTS)
2886 bbox: function ( geom, _ignoreGeo /* Internal Use Only */ ) {
2887 var result, wasGeodetic = false;
2890 } else if ( geom.bbox ) {
2891 result = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom.bbox ) ) ? $.geo.proj.fromGeodetic( geom.bbox ) : geom.bbox;
2893 result = [ pos_oo, pos_oo, neg_oo, neg_oo ];
2895 var coordinates = this._allCoordinates( geom ),
2898 if ( coordinates.length === 0 ) {
2902 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( coordinates ) ) {
2904 coordinates = $.geo.proj.fromGeodetic( coordinates );
2907 for ( ; curCoord < coordinates.length; curCoord++ ) {
2908 result[0] = Math.min(coordinates[curCoord][0], result[0]);
2909 result[1] = Math.min(coordinates[curCoord][1], result[1]);
2910 result[2] = Math.max(coordinates[curCoord][0], result[2]);
2911 result[3] = Math.max(coordinates[curCoord][1], result[3]);
2915 return wasGeodetic ? $.geo.proj.toGeodetic(result) : result;
2920 centroid: function( geom, _ignoreGeo /* Internal Use Only */ ) {
2921 switch (geom.type) {
2923 return $.extend({}, geom);
2929 coords = $.merge( [ ], geom.type == "Polygon" ? geom.coordinates[0] : geom.coordinates ),
2931 bbox = [ pos_oo, pos_oo, neg_oo, neg_oo ];
2933 var wasGeodetic = false;
2934 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( coords ) ) {
2936 coords = $.geo.proj.fromGeodetic(coords);
2939 //if (coords[0][0] != coords[coords.length - 1][0] || coords[0][1] != coords[coords.length - 1][1]) {
2940 // coords.push(coords[0]);
2943 for (; i <= coords.length; i++) {
2944 j = i % coords.length;
2946 bbox[0] = Math.min(coords[j][0], bbox[0]);
2947 bbox[1] = Math.min(coords[j][1], bbox[1]);
2948 bbox[2] = Math.max(coords[j][0], bbox[2]);
2949 bbox[3] = Math.max(coords[j][1], bbox[3]);
2951 n = (coords[i - 1][0] * coords[j][1]) - (coords[j][0] * coords[i - 1][1]);
2953 c[0] += (coords[i - 1][0] + coords[j][0]) * n;
2954 c[1] += (coords[i - 1][1] + coords[j][1]) * n;
2958 if (coords.length > 0) {
2959 c[0] = Math.min( Math.max( coords[0][0], bbox[ 0 ] ), bbox[ 2 ] );
2960 c[1] = Math.min( Math.max( coords[0][1], bbox[ 1 ] ), bbox[ 3 ] );
2961 return { type: "Point", coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c };
2971 c[0] = Math.min( Math.max( c[0] / a, bbox[ 0 ] ), bbox[ 2 ] );
2972 c[1] = Math.min( Math.max( c[1] / a, bbox[ 1 ] ), bbox[ 3 ] );
2974 return { type: "Point", coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c };
2981 contains: function (geom1, geom2) {
2982 if (geom1.type != "Polygon") {
2986 switch (geom2.type) {
2988 return this._containsPolygonPoint(geom1.coordinates, geom2.coordinates);
2991 return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates);
2994 return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates[0]);
3001 _containsPolygonPoint: function (polygonCoordinates, pointCoordinate) {
3002 if (polygonCoordinates.length === 0 || polygonCoordinates[0].length < 4) {
3007 a = polygonCoordinates[0][0],
3012 for (; i < polygonCoordinates[0].length; i++) {
3013 b = polygonCoordinates[0][i];
3015 if ((a[1] <= pointCoordinate[1] && pointCoordinate[1] < b[1]) || (b[1] <= pointCoordinate[1] && pointCoordinate[1] < a[1]) && (pointCoordinate[0] < a[0] || pointCoordinate[0] < b[0])) {
3016 x = a[0] + (b[0] - a[0]) * (pointCoordinate[1] - a[1]) / (b[1] - a[1]);
3018 if (x > pointCoordinate[0]) {
3026 return rayCross % 2 == 1;
3029 _containsPolygonLineString: function (polygonCoordinates, lineStringCoordinates) {
3030 for (var i = 0; i < lineStringCoordinates.length; i++) {
3031 if (!this._containsPolygonPoint(polygonCoordinates, lineStringCoordinates[i])) {
3040 distance: function ( geom1, geom2, _ignoreGeo /* Internal Use Only */ ) {
3041 var geom1CoordinatesProjected = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom1.coordinates ) ) ? $.geo.proj.fromGeodetic(geom1.coordinates) : geom1.coordinates,
3042 geom2CoordinatesProjected = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom2.coordinates ) ) ? $.geo.proj.fromGeodetic(geom2.coordinates) : geom2.coordinates;
3044 switch (geom1.type) {
3046 switch (geom2.type) {
3048 return this._distancePointPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
3050 return this._distanceLineStringPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
3052 return this._containsPolygonPoint(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
3059 switch (geom2.type) {
3061 return this._distanceLineStringPoint(geom1CoordinatesProjected, geom2CoordinatesProjected);
3063 return this._distanceLineStringLineString(geom1CoordinatesProjected, geom2CoordinatesProjected);
3065 return this._containsPolygonLineString(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
3072 switch (geom2.type) {
3074 return this._containsPolygonPoint(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
3076 return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
3078 return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected[0]) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected[0]);
3086 _distancePointPoint: function (coordinate1, coordinate2) {
3087 var dx = coordinate2[0] - coordinate1[0],
3088 dy = coordinate2[1] - coordinate1[1];
3089 return Math.sqrt((dx * dx) + (dy * dy));
3092 _distanceLineStringPoint: function (lineStringCoordinates, pointCoordinate) {
3093 var minDist = pos_oo;
3095 if (lineStringCoordinates.length > 0) {
3096 var a = lineStringCoordinates[0],
3098 apx = pointCoordinate[0] - a[0],
3099 apy = pointCoordinate[1] - a[1];
3101 if (lineStringCoordinates.length == 1) {
3102 return Math.sqrt(apx * apx + apy * apy);
3104 for (var i = 1; i < lineStringCoordinates.length; i++) {
3105 var b = lineStringCoordinates[i],
3109 bpx = pointCoordinate[0] - b[0],
3110 bpy = pointCoordinate[1] - b[1],
3112 d = this._distanceSegmentPoint(abx, aby, apx, apy, bpx, bpy);
3129 return Math.sqrt(minDist);
3132 _distanceSegmentPoint: function (abx, aby, apx, apy, bpx, bpy) {
3133 var dot1 = abx * apx + aby * apy;
3136 return apx * apx + apy * apy;
3139 var dot2 = abx * abx + aby * aby;
3142 return bpx * bpx + bpy * bpy;
3145 return apx * apx + apy * apy - dot1 * dot1 / dot2;
3148 _distanceLineStringLineString: function (lineStringCoordinates1, lineStringCoordinates2) {
3149 var minDist = pos_oo;
3150 for (var i = 0; i < lineStringCoordinates2.length; i++) {
3151 minDist = Math.min(minDist, this._distanceLineStringPoint(lineStringCoordinates1, lineStringCoordinates2[i]));
3158 _buffer: function( geom, distance, _ignoreGeo /* Internal Use Only */ ) {
3159 var wasGeodetic = false,
3160 coords = geom.coordinates;
3162 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom.coordinates ) ) {
3164 coords = $.geo.proj.fromGeodetic( geom.coordinates );
3167 if ( geom.type === "Point" ) {
3168 var resultCoords = [],
3173 for ( ; i <= slices; i++ ) {
3174 a = ( i * 360 / slices ) * ( Math.PI / 180 );
3175 resultCoords.push( [
3176 coords[ 0 ] + Math.cos( a ) * distance,
3177 coords[ 1 ] + Math.sin( a ) * distance
3183 coordinates: [ ( wasGeodetic ? $.geo.proj.toGeodetic( resultCoords ) : resultCoords ) ]
3195 _flatten: function (geom) {
3196 // return an array of all basic geometries
3198 var geometries = [],
3200 switch (geom.type) {
3202 $.merge(geometries, this._flatten(geom.geometry));
3205 case "FeatureCollection":
3206 for (; curGeom < geom.features.length; curGeom++) {
3207 $.merge(geometries, this._flatten(geom.features[curGeom].geometry));
3211 case "GeometryCollection":
3212 for (; curGeom < geom.geometries.length; curGeom++) {
3213 $.merge(geometries, this._flatten(geom.geometries[curGeom]));
3218 geometries[0] = geom;
3224 length: function( geom, _ignoreGeo /* Internal Use Only */ ) {
3226 lineStringCoordinates,
3229 switch ( geom.type ) {
3234 lineStringCoordinates = geom.coordinates;
3238 lineStringCoordinates = geom.coordinates[ 0 ];
3242 if ( lineStringCoordinates ) {
3243 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( lineStringCoordinates ) ) {
3244 lineStringCoordinates = $.geo.proj.fromGeodetic( lineStringCoordinates );
3247 for ( ; i < lineStringCoordinates.length; i++ ) {
3248 dx = lineStringCoordinates[ i ][0] - lineStringCoordinates[ i - 1 ][0];
3249 dy = lineStringCoordinates[ i ][1] - lineStringCoordinates[ i - 1 ][1];
3250 sum += Math.sqrt((dx * dx) + (dy * dy));
3256 // return undefined;
3259 area: function( geom, _ignoreGeo /* Internal Use Only */ ) {
3264 switch ( geom.type ) {
3270 polygonCoordinates = geom.coordinates[ 0 ];
3274 if ( polygonCoordinates ) {
3275 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( polygonCoordinates ) ) {
3276 polygonCoordinates = $.geo.proj.fromGeodetic( polygonCoordinates );
3279 for ( ; i <= polygonCoordinates.length; i++) {
3280 j = i % polygonCoordinates.length;
3281 sum += ( polygonCoordinates[ i - 1 ][ 0 ] - polygonCoordinates[ j ][ 0 ] ) * ( polygonCoordinates[ i - 1 ][ 1 ] + polygonCoordinates[ j ][ 1 ] ) / 2;
3284 return Math.abs( sum );
3288 pointAlong: function( geom, percentage, _ignoreGeo /* Internal Use Only */ ) {
3289 var totalLength = 0,
3290 previousPercentageSum = 0,
3292 remainderPercentageSum,
3294 lineStringCoordinates,
3295 segmentLengths = [],
3298 wasGeodetic = false;
3300 switch ( geom.type ) {
3302 return $.extend( { }, geom );
3305 lineStringCoordinates = geom.coordinates;
3309 lineStringCoordinates = geom.coordinates[ 0 ];
3313 if ( lineStringCoordinates ) {
3314 if ( percentage === 0 ) {
3317 coordinates: [ lineStringCoordinates[ 0 ][ 0 ], lineStringCoordinates[ 0 ][ 1 ] ]
3319 } else if ( percentage === 1 ) {
3320 i = lineStringCoordinates.length - 1;
3323 coordinates: [ lineStringCoordinates[ i ][ 0 ], lineStringCoordinates[ i ][ 1 ] ]
3326 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( lineStringCoordinates ) ) {
3328 lineStringCoordinates = $.geo.proj.fromGeodetic( lineStringCoordinates );
3331 for ( ; i < lineStringCoordinates.length; i++ ) {
3332 dx = lineStringCoordinates[ i ][ 0 ] - lineStringCoordinates[ i - 1 ][ 0 ];
3333 dy = lineStringCoordinates[ i ][ 1 ] - lineStringCoordinates[ i - 1 ][ 1 ];
3334 len = Math.sqrt((dx * dx) + (dy * dy));
3335 segmentLengths.push( len );
3339 for ( i = 0; i < segmentLengths.length && percentageSum < percentage; i++ ) {
3340 previousPercentageSum = percentageSum;
3341 percentageSum += ( segmentLengths[ i ] / totalLength );
3344 remainderPercentageSum = percentage - previousPercentageSum;
3346 c0 = lineStringCoordinates[ i - 1 ];
3347 c1 = lineStringCoordinates[ i ];
3350 c0[ 0 ] + ( remainderPercentageSum * ( c1[ 0 ] - c0[ 0 ] ) ),
3351 c0[ 1 ] + ( remainderPercentageSum * ( c1[ 1 ] - c0[ 1 ] ) )
3356 coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c
3366 _WKT: (function () {
3367 function pointToString(value) {
3368 return "POINT " + pointToUntaggedString(value.coordinates);
3371 function pointToUntaggedString(coordinates) {
3372 if (!(coordinates && coordinates.length)) {
3375 return "(" + coordinates.join(" ") + ")";
3379 function lineStringToString(value) {
3380 return "LINESTRING " + lineStringToUntaggedString(value.coordinates);
3383 function lineStringToUntaggedString(coordinates) {
3384 if (!(coordinates && coordinates.length)) {
3389 for (var i = 0; i < coordinates.length; i++) {
3390 points.push(coordinates[i].join(" "));
3393 return "(" + points + ")";
3397 function polygonToString(value) {
3398 return "POLYGON " + polygonToUntaggedString(value.coordinates);
3401 function polygonToUntaggedString(coordinates) {
3402 if (!(coordinates && coordinates.length)) {
3405 var lineStrings = [];
3407 for (var i = 0; i < coordinates.length; i++) {
3408 lineStrings.push(lineStringToUntaggedString(coordinates[i]));
3411 return "(" + lineStrings + ")";
3415 function multiPointToString(value) {
3416 return "MULTIPOINT " + lineStringToUntaggedString(value.coordinates);
3419 function multiLineStringToString(value) {
3420 return "MULTILINSTRING " + polygonToUntaggedString(value.coordinates);
3423 function multiPolygonToString(value) {
3424 return "MULTIPOLYGON " + multiPolygonToUntaggedString(value.coordinates);
3427 function multiPolygonToUntaggedString(coordinates) {
3428 if (!(coordinates && coordinates.length)) {
3432 for (var i = 0; i < coordinates.length; i++) {
3433 polygons.push(polygonToUntaggedString(coordinates[i]));
3435 return "(" + polygons + ")";
3439 function geometryCollectionToString(value) {
3440 return "GEOMETRYCOLLECTION " + geometryCollectionToUntaggedString(value.geometries);
3443 function geometryCollectionToUntaggedString(geometries) {
3444 if (!(geometries && geometries.length)) {
3447 var geometryText = [];
3448 for (var i = 0; i < geometries.length; i++) {
3449 geometryText.push(stringify(geometries[i]));
3451 return "(" + geometries + ")";
3455 function stringify(value) {
3456 if (!(value && value.type)) {
3459 switch (value.type) {
3461 return pointToString(value);
3464 return lineStringToString(value);
3467 return polygonToString(value);
3470 return multiPointToString(value);
3472 case "MultiLineString":
3473 return multiLineStringToString(value);
3475 case "MultiPolygon":
3476 return multiPolygonToString(value);
3478 case "GeometryCollection":
3479 return geometryCollectionToString(value);
3487 function pointParseUntagged(wkt) {
3488 var pointString = wkt.match( /\(\s*([\d\.\-]+)\s+([\d\.\-]+)\s*\)/ );
3489 return pointString && pointString.length > 2 ? {
3492 parseFloat(pointString[1]),
3493 parseFloat(pointString[2])
3498 function lineStringParseUntagged(wkt) {
3499 var lineString = wkt.match( /\s*\((.*)\)/ ),
3505 if ( lineString && lineString.length > 1 ) {
3506 pointStrings = lineString[ 1 ].match( /[\d\.\-]+\s+[\d\.\-]+/g );
3508 for ( ; i < pointStrings.length; i++ ) {
3509 pointParts = pointStrings[ i ].match( /\s*([\d\.\-]+)\s+([\d\.\-]+)\s*/ );
3510 coords[ i ] = [ parseFloat( pointParts[ 1 ] ), parseFloat( pointParts[ 2 ] ) ];
3522 function polygonParseUntagged(wkt) {
3523 var polygon = wkt.match( /\s*\(\s*\((.*)\)\s*\)/ ),
3529 if ( polygon && polygon.length > 1 ) {
3530 pointStrings = polygon[ 1 ].match( /[\d\.\-]+\s+[\d\.\-]+/g );
3532 for ( ; i < pointStrings.length; i++ ) {
3533 pointParts = pointStrings[ i ].match( /\s*([\d\.\-]+)\s+([\d\.\-]+)\s*/ );
3534 coords[ i ] = [ parseFloat( pointParts[ 1 ] ), parseFloat( pointParts[ 2 ] ) ];
3539 coordinates: [ coords ]
3546 function multiPointParseUntagged(wkt) {
3549 if ( wkt.indexOf( "((" ) === -1 ) {
3550 multiSomething = lineStringParseUntagged( wkt );
3552 multiSomething = multiLineStringParseUntagged( wkt );
3553 multiSomething.coordinates = $.geo._allCoordinates( multiSomething );
3556 multiSomething.type = "MultiPoint";
3558 return multiSomething;
3561 function multiLineStringParseUntagged(wkt) {
3562 var lineStringsWkt = wkt.substr( 1, wkt.length - 2 ),
3563 lineStrings = lineStringsWkt.split( ")),((" ),
3566 type: "MultiLineString",
3570 for ( ; i < lineStrings.length; i++ ) {
3571 multiLineString.coordinates.push( lineStringParseUntagged( lineStrings[ i ] ).coordinates );
3574 return multiLineString;
3577 function multiPolygonParseUntagged(wkt) {
3578 var polygonsWkt = wkt.substr( 1, wkt.length - 2 ),
3579 polygons = polygonsWkt.split( ")),((" ),
3582 type: "MultiPolygon",
3586 for ( ; i < polygons.length; i++ ) {
3587 multiPolygon.coordinates.push( polygonParseUntagged( polygons[ i ] ).coordinates );
3590 return multiPolygon;
3593 function geometryCollectionParseUntagged( wkt ) {
3594 var geometriesWkt = wkt.substr( 1, wkt.length - 2 ),
3595 geometries = geometriesWkt.match( /\),[a-zA-Z]/g ),
3596 geometryCollection = {
3597 type: "GeometryCollection",
3601 i = 0, curStart = 0, curLen;
3603 if ( geometries && geometries.length > 0 ) {
3604 for ( ; i < geometries.length; i++ ) {
3605 curLen = geometriesWkt.indexOf( geometries[ i ], curStart ) - curStart + 1;
3606 curGeom = parse( geometriesWkt.substr( curStart, curLen ) );
3608 geometryCollection.geometries.push( curGeom );
3610 curStart += curLen + 1;
3614 curGeom = parse( geometriesWkt.substr( curStart ) );
3616 geometryCollection.geometries.push( curGeom );
3619 return geometryCollection;
3625 function parse(wkt) {
3628 var typeIndex = wkt.indexOf( "(" ),
3629 untagged = wkt.substr( typeIndex );
3631 switch ($.trim(wkt.substr(0, typeIndex)).toUpperCase()) {
3633 return pointParseUntagged( untagged );
3636 return lineStringParseUntagged( untagged );
3639 return polygonParseUntagged( untagged );
3642 return multiPointParseUntagged( untagged );
3644 case "MULTILINESTRING":
3645 return multiLineStringParseUntagged( untagged );
3647 case "MULTIPOLYGON":
3648 return multiPolygonParseUntagged( untagged );
3650 case "GEOMETRYCOLLECTION":
3651 return geometryCollectionParseUntagged( untagged );
3659 stringify: stringify,
3666 // projection functions
3669 proj: (function () {
3670 var halfPi = 1.5707963267948966192,
3671 quarterPi = 0.7853981633974483096,
3672 radiansPerDegree = 0.0174532925199432958,
3673 degreesPerRadian = 57.295779513082320877,
3674 semiMajorAxis = 6378137;
3677 fromGeodeticPos: function (coordinate) {
3679 semiMajorAxis * coordinate[ 0 ] * radiansPerDegree,
3680 semiMajorAxis * Math.log(Math.tan(quarterPi + coordinate[ 1 ] * radiansPerDegree / 2))
3684 fromGeodetic: function ( coordinates ) {
3685 if ( ! $.geo._isGeodetic( coordinates ) ) {
3689 var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
3690 fromGeodeticPos = this.fromGeodeticPos;
3692 if (!isMultiPointOrLineString && coordinates.length == 4) {
3694 var min = fromGeodeticPos([ coordinates[ 0 ], coordinates[ 1 ] ]),
3695 max = fromGeodeticPos([ coordinates[ 2 ], coordinates[ 3 ] ]);
3696 return [ min[ 0 ], min[ 1 ], max[ 0 ], max[ 1 ] ];
3699 var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
3700 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
3704 if (!isMultiPolygon) {
3705 if (!isMultiLineStringOrPolygon) {
3706 if (!isMultiPointOrLineString) {
3707 coordinates = [ coordinates ];
3709 coordinates = [ coordinates ];
3711 coordinates = [ coordinates ];
3714 for ( i = 0; i < coordinates.length; i++ ) {
3716 for ( j = 0; j < coordinates[ i ].length; j++ ) {
3717 result[ i ][ j ] = [ ];
3718 for ( k = 0; k < coordinates[ i ][ j ].length; k++ ) {
3719 result[ i ][ j ][ k ] = fromGeodeticPos(coordinates[ i ][ j ][ k ]);
3724 return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
3728 toGeodeticPos: function (coordinate) {
3730 (coordinate[ 0 ] / semiMajorAxis) * degreesPerRadian,
3731 (halfPi - 2 * Math.atan(1 / Math.exp(coordinate[ 1 ] / semiMajorAxis))) * degreesPerRadian
3735 toGeodetic: function (coordinates) {
3736 if ( $.geo._isGeodetic( coordinates ) ) {
3740 var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
3741 toGeodeticPos = this.toGeodeticPos;
3743 if (!isMultiPointOrLineString && coordinates.length == 4) {
3745 var min = toGeodeticPos([ coordinates[ 0 ], coordinates[ 1 ] ]),
3746 max = toGeodeticPos([ coordinates[ 2 ], coordinates[ 3 ] ]);
3747 return [ min[ 0 ], min[ 1 ], max[ 0 ], max[ 1 ] ];
3750 var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
3751 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
3755 if (!isMultiPolygon) {
3756 if (!isMultiLineStringOrPolygon) {
3757 if (!isMultiPointOrLineString) {
3758 coordinates = [ coordinates ];
3760 coordinates = [ coordinates ];
3762 coordinates = [ coordinates ];
3765 for ( i = 0; i < coordinates.length; i++ ) {
3767 for ( j = 0; j < coordinates[ i ].length; j++ ) {
3768 result[ i ][ j ] = [ ];
3769 for ( k = 0; k < coordinates[ i ][ j ].length; k++ ) {
3770 result[ i ][ j ][ k ] = toGeodeticPos(coordinates[ i ][ j ][ k ]);
3775 return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
3782 // service types (defined in other files)
3789 (function ($, undefined) {
3790 var _ieVersion = ( function () {
3791 var v = 5, div = document.createElement("div"), a = div.all || [];
3793 div.innerHTML = "<!--[if gt IE " + (++v) + "]><br><![endif]-->";
3795 return v > 6 ? v : !v;
3798 $.widget("geo.geographics", {
3802 _trueDoubleBuffer: true,
3807 _$canvas: undefined,
3808 _context: undefined,
3810 _$canvasSceneFront: undefined, //< if _trueCanvas, where canvas images get written (front buffer)
3811 _$canvasSceneBack: undefined, //< if _trueCanvas, where canvas images get written (back buffer)
3813 _requireFlip: false,
3815 _blitcanvas: undefined,
3816 _blitcontext: undefined,
3818 _$labelsContainerFront: undefined,
3819 _$labelsContainerBack: undefined,
3824 borderRadius: "8px",
3830 //stroke: undefined,
3833 visibility: "visible",
3840 _create: function () {
3841 this._$elem = this.element;
3842 this._options = this.options;
3845 webkitTransform: "translateZ(0)",
3846 display: "inline-block",
3851 if (this._$elem.css("position") == "static") {
3852 this._$elem.css("position", "relative");
3855 this._$elem.addClass( "geo-graphics" );
3857 this._width = this._$elem.width();
3858 this._height = this._$elem.height();
3860 if (!(this._width && this._height)) {
3861 this._width = parseInt(this._$elem.css("width"), 10);
3862 this._height = parseInt(this._$elem.css("height"), 10);
3865 var posCss = 'position:absolute;left:0;top:0;margin:0;padding:0;',
3866 sizeCss = 'width:' + this._width + 'px;height:' + this._height + 'px;',
3867 sizeAttr = 'width="' + this._width + '" height="' + this._height + '"';
3869 this._blitcanvas = document.createElement( "canvas" );
3871 if ( this._blitcanvas.getContext ) {
3872 //this._$elem.append('<canvas ' + sizeAttr + ' style="-webkit-transform:translateZ(0);' + posCss + '"></canvas>');
3873 //this._$canvas = this._$elem.children(':last');
3874 this._$canvas = $('<canvas ' + sizeAttr + ' style="-webkit-transform:translateZ(0);' + posCss + '"></canvas>');
3876 // test _trueDoubleBuffer
3877 this._blitcanvas.width = 1;
3878 this._blitcanvas.height = 1;
3879 this._trueDoubleBuffer = this._blitcanvas.toDataURL().length > 6;
3881 if ( !(this._options.doubleBuffer && this._trueDoubleBuffer) ) {
3882 this._$elem.append( this._$canvas );
3885 this._context = this._$canvas[0].getContext("2d");
3887 //this._blitcanvas = document.createElement( "canvas" );
3888 this._blitcanvas.width = this._width;
3889 this._blitcanvas.height = this._height;
3890 this._blitcontext = this._blitcanvas.getContext("2d");
3892 // create our front & back buffers
3893 this._$canvasSceneFront = $('<img id="scene0" style="-webkit-transform:translateZ(0);' + posCss + sizeCss + '" />');
3894 this._$canvasSceneBack = $('<img id="scene1" style="-webkit-transform:translateZ(0);' + posCss + sizeCss + '" />');
3896 } else if (_ieVersion <= 8) {
3897 this._trueCanvas = false;
3898 this._$elem.append( '<div ' + sizeAttr + ' style="' + posCss + sizeCss + '"></div>');
3899 this._$canvas = this._$elem.children(':last');
3901 G_vmlCanvasManager.initElement(this._$canvas[0]);
3902 this._context = this._$canvas[0].getContext("2d");
3903 this._$canvas.children().css({ backgroundColor: "transparent", width: this._width, height: this._height });
3906 // create our front & back label containers
3907 this._$labelsContainerFront = $('<div class="geo-labels-container" style="-webkit-transform:translateZ(0);' + posCss + sizeCss + '"></div>');
3908 this._$labelsContainerBack = $('<div class="geo-labels-container" style="-webkit-transform:translateZ(0);' + posCss + sizeCss + '"></div>');
3911 _setOption: function (key, value) {
3912 if (key == "style") {
3913 value = $.extend({}, this._options.style, value);
3915 $.Widget.prototype._setOption.apply(this, arguments);
3918 destroy: function () {
3919 $.Widget.prototype.destroy.apply(this, arguments);
3920 this._$elem.html("");
3921 this._$elem.removeClass( "geo-graphics" );
3924 clear: function () {
3925 this._context.clearRect(0, 0, this._width, this._height);
3926 this._labelsHtml = "";
3928 //if ( this._options.doubleBuffer ) console.log("clear:_end " + $.now());
3932 drawArc: function (coordinates, startAngle, sweepAngle, style) {
3933 style = this._getGraphicStyle(style);
3935 if (style.visibility != "hidden" && style.opacity > 0 && style.widthValue > 0 && style.heightValue > 0) {
3936 var r = Math.min(style.widthValue, style.heightValue) / 2;
3938 startAngle = (startAngle * Math.PI / 180);
3939 sweepAngle = (sweepAngle * Math.PI / 180);
3941 this._context.save();
3942 this._context.translate(coordinates[0], coordinates[1]);
3943 if (style.widthValue > style.heightValue) {
3944 this._context.scale(style.widthValue / style.heightValue, 1);
3946 this._context.scale(1, style.heightValue / style.widthValue);
3949 this._context.beginPath();
3950 this._context.arc(0, 0, r, startAngle, sweepAngle, false);
3952 if (this._trueCanvas) {
3953 this._context.restore();
3957 this._context.fillStyle = style.fill;
3958 this._context.globalAlpha = style.opacity * style.fillOpacity;
3959 this._context.fill();
3962 if (style.doStroke) {
3963 this._context.lineJoin = "round";
3964 this._context.lineWidth = style.strokeWidthValue;
3965 this._context.strokeStyle = style.stroke;
3967 this._context.globalAlpha = style.opacity * style.strokeOpacity;
3968 this._context.stroke();
3971 if (!this._trueCanvas) {
3972 this._context.restore();
3976 //if ( this._options.doubleBuffer ) console.log("drawArc:_end " + $.now());
3980 drawPoint: function (coordinates, style) {
3981 style = this._getGraphicStyle(style);
3982 if (style.widthValue == style.heightValue && style.heightValue == style.borderRadiusValue) {
3983 this.drawArc(coordinates, 0, 360, style);
3984 } else if (style.visibility != "hidden" && style.opacity > 0) {
3985 style.borderRadiusValue = Math.min(Math.min(style.widthValue, style.heightValue) / 2, style.borderRadiusValue);
3986 coordinates[0] -= style.widthValue / 2;
3987 coordinates[1] -= style.heightValue / 2;
3988 this._context.beginPath();
3989 this._context.moveTo(coordinates[0] + style.borderRadiusValue, coordinates[1]);
3990 this._context.lineTo(coordinates[0] + style.widthValue - style.borderRadiusValue, coordinates[1]);
3991 this._context.quadraticCurveTo(coordinates[0] + style.widthValue, coordinates[1], coordinates[0] + style.widthValue, coordinates[1] + style.borderRadiusValue);
3992 this._context.lineTo(coordinates[0] + style.widthValue, coordinates[1] + style.heightValue - style.borderRadiusValue);
3993 this._context.quadraticCurveTo(coordinates[0] + style.widthValue, coordinates[1] + style.heightValue, coordinates[0] + style.widthValue - style.borderRadiusValue, coordinates[1] + style.heightValue);
3994 this._context.lineTo(coordinates[0] + style.borderRadiusValue, coordinates[1] + style.heightValue);
3995 this._context.quadraticCurveTo(coordinates[0], coordinates[1] + style.heightValue, coordinates[0], coordinates[1] + style.heightValue - style.borderRadiusValue);
3996 this._context.lineTo(coordinates[0], coordinates[1] + style.borderRadiusValue);
3997 this._context.quadraticCurveTo(coordinates[0], coordinates[1], coordinates[0] + style.borderRadiusValue, coordinates[1]);
3998 this._context.closePath();
4001 this._context.fillStyle = style.fill;
4002 this._context.globalAlpha = style.opacity * style.fillOpacity;
4003 this._context.fill();
4006 if (style.doStroke) {
4007 this._context.lineJoin = "round";
4008 this._context.lineWidth = style.strokeWidthValue;
4009 this._context.strokeStyle = style.stroke;
4011 this._context.globalAlpha = style.opacity * style.strokeOpacity;
4013 this._context.stroke();
4016 //if ( this._options.doubleBuffer ) console.log("drawPoint:_end " + $.now());
4021 drawLineString: function (coordinates, style) {
4022 this._drawLines([coordinates], false, style);
4025 drawPolygon: function (coordinates, style) {
4026 if ( !this._trueCanvas || coordinates.length == 1 ) {
4027 // either we don't have fancy rendering or there's no need for it (no holes)
4028 this._drawLines( coordinates, true, style );
4030 if ( !coordinates || !coordinates.length || coordinates[ 0 ].length < 3 ) {
4031 // this is not a Polygon or it doesn't have a proper outer ring
4035 style = this._getGraphicStyle(style);
4037 var pixelBbox, i, j;
4039 if ( style.visibility != "hidden" && style.opacity > 0 ) {
4040 this._blitcontext.clearRect(0, 0, this._width, this._height);
4042 if ( style.doFill ) {
4043 if ( coordinates.length > 1 ) {
4044 // stencil inner rings
4045 this._blitcontext.globalCompositeOperation = "source-out";
4046 this._blitcontext.globalAlpha = 1;
4048 for ( i = 1; i < coordinates.length; i++ ) {
4049 this._blitcontext.beginPath();
4050 this._blitcontext.moveTo( coordinates[ i ][ 0 ][ 0 ], coordinates[ i ][ 0 ][ 1 ] );
4051 for ( j = 1; j < coordinates[ i ].length; j++ ) {
4052 this._blitcontext.lineTo( coordinates[ i ][ j ][ 0 ], coordinates[ i ][ j ][ 1 ] );
4054 this._blitcontext.closePath();
4056 this._blitcontext.fill( );
4062 this._blitcontext.beginPath();
4063 this._blitcontext.moveTo( coordinates[ 0 ][ 0 ][ 0 ], coordinates[ 0 ][ 0 ][ 1 ] );
4065 pixelBbox = [ coordinates[ 0 ][ 0 ][ 0 ] - style.strokeWidthValue, coordinates[ 0 ][ 0 ][ 1 ] - style.strokeWidthValue, coordinates[ 0 ][ 0 ][ 0 ] + style.strokeWidthValue, coordinates[ 0 ][ 0 ][ 1 ] + style.strokeWidthValue ];
4067 for ( i = 1; i < coordinates[ 0 ].length - 1; i++ ) {
4068 this._blitcontext.lineTo( coordinates[ 0 ][ i ][ 0 ], coordinates[ 0 ][ i ][ 1 ] );
4070 pixelBbox[ 0 ] = Math.min( coordinates[ 0 ][ i ][ 0 ] - style.strokeWidthValue, pixelBbox[ 0 ] );
4071 pixelBbox[ 1 ] = Math.min( coordinates[ 0 ][ i ][ 1 ] - style.strokeWidthValue, pixelBbox[ 1 ] );
4072 pixelBbox[ 2 ] = Math.max( coordinates[ 0 ][ i ][ 0 ] + style.strokeWidthValue, pixelBbox[ 2 ] );
4073 pixelBbox[ 3 ] = Math.max( coordinates[ 0 ][ i ][ 1 ] + style.strokeWidthValue, pixelBbox[ 3 ] );
4076 this._blitcontext.closePath();
4078 this._blitcontext.globalCompositeOperation = "source-out";
4079 if ( style.doFill ) {
4081 this._blitcontext.fillStyle = style.fill;
4082 this._blitcontext.globalAlpha = style.opacity * style.fillOpacity;
4083 this._blitcontext.fill( );
4086 this._blitcontext.globalCompositeOperation = "source-over";
4087 if ( style.doStroke ) {
4088 // stroke outer ring
4089 this._blitcontext.lineCap = this._blitcontext.lineJoin = "round";
4090 this._blitcontext.lineWidth = style.strokeWidthValue;
4091 this._blitcontext.strokeStyle = style.stroke;
4093 this._blitcontext.globalAlpha = style.opacity * style.strokeOpacity;
4094 this._blitcontext.stroke( );
4096 if ( coordinates.length > 1 ) {
4097 // stroke inner rings
4098 for ( i = 1; i < coordinates.length; i++ ) {
4099 this._blitcontext.beginPath();
4100 this._blitcontext.moveTo( coordinates[ i ][ 0 ][ 0 ], coordinates[ i ][ 0 ][ 1 ] );
4101 for ( j = 1; j < coordinates[ i ].length; j++ ) {
4102 this._blitcontext.lineTo( coordinates[ i ][ j ][ 0 ], coordinates[ i ][ j ][ 1 ] );
4104 this._blitcontext.closePath();
4106 this._blitcontext.stroke( );
4112 pixelBbox[ 0 ] = Math.min( Math.max( pixelBbox[ 0 ], 0), this._width );
4113 pixelBbox[ 1 ] = Math.min( Math.max( pixelBbox[ 1 ], 0), this._height );
4114 pixelBbox[ 2 ] = Math.min( Math.max( pixelBbox[ 2 ], 0), this._width );
4115 pixelBbox[ 3 ] = Math.min( Math.max( pixelBbox[ 3 ], 0), this._height );
4117 if ( pixelBbox[ 0 ] !== pixelBbox[ 2 ] && pixelBbox[ 1 ] !== pixelBbox[ 3 ] ) {
4118 this._context.drawImage(this._blitcanvas, pixelBbox[ 0 ], pixelBbox[ 1 ], pixelBbox[ 2 ] - pixelBbox[ 0 ], pixelBbox[ 3 ] - pixelBbox[ 1 ], pixelBbox[ 0 ], pixelBbox[ 1 ], pixelBbox[ 2 ] - pixelBbox[ 0 ], pixelBbox[ 3 ] - pixelBbox[ 1 ] );
4120 //if ( this._options.doubleBuffer ) console.log("drawPolygon:_end " + $.now());
4127 drawBbox: function (bbox, style) {
4137 drawLabel: function( coordinates, label ) {
4138 this._labelsHtml += '<div class="geo-label" style="-webkit-transform:translateZ(0);position:absolute; left:' + ( coordinates[ 0 ] / this._width * 100 ) + '%; top:' + ( coordinates[ 1 ] / this._height * 100 ) + '%;">' + label + '</div>';
4141 resize: function( ) {
4142 this._width = this._$elem.width();
4143 this._height = this._$elem.height();
4145 if (!(this._width && this._height)) {
4146 this._width = parseInt(this._$elem.css("width"), 10);
4147 this._height = parseInt(this._$elem.css("height"), 10);
4150 if ( this._trueCanvas ) {
4151 this._$canvas[0].width = this._width;
4152 this._$canvas[0].height = this._height;
4154 this._$canvasSceneFront.css( {
4156 height: this._height
4159 this._$canvasSceneBack.css( {
4161 height: this._height
4164 this._$canvas.css( {
4166 height: this._height
4170 this._$labelsContainerFront.css( {
4172 height: this._height
4175 this._$labelsContainerBack.css( {
4177 height: this._height
4181 interactiveTransform: function( origin, scale ) {
4182 if ( this._timeoutEnd ) {
4183 clearTimeout( this._timeoutEnd );
4184 this._timeoutEnd = null;
4187 // hide labels for now until they are on the interactive div
4188 //this._$labelsContainerFront.html("");
4190 if ( this._trueCanvas ) {
4191 if ( this._options.doubleBuffer && this._trueDoubleBuffer ) {
4194 if ( this._requireFlip ) {
4195 var geographics = this;
4197 var oldCanvasScene = geographics._$canvasSceneFront;
4199 geographics._$canvasSceneFront = geographics._$canvasSceneBack.css( {
4202 width: geographics._width,
4203 height: geographics._height
4204 } ).prop( "src", geographics._$canvas[ 0 ].toDataURL( ) ).prependTo( geographics._$elem );
4206 geographics._$canvasSceneBack = oldCanvasScene.detach();
4208 geographics._requireFlip = false;
4212 //console.log("geographics:interactiveTransform " + this._$canvasSceneFront.prop( "id" ) + ": origin: " + origin.toString() + ", scale: " + scale);
4213 // transform a finished scene, can assume no drawing during these calls
4214 this._$canvasSceneFront.css( {
4215 left: Math.round( origin[ 0 ] ),
4216 top: Math.round( origin[ 1 ] ),
4217 width: this._width * scale,
4218 height: this._height * scale
4221 this._context.clearRect(0, 0, this._width, this._height);
4224 this._context.clearRect(0, 0, this._width, this._height);
4228 this._$labelsContainerFront.css( {
4229 left: Math.round( origin[ 0 ] ),
4230 top: Math.round( origin[ 1 ] ),
4231 width: this._width * scale,
4232 height: this._height * scale
4237 // end/finalize a scene
4238 if ( this._timeoutEnd ) {
4239 clearTimeout( this._timeoutEnd );
4240 this._timeoutEnd = null;
4243 this._requireFlip = true;
4245 var geographics = this;
4247 function endCallback( ) {
4248 if ( !geographics._timeoutEnd ) {
4249 // something has canceled the draw
4253 if ( geographics._trueCanvas && geographics._options.doubleBuffer && geographics._trueDoubleBuffer ) {
4254 //console.log(" endCallback...");
4256 //geographics._$canvasSceneFront =
4257 geographics._$canvasSceneBack.prop( "src", "" ).one( "load", function( e ) {
4258 //console.log(" ...flip: show " + geographics._$canvasSceneBack.prop( "id" ) + ", hide " + geographics._$canvasSceneFront.prop("id"));
4259 geographics._requireFlip = false;
4260 var oldCanvasScene = geographics._$canvasSceneFront;
4262 geographics._$canvasSceneFront = geographics._$canvasSceneBack.css( {
4265 width: geographics._width,
4266 height: geographics._height
4267 } ).prependTo( geographics._$elem );
4269 geographics._$canvasSceneBack = oldCanvasScene.detach();
4270 } ).prop( "src", geographics._$canvas[ 0 ].toDataURL( ) );
4274 geographics._$labelsContainerBack.html( geographics._labelsHtml );
4276 var oldLabelsContainer = geographics._$labelsContainerFront;
4278 geographics._$labelsContainerFront = geographics._$labelsContainerBack.css( {
4281 width: geographics._width,
4282 height: geographics._height
4283 } ).prependTo( geographics._$elem );
4285 geographics._$labelsContainerBack = oldLabelsContainer.detach();
4288 geographics._timeoutEnd = null;
4291 //if ( this._options.doubleBuffer ) {
4292 this._timeoutEnd = setTimeout( endCallback, 20 );
4294 //geographics._$labelsContainerFront.html( geographics._labelsHtml );
4298 _getGraphicStyle: function (style) {
4299 function safeParse(value) {
4300 value = parseInt(value, 10);
4301 return (+value + '') === value ? +value : value;
4304 style = $.extend({}, this._options.style, style);
4305 style.borderRadiusValue = safeParse(style.borderRadius);
4306 style.fill = style.fill || style.color;
4307 style.doFill = style.fill && style.fillOpacity > 0;
4308 style.stroke = style.stroke || style.color;
4309 style.strokeWidthValue = safeParse(style.strokeWidth);
4310 style.doStroke = style.stroke && style.strokeOpacity > 0 && style.strokeWidthValue > 0;
4311 style.widthValue = safeParse(style.width);
4312 style.heightValue = safeParse(style.height);
4316 _drawLines: function (coordinates, close, style) {
4317 if (!coordinates || !coordinates.length || coordinates[0].length < 2) {
4322 style = this._getGraphicStyle(style);
4324 if (style.visibility != "hidden" && style.opacity > 0) {
4325 this._context.beginPath();
4327 for (i = 0; i < coordinates.length; i++) {
4328 this._context.moveTo(coordinates[i][0][0], coordinates[i][0][1]);
4329 for (j = 1; j < coordinates[i].length; j++) {
4330 this._context.lineTo(coordinates[i][j][0], coordinates[i][j][1]);
4335 this._context.closePath();
4338 if (close && style.doFill) {
4339 this._context.fillStyle = style.fill;
4340 this._context.globalAlpha = style.opacity * style.fillOpacity;
4341 this._context.fill();
4344 if (style.doStroke) {
4345 this._context.lineCap = this._context.lineJoin = "round";
4346 this._context.lineWidth = style.strokeWidthValue;
4347 this._context.strokeStyle = style.stroke;
4349 this._context.globalAlpha = style.opacity * style.strokeOpacity;
4350 this._context.stroke();
4353 //if ( this._options.doubleBuffer ) console.log("_drawLines:_end " + $.now());
4361 (function ($, undefined) {
4362 var _widgetIdSeed = 0,
4363 _ieVersion = ( function () {
4364 var v = 5, div = document.createElement("div"), a = div.all || [];
4366 div.innerHTML = "<!--[if gt IE " + (++v) + "]><br><![endif]-->";
4368 return v > 6 ? v : !v;
4372 bbox: [-180, -85, 180, 85],
4373 bboxMax: [-180, -85, 180, 85],
4376 "static": "default",
4377 pan: "url(data:image/vnd.microsoft.icon;base64,AAACAAEAICACAAgACAAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAAAEAAAAAAAAAAAAAAgAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAA/AAAAfwAAAP+AAAH/gAAB/8AAA//AAAd/wAAGf+AAAH9gAADbYAAA2yAAAZsAAAGbAAAAGAAAAAAAAA//////////////////////////////////////////////////////////////////////////////////////gH///4B///8Af//+AD///AA///wAH//4AB//8AAf//AAD//5AA///gAP//4AD//8AF///AB///5A////5///8=), move",
4379 dragBox: "crosshair",
4380 dragCircle: "crosshair",
4381 drawPoint: "crosshair",
4382 drawLineString: "crosshair",
4383 drawPolygon: "crosshair",
4384 measureLength: "crosshair",
4385 measureArea: "crosshair"
4388 length: "{{:length.toFixed( 2 )}} m",
4389 area: "{{:area.toFixed( 2 )}} sq m"
4401 src: function (view) {
4402 return "http://otile" + ((view.index % 4) + 1) + ".mqcdn.com/tiles/1.0.0/osm/" + view.zoom + "/" + view.tile.column + "/" + view.tile.row + ".png";
4404 attr: "Tiles Courtesy of <a href='http://www.mapquest.com/' target='_blank'>MapQuest</a> <img src='http://developer.mapquest.com/content/osm/mq_logo.png'>"
4411 basePixelSize: 156543.03392799936,
4412 origin: [-20037508.342787, 20037508.342787]
4417 zoomMax: Number.POSITIVE_INFINITY,
4421 $.widget("geo.geomap", {
4422 // private widget members
4423 _$elem: undefined, //< map div for maps, service div for services
4424 _map: undefined, //< only defined in services
4426 _createdGraphics: false,
4433 _$resizeContainer: undefined, //< all elements that should match _contentBounds' size
4435 _$eventTarget: undefined,
4436 _$contentFrame: undefined,
4437 _$existingChildren: undefined,
4438 _$attrList: undefined,
4439 _$servicesContainer: undefined,
4440 _$shapesContainers: undefined, //< all shapesContainer divs (map only)
4442 _$panContainer: undefined, //< all non-service elements that move while panning
4443 _$shapesContainer: undefined, //< just "our" shapesContainer div (map & service)
4444 _$drawContainer: undefined,
4445 _$measureContainer: undefined,
4446 _$measureLabel: undefined,
4450 _currentServices: [], //< internal copy
4453 _pixelSize: undefined,
4454 _centerMax: undefined,
4455 _pixelSizeMax: undefined,
4457 _userGeodetic: true,
4459 _centerInteractive: undefined,
4460 _pixelSizeInteractive: undefined,
4461 _timeoutInteractive: null,
4462 _triggerInteractive: false,
4464 _timeoutRefreshShapes: null,
4468 _wheelTimeout: null,
4471 _zoomFactor: 2, //< determines what a zoom level means
4473 _fullZoomFactor: 2, //< interactiveScale factor needed to zoom a whole level
4474 _partialZoomFactor: 1.18920711500273, //< interactiveScale factor needed to zoom a fraction of a level (the fourth root of 2)
4476 _mouseDown: undefined,
4478 _toolPan: undefined,
4479 _shiftDown: undefined,
4481 _current: undefined,
4482 _downDate: undefined,
4483 _moveDate: undefined,
4484 _clickDate: undefined,
4485 _lastMove: undefined,
4486 _lastDrag: undefined,
4488 _windowHandler: null,
4489 _resizeTimeout: null,
4491 _panning: undefined,
4492 _velocity: undefined,
4493 _friction: undefined,
4495 _supportTouch: undefined,
4496 _softDblClick: undefined,
4498 _isDbltap: undefined,
4500 _isMultiTouch: undefined,
4501 _multiTouchAnchor: [], //< TouchList
4502 _multiTouchAnchorBbox: undefined, //< bbox
4503 _multiTouchCurrentBbox: undefined, //< bbox
4505 _drawTimeout: null, //< used in drawPoint mode so we don't send two shape events on dbltap
4506 _drawPixels: [], //< an array of coordinate arrays for drawing lines & polygons, in pixel coordinates
4509 _graphicShapes: [], //< an array of objects containing style object refs & GeoJSON object refs
4515 options: $.extend({}, _defaultOptions),
4517 _createWidget: function (options, element) {
4518 this._$elem = $(element);
4520 if (this._$elem.is(".geo-service")) {
4521 this._graphicShapes = [];
4522 $.Widget.prototype._createWidget.apply(this, arguments);
4526 this._widgetId = _widgetIdSeed++;
4527 this._tmplLengthId = "geoMeasureLength" + this._widgetId;
4528 this._tmplAreaId = "geoMeasureArea" + this._widgetId;
4530 this._$elem.addClass("geo-map").css( {
4531 webkitTransform: "translateZ(0)"
4535 this._initOptions = options || {};
4537 this._forcePosition(this._$elem);
4539 this._$elem.css("text-align", "left");
4541 var size = this._findMapSize();
4542 this._contentBounds = {
4543 x: parseInt(this._$elem.css("padding-left"), 10),
4544 y: parseInt(this._$elem.css("padding-top"), 10),
4545 width: size["width"],
4546 height: size["height"]
4549 this._createChildren();
4551 this._center = [ 0, 0 ];
4552 this._centerMax = [ 0, 0 ];
4553 this._centerInteractive = [ 0, 0 ];
4555 this.options["pixelSize"] = this._pixelSize = this._pixelSizeMax = 156543.03392799936;
4563 this._isDbltap = false;
4565 this._anchor = [ 0, 0 ];
4566 this._current = [ 0, 0 ];
4567 this._lastMove = [ 0, 0 ];
4568 this._lastDrag = [ 0, 0 ];
4569 this._velocity = [ 0, 0 ];
4571 this._friction = [0.8, 0.8];
4575 this._clickDate = 0;
4577 this._drawPixels = [];
4578 this._drawCoords = [];
4579 this._graphicShapes = [];
4582 $.Widget.prototype._createWidget.apply(this, arguments);
4585 _create: function () {
4586 this._options = this.options;
4588 if (this._$elem.is(".geo-service")) {
4589 this._map = this._$elem.data( "geoMap" );
4590 this._$elem.data( "geoService", this );
4596 this._supportTouch = "ontouchend" in document;
4597 this._softDblClick = this._supportTouch || _ieVersion == 7;
4600 touchStartEvent = this._supportTouch ? "touchstart" : "mousedown",
4601 touchStopEvent = this._supportTouch ? "touchend touchcancel" : "mouseup",
4602 touchMoveEvent = this._supportTouch ? "touchmove" : "mousemove";
4604 $(document).keydown($.proxy(this._document_keydown, this));
4606 this._$eventTarget.dblclick($.proxy(this._eventTarget_dblclick, this));
4608 this._$eventTarget.bind(touchStartEvent, $.proxy(this._eventTarget_touchstart, this));
4610 var dragTarget = (this._$eventTarget[0].setCapture) ? this._$eventTarget : $(document);
4611 dragTarget.bind(touchMoveEvent, $.proxy(this._dragTarget_touchmove, this));
4612 dragTarget.bind(touchStopEvent, $.proxy(this._dragTarget_touchstop, this));
4614 this._$eventTarget.mousewheel($.proxy(this._eventTarget_mousewheel, this));
4616 this._windowHandler = function () {
4617 if (geomap._resizeTimeout) {
4618 clearTimeout(geomap._resizeTimeout);
4620 geomap._resizeTimeout = setTimeout(function () {
4621 if (geomap._created) {
4622 geomap._$elem.geomap( "resize", true );
4627 $(window).resize(this._windowHandler);
4629 this._$drawContainer.geographics({ style: this._initOptions.drawStyle || {}, doubleBuffer: false });
4630 this._options["drawStyle"] = this._$drawContainer.geographics("option", "style");
4632 this._$shapesContainer.geographics( { style: this._initOptions.shapeStyle || { } } );
4633 this._createdGraphics = true;
4635 this._options["shapeStyle"] = this._$shapesContainer.geographics("option", "style");
4637 if (this._initOptions) {
4638 // always init tilingScheme right away, even if it's null
4639 if ( this._initOptions.tilingScheme !== undefined ) {
4640 this._setOption("tilingScheme", this._initOptions.tilingScheme || null, false);
4643 if ( this._initOptions.services ) {
4644 // jQuery UI Widget Factory merges user services with our default, we want to clobber the default
4645 this._options[ "services" ] = $.merge( [ ], this._initOptions.services );
4647 if (this._initOptions.bboxMax) {
4648 this._setOption("bboxMax", this._initOptions.bboxMax, false);
4649 this._setOption("bbox", this._initOptions.bboxMax, false);
4651 if (this._initOptions.zoomMin !== undefined) {
4652 this._setOption("zoomMin", this._initOptions.zoomMin, false);
4654 if (this._initOptions.zoomMax !== undefined) {
4655 this._setOption("zoomMax", this._initOptions.zoomMax, false);
4657 if (this._initOptions.bbox) {
4658 this._setOption("bbox", this._initOptions.bbox, false);
4660 if (this._initOptions.center) {
4661 this._setOption("center", this._initOptions.center, false);
4663 if (this._initOptions.zoom !== undefined) {
4664 this._setOption("zoom", this._initOptions.zoom, false);
4668 $.templates( this._tmplLengthId, this._options[ "measureLabels" ].length );
4669 $.templates( this._tmplAreaId, this._options[ "measureLabels" ].area );
4671 this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
4673 this._createServices();
4676 this._created = true;
4679 _setOption: function (key, value, refresh) {
4680 if ( key == "pixelSize" ) {
4684 refresh = (refresh === undefined || refresh);
4686 if ( this._$elem.is( ".geo-map" ) ) {
4687 this._panFinalize();
4690 var center, pixelSize, bbox, zoom;
4694 if ( this._created ) {
4695 this._clearInteractiveTimeout( );
4698 this._userGeodetic = $.geo.proj && $.geo._isGeodetic( value );
4699 if ( this._userGeodetic ) {
4700 value = $.geo.proj.fromGeodetic( value );
4703 center = [value[0] + (value[2] - value[0]) / 2, value[1] + (value[3] - value[1]) / 2];
4704 pixelSize = Math.max($.geo.width(value, true) / this._contentBounds.width, $.geo.height(value, true) / this._contentBounds.height);
4707 zoom = this._getZoom( center, pixelSize );
4709 if ( this._options[ "tilingScheme" ] ) {
4710 pixelSize = this._getPixelSize( Math.min( Math.max( zoom, this._options[ "zoomMin" ] ), this._options[ "zoomMax" ] ) );
4712 if ( zoom < this._options[ "zoomMin" ] ) {
4713 pixelSize = this._getPixelSize( this._options[ "zoomMin" ] );
4714 } else if ( zoom > this._options[ "zoomMax" ] ) {
4715 pixelSize = this._getPixelSize( this._options[ "zoomMax" ] );
4719 if ( this._created ) {
4720 this._setInteractiveCenterAndSize( center, pixelSize );
4721 this._setInteractiveTimeout( false );
4723 this._setCenterAndSize( center, pixelSize, false, refresh );
4726 value = this._getBbox( center, pixelSize );
4730 this._userGeodetic = $.geo.proj && $.geo._isGeodetic( value );
4734 if ( this._created ) {
4735 this._clearInteractiveTimeout( );
4738 this._userGeodetic = $.geo.proj && $.geo._isGeodetic( value );
4739 if ( this._userGeodetic ) {
4740 value = $.geo.proj.fromGeodetic( value );
4743 if ( this._created ) {
4744 this._setInteractiveCenterAndSize( value, this._pixelSizeInteractive );
4745 this._interactiveTransform( );
4746 this._setInteractiveTimeout( false );
4748 this._setCenterAndSize( value, this._pixelSize, false, refresh );
4752 case "measureLabels":
4753 value = $.extend( this._options[ "measureLabels" ], value );
4756 $.templates( this._tmplLengthId, this._options[ "measureLabels" ].length );
4757 $.templates( this._tmplAreaId, this._options[ "measureLabels" ].area );
4762 if (this._$drawContainer) {
4763 this._$drawContainer.geographics("option", "style", value);
4764 value = this._$drawContainer.geographics("option", "style");
4769 if ( this._$elem.is( ".geo-service" ) && !this._createdGraphics ) {
4770 this._createServiceGraphics( );
4773 if ( this._createdGraphics ) {
4774 this._$shapesContainer.geographics("option", "style", value);
4775 value = this._$shapesContainer.geographics("option", "style");
4780 this._resetDrawing( );
4781 this._$eventTarget.css("cursor", this._options["cursors"][value]);
4785 if ( this._created ) {
4786 this._setZoom(value, false, refresh);
4788 value = Math.max( value, 0 );
4789 this._setCenterAndSize( this._center, this._getPixelSize( value ), false, refresh );
4794 $.Widget.prototype._setOption.apply(this, arguments);
4799 if ( this._userGeodetic ) {
4800 this._options[ "bbox" ] = $.geo.proj.toGeodetic( this._options[ "bbox" ] );
4801 this._options[ "center" ] = $.geo.proj.toGeodetic( this._center );
4805 case "tilingScheme":
4806 if ( value !== null ) {
4807 this._pixelSizeMax = this._getPixelSize( 0 );
4809 value.origin[ 0 ] + this._pixelSizeMax * value.tileWidth / 2,
4810 value.origin[ 1 ] + this._pixelSizeMax * value.tileHeight / 2
4816 if ( $.geo.proj && $.geo._isGeodetic( value ) ) {
4817 bbox = $.geo.proj.fromGeodetic( value );
4822 this._centerMax = $.geo.center( bbox );
4823 this._pixelSizeMax = Math.max( $.geo.width( bbox, true ) / this._contentBounds.width, $.geo.height( bbox, true ) / this._contentBounds.height );
4827 this._createServices();
4830 this._refreshAllShapes();
4835 if ( refresh && this._createdGraphics ) {
4836 this._$shapesContainer.geographics("clear");
4837 this._refreshShapes( this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes );
4843 destroy: function () {
4844 if ( this._$elem.is(".geo-service") ) {
4845 if ( this._createdGraphics ) {
4846 this._$shapesContainer.geographics("destroy");
4847 this._$shapesContainer = undefined;
4848 this._createdGraphics = false;
4851 clearTimeout( this._timeoutInteractive );
4852 this._timeoutInteractive = null;
4854 this._created = false;
4856 $(window).unbind("resize", this._windowHandler);
4858 for ( var i = 0; i < this._currentServices.length; i++ ) {
4859 this._currentServices[ i ].serviceContainer.geomap("destroy");
4860 $.geo["_serviceTypes"][this._currentServices[i].type].destroy(this, this._$servicesContainer, this._currentServices[i]);
4863 this._$shapesContainer.geographics("destroy");
4864 this._$shapesContainer = undefined;
4865 this._createdGraphics = false;
4867 this._$drawContainer.geographics("destroy");
4868 this._$drawContainer = undefined;
4870 this._$existingChildren.detach();
4871 this._$elem.html("");
4872 this._$elem.append(this._$existingChildren);
4873 this._$elem.removeClass("geo-map");
4876 $.Widget.prototype.destroy.apply(this, arguments);
4879 toMap: function (p) {
4881 return this._userGeodetic ? $.geo.proj.toGeodetic(p) : p;
4884 toPixel: function ( p, _center /* Internal Use Only */, _pixelSize /* Internal Use Only */ ) {
4885 return this._toPixel( $.geo.proj ? $.geo.proj.fromGeodetic( p ) : p, _center, _pixelSize );
4888 opacity: function ( value, _serviceContainer ) {
4889 if ( this._$elem.is( ".geo-service" ) ) {
4890 this._$elem.closest( ".geo-map" ).geomap( "opacity", value, this._$elem );
4892 if ( value >= 0 || value <= 1 ) {
4893 for ( var i = 0; i < this._currentServices.length; i++ ) {
4894 var service = this._currentServices[ i ];
4895 if ( !_serviceContainer || service.serviceContainer[ 0 ] == _serviceContainer[ 0 ] ) {
4896 service.style.opacity = value;
4898 // update the original service object's style property
4899 service.serviceObject.style = $.extend( { }, service.serviceObject.style, service.style );
4901 $.geo[ "_serviceTypes" ][ service.type ].opacity( this, service );
4908 toggle: function ( value, _serviceContainer ) {
4909 if ( this._$elem.is( ".geo-service" ) ) {
4910 this._$elem.closest( ".geo-map" ).geomap( "toggle", value, this._$elem );
4913 for ( var i = 0; i < this._currentServices.length; i++ ) {
4914 var service = this._currentServices[ i ];
4916 if ( !_serviceContainer || service.serviceContainer[ 0 ] == _serviceContainer[ 0 ] ) {
4917 if ( value === undefined ) {
4918 // toggle visibility
4919 value = ( service.style.visibility !== "visible" );
4922 service.style.visibility = ( value ? "visible" : "hidden" );
4924 // update the original service object's style property
4925 service.serviceObject.style = $.extend( { }, service.serviceObject.style, service.style );
4927 service.serviceContainer.toggle( value );
4930 $.geo[ "_serviceTypes" ][ service.type ].refresh( this, service );
4937 zoom: function (numberOfLevels) {
4938 if (numberOfLevels !== null) {
4939 this._setZoom(this._options["zoom"] + numberOfLevels, false, true);
4943 refresh: function ( force, _serviceContainer ) {
4944 if ( this._$elem.is( ".geo-service" ) ) {
4945 this._$elem.closest( ".geo-map" ).geomap( "refresh", force, this._$elem );
4947 this._refresh( force, _serviceContainer );
4948 this._refreshAllShapes( );
4952 resize: function ( _trigger /* Internal Use Only */ ) {
4953 var size = this._findMapSize(),
4954 dx = size["width"]/2 - this._contentBounds.width/2,
4955 dy = size["height"]/2 - this._contentBounds.height/2,
4958 this._contentBounds = {
4959 x: parseInt(this._$elem.css("padding-left"), 10),
4960 y: parseInt(this._$elem.css("padding-top"), 10),
4961 width: size["width"],
4962 height: size["height"]
4965 this._$resizeContainer.css( {
4966 width: size["width"],
4967 height: size["height"]
4970 for (i = 0; i < this._currentServices.length; i++) {
4971 $.geo["_serviceTypes"][this._currentServices[i].type].resize(this, this._currentServices[i]);
4974 this._$elem.find( ".geo-graphics" ).css( {
4975 width: size["width"],
4976 height: size["height"]
4977 }).geographics( "resize" );
4979 for (i = 0; i < this._drawPixels.length; i++) {
4980 this._drawPixels[i][0] += dx;
4981 this._drawPixels[i][1] += dy;
4984 this._setCenterAndSize(this._center, this._pixelSize, _trigger, true);
4987 append: function ( shape, style, label, refresh ) {
4988 if ( shape && ( $.isPlainObject( shape ) || ( $.isArray( shape ) && shape.length > 0 ) ) ) {
4989 if ( !this._createdGraphics ) {
4990 this._createServiceGraphics( );
4993 var shapes, arg, i, realStyle, realLabel, realRefresh;
4995 if ( $.isArray( shape ) ) {
4997 } else if ( shape.type == "FeatureCollection" ) {
4998 shapes = shape.features;
5003 for ( i = 1; i < arguments.length; i++ ) {
5004 arg = arguments[ i ];
5006 if ( typeof arg === "object" ) {
5008 } else if ( typeof arg === "number" || typeof arg === "string" ) {
5010 } else if ( typeof arg === "boolean" ) {
5015 for ( i = 0; i < shapes.length; i++ ) {
5016 if ( shapes[ i ].type != "Point" ) {
5017 var bbox = $.geo.bbox( shapes[ i ] );
5018 if ( $.geo.proj && $.geo._isGeodetic( bbox ) ) {
5019 bbox = $.geo.proj.fromGeodetic( bbox );
5021 $.data( shapes[ i ], "geoBbox", bbox );
5024 this._graphicShapes.push( {
5031 if ( realRefresh === undefined || realRefresh ) {
5032 if ( this._$elem.is( ".geo-service" ) ) {
5033 this._refresh( false, this._$elem );
5037 this._refreshAllShapes( );
5042 empty: function ( refresh ) {
5043 for ( var i = 0; i < this._graphicShapes.length; i++ ) {
5044 $.removeData( this._graphicShapes[ i ].shape, "geoBbox" );
5047 this._graphicShapes = [];
5049 if ( refresh === undefined || refresh ) {
5050 if ( this._$elem.is( ".geo-service" ) ) {
5051 this._refresh( false, this._$elem );
5055 this._refreshAllShapes( );
5059 find: function ( selector, pixelTolerance ) {
5060 var isPoint = $.isPlainObject( selector ),
5061 searchPixel = isPoint ? this._map.toPixel( selector.coordinates ) : undefined,
5062 mapTol = this._map._pixelSize * pixelTolerance,
5069 for ( ; i < this._graphicShapes.length; i++ ) {
5070 graphicShape = this._graphicShapes[ i ];
5073 if ( graphicShape.shape.type == "Point" ) {
5074 if ( $.geo.distance( graphicShape.shape, selector ) <= mapTol ) {
5075 result.push( graphicShape.shape );
5078 var bbox = $.data( graphicShape.shape, "geoBbox" ),
5091 coordinates: $.geo.proj && $.geo._isGeodetic( selector.coordinates ) ? $.geo.proj.fromGeodetic( selector.coordinates ) : selector.coordinates
5094 if ( $.geo.distance( bboxPolygon, projectedPoint, true ) <= mapTol ) {
5095 geometries = $.geo._flatten( graphicShape.shape );
5096 for ( curGeom = 0; curGeom < geometries.length; curGeom++ ) {
5097 if ( $.geo.distance( geometries[ curGeom ], selector ) <= mapTol ) {
5098 result.push( graphicShape.shape );
5105 result.push( graphicShape.shape );
5109 if ( this._$elem.is( ".geo-map" ) ) {
5110 this._$elem.find( ".geo-service" ).each( function( ) {
5111 result = $.merge( result, $( this ).geomap( "find", selector, pixelTolerance ) );
5118 remove: function ( shape, refresh ) {
5119 if ( shape && ( $.isPlainObject( shape ) || ( $.isArray( shape ) && shape.length > 0 ) ) ) {
5120 var shapes = $.isArray( shape ) ? shape : [ shape ],
5123 for ( var i = 0; i < this._graphicShapes.length; i++ ) {
5124 if ( $.inArray( this._graphicShapes[ i ].shape, shapes ) >= 0 ) {
5125 $.removeData( shape, "geoBbox" );
5126 rest = this._graphicShapes.slice( i + 1 );
5127 this._graphicShapes.length = i;
5128 this._graphicShapes.push.apply( this._graphicShapes, rest );
5133 if ( refresh === undefined || refresh ) {
5134 if ( this._$elem.is( ".geo-service" ) ) {
5135 this._refresh( false, this._$elem );
5139 this._refreshAllShapes( );
5144 _getBbox: function (center, pixelSize) {
5145 center = center || this._center;
5146 pixelSize = pixelSize || this._pixelSize;
5148 // calculate the internal bbox
5149 var halfWidth = this._contentBounds[ "width" ] / 2 * pixelSize,
5150 halfHeight = this._contentBounds[ "height" ] / 2 * pixelSize;
5151 return [ center[ 0 ] - halfWidth, center[ 1 ] - halfHeight, center[ 0 ] + halfWidth, center[ 1 ] + halfHeight ];
5154 _setBbox: function (value, trigger, refresh) {
5155 var center = [value[0] + (value[2] - value[0]) / 2, value[1] + (value[3] - value[1]) / 2],
5156 pixelSize = Math.max($.geo.width(value, true) / this._contentBounds.width, $.geo.height(value, true) / this._contentBounds.height),
5157 zoom = this._getZoom( center, pixelSize );
5160 if ( this._options[ "tilingScheme" ] ) {
5161 pixelSize = this._getPixelSize( Math.min( Math.max( zoom, this._options[ "zoomMin" ] ), this._options[ "zoomMax" ] ) );
5163 if ( zoom < this._options[ "zoomMin" ] ) {
5164 pixelSize = this._getPixelSize( this._options[ "zoomMin" ] );
5165 } else if ( zoom > this._options[ "zoomMax" ] ) {
5166 pixelSize = this._getPixelSize( this._options[ "zoomMax" ] );
5170 this._setInteractiveCenterAndSize( center, pixelSize );
5171 this._interactiveTransform( );
5174 _getBboxMax: function () {
5175 // calculate the internal bboxMax
5176 var halfWidth = this._contentBounds["width"] / 2 * this._pixelSizeMax,
5177 halfHeight = this._contentBounds["height"] / 2 * this._pixelSizeMax;
5178 return [this._centerMax[0] - halfWidth, this._centerMax[1] - halfHeight, this._centerMax[0] + halfWidth, this._centerMax[1] + halfHeight];
5181 _getCenter: function () {
5182 return this._center;
5185 _getContentBounds: function () {
5186 return this._contentBounds;
5189 _getServicesContainer: function () {
5190 return this._$servicesContainer;
5193 _getZoom: function ( center, pixelSize ) {
5194 // calculate the internal zoom level, vs. public zoom property
5195 // this does not take zoomMin or zoomMax into account
5196 center = center || this._center;
5197 pixelSize = pixelSize || this._pixelSize;
5199 var tilingScheme = this._options["tilingScheme"];
5200 if ( tilingScheme ) {
5201 if ( tilingScheme.pixelSizes ) {
5202 var roundedPixelSize = Math.floor(pixelSize * 1000),
5203 levels = tilingScheme.pixelSizes.length,
5206 for ( ; i >= 0; i-- ) {
5207 if ( Math.floor( tilingScheme.pixelSizes[ i ] * 1000 ) >= roundedPixelSize ) {
5214 return Math.round( Math.log( tilingScheme.basePixelSize / pixelSize) / Math.log( 2 ) );
5217 var ratio = this._contentBounds["width"] / this._contentBounds["height"],
5218 bbox = $.geo.reaspect( this._getBbox( center, pixelSize ), ratio, true ),
5219 bboxMax = $.geo.reaspect(this._getBboxMax(), ratio, true);
5221 return Math.round( Math.log($.geo.width(bboxMax, true) / $.geo.width(bbox, true)) / Math.log(this._zoomFactor) );
5225 _setZoom: function ( value, trigger, refresh ) {
5226 // set the map widget's zoom, taking zoomMin and zoomMax into account
5227 this._clearInteractiveTimeout( );
5229 value = Math.min( Math.max( value, this._options[ "zoomMin" ] ), this._options[ "zoomMax" ] );
5231 this._setInteractiveCenterAndSize( this._centerInteractive, this._getPixelSize( value ) );
5232 this._interactiveTransform( );
5233 this._setInteractiveTimeout( trigger );
5236 _createChildren: function () {
5237 this._$existingChildren = this._$elem.children();
5239 this._forcePosition(this._$existingChildren);
5241 this._$existingChildren.detach().css( {
5242 mozUserSelect: "none"
5246 var contentSizeCss = "width:" + this._contentBounds["width"] + "px; height:" + this._contentBounds["height"] + "px; margin:0; padding:0;",
5247 contentPosCss = "position:absolute; left:0; top:0;";
5249 this._$elem.prepend('<div class="geo-event-target geo-content-frame" style="position:absolute; left:' + this._contentBounds.x + 'px; top:' + this._contentBounds.y + 'px;' + contentSizeCss + 'overflow:hidden; -khtml-user-select:none; -moz-user-select:none; -webkit-user-select:none; user-select:none;" unselectable="on"></div>');
5250 this._$eventTarget = this._$contentFrame = this._$elem.children(':first');
5252 this._$contentFrame.append('<div class="geo-services-container" style="' + contentPosCss + contentSizeCss + '"></div>');
5253 this._$servicesContainer = this._$contentFrame.children(':last');
5255 this._$contentFrame.append('<div class="geo-shapes-container" style="' + contentPosCss + contentSizeCss + '"></div>');
5256 this._$shapesContainer = this._$contentFrame.children(':last');
5258 this._$contentFrame.append( '<ul style="position: absolute; bottom: 8px; left: 8px; list-style-type: none; max-width: 50%; padding: 0; margin: 0;"></ul>' );
5259 this._$attrList = this._$contentFrame.children( ":last" );
5261 this._$contentFrame.append('<div class="geo-draw-container" style="' + contentPosCss + contentSizeCss + '"></div>');
5262 this._$drawContainer = this._$contentFrame.children(':last');
5264 this._$contentFrame.append('<div class="geo-measure-container" style="' + contentPosCss + contentSizeCss + '"><span class="geo-measure-label" style="' + contentPosCss + '; display: none;"></span></div>');
5265 this._$measureContainer = this._$contentFrame.children(':last');
5266 this._$measureLabel = this._$measureContainer.children();
5268 this._$panContainer = $( [ this._$shapesContainer[ 0 ], this._$drawContainer[ 0 ], this._$measureContainer[ 0 ] ] );
5270 this._$resizeContainer = $( [ this._$contentFrame[ 0 ], this._$servicesContainer[ 0 ], this._$eventTarget[ 0 ], this._$measureContainer[ 0 ] ] );
5272 this._$contentFrame.append(this._$existingChildren);
5274 if ( ! $("#geo-measure-style").length ) {
5275 $("head").prepend( '<style type="text/css" id="geo-measure-style">.geo-measure-label { margin: 4px 0 0 6px; font-family: sans-serif;' + ( _ieVersion ? 'letter-spacing: 2px; color: #444; filter:progid:DXImageTransform.Microsoft.DropShadow(Color=white, OffX=1, OffY=2, Positive=true);' : 'color: #000; text-shadow: #fff 1px 2px; font-weight: bold;' ) + ' }</style>' );
5279 _createServices: function () {
5282 for ( i = 0; i < this._currentServices.length; i++ ) {
5283 this._currentServices[ i ].serviceContainer.geomap( "destroy" );
5284 $.geo[ "_serviceTypes" ][ this._currentServices[ i ].type ].destroy( this, this._$servicesContainer, this._currentServices[ i ] );
5287 this._currentServices = [ ];
5288 this._$servicesContainer.html( "" );
5289 this._$attrList.html( "" );
5291 for ( i = 0; i < this._options[ "services" ].length; i++ ) {
5292 service = this._currentServices[ i ] = $.extend( { }, this._options[ "services" ][ i ] );
5294 // keep a reference to the original
5295 service.serviceObject = this._options[ "services" ][ i ];
5297 // default the service style property on our copy
5298 service.style = $.extend( {
5299 visibility: "visible",
5303 var idString = service.id ? ' id="' + service.id + '"' : "",
5304 classString = 'class="geo-service ' + ( service["class"] ? service["class"] : '' ) + '"',
5305 scHtml = '<div ' + idString + classString + ' style="-webkit-transform:translateZ(0);position:absolute; left:0; top:0; width:32px; height:32px; margin:0; padding:0; display:' + ( service.style.visibility === "visible" ? "block" : "none" ) + ';"></div>',
5308 this._$servicesContainer.append( scHtml );
5309 serviceContainer = this._$servicesContainer.children( ":last" );
5310 service.serviceContainer = serviceContainer;
5312 $.geo[ "_serviceTypes" ][ service.type ].create( this, serviceContainer, service, i );
5314 serviceContainer.data( "geoMap", this ).geomap();
5316 if ( service.attr ) {
5317 this._$attrList.append( '<li>' + service.attr + '</li>' );
5321 // start with our map-level shapesContainer
5322 this._$shapesContainers = this._$shapesContainer;
5324 this._$attrList.find( "a" ).css( {
5325 position: "relative",
5330 _createServiceGraphics: function( ) {
5331 // only called in the context of a service-level geomap
5332 var $contentFrame = this._$elem.closest( ".geo-content-frame" );
5333 this._$elem.append('<div class="geo-shapes-container" style="position:absolute; left:0; top:0; width:' + $contentFrame.css( "width" ) + '; height:' + $contentFrame.css( "height" ) + '; margin:0; padding:0;"></div>');
5334 this._$shapesContainer = this._$elem.children(':last');
5336 this._map._$shapesContainers = this._map._$shapesContainers.add( this._$shapesContainer );
5338 this._$shapesContainer.geographics( );
5339 this._createdGraphics = true;
5341 this._options["shapeStyle"] = this._$shapesContainer.geographics("option", "style");
5344 _refreshDrawing: function ( ) {
5345 this._$drawContainer.geographics("clear");
5347 if ( this._drawPixels.length > 0 ) {
5348 var mode = this._options[ "mode" ],
5349 pixels = this._drawPixels,
5350 coords = this._drawCoords,
5358 case "measureLength":
5359 mode = "drawLineString";
5364 label = $.render[ this._tmplLengthId ]( { length: $.geo.length( labelShape, true ) } );
5365 labelPixel = $.merge( [], pixels[ pixels.length - 1 ] );
5369 mode = "drawPolygon";
5373 coordinates: [ $.merge( [ ], coords ) ]
5375 labelShape.coordinates[ 0 ].push( coords[ 0 ] );
5377 label = $.render[ this._tmplAreaId ]( { area: $.geo.area( labelShape, true ) } );
5378 labelPixel = this._toPixel( $.geo.centroid( labelShape ).coordinates );
5379 pixels = [ pixels ];
5383 pixels = [ pixels ];
5387 this._$drawContainer.geographics( mode, pixels );
5390 this._$measureLabel.html( label );
5392 widthOver = this._contentBounds.width - ( this._$measureLabel.outerWidth( true ) + labelPixel[ 0 ] );
5393 heightOver = this._contentBounds.height - ( this._$measureLabel.outerHeight( true ) + labelPixel[ 1 ] );
5395 if ( widthOver < 0 ) {
5396 labelPixel[ 0 ] += widthOver;
5399 if ( heightOver < 0 ) {
5400 labelPixel[ 1 ] += heightOver;
5403 this._$measureLabel.css( {
5404 left: Math.max( labelPixel[ 0 ], 0 ),
5405 top: Math.max( labelPixel[ 1 ], 0 )
5411 _resetDrawing: function () {
5412 this._drawPixels = [];
5413 this._drawCoords = [];
5414 this._$drawContainer.geographics("clear");
5415 this._$measureLabel.hide();
5418 _refreshAllShapes: function ( ) {
5419 this._timeoutRefreshShapes = null;
5425 for ( ; i < this._currentServices.length; i++ ) {
5426 service = this._currentServices[ i ];
5427 geoService = service.serviceContainer.data( "geoService" );
5429 if ( geoService._createdGraphics ) {
5430 geoService._$shapesContainer.geographics( "clear" );
5431 if ( geoService._graphicShapes.length > 0 ) {
5432 geoService._refreshShapes( geoService._$shapesContainer, geoService._graphicShapes, geoService._graphicShapes, geoService._graphicShapes );
5437 if ( this._createdGraphics ) {
5438 this._$shapesContainer.geographics( "clear" );
5439 if ( this._graphicShapes.length > 0 ) {
5440 this._refreshShapes( this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes );
5446 _refreshShapes: function (geographics, shapes, styles, labels, center, pixelSize) {
5454 bbox = this._map._getBbox(center, pixelSize);
5457 if ( shapes.length > 0 ) {
5458 console.log( "_refreshShapes " + $.now() );
5461 for (i = 0; i < shapes.length; i++) {
5462 shape = shapes[i].shape || shapes[i];
5463 shape = shape.geometry || shape;
5464 shapeBbox = $.data(shape, "geoBbox");
5466 if ( shapeBbox && $.geo._bboxDisjoint( bbox, shapeBbox ) ) {
5470 style = $.isArray(styles) ? styles[i].style : styles;
5471 label = $.isArray(labels) ? labels[i].label : labels;
5472 hasLabel = ( label !== undefined );
5473 labelPixel = undefined;
5475 switch (shape.type) {
5477 labelPixel = this._map.toPixel( shape.coordinates, center, pixelSize );
5478 this._$shapesContainer.geographics("drawPoint", labelPixel, style);
5481 this._$shapesContainer.geographics("drawLineString", this._map.toPixel(shape.coordinates, center, pixelSize), style);
5483 labelPixel = this._map.toPixel( $.geo.pointAlong( shape, 0.5 ).coordinates, center, pixelSize );
5487 this._$shapesContainer.geographics("drawPolygon", this._map.toPixel(shape.coordinates, center, pixelSize), style);
5489 labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
5493 for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
5494 this._$shapesContainer.geographics("drawPoint", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
5497 labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
5500 case "MultiLineString":
5501 for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
5502 this._$shapesContainer.geographics("drawLineString", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
5505 labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
5508 case "MultiPolygon":
5509 for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
5510 this._$shapesContainer.geographics("drawPolygon", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
5513 labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
5517 case "GeometryCollection":
5518 this._refreshShapes(geographics, shape.geometries, style, label, center, pixelSize);
5522 if ( hasLabel && labelPixel ) {
5523 this._$shapesContainer.geographics( "drawLabel", labelPixel, label );
5528 _findMapSize: function () {
5529 // really, really attempt to find a size for this thing
5530 // even if it's hidden (look at parents)
5531 var size = { width: 0, height: 0 },
5532 sizeContainer = this._$elem;
5534 while (sizeContainer.size() && !(size["width"] > 0 && size["height"] > 0)) {
5535 size = { width: sizeContainer.width(), height: sizeContainer.height() };
5536 if (size["width"] <= 0 || size["height"] <= 0) {
5537 size = { width: parseInt(sizeContainer.css("width"), 10), height: parseInt(sizeContainer.css("height"), 10) };
5539 sizeContainer = sizeContainer.parent();
5544 _forcePosition: function (elem) {
5545 var cssPosition = elem.css("position");
5546 if (cssPosition != "relative" && cssPosition != "absolute" && cssPosition != "fixed") {
5547 elem.css("position", "relative");
5551 _getPixelSize: function ( zoom ) {
5552 var tilingScheme = this._options["tilingScheme"];
5553 if (tilingScheme !== null) {
5555 return tilingScheme.pixelSizes ? tilingScheme.pixelSizes[0] : tilingScheme.basePixelSize;
5558 zoom = Math.round(zoom);
5559 zoom = Math.max(zoom, 0);
5560 var levels = tilingScheme.pixelSizes ? tilingScheme.pixelSizes.length : tilingScheme.levels;
5561 zoom = Math.min(zoom, levels - 1);
5563 if ( tilingScheme.pixelSizes ) {
5564 return tilingScheme.pixelSizes[zoom];
5566 return tilingScheme.basePixelSize / Math.pow(2, zoom);
5569 var bbox = $.geo.scaleBy( this._getBboxMax(), 1 / Math.pow( this._zoomFactor, zoom ), true );
5570 return Math.max( $.geo.width( bbox, true ) / this._contentBounds.width, $.geo.height( bbox, true ) / this._contentBounds.height );
5574 _getZoomCenterAndSize: function ( anchor, zoomDelta, full ) {
5575 var zoomFactor = ( full ? this._fullZoomFactor : this._partialZoomFactor ),
5576 scale = Math.pow( zoomFactor, -zoomDelta ),
5577 pixelSize = this._pixelSizeInteractive * scale,
5578 zoom = this._getZoom(this._centerInteractive, pixelSize);
5581 if ( full && this._options[ "tilingScheme" ] ) {
5582 pixelSize = this._getPixelSize( Math.min( Math.max( zoom, this._options[ "zoomMin" ] ), this._options[ "zoomMax" ] ) );
5584 if ( zoomDelta < 0 && zoom < this._options[ "zoomMin" ] ) {
5585 pixelSize = this._pixelSizeInteractive;
5586 } else if ( zoomDelta > 0 && zoom > this._options[ "zoomMax" ] ) {
5587 pixelSize = this._pixelSizeInteractive;
5591 var ratio = pixelSize / this._pixelSizeInteractive,
5592 anchorMapCoord = this._toMap( anchor, this._centerInteractive, this._pixelSizeInteractive ),
5593 centerDelta = [(this._centerInteractive[0] - anchorMapCoord[0]) * ratio, (this._centerInteractive[1] - anchorMapCoord[1]) * ratio],
5594 scaleCenter = [anchorMapCoord[0] + centerDelta[0], anchorMapCoord[1] + centerDelta[1]];
5596 return { pixelSize: pixelSize, center: scaleCenter };
5599 _mouseWheelFinish: function ( refresh ) {
5600 this._wheelTimeout = null;
5602 if (this._wheelLevel !== 0) {
5603 var wheelCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, this._options[ "tilingScheme" ] !== null );
5605 this._wheelLevel = 0;
5606 } else if ( refresh ) {
5608 this._refreshAllShapes( );
5612 _panFinalize: function () {
5613 if (this._panning) {
5614 this._velocity = [0, 0];
5616 var dx = this._current[0] - this._anchor[0],
5617 dy = this._current[1] - this._anchor[1],
5618 image = this._options[ "axisLayout" ] === "image",
5619 dxMap = -dx * this._pixelSize,
5620 dyMap = ( image ? -1 : 1 ) * dy * this._pixelSize;
5622 this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
5625 this._anchor = this._current;
5626 this._mouseDown = this._toolPan = this._panning = false;
5630 _panMove: function () {
5631 if ( ! this._options[ "pannable" ] ) {
5635 var dx = this._current[0] - this._lastDrag[0],
5636 dy = this._current[1] - this._lastDrag[1],
5641 if (this._toolPan || dx > 3 || dx < -3 || dy > 3 || dy < -3) {
5642 if (!this._toolPan) {
5643 this._toolPan = true;
5644 this._$eventTarget.css("cursor", this._options["cursors"]["pan"]);
5647 if (this._mouseDown) {
5648 this._velocity = [dx, dy];
5651 if (dx !== 0 || dy !== 0) {
5652 this._panning = true;
5653 this._lastDrag = this._current;
5655 this._centerInteractive[ 0 ] -= ( dx * this._pixelSizeInteractive );
5656 this._centerInteractive[ 1 ] += ( ( this._options[ "axisLayout" ] === "image" ? -1 : 1 ) * dy * this._pixelSizeInteractive );
5657 this._setInteractiveCenterAndSize( this._centerInteractive, this._pixelSizeInteractive );
5658 this._interactiveTransform( );
5663 _clearInteractiveTimeout: function() {
5664 if ( this._timeoutRefreshShapes ) {
5665 clearTimeout( this._timeoutRefreshShapes );
5666 this._timeoutRefreshShapes = null;
5669 if ( this._timeoutInteractive ) {
5670 clearTimeout( this._timeoutInteractive );
5671 this._timeoutInteractive = null;
5674 this._centerInteractive[ 0 ] = this._center[ 0 ];
5675 this._centerInteractive[ 1 ] = this._center[ 1 ];
5676 this._pixelSizeInteractive = this._pixelSize;
5681 _interactiveTransform: function( ) {
5682 var mapWidth = this._contentBounds[ "width" ],
5683 mapHeight = this._contentBounds[ "height" ],
5685 halfWidth = mapWidth / 2,
5686 halfHeight = mapHeight / 2,
5688 bbox = [ this._centerInteractive[ 0 ] - halfWidth, this._centerInteractive[ 1 ] - halfHeight, this._centerInteractive[ 0 ] + halfWidth, this._centerInteractive[ 1 ] + halfHeight ];
5690 var scalePixelSize = this._pixelSize,
5691 scaleRatio = scalePixelSize / this._pixelSizeInteractive;
5693 if ( scalePixelSize > 0 ) {
5694 scaleRatio = Math.round(scaleRatio * 1000) / 1000;
5696 var oldMapOrigin = this._toMap( [ 0, 0 ] ),
5697 newPixelPoint = this._toPixel( oldMapOrigin, this._centerInteractive, this._pixelSizeInteractive );
5700 this._$shapesContainers.geographics("interactiveTransform", newPixelPoint, scaleRatio);
5703 $scaleContainer.css( {
5704 left: Math.round( newPixelPoint[ 0 ] ),
5705 top: Math.round( newPixelPoint[ 1 ] ),
5706 width: mapWidth * scaleRatio,
5707 height: mapHeight * scaleRatio
5732 for ( var i = 0; i < this._currentServices.length; i++ ) {
5733 service = this._currentServices[ i ];
5734 $.geo[ "_serviceTypes" ][ service.type ].interactiveTransform( this, service, this._centerInteractive, this._pixelSizeInteractive );
5737 if (this._drawCoords.length > 0) {
5738 this._drawPixels = this._toPixel( this._drawCoords, this._centerInteractive, this._pixelSizeInteractive );
5739 this._refreshDrawing();
5743 _interactiveTimeout: function( ) {
5744 if ( this._isMultiTouch ) {
5745 this._timeoutInteractive = setTimeout( $.proxy( interactiveTimeout, this ), 128 );
5746 } else if ( this._created && this._timeoutInteractive ) {
5747 this._setCenterAndSize( this._centerInteractive, this._pixelSizeInteractive, this._triggerInteractive, true );
5748 this._timeoutInteractive = null;
5749 this._triggerInteractive = false;
5751 this._timeoutRefreshShapes = setTimeout( $.proxy( this._refreshAllShapes, this ), 128 );
5755 _setInteractiveTimeout: function( trigger ) {
5756 this._timeoutInteractive = setTimeout( $.proxy( this._interactiveTimeout, this ), 128 );
5757 this._triggerInteractive |= trigger;
5760 _refresh: function ( force, _serviceContainer ) {
5764 for ( ; i < this._currentServices.length; i++ ) {
5765 service = this._currentServices[ i ];
5766 if ( !_serviceContainer || service.serviceContainer[ 0 ] == _serviceContainer[ 0 ] ) {
5767 $.geo[ "_serviceTypes" ][ service.type ].refresh( this, service, force );
5772 _setInteractiveCenterAndSize: function ( center, pixelSize ) {
5773 // set the temporary (interactive) center & size
5774 // also, update the public-facing options
5775 // this does not take zoomMin or zoomMax into account
5776 this._centerInteractive[ 0 ] = center[ 0 ];
5777 this._centerInteractive[ 1 ] = center[ 1 ];
5778 this._pixelSizeInteractive = pixelSize;
5780 if ( this._userGeodetic ) {
5781 this._options["bbox"] = $.geo.proj.toGeodetic( this._getBbox( center, pixelSize ) );
5782 this._options["center"] = $.geo.proj.toGeodetic( center );
5784 this._options["bbox"] = this._getBbox( center, pixelSize );
5785 this._options["center"][ 0 ] = center[ 0 ];
5786 this._options["center"][ 1 ] = center[ 1 ];
5789 this._options["pixelSize"] = pixelSize;
5790 this._options["zoom"] = this._getZoom( center, pixelSize );
5793 _setCenterAndSize: function (center, pixelSize, trigger, refresh) {
5794 if ( ! $.isArray( center ) || center.length != 2 || typeof center[ 0 ] !== "number" || typeof center[ 1 ] !== "number" ) {
5798 // the final call during any extent change
5799 // only called by timeoutInteractive & resize
5801 var zoom = this._getZoom( center, pixelSize );
5803 if ( this._options[ "tilingScheme" ] ) {
5804 this._pixelSizeInteractive = pixelSize = this._getPixelSize( Math.min( Math.max( zoom, this._options[ "zoomMin" ] ), this._options[ "zoomMax" ] ) );
5806 if ( zoom < this._options[ "zoomMin" ] ) {
5807 this._pixelSizeInteractive = pixelSize = this._getPixelSize( this._options[ "zoomMin" ] );
5808 } else if ( zoom > this._options[ "zoomMax" ] ) {
5809 this._pixelSizeInteractive = pixelSize = this._getPixelSize( this._options[ "zoomMax" ] );
5813 this._center[ 0 ] = center[ 0 ];
5814 this._center[ 1 ] = center[ 1 ];
5815 this._options["pixelSize"] = this._pixelSize = pixelSize;
5817 if ( this._userGeodetic ) {
5818 this._options["bbox"] = $.geo.proj.toGeodetic( this._getBbox() );
5819 this._options["center"] = $.geo.proj.toGeodetic( this._center );
5821 this._options["bbox"] = this._getBbox();
5822 this._options["center"] = $.merge( [ ], center );
5825 this._options["zoom"] = zoom;
5828 this._trigger("bboxchange", window.event, { bbox: $.merge( [ ], this._options["bbox"] ) });
5833 this._refreshAllShapes( );
5834 this._refreshDrawing();
5838 _requestQueued: function ( ) {
5839 if ( this._loadCount === 0 ) {
5840 this._trigger( "loadstart", window.event );
5845 _requestComplete: function ( ) {
5847 if ( this._loadCount <= 0 ) {
5848 this._loadCount = 0;
5849 this._trigger( "loadend", window.event );
5853 _toMap: function (p, center, pixelSize) {
5854 // ignores $.geo.proj
5856 center = center || this._center;
5857 pixelSize = pixelSize || this._pixelSize;
5859 var isMultiPointOrLineString = $.isArray( p[ 0 ] ),
5860 isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray( p[ 0 ][ 0 ] ),
5861 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray( p[ 0 ][ 0 ][ 0 ] ),
5862 width = this._contentBounds["width"],
5863 height = this._contentBounds["height"],
5864 halfWidth = width / 2 * pixelSize,
5865 halfHeight = height / 2 * pixelSize,
5866 bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight],
5867 xRatio = $.geo.width(bbox, true) / width,
5868 yRatio = $.geo.height(bbox, true) / height,
5870 image = this._options[ "axisLayout" ] === "image",
5874 if ( !isMultiPolygon ) {
5875 if ( !isMultiLineStringOrPolygon ) {
5876 if ( !isMultiPointOrLineString ) {
5884 for ( i = 0; i < p.length; i++ ) {
5886 for ( j = 0; j < p[ i ].length; j++ ) {
5887 result[ i ][ j ] = [ ];
5888 for ( k = 0; k < p[ i ][ j ].length; k++ ) {
5889 yOffset = (p[ i ][ j ][ k ][1] * yRatio);
5890 result[ i ][ j ][ k ] = [
5891 bbox[ 0 ] + ( p[ i ][ j ][ k ][ 0 ] * xRatio ),
5892 image ? bbox[ 1 ] + yOffset : bbox[ 3 ] - yOffset
5898 return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
5901 _toPixel: function (p, center, pixelSize) {
5902 // ignores $.geo.proj
5904 center = center || this._center;
5905 pixelSize = pixelSize || this._pixelSize;
5907 var isMultiPointOrLineString = $.isArray( p[ 0 ] ),
5908 isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray( p[ 0 ][ 0 ] ),
5909 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray( p[ 0 ][ 0 ][ 0 ] ),
5910 width = this._contentBounds["width"],
5911 height = this._contentBounds["height"],
5912 halfWidth = width / 2 * pixelSize,
5913 halfHeight = height / 2 * pixelSize,
5914 bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight],
5915 bboxWidth = $.geo.width(bbox, true),
5916 bboxHeight = $.geo.height(bbox, true),
5917 image = this._options[ "axisLayout" ] === "image",
5918 xRatio = width / bboxWidth,
5919 yRatio = height / bboxHeight,
5923 if ( !isMultiPolygon ) {
5924 if ( !isMultiLineStringOrPolygon ) {
5925 if ( !isMultiPointOrLineString ) {
5933 for ( i = 0; i < p.length; i++ ) {
5935 for ( j = 0; j < p[ i ].length; j++ ) {
5936 result[ i ][ j ] = [ ];
5937 for ( k = 0; k < p[ i ][ j ].length; k++ ) {
5938 result[ i ][ j ][ k ] = [
5939 Math.round( ( p[ i ][ j ][ k ][ 0 ] - bbox[ 0 ] ) * xRatio ),
5940 Math.round( ( image ? p[ i ][ j ][ k ][ 1 ] - bbox[ 1 ] : bbox[ 3 ] - p[ i ][ j ][ k ][ 1 ] ) * yRatio )
5946 return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
5949 _document_keydown: function (e) {
5950 var len = this._drawCoords.length;
5951 if (len > 0 && e.which == 27) {
5953 this._resetDrawing();
5956 this._drawCoords[len - 2] = $.merge( [], this._drawCoords[ len - 1 ] );
5957 this._drawPixels[len - 2] = $.merge( [], this._drawPixels[ len - 1 ] );
5959 this._drawCoords.length--;
5960 this._drawPixels.length--;
5962 this._refreshDrawing();
5967 _eventTarget_dblclick_zoom: function(e) {
5968 var doInteractiveTimeout = this._clearInteractiveTimeout( );
5970 this._trigger("dblclick", e, { type: "Point", coordinates: this._toMap(this._current, this._centerInteractive, this._pixelSizeInteractive ) });
5972 if (!e.isDefaultPrevented()) {
5973 var centerAndSize = this._getZoomCenterAndSize(this._current, 1, true );
5975 this._setInteractiveCenterAndSize( centerAndSize.center, centerAndSize.pixelSize );
5976 this._interactiveTransform( );
5978 doInteractiveTimeout = true;
5981 if ( doInteractiveTimeout ) {
5982 this._setInteractiveTimeout( true );
5986 _eventTarget_dblclick: function (e) {
5987 if ( this._options[ "mode" ] === "static" ) {
5991 if (this._drawTimeout) {
5992 window.clearTimeout(this._drawTimeout);
5993 this._drawTimeout = null;
5996 var offset = $(e.currentTarget).offset();
5998 switch (this._options["mode"]) {
5999 case "drawLineString":
6000 case "measureLength":
6001 if ( this._drawCoords.length > 1 && ! ( this._drawCoords[0][0] == this._drawCoords[1][0] &&
6002 this._drawCoords[0][1] == this._drawCoords[1][1] ) ) {
6003 this._drawCoords.length--;
6004 this._trigger( "shape", e, {
6006 coordinates: this._userGeodetic ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords
6009 this._eventTarget_dblclick_zoom(e);
6011 this._resetDrawing();
6016 if ( this._drawCoords.length > 1 && ! ( this._drawCoords[0][0] == this._drawCoords[1][0] &&
6017 this._drawCoords[0][1] == this._drawCoords[1][1] ) ) {
6018 var endIndex = this._drawCoords.length - 1;
6020 this._drawCoords[endIndex] = $.merge( [], this._drawCoords[0] );
6021 this._trigger( "shape", e, {
6023 coordinates: [ this._userGeodetic ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords ]
6027 this._eventTarget_dblclick_zoom(e);
6029 this._resetDrawing();
6033 this._eventTarget_dblclick_zoom(e);
6040 _eventTarget_touchstart: function (e) {
6041 var mode = this._options[ "mode" ],
6042 shift = this._options[ "shift" ],
6043 defaultShift = ( mode === "dragBox" ? "dragBox" : "zoom" );
6045 if ( mode === "static" ) {
6049 if ( !this._supportTouch && e.which != 1 ) {
6053 var doInteractiveTimeout = this._clearInteractiveTimeout( );
6055 var offset = $(e.currentTarget).offset(),
6056 touches = e.originalEvent.changedTouches;
6058 if ( this._supportTouch ) {
6059 this._multiTouchAnchor = $.merge( [ ], touches );
6061 this._isMultiTouch = this._multiTouchAnchor.length > 1;
6063 if ( this._isMultiTouch ) {
6064 this._multiTouchCurrentBbox = [
6065 touches[0].pageX - offset.left,
6066 touches[0].pageY - offset.top,
6067 touches[1].pageX - offset.left,
6068 touches[1].pageY - offset.top
6071 this._multiTouchAnchorBbox = $.merge( [ ], this._multiTouchCurrentBbox );
6073 this._current = $.geo.center( this._multiTouchCurrentBbox, true );
6075 this._multiTouchCurrentBbox = [
6076 touches[0].pageX - offset.left,
6077 touches[0].pageY - offset.top,
6082 this._current = [ touches[0].pageX - offset.left, touches[0].pageY - offset.top ];
6085 this._current = [e.pageX - offset.left, e.pageY - offset.top];
6088 if (this._softDblClick) {
6089 var downDate = $.now();
6090 if (downDate - this._downDate < 750) {
6092 var dx = this._current[0] - this._anchor[0],
6093 dy = this._current[1] - this._anchor[1],
6094 distance = Math.sqrt((dx * dx) + (dy * dy));
6096 this._isTap = false;
6098 this._current = $.merge( [ ], this._anchor );
6102 if (this._isDbltap) {
6103 this._isDbltap = false;
6105 this._isDbltap = this._isTap;
6108 this._isDbltap = false;
6111 this._downDate = downDate;
6114 this._mouseDown = true;
6115 this._anchor = $.merge( [ ], this._current );
6117 if (!this._inOp && e.shiftKey && shift !== "off") {
6118 this._shiftDown = true;
6119 this._$eventTarget.css( "cursor", this._options[ "cursors" ][ shift === "default" ? defaultShift : shift ] );
6120 } else if ( !this._isMultiTouch && ( this._options[ "pannable" ] || mode === "dragBox" || mode === "dragCircle" ) ) {
6123 if ( mode !== "zoom" && mode !== "dragBox" && mode !== "dragCircle" ) {
6124 this._lastDrag = this._current;
6126 if (e.currentTarget.setCapture) {
6127 e.currentTarget.setCapture();
6134 if ( doInteractiveTimeout ) {
6135 this._setInteractiveTimeout( true );
6141 _dragTarget_touchmove: function (e) {
6142 if ( this._options[ "mode" ] === "static" ) {
6146 var doInteractiveTimeout = false;
6147 if ( this._mouseDown ) {
6148 doInteractiveTimeout = this._clearInteractiveTimeout( );
6151 var offset = this._$eventTarget.offset(),
6152 drawCoordsLen = this._drawCoords.length,
6153 touches = e.originalEvent.changedTouches,
6158 if ( this._supportTouch ) {
6159 if ( !this._isMultiTouch && this._mouseDown && this._multiTouchAnchor.length > 0 && touches[ 0 ].identifier !== this._multiTouchAnchor[ 0 ].identifier ) {
6160 // switch to multitouch
6161 this._mouseDown = false;
6162 this._isMultiTouch = true;
6163 this._wheelLevel = 0;
6165 this._multiTouchAnchor.push( touches[ 0 ] );
6170 this._multiTouchCurrentBbox = [
6171 this._multiTouchCurrentBbox[ 0 ],
6172 this._multiTouchCurrentBbox[ 1 ],
6173 this._multiTouchAnchor[1].pageX - offset.left,
6174 this._multiTouchAnchor[1].pageY - offset.top
6177 this._multiTouchAnchorBbox = $.merge( [ ], this._multiTouchCurrentBbox );
6179 this._mouseDown = true;
6180 this._anchor = this._current = $.geo.center( this._multiTouchCurrentBbox, true );
6183 if ( doInteractiveTimeout ) {
6184 this._setInteractiveTimeout( true );
6189 if ( this._isMultiTouch ) {
6191 for ( ; i < touches.length; i++ ) {
6192 if ( touches[ i ].identifier === this._multiTouchAnchor[ 0 ].identifier ) {
6193 this._multiTouchCurrentBbox[ 0 ] = touches[ i ].pageX - offset.left;
6194 this._multiTouchCurrentBbox[ 1 ] = touches[ i ].pageY - offset.top;
6195 } else if ( touches[ i ].identifier === this._multiTouchAnchor[ 1 ].identifier ) {
6196 this._multiTouchCurrentBbox[ 2 ] = touches[ i ].pageX - offset.left;
6197 this._multiTouchCurrentBbox[ 3 ] = touches[ i ].pageY - offset.top;
6201 var anchorDistance = $.geo._distancePointPoint( [ this._multiTouchAnchorBbox[ 0 ], this._multiTouchAnchorBbox[ 1 ] ], [ this._multiTouchAnchorBbox[ 2 ], this._multiTouchAnchorBbox[ 3 ] ] ),
6202 currentDistance = $.geo._distancePointPoint( [ this._multiTouchCurrentBbox[ 0 ], this._multiTouchCurrentBbox[ 1 ] ], [ this._multiTouchCurrentBbox[ 2 ], this._multiTouchCurrentBbox[ 3 ] ] );
6204 current = $.geo.center( this._multiTouchCurrentBbox, true );
6206 var wheelLevel = ( ( currentDistance - anchorDistance ) / anchorDistance );
6208 if ( wheelLevel > 0 ) {
6214 var delta = wheelLevel - this._wheelLevel;
6216 this._wheelLevel = wheelLevel;
6218 var pinchCenterAndSize = this._getZoomCenterAndSize( this._anchor, delta, false );
6220 this._setInteractiveCenterAndSize( pinchCenterAndSize.center, pinchCenterAndSize.pixelSize );
6221 this._interactiveTransform( );
6223 doInteractiveTimeout = true;
6225 current = $.geo.center( this._multiTouchCurrentBbox, true );
6227 current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
6230 current = [e.pageX - offset.left, e.pageY - offset.top];
6233 if (current[0] === this._lastMove[0] && current[1] === this._lastMove[1]) {
6236 if ( doInteractiveTimeout ) {
6237 this._setInteractiveTimeout( true );
6243 if ( _ieVersion == 7 ) {
6244 this._isDbltap = this._isTap = false;
6247 if (this._mouseDown) {
6248 this._current = current;
6249 this._moveDate = $.now();
6252 if ( this._isMultiTouch ) {
6253 e.preventDefault( );
6254 this._isDbltap = this._isTap = false;
6255 if ( doInteractiveTimeout ) {
6256 this._setInteractiveTimeout( true );
6261 var mode = this._options["mode"],
6262 shift = this._options[ "shift" ],
6263 defaultShift = ( mode === "dragBox" ? "dragBox" : "zoom" ),
6266 if ( this._shiftDown ) {
6267 mode = ( shift === "default" ? defaultShift : shift );
6273 if ( this._mouseDown ) {
6274 this._$drawContainer.geographics( "clear" );
6275 this._$drawContainer.geographics( "drawBbox", [
6282 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
6287 if ( this._mouseDown ) {
6288 dx = current[ 0 ] - this._anchor[ 0 ];
6289 dy = current[ 1 ] - this._anchor[ 1 ];
6290 circleSize = Math.sqrt( ( dx * dx) + ( dy * dy ) ) * 2;
6291 //circleSize = Math.max( Math.abs( current[ 0 ] - this._anchor[ 0 ] ), Math.abs( current[ 1 ] - this._anchor[ 1 ] ) ) * 2;
6293 // not part of _refreshDrawing
6294 this._$drawContainer.geographics( "clear" );
6295 this._$drawContainer.geographics( "drawArc", this._anchor, 0, 360, {
6300 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
6304 case "drawLineString":
6306 case "measureLength":
6308 if (this._mouseDown || this._toolPan) {
6310 doInteractiveTimeout = true;
6312 if (drawCoordsLen > 0) {
6313 this._drawCoords[drawCoordsLen - 1] = this._toMap( current, this._centerInteractive, this._pixelSizeInteractive );
6314 this._drawPixels[drawCoordsLen - 1] = current;
6316 this._refreshDrawing();
6319 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
6324 if (this._mouseDown || this._toolPan) {
6326 doInteractiveTimeout = true;
6328 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
6333 this._lastMove = current;
6335 if ( doInteractiveTimeout ) {
6336 this._setInteractiveTimeout( true );
6345 _dragTarget_touchstop: function (e) {
6346 if ( this._options[ "mode" ] === "static" ) {
6350 if ( !this._mouseDown ) {
6351 if ( _ieVersion == 7 ) {
6352 // ie7 doesn't appear to trigger dblclick on this._$eventTarget,
6353 // we fake regular click here to cause soft dblclick
6354 this._eventTarget_touchstart(e);
6356 // Chrome & Firefox trigger a rogue mouseup event when doing a dblclick maximize in Windows(/Linux?)
6362 var doInteractiveTimeout = this._clearInteractiveTimeout( );
6364 var mouseWasDown = this._mouseDown,
6365 wasToolPan = this._toolPan,
6366 offset = this._$eventTarget.offset(),
6367 mode = this._options[ "mode" ],
6368 shift = this._options[ "shift" ],
6369 defaultShift = ( mode === "dragBox" ? "dragBox" : "zoom" ),
6370 current, i, clickDate,
6375 if ( this._shiftDown ) {
6376 mode = ( shift === "default" ? defaultShift : shift );
6379 if (this._supportTouch) {
6380 current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
6381 this._multiTouchAnchor = [];
6384 current = [e.pageX - offset.left, e.pageY - offset.top];
6387 if (this._softDblClick) {
6389 dx = current[0] - this._anchor[0];
6390 dy = current[1] - this._anchor[1];
6391 if (Math.sqrt((dx * dx) + (dy * dy)) <= 8) {
6392 current = $.merge( [ ], this._anchor );
6397 dx = current[0] - this._anchor[0];
6398 dy = current[1] - this._anchor[1];
6400 this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
6402 this._shiftDown = this._mouseDown = this._toolPan = false;
6404 if ( this._isMultiTouch ) {
6405 e.preventDefault( );
6406 this._isMultiTouch = false;
6408 this._wheelLevel = 0;
6410 if ( doInteractiveTimeout ) {
6411 this._setInteractiveTimeout( true );
6416 if (document.releaseCapture) {
6417 document.releaseCapture();
6421 clickDate = $.now();
6422 this._current = current;
6427 if ( dx !== 0 || dy !== 0 ) {
6428 var minSize = this._pixelSize * 6,
6429 bboxCoords = this._toMap( [ [
6430 Math.min( this._anchor[ 0 ], current[ 0 ] ),
6431 Math.max( this._anchor[ 1 ], current[ 1 ] )
6433 Math.max( this._anchor[ 0 ], current[ 0 ] ),
6434 Math.min( this._anchor[ 1 ], current[ 1 ] )
6444 if ( mode === "zoom" ) {
6445 if ( ( bbox[2] - bbox[0] ) < minSize && ( bbox[3] - bbox[1] ) < minSize ) {
6446 bbox = $.geo.scaleBy( this._getBbox( $.geo.center( bbox, true ) ), 0.5, true );
6449 this._setBbox(bbox, true, true);
6450 doInteractiveTimeout = true;
6452 triggerShape = $.geo.polygonize( bbox, true );
6453 triggerShape.bbox = bbox;
6455 if ( this._userGeodetic ) {
6456 triggerShape.coordinates = $.geo.proj.toGeodetic( triggerShape.coordinates );
6457 triggerShape.bbox = $.geo.proj.toGeodetic( triggerShape.bbox );
6459 this._trigger( "shape", e, triggerShape );
6462 if ( mode === "dragBox" ) {
6463 coordBuffer = this._toMap( current );
6467 coordinates: [ coordBuffer[ 0 ], coordBuffer[ 1 ] ],
6468 bbox: [ coordBuffer[ 0 ], coordBuffer[ 1 ], coordBuffer[ 0 ], coordBuffer[ 1 ] ]
6471 if ( this._userGeodetic ) {
6472 triggerShape.coordinates = $.geo.proj.toGeodetic( triggerShape.coordinates );
6473 triggerShape.bbox = $.geo.proj.toGeodetic( triggerShape.bbox );
6476 this._trigger( "shape", e, triggerShape );
6480 this._resetDrawing();
6484 if ( dx !== 0 || dy !== 0 ) {
6485 var image = this._options[ "axisLayout" ] === "image",
6486 d = Math.sqrt( ( dx * dx) + ( dy * dy ) ),
6490 this._drawPixels.length = n + 1;
6492 for ( i = 0; i < n; i++ ) {
6493 a = ( i * 360 / n ) * ( Math.PI / 180 );
6494 this._drawPixels[ i ] = [
6495 this._anchor[ 0 ] + Math.cos( a ) * d,
6496 this._anchor[ 1 ] + Math.sin( a ) * d
6500 this._drawPixels[ n ] = [
6501 this._drawPixels[ 0 ][ 0 ],
6502 this._drawPixels[ 0 ][ 1 ]
6505 // using coordBuffer for bbox coords
6506 coordBuffer = this._toMap( [
6507 [ this._anchor[ 0 ] - d, this._anchor[ 1 ] + ( image ? -d : d ) ],
6508 [ this._anchor[ 0 ] + d, this._anchor[ 1 ] + ( image ? d : -d ) ]
6513 coordinates: [ this._toMap( this._drawPixels ) ],
6514 bbox: [ coordBuffer[ 0 ][ 0 ], coordBuffer[ 0 ][ 1 ], coordBuffer[ 1 ][ 0 ], coordBuffer[ 1 ][ 1 ] ]
6517 if ( this._userGeodetic ) {
6518 triggerShape.coordinates = $.geo.proj.toGeodetic( triggerShape.coordinates );
6519 triggerShape.bbox = $.geo.proj.toGeodetic( triggerShape.bbox );
6522 this._trigger( "shape", e, triggerShape );
6524 this._resetDrawing();
6526 coordBuffer = this._toMap( current );
6530 coordinates: [ coordBuffer[ 0 ], coordBuffer[ 1 ] ],
6531 bbox: [ coordBuffer[ 0 ], coordBuffer[ 1 ], coordBuffer[ 0 ], coordBuffer[ 1 ] ]
6534 if ( this._userGeodetic ) {
6535 triggerShape.coordinates = $.geo.proj.toGeodetic( triggerShape.coordinates );
6536 triggerShape.bbox = $.geo.proj.toGeodetic( triggerShape.bbox );
6539 this._trigger( "shape", e, triggerShape );
6544 if (this._drawTimeout) {
6545 window.clearTimeout(this._drawTimeout);
6546 this._drawTimeout = null;
6550 this._panFinalize();
6552 if (clickDate - this._clickDate > 100) {
6554 this._drawTimeout = setTimeout(function () {
6555 if (geomap._drawTimeout) {
6556 geomap._trigger("shape", e, { type: "Point", coordinates: geomap.toMap(current) });
6557 geomap._inOp = false;
6558 geomap._drawTimeout = null;
6565 case "drawLineString":
6567 case "measureLength":
6570 this._panFinalize();
6572 i = (this._drawCoords.length === 0 ? 0 : this._drawCoords.length - 1);
6574 this._drawCoords[i] = this._toMap(current);
6575 this._drawPixels[i] = current;
6577 if (i < 2 || !(this._drawCoords[i][0] == this._drawCoords[i-1][0] &&
6578 this._drawCoords[i][1] == this._drawCoords[i-1][1])) {
6579 this._drawCoords[i + 1] = this._toMap( current, this._centerInteractive, this._pixelSizeInteractive );
6580 this._drawPixels[i + 1] = current;
6583 this._refreshDrawing();
6589 this._panFinalize();
6591 if (clickDate - this._clickDate > 100) {
6592 this._trigger("click", e, { type: "Point", coordinates: this.toMap(current) });
6599 this._clickDate = clickDate;
6601 if (this._softDblClick && this._isDbltap) {
6602 this._isDbltap = this._isTap = false;
6603 if ( doInteractiveTimeout ) {
6604 this._setInteractiveTimeout( true );
6606 this._$eventTarget.trigger("dblclick", e);
6611 if ( doInteractiveTimeout ) {
6612 this._setInteractiveTimeout( true );
6621 _eventTarget_mousewheel: function (e, delta) {
6622 if ( this._options[ "mode" ] === "static" || this._options[ "scroll" ] === "off" ) {
6628 if ( this._mouseDown ) {
6633 this._clearInteractiveTimeout( );
6636 delta = Math.ceil( delta );
6638 delta = Math.floor( delta );
6641 var offset = $(e.currentTarget).offset();
6642 this._anchor = [e.pageX - offset.left, e.pageY - offset.top];
6644 var wheelCenterAndSize = this._getZoomCenterAndSize( this._anchor, delta, this._options[ "tilingScheme" ] !== null ),
6648 this._setInteractiveCenterAndSize( wheelCenterAndSize.center, wheelCenterAndSize.pixelSize );
6649 this._interactiveTransform( );
6651 this._setInteractiveTimeout( true );
6661 (function ($, undefined) {
6662 $.geo._serviceTypes.tiled = (function () {
6664 create: function (map, serviceContainer, service, index) {
6665 var serviceState = $.data(service, "geoServiceState");
6667 if ( !serviceState ) {
6673 var scHtml = '<div data-geo-service="tiled" style="-webkit-transform:translateZ(0); position:absolute; left:0; top:0; width:8px; height:8px; margin:0; padding:0;"></div>';
6675 serviceContainer.append(scHtml);
6677 serviceState.serviceContainer = serviceContainer.children( ":last" );
6679 $.data(service, "geoServiceState", serviceState);
6682 return serviceState.serviceContainer;
6685 destroy: function (map, serviceContainer, service) {
6686 var serviceState = $.data(service, "geoServiceState");
6688 serviceState.serviceContainer.remove();
6690 $.removeData(service, "geoServiceState");
6693 interactiveTransform: function ( map, service, center, pixelSize ) {
6694 //console.log( "tiled.interactiveTransform( " + center.join( ", " ) + ", " + pixelSize + ")" );
6695 var serviceState = $.data( service, "geoServiceState" ),
6696 tilingScheme = map.options[ "tilingScheme" ];
6698 if ( serviceState ) {
6699 this._cancelUnloaded( map, service );
6701 serviceState.serviceContainer.children( ).each( function ( i ) {
6702 var $scaleContainer = $(this),
6703 scalePixelSize = $scaleContainer.data("pixelSize"),
6704 scaleRatio = scalePixelSize / pixelSize;
6706 if ( scalePixelSize > 0 ) {
6707 scaleRatio = Math.round(scaleRatio * 1000) / 1000;
6709 var oldMapCoord = $scaleContainer.data("scaleOrigin"),
6710 newPixelPoint = map._toPixel(oldMapCoord, center, pixelSize);
6712 $scaleContainer.css( {
6713 left: Math.round(newPixelPoint[0]) + "px",
6714 top: Math.round(newPixelPoint[1]) + "px",
6715 width: tilingScheme.tileWidth * scaleRatio,
6716 height: tilingScheme.tileHeight * scaleRatio
6720 if ( $("body")[0].filters !== undefined ) {
6721 $scaleContainer.children().each( function ( i ) {
6722 $( this ).css( "filter", "progid:DXImageTransform.Microsoft.Matrix(FilterType=bilinear,M11=" + scaleRatio + ",M22=" + scaleRatio + ",sizingmethod='auto expand')" );
6731 refresh: function (map, service, force) {
6732 //console.log( "tiled.refresh( " + map._center.join( ", " ) + ", " + map._pixelSize + ")" );
6733 var serviceState = $.data( service, "geoServiceState" );
6735 this._cancelUnloaded(map, service);
6737 if ( serviceState && force ) {
6738 // if hidden atm, we want to make sure we reload this service after it becomes visible
6739 serviceState.reloadTiles = true;
6742 if ( serviceState && service && service.style.visibility === "visible" && !( serviceState.serviceContainer.is( ":hidden" ) ) ) {
6743 var bbox = map._getBbox(),
6744 pixelSize = map._pixelSize,
6747 $serviceContainer = serviceState.serviceContainer,
6749 contentBounds = map._getContentBounds(),
6750 mapWidth = contentBounds["width"],
6751 mapHeight = contentBounds["height"],
6753 image = map.options[ "axisLayout" ] === "image",
6754 ySign = image ? +1 : -1,
6756 tilingScheme = map.options["tilingScheme"],
6757 tileWidth = tilingScheme.tileWidth,
6758 tileHeight = tilingScheme.tileHeight,
6760 tileX = Math.floor((bbox[0] - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
6761 tileY = Math.max( Math.floor( ( image ? bbox[1] - tilingScheme.origin[1] : tilingScheme.origin[1] - bbox[ 3 ] ) / (pixelSize * tileHeight) ), 0 ),
6762 tileX2 = Math.ceil((bbox[2] - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
6763 tileY2 = Math.ceil( ( image ? bbox[3] - tilingScheme.origin[1] : tilingScheme.origin[1] - bbox[ 1 ] ) / (pixelSize * tileHeight) ),
6765 bboxMax = map._getBboxMax(),
6766 pixelSizeAtZero = map._getPixelSize(0),
6767 ratio = pixelSizeAtZero / pixelSize,
6768 fullXAtScale = Math.floor((bboxMax[0] - tilingScheme.origin[0]) / (pixelSizeAtZero * tileWidth)) * ratio,
6769 fullYAtScale = Math.floor((tilingScheme.origin[1] + ySign * bboxMax[3]) / (pixelSizeAtZero * tileHeight)) * ratio,
6771 fullXMinX = tilingScheme.origin[0] + (fullXAtScale * tileWidth) * pixelSize,
6772 fullYMinOrMaxY = tilingScheme.origin[1] + ySign * (fullYAtScale * tileHeight) * pixelSize,
6774 serviceLeft = Math.round((fullXMinX - bbox[0]) / pixelSize),
6775 serviceTop = Math.round( ( image ? fullYMinOrMaxY - bbox[1] : bbox[3] - fullYMinOrMaxY ) / pixelSize),
6777 scaleContainers = $serviceContainer.children().show(),
6778 scaleContainer = scaleContainers.filter("[data-pixel-size='" + pixelSize + "']").appendTo($serviceContainer),
6780 opacity = service.style.opacity,
6784 loadImageDeferredDone = function( url ) {
6785 // when a Deferred call is done, add the image to the map
6786 // a reference to the correct img element is on the Deferred object itself
6787 serviceObj._loadImage( $.data( this, "img" ), url, pixelSize, map, serviceState, opacity );
6790 loadImageDeferredFail = function( ) {
6791 $.data( this, "img" ).remove( );
6792 serviceState.loadCount--;
6793 map._requestComplete();
6796 if (serviceState.reloadTiles) {
6797 scaleContainers.find("img").attr("data-dirty", "true");
6800 if (!scaleContainer.size()) {
6801 $serviceContainer.append("<div style='-webkit-transform:translateZ(0);position:absolute; left:" + serviceLeft % tileWidth + "px; top:" + serviceTop % tileHeight + "px; width:" + tileWidth + "px; height:" + tileHeight + "px; margin:0; padding:0;' data-pixel-size='" + pixelSize + "'></div>");
6802 scaleContainer = $serviceContainer.children(":last").data("scaleOrigin", map._toMap( [ (serviceLeft % tileWidth), (serviceTop % tileHeight) ] ) );
6804 scaleContainer.css({
6805 left: (serviceLeft % tileWidth) + "px",
6806 top: (serviceTop % tileHeight) + "px"
6807 }).data("scaleOrigin", map._toMap( [ (serviceLeft % tileWidth), (serviceTop % tileHeight) ] ) );
6809 scaleContainer.children().each(function (i) {
6812 tile = $img.attr("data-tile").split(",");
6815 left: Math.round(((parseInt(tile[0], 10) - fullXAtScale) * 100) + (serviceLeft - (serviceLeft % tileWidth)) / tileWidth * 100) + "%",
6816 top: Math.round(((parseInt(tile[1], 10) - fullYAtScale) * 100) + (serviceTop - (serviceTop % tileHeight)) / tileHeight * 100) + "%"
6820 $img.fadeTo(0, opacity);
6825 for (x = tileX; x < tileX2; x++) {
6826 for (y = tileY; y < tileY2; y++) {
6827 var tileStr = "" + x + "," + y,
6828 $img = scaleContainer.children("[data-tile='" + tileStr + "']").removeAttr("data-dirty");
6830 if ($img.size() === 0 || serviceState.reloadTiles) {
6832 tilingScheme.origin[0] + (x * tileWidth) * pixelSize,
6833 tilingScheme.origin[1] + ySign * (y * tileHeight) * pixelSize
6837 tilingScheme.origin[0] + ((x + 1) * tileWidth - 1) * pixelSize,
6838 tilingScheme.origin[1] + ySign * ((y + 1) * tileHeight - 1) * pixelSize
6841 tileBbox = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]],
6843 urlProp = ( service.hasOwnProperty( "src" ) ? "src" : "getUrl" ),
6848 zoom: map._getZoom(),
6853 index: Math.abs(y + x)
6855 isFunc = $.isFunction( service[ urlProp ] ),
6859 imageUrl = service[ urlProp ]( urlArgs );
6861 $.templates( "geoSrc", service[ urlProp ] );
6862 imageUrl = $.render[ "geoSrc" ]( urlArgs );
6865 serviceState.loadCount++;
6866 map._requestQueued();
6868 if (serviceState.reloadTiles && $img.size() > 0) {
6869 $img.attr("src", imageUrl);
6871 var imgMarkup = "<img style='-webkit-transform:translateZ(0);position:absolute; " +
6872 "left:" + (((x - fullXAtScale) * 100) + (serviceLeft - (serviceLeft % tileWidth)) / tileWidth * 100) + "%; " +
6873 "top:" + (((y - fullYAtScale) * 100) + (serviceTop - (serviceTop % tileHeight)) / tileHeight * 100) + "%; ";
6875 imgMarkup += "width: 100%; height: 100%;";
6879 if ($("body")[0].filters === undefined) {
6880 imgMarkup += "width: 100%; height: 100%;";
6884 imgMarkup += "margin:0; padding:0; -khtml-user-select:none; -moz-user-select:none; -webkit-user-select:none; user-select:none; display:none;' unselectable='on' data-tile='" + tileStr + "' />";
6886 scaleContainer.append(imgMarkup);
6887 $img = scaleContainer.children(":last");
6890 if ( typeof imageUrl === "string" ) {
6891 serviceObj._loadImage( $img, imageUrl, pixelSize, map, serviceState, opacity );
6892 } else if ( imageUrl ) {
6894 $.data( imageUrl, "img", $img );
6895 imageUrl.done( loadImageDeferredDone ).fail( loadImageDeferredFail );
6903 scaleContainers.find("[data-dirty]").remove();
6904 serviceState.reloadTiles = false;
6908 resize: function (map, service) {
6911 opacity: function ( map, service ) {
6912 var serviceState = $.data( service, "geoServiceState" );
6913 serviceState.serviceContainer.find( "img" ).stop( true ).fadeTo( "fast", service.style.opacity );
6916 toggle: function ( map, service ) {
6917 var serviceState = $.data( service, "geoServiceState" );
6918 serviceState.serviceContainer.css( "display", service.style.visibility === "visible" ? "block" : "none" );
6921 _cancelUnloaded: function (map, service) {
6922 var serviceState = $.data( service, "geoServiceState" );
6924 if (serviceState && serviceState.loadCount > 0) {
6925 serviceState.serviceContainer.find("img:hidden").remove();
6926 while (serviceState.loadCount > 0) {
6927 serviceState.loadCount--;
6928 map._requestComplete();
6933 _loadImage: function ( $img, url, pixelSize, map, serviceState, opacity ) {
6934 var serviceContainer = serviceState.serviceContainer;
6936 $img.load(function (e) {
6938 $(e.target).fadeTo(0, opacity);
6943 serviceState.loadCount--;
6944 map._requestComplete();
6946 if (serviceState.loadCount <= 0) {
6947 serviceContainer.children(":not([data-pixel-size='" + pixelSize + "'])").remove();
6948 serviceState.loadCount = 0;
6950 }).error(function (e) {
6951 $(e.target).remove();
6952 serviceState.loadCount--;
6953 map._requestComplete();
6955 if (serviceState.loadCount <= 0) {
6956 serviceContainer.children(":not([data-pixel-size='" + pixelSize + "'])").remove();
6957 serviceState.loadCount = 0;
6959 }).attr("src", url);
6965 (function ($, undefined) {
6966 $.geo._serviceTypes.shingled = (function () {
6968 create: function (map, serviceContainer, service, index) {
6969 var serviceState = $.data(service, "geoServiceState");
6971 if ( !serviceState ) {
6976 var scHtml = '<div data-geo-service="shingled" style="-webkit-transform:translateZ(0);position:absolute; left:0; top:0; width:16px; height:16px; margin:0; padding:0;"></div>';
6978 serviceContainer.append(scHtml);
6980 serviceState.serviceContainer = serviceContainer.children(":last");
6981 $.data(service, "geoServiceState", serviceState);
6984 return serviceState.serviceContainer;
6987 destroy: function (map, serviceContainer, service) {
6988 var serviceState = $.data(service, "geoServiceState");
6990 serviceState.serviceContainer.remove();
6992 $.removeData(service, "geoServiceState");
6995 interactiveTransform: function ( map, service, center, pixelSize ) {
6996 var serviceState = $.data( service, "geoServiceState" ),
6998 contentBounds = map._getContentBounds(),
6999 mapWidth = contentBounds[ "width" ],
7000 mapHeight = contentBounds[ "height" ],
7002 halfWidth = mapWidth / 2,
7003 halfHeight = mapHeight / 2,
7005 bbox = [ center[ 0 ] - halfWidth, center[ 1 ] - halfHeight, center[ 0 ] + halfWidth, center[ 1 ] + halfHeight ];
7007 if ( serviceState ) {
7008 this._cancelUnloaded( map, service );
7010 serviceState.serviceContainer.children( ).each( function ( i ) {
7011 var $scaleContainer = $(this),
7012 scalePixelSize = $scaleContainer.data( "pixelSize" ),
7013 scaleRatio = scalePixelSize / pixelSize;
7015 if ( scalePixelSize > 0 ) {
7016 scaleRatio = Math.round(scaleRatio * 1000) / 1000;
7018 var oldMapOrigin = $scaleContainer.data( "origin" ),
7019 newPixelPoint = map._toPixel( oldMapOrigin, center, pixelSize );
7021 $scaleContainer.css( {
7022 left: Math.round( newPixelPoint[ 0 ] ),
7023 top: Math.round( newPixelPoint[ 1 ] ),
7024 width: mapWidth * scaleRatio,
7025 height: mapHeight * scaleRatio
7032 refresh: function (map, service) {
7033 var serviceState = $.data(service, "geoServiceState");
7035 this._cancelUnloaded(map, service);
7037 if ( serviceState && service && service.style.visibility === "visible" && !( serviceState.serviceContainer.is( ":hidden" ) ) ) {
7039 var bbox = map._getBbox(),
7040 pixelSize = map._pixelSize,
7043 serviceContainer = serviceState.serviceContainer,
7045 contentBounds = map._getContentBounds(),
7046 mapWidth = contentBounds["width"],
7047 mapHeight = contentBounds["height"],
7049 scaleContainer = serviceContainer.children('[data-pixel-size="' + pixelSize + '"]'),
7051 opacity = service.style.opacity,
7056 serviceContainer.find("img").attr("data-keep-alive", "0");
7059 if ( !scaleContainer.size() ) {
7060 serviceContainer.append('<div style="-webkit-transform:translateZ(0);position:absolute; left:0px; top: 0px; width:' + mapWidth + 'px; height:' + mapHeight + 'px; margin:0; padding:0;" data-pixel-size="' + pixelSize + '" data-origin="[' + map._toMap( [ 0, 0 ] ) + ']"></div>');
7061 scaleContainer = serviceContainer.children(":last");
7064 var urlProp = ( service.hasOwnProperty("src") ? "src" : "getUrl" ),
7069 zoom: map._getZoom(),
7073 isFunc = $.isFunction( service[ urlProp ] ),
7075 imagePos = scaleContainer.position( );
7077 imagePos.left = - ( imagePos.left );
7078 imagePos.top = - ( imagePos.top );
7081 imageUrl = service[ urlProp ]( urlArgs );
7083 $.templates( "geoSrc", service[ urlProp ] );
7084 imageUrl = $.render[ "geoSrc" ]( urlArgs );
7087 serviceState.loadCount++;
7088 map._requestQueued();
7090 scaleContainer.append('<img style="-webkit-transform:translateZ(0);position:absolute; left:' + ( imagePos.left / scaleContainer.width( ) * 100 ) + '%; top:' + ( imagePos.top / scaleContainer.height( ) * 100 ) + '%; width:100%; height:100%; margin:0; padding:0; -khtml-user-select:none; -moz-user-select:none; -webkit-user-select:none; user-select:none; display:none;" unselectable="on" />');
7091 $img = scaleContainer.children(":last").data("center", map._center);
7093 if ( typeof imageUrl === "string" ) {
7094 serviceObj._loadImage( $img, imageUrl, pixelSize, map, serviceState, opacity );
7097 imageUrl.done( function( url ) {
7098 serviceObj._loadImage( $img, url, pixelSize, map, serviceState, opacity );
7099 } ).fail( function( ) {
7101 serviceState.loadCount--;
7102 map._requestComplete();
7109 resize: function (map, service) {
7110 var serviceState = $.data(service, "geoServiceState");
7112 if ( serviceState && service && service.style.visibility === "visible" ) {
7113 this._cancelUnloaded(map, service);
7115 var serviceContainer = serviceState.serviceContainer,
7117 contentBounds = map._getContentBounds(),
7118 mapWidth = contentBounds["width"],
7119 mapHeight = contentBounds["height"],
7121 scaleContainers = serviceContainer.children();
7123 scaleContainers.attr("data-pixel-size", "0");
7125 scaleContainers.each( function ( i ) {
7126 var $scaleContainer = $(this),
7127 position = $scaleContainer.position( );
7129 var oldMapOrigin = $scaleContainer.data( "origin" ),
7130 newPixelPoint = map._toPixel( oldMapOrigin );
7132 $scaleContainer.css( {
7133 left: position.left + ( mapWidth - $scaleContainer.width( ) ) / 2,
7134 top: position.top + ( mapHeight - $scaleContainer.height( ) ) / 2
7141 scaleContainer.css({
7142 left: halfWidth + 'px',
7143 top: halfHeight + 'px'
7149 opacity: function ( map, service ) {
7150 var serviceState = $.data( service, "geoServiceState" );
7151 serviceState.serviceContainer.find( "img" ).stop( true ).fadeTo( "fast", service.style.opacity );
7154 toggle: function (map, service) {
7155 var serviceState = $.data(service, "geoServiceState");
7156 serviceState.serviceContainer.css("display", service.style.visibility === "visible" ? "block" : "none");
7159 _cancelUnloaded: function (map, service) {
7160 var serviceState = $.data(service, "geoServiceState");
7162 if (serviceState && serviceState.loadCount > 0) {
7163 serviceState.serviceContainer.find("img:hidden").remove();
7164 while (serviceState.loadCount > 0) {
7165 serviceState.loadCount--;
7166 map._requestComplete();
7171 _loadImage: function ( $img, url, pixelSize, map, serviceState, opacity ) {
7172 var serviceContainer = serviceState.serviceContainer;
7174 $img.load(function (e) {
7175 if ( !$.contains(document.body, e.target.jquery ? e.target[0] : e.target) ) {
7176 // this image has been canceled and removed from the DOM
7181 $(e.target).fadeTo(0, opacity);
7186 serviceState.loadCount--;
7187 map._requestComplete();
7189 if (serviceState.loadCount <= 0) {
7191 serviceContainer.children(':not([data-pixel-size="' + pixelSize + '"])').remove();
7193 serviceContainer.find( "img[data-keep-alive]" ).remove( );
7195 serviceState.loadCount = 0;
7197 }).error(function (e) {
7198 if ( !$.contains(document.body, e.target.jquery ? e.target[0] : e.target) ) {
7199 // this image has been canceled and removed from the DOM
7203 $(e.target).remove();
7204 serviceState.loadCount--;
7205 map._requestComplete();
7207 if (serviceState.loadCount <= 0) {
7208 serviceContainer.children(":not([data-pixel-size='" + pixelSize + "'])").remove();
7209 serviceState.loadCount = 0;
7211 }).attr("src", url);