e3db45235bcca2b8aa035c69eefc97a9c285aedc
[platform/framework/web/web-ui-fw.git] / libs / js / jquery-geo-1.0b2 / docs / jquery.geo-test.js
1 /*! jQuery Geo - vtest - 2012-11-02
2  * http://jquerygeo.com
3  * Copyright (c) 2012 Ryan Westphal/Applied Geographics, Inc.; Licensed MIT, GPL */
4
5 // Copyright 2006 Google Inc.
6 //
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
10 //
11 //   http://www.apache.org/licenses/LICENSE-2.0
12 //
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.
18
19
20 // Known Issues:
21 //
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
31 //   doctype to HTML5
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.
37
38 // Only add this code if we do not already have a canvas implementation
39 if (!document.createElement('canvas').getContext) {
40
41   (function () {
42
43     // alias some functions to make (compiled) code shorter
44     var m = Math;
45     var mr = m.round;
46     var ms = m.sin;
47     var mc = m.cos;
48     var abs = m.abs;
49     var sqrt = m.sqrt;
50
51     // this is used for sub pixel precision
52     var Z = 10;
53     var Z2 = Z / 2;
54
55     var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
56
57     /**
58     * This funtion is assigned to the <canvas> elements as element.getContext().
59     * @this {HTMLElement}
60     * @return {CanvasRenderingContext2D_}
61     */
62     function getContext() {
63       return this.context_ ||
64         (this.context_ = new CanvasRenderingContext2D_(this));
65     }
66
67     var slice = Array.prototype.slice;
68
69     /**
70     * Binds a function to an object. The returned function will always use the
71     * passed in {@code obj} as {@code this}.
72     *
73     * Example:
74     *
75     *   g = bind(f, obj, a, b)
76     *   g(c, d) // will do f.call(obj, a, b, c, d)
77     *
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
80     *     is called
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
84     */
85     function bind(f, obj, var_args) {
86       var a = slice.call(arguments, 2);
87       return function () {
88         return f.apply(obj, a.concat(slice.call(arguments)));
89       };
90     }
91
92     function encodeHtmlAttribute(s) {
93       return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
94     }
95
96     function addNamespace(doc, prefix, urn) {
97       if (!doc.namespaces[prefix]) {
98         doc.namespaces.add(prefix, urn, '#default#VML');
99       }
100     }
101
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');
105
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}';
113       }
114     }
115
116     // Add namespaces and stylesheet at startup.
117     addNamespacesAndStylesheet(document);
118
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
123         // recognized.
124         doc.createElement('canvas');
125         doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
126       },
127
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]);
133         }
134       },
135
136       /**
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.
143       */
144       initElement: function (el) {
145         if (!el.getContext) {
146           el.getContext = getContext;
147
148           // Add namespaces and stylesheet to document of the element.
149           addNamespacesAndStylesheet(el.ownerDocument);
150
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.
154           el.innerHTML = '';
155
156           // do not use inline function because that will leak memory
157           el.attachEvent('onpropertychange', onPropertyChange);
158           el.attachEvent('onresize', onResize);
159
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';
165           } else {
166             el.width = el.clientWidth;
167           }
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';
172           } else {
173             el.height = el.clientHeight;
174           }
175           //el.getContext().setCoordsize_()
176         }
177         return el;
178       }
179     };
180
181     function onPropertyChange(e) {
182       var el = e.srcElement;
183
184       switch (e.propertyName) {
185         case 'width':
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';
190           break;
191         case 'height':
192           el.getContext().clearRect();
193           el.style.height = el.attributes.height.nodeValue + 'px';
194           el.firstChild.style.height = el.clientHeight + 'px';
195           break;
196       }
197     }
198
199     function onResize(e) {
200       var el = e.srcElement;
201       if (el.firstChild) {
202         el.firstChild.style.width = el.clientWidth + 'px';
203         el.firstChild.style.height = el.clientHeight + 'px';
204       }
205     }
206
207     G_vmlCanvasManager_.init();
208
209     // precompute "00" to "FF"
210     var decToHex = [];
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);
214       }
215     }
216
217     function createMatrixIdentity() {
218       return [
219       [1, 0, 0],
220       [0, 1, 0],
221       [0, 0, 1]
222     ];
223     }
224
225     function matrixMultiply(m1, m2) {
226       var result = createMatrixIdentity();
227
228       for (var x = 0; x < 3; x++) {
229         for (var y = 0; y < 3; y++) {
230           var sum = 0;
231
232           for (var z = 0; z < 3; z++) {
233             sum += m1[x][z] * m2[z][y];
234           }
235
236           result[x][y] = sum;
237         }
238       }
239       return result;
240     }
241
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;
254       o2.font          = o1.font;
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_;
260     }
261
262     //  var colorData = {
263     //    aliceblue: '#F0F8FF',
264     //    antiquewhite: '#FAEBD7',
265     //    aquamarine: '#7FFFD4',
266     //    azure: '#F0FFFF',
267     //    beige: '#F5F5DC',
268     //    bisque: '#FFE4C4',
269     //    black: '#000000',
270     //    blanchedalmond: '#FFEBCD',
271     //    blueviolet: '#8A2BE2',
272     //    brown: '#A52A2A',
273     //    burlywood: '#DEB887',
274     //    cadetblue: '#5F9EA0',
275     //    chartreuse: '#7FFF00',
276     //    chocolate: '#D2691E',
277     //    coral: '#FF7F50',
278     //    cornflowerblue: '#6495ED',
279     //    cornsilk: '#FFF8DC',
280     //    crimson: '#DC143C',
281     //    cyan: '#00FFFF',
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',
311     //    gold: '#FFD700',
312     //    goldenrod: '#DAA520',
313     //    grey: '#808080',
314     //    greenyellow: '#ADFF2F',
315     //    honeydew: '#F0FFF0',
316     //    hotpink: '#FF69B4',
317     //    indianred: '#CD5C5C',
318     //    indigo: '#4B0082',
319     //    ivory: '#FFFFF0',
320     //    khaki: '#F0E68C',
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',
340     //    linen: '#FAF0E6',
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',
367     //    peru: '#CD853F',
368     //    pink: '#FFC0CB',
369     //    plum: '#DDA0DD',
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',
383     //    snow: '#FFFAFA',
384     //    springgreen: '#00FF7F',
385     //    steelblue: '#4682B4',
386     //    tan: '#D2B48C',
387     //    thistle: '#D8BFD8',
388     //    tomato: '#FF6347',
389     //    turquoise: '#40E0D0',
390     //    violet: '#EE82EE',
391     //    wheat: '#F5DEB3',
392     //    whitesmoke: '#F5F5F5',
393     //    yellowgreen: '#9ACD32'
394     //  };
395
396
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') {
403         parts[3] = 1;
404       }
405       return parts;
406     }
407
408     function percent(s) {
409       return parseFloat(s) / 100;
410     }
411
412     function clamp(v, min, max) {
413       return Math.min(max, Math.max(min, v));
414     }
415
416     function hslToRgb(parts) {
417       var r, g, b, h, s, l;
418       h = parseFloat(parts[0]) / 360 % 360;
419       if (h < 0)
420         h++;
421       s = clamp(percent(parts[1]), 0, 1);
422       l = clamp(percent(parts[2]), 0, 1);
423       if (s == 0) {
424         r = g = b = l; // achromatic
425       } else {
426         var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
427         var p = 2 * l - q;
428         r = hueToRgb(p, q, h + 1 / 3);
429         g = hueToRgb(p, q, h);
430         b = hueToRgb(p, q, h - 1 / 3);
431       }
432
433       return '#' + decToHex[Math.floor(r * 255)] +
434         decToHex[Math.floor(g * 255)] +
435         decToHex[Math.floor(b * 255)];
436     }
437
438     function hueToRgb(m1, m2, h) {
439       if (h < 0)
440         h++;
441       if (h > 1)
442         h--;
443
444       if (6 * h < 1)
445         return m1 + (m2 - m1) * 6 * h;
446       else if (2 * h < 1)
447         return m2;
448       else if (3 * h < 2)
449         return m1 + (m2 - m1) * (2 / 3 - h) * 6;
450       else
451         return m1;
452     }
453
454     var processStyleCache = {};
455
456     function processStyle(styleString) {
457       if (styleString in processStyleCache) {
458         return processStyleCache[styleString];
459       }
460
461       var str, alpha = 1;
462
463       styleString = String(styleString);
464       if (styleString.charAt(0) == '#') {
465         str = styleString;
466       } else if (/^rgb/.test(styleString)) {
467         var parts = getRgbHslContent(styleString);
468         var str = '#', n;
469         for (var i = 0; i < 3; i++) {
470           if (parts[i].indexOf('%') != -1) {
471             n = Math.floor(percent(parts[i]) * 255);
472           } else {
473             n = +parts[i];
474           }
475           str += decToHex[clamp(n, 0, 255)];
476         }
477         alpha = +parts[3];
478       } else if (/^hsl/.test(styleString)) {
479         var parts = getRgbHslContent(styleString);
480         str = hslToRgb(parts);
481         alpha = parts[3];
482       } else {
483         str = /*colorData[styleString] ||*/styleString;
484       }
485       return processStyleCache[styleString] = { color: str, alpha: alpha };
486     }
487
488     var DEFAULT_STYLE = {
489       style: 'normal',
490       variant: 'normal',
491       weight: 'normal',
492       size: 10,
493       family: 'sans-serif'
494     };
495
496     // Internal text style cache
497     //  var fontStyleCache = {};
498
499     //  function processFontStyle(styleString) {
500     //    if (fontStyleCache[styleString]) {
501     //      return fontStyleCache[styleString];
502     //    }
503
504     //    var el = document.createElement('div');
505     //    var style = el.style;
506     //    try {
507     //      style.font = styleString;
508     //    } catch (ex) {
509     //      // Ignore failures to set to invalid font.
510     //    }
511
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
518     //    };
519     //  }
520
521     //  function getComputedStyle(style, element) {
522     //    var computedStyle = {};
523
524     //    for (var p in style) {
525     //      computedStyle[p] = style[p];
526     //    }
527
528     //    // Compute the size
529     //    var canvasFontSize = parseFloat(element.currentStyle.fontSize),
530     //        fontSize = parseFloat(style.size);
531
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;
542     //    } else {
543     //      computedStyle.size = canvasFontSize;
544     //    }
545
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;
549
550     //    return computedStyle;
551     //  }
552
553     //  function buildStyle(style) {
554     //    return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
555     //        style.size + 'px ' + style.family;
556     //  }
557
558     var lineCapMap = {
559       'butt': 'flat',
560       'round': 'round'
561     };
562
563     function processLineCap(lineCap) {
564       return lineCapMap[lineCap] || 'square';
565     }
566
567     /**
568     * This class implements CanvasRenderingContext2D interface as described by
569     * the WHATWG.
570     * @param {HTMLElement} canvasElement The element that the 2D context should
571     * be associated with
572     */
573     function CanvasRenderingContext2D_(canvasElement) {
574       this.m_ = createMatrixIdentity();
575
576       this.mStack_ = [];
577       this.aStack_ = [];
578       this.currentPath_ = [];
579
580       // Canvas context properties
581       this.strokeStyle = '#000';
582       this.fillStyle = '#000';
583
584       this.lineWidth = 1;
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;
593
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);
599
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);
605
606       this.element_ = el;
607       this.arcScaleX_ = 1;
608       this.arcScaleY_ = 1;
609       this.lineScale_ = 1;
610     }
611
612     var contextPrototype = CanvasRenderingContext2D_.prototype;
613     contextPrototype.clearRect = function () {
614       if (this.textMeasureEl_) {
615         this.textMeasureEl_.removeNode(true);
616         this.textMeasureEl_ = null;
617       }
618       this.element_.innerHTML = '';
619     };
620
621     contextPrototype.beginPath = function () {
622       // TODO: Branch current matrix so that save/restore has no effect
623       //       as per safari docs.
624       this.currentPath_ = [];
625     };
626
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;
632     };
633
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 });
637
638       this.currentX_ = p.x;
639       this.currentY_ = p.y;
640     };
641
642     contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
643                                               aCP2x, aCP2y,
644                                               aX, aY) {
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);
649     };
650
651     // Helper function that takes the already fixed cordinates.
652     function bezierCurveTo(self, cp1, cp2, p) {
653       self.currentPath_.push({
654         type: 'bezierCurveTo',
655         cp1x: cp1.x,
656         cp1y: cp1.y,
657         cp2x: cp2.x,
658         cp2y: cp2.y,
659         x: p.x,
660         y: p.y
661       });
662       self.currentX_ = p.x;
663       self.currentY_ = p.y;
664     }
665
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
669
670       var cp = getCoords(this, aCPx, aCPy);
671       var p = getCoords(this, aX, aY);
672
673       var cp1 = {
674         x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
675         y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
676       };
677       var cp2 = {
678         x: cp1.x + (p.x - this.currentX_) / 3.0,
679         y: cp1.y + (p.y - this.currentY_) / 3.0
680       };
681
682       bezierCurveTo(this, cp1, cp2, p);
683     };
684
685     contextPrototype.arc = function (aX, aY, aRadius,
686                                   aStartAngle, aEndAngle, aClockwise) {
687       aRadius *= Z;
688       var arcType = aClockwise ? 'at' : 'wa';
689
690       var xStart = aX + mc(aStartAngle) * aRadius - Z2;
691       var yStart = aY + ms(aStartAngle) * aRadius - Z2;
692
693       var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
694       var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
695
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
700       }
701
702       var p = getCoords(this, aX, aY);
703       var pStart = getCoords(this, xStart, yStart);
704       var pEnd = getCoords(this, xEnd, yEnd);
705
706       this.currentPath_.push({ type: arcType,
707         x: p.x,
708         y: p.y,
709         radius: aRadius,
710         xStart: pStart.x,
711         yStart: pStart.y,
712         xEnd: pEnd.x,
713         yEnd: pEnd.y
714       });
715
716     };
717
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);
723     //    this.closePath();
724     //  };
725
726     //  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
727     //    var oldPath = this.currentPath_;
728     //    this.beginPath();
729
730     //    this.moveTo(aX, aY);
731     //    this.lineTo(aX + aWidth, aY);
732     //    this.lineTo(aX + aWidth, aY + aHeight);
733     //    this.lineTo(aX, aY + aHeight);
734     //    this.closePath();
735     //    this.stroke();
736
737     //    this.currentPath_ = oldPath;
738     //  };
739
740     //  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
741     //    var oldPath = this.currentPath_;
742     //    this.beginPath();
743
744     //    this.moveTo(aX, aY);
745     //    this.lineTo(aX + aWidth, aY);
746     //    this.lineTo(aX + aWidth, aY + aHeight);
747     //    this.lineTo(aX, aY + aHeight);
748     //    this.closePath();
749     //    this.fill();
750
751     //    this.currentPath_ = oldPath;
752     //  };
753
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;
760     //    return gradient;
761     //  };
762
763     //  contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
764     //                                                   aX1, aY1, aR1) {
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;
772     //    return gradient;
773     //  };
774
775     //  contextPrototype.drawImage = function(image, var_args) {
776     //    var dx, dy, dw, dh, sx, sy, sw, sh;
777
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';
783
784     //    // get the original size
785     //    var w = image.width;
786     //    var h = image.height;
787
788     //    // and remove overides
789     //    image.runtimeStyle.width = oldRuntimeWidth;
790     //    image.runtimeStyle.height = oldRuntimeHeight;
791
792     //    if (arguments.length == 3) {
793     //      dx = arguments[1];
794     //      dy = arguments[2];
795     //      sx = sy = 0;
796     //      sw = dw = w;
797     //      sh = dh = h;
798     //    } else if (arguments.length == 5) {
799     //      dx = arguments[1];
800     //      dy = arguments[2];
801     //      dw = arguments[3];
802     //      dh = arguments[4];
803     //      sx = sy = 0;
804     //      sw = w;
805     //      sh = h;
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];
815     //    } else {
816     //      throw Error('Invalid number of arguments');
817     //    }
818
819     //    var d = getCoords(this, dx, dy);
820
821     //    var w2 = sw / 2;
822     //    var h2 = sh / 2;
823
824     //    var vmlStr = [];
825
826     //    var W = 10;
827     //    var H = 10;
828
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;');
834
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.
839
840     //    if (this.m_[0][0] != 1 || this.m_[0][1] ||
841     //        this.m_[1][1] != 1 || this.m_[1][0]) {
842     //      var filter = [];
843
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), '');
851
852     //      // Bounding box calculation (need to minimize displayed area so that
853     //      // filters don't waste time on unused pixels.
854     //      var max = d;
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);
858
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);
861
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');");
865
866     //    } else {
867     //      vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
868     //    }
869
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, '"',
878     //                ' />',
879     //                '</g_vml_:group>');
880
881     //    this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
882     //  };
883
884     contextPrototype.stroke = function (aFill) {
885       var lineStr = [];
886       var lineOpen = false;
887
888       var W = 10;
889       var H = 10;
890
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, '"',
897                  ' path="');
898
899       var newSeq = false;
900       var min = { x: null, y: null };
901       var max = { x: null, y: null };
902
903       for (var i = 0; i < this.currentPath_.length; i++) {
904         var p = this.currentPath_[i];
905         var c;
906
907         switch (p.type) {
908           case 'moveTo':
909             c = p;
910             lineStr.push(' m ', mr(p.x), ',', mr(p.y));
911             break;
912           case 'lineTo':
913             lineStr.push(' l ', mr(p.x), ',', mr(p.y));
914             break;
915           case 'close':
916             lineStr.push(' x ');
917             p = null;
918             break;
919           case 'bezierCurveTo':
920             lineStr.push(' c ',
921                        mr(p.cp1x), ',', mr(p.cp1y), ',',
922                        mr(p.cp2x), ',', mr(p.cp2y), ',',
923                        mr(p.x), ',', mr(p.y));
924             break;
925           case 'at':
926           case 'wa':
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));
934             break;
935         }
936
937
938         // TODO: Following is broken for curves due to
939         //       move to proper paths.
940
941         // Figure out dimensions so we can do gradient fills
942         // properly
943         if (p) {
944           if (min.x == null || p.x < min.x) {
945             min.x = p.x;
946           }
947           if (max.x == null || p.x > max.x) {
948             max.x = p.x;
949           }
950           if (min.y == null || p.y < min.y) {
951             min.y = p.y;
952           }
953           if (max.y == null || p.y > max.y) {
954             max.y = p.y;
955           }
956         }
957       }
958       lineStr.push(' ">');
959
960       if (!aFill) {
961         appendStroke(this, lineStr);
962       } else {
963         appendFill(this, lineStr, min, max);
964       }
965
966       lineStr.push('</g_vml_:shape>');
967
968       this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
969     };
970
971     function appendStroke(ctx, lineStr) {
972       var a = processStyle(ctx.strokeStyle);
973       var color = a.color;
974       var opacity = a.alpha * ctx.globalAlpha;
975       var lineWidth = ctx.lineScale_ * ctx.lineWidth;
976
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.
979       if (lineWidth < 1) {
980         opacity *= lineWidth;
981       }
982
983       lineStr.push(
984       '<g_vml_:stroke',
985       ' opacity="', opacity, '"',
986       ' joinstyle="', ctx.lineJoin, '"',
987       ' miterlimit="', ctx.miterLimit, '"',
988       ' endcap="', processLineCap(ctx.lineCap), '"',
989       ' weight="', lineWidth, 'px"',
990       ' color="', color, '" />'
991     );
992     }
993
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.
1002       //      var angle = 0;
1003       //      var focus = {x: 0, y: 0};
1004
1005       //      // additional offset
1006       //      var shift = 0;
1007       //      // scale factor for offset
1008       //      var expansion = 1;
1009
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;
1020
1021       //        // The angle should be a non-negative number.
1022       //        if (angle < 0) {
1023       //          angle += 360;
1024       //        }
1025
1026       //        // Very small angles produce an unexpected result because they are
1027       //        // converted to a scientific notation string.
1028       //        if (angle < 1e-6) {
1029       //          angle = 0;
1030       //        }
1031       //      } else {
1032       //        var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
1033       //        focus = {
1034       //          x: (p0.x - min.x) / width,
1035       //          y: (p0.y - min.y) / height
1036       //        };
1037
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;
1043       //      }
1044
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;
1050       //      });
1051
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;
1057
1058       //      var colors = [];
1059       //      for (var i = 0; i < length; i++) {
1060       //        var stop = stops[i];
1061       //        colors.push(stop.offset * expansion + shift + ' ' + stop.color);
1062       //      }
1063
1064       //      // When colors attribute is used, the meanings of opacity and o:opacity2
1065       //      // are reversed.
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',
1080       //                     ' position="',
1081       //                     deltaLeft / width * arcScaleX * arcScaleX, ',',
1082       //                     deltaTop / height * arcScaleY * arcScaleY, '"',
1083       //                     ' type="tile"',
1084       //                     // TODO: Figure out the correct size to fit the scale.
1085       //                     //' size="', w, 'px ', h, 'px"',
1086       //                     ' src="', fillStyle.src_, '" />');
1087       //       }
1088       //    } else {
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,
1093                    '" />');
1094       //     }
1095     }
1096
1097     contextPrototype.fill = function () {
1098       this.stroke(true);
1099     };
1100
1101     contextPrototype.closePath = function () {
1102       this.currentPath_.push({ type: 'close' });
1103     };
1104
1105     function getCoords(ctx, aX, aY) {
1106       var m = ctx.m_;
1107       return {
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
1110       };
1111     };
1112
1113     contextPrototype.save = function () {
1114       var o = {};
1115       copyState(this, o);
1116       this.aStack_.push(o);
1117       this.mStack_.push(this.m_);
1118       this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
1119     };
1120
1121     contextPrototype.restore = function () {
1122       if (this.aStack_.length) {
1123         copyState(this.aStack_.pop(), this);
1124         this.m_ = this.mStack_.pop();
1125       }
1126     };
1127
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]);
1132     }
1133
1134     function setM(ctx, m, updateLineScale) {
1135       if (!matrixIsFinite(m)) {
1136         return;
1137       }
1138       ctx.m_ = m;
1139
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
1144         // for width.
1145         var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
1146         ctx.lineScale_ = sqrt(abs(det));
1147       }
1148     }
1149
1150     contextPrototype.translate = function (aX, aY) {
1151       var m1 = [
1152       [1, 0, 0],
1153       [0, 1, 0],
1154       [aX, aY, 1]
1155     ];
1156
1157       setM(this, matrixMultiply(m1, this.m_), false);
1158     };
1159
1160     //  contextPrototype.rotate = function(aRot) {
1161     //    var c = mc(aRot);
1162     //    var s = ms(aRot);
1163
1164     //    var m1 = [
1165     //      [c,  s, 0],
1166     //      [-s, c, 0],
1167     //      [0,  0, 1]
1168     //    ];
1169
1170     //    setM(this, matrixMultiply(m1, this.m_), false);
1171     //  };
1172
1173     contextPrototype.scale = function (aX, aY) {
1174       this.arcScaleX_ *= aX;
1175       this.arcScaleY_ *= aY;
1176       var m1 = [
1177       [aX, 0, 0],
1178       [0, aY, 0],
1179       [0, 0, 1]
1180     ];
1181
1182       setM(this, matrixMultiply(m1, this.m_), true);
1183     };
1184
1185     //  contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
1186     //    var m1 = [
1187     //      [m11, m12, 0],
1188     //      [m21, m22, 0],
1189     //      [dx,  dy,  1]
1190     //    ];
1191
1192     //    setM(this, matrixMultiply(m1, this.m_), true);
1193     //  };
1194
1195     //  contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
1196     //    var m = [
1197     //      [m11, m12, 0],
1198     //      [m21, m22, 0],
1199     //      [dx,  dy,  1]
1200     //    ];
1201
1202     //    setM(this, m, true);
1203     //  };
1204
1205     /**
1206     * The text drawing function.
1207     * The maxWidth argument isn't taken in account, since no browser supports
1208     * it yet.
1209     */
1210     //  contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
1211     //    var m = this.m_,
1212     //        delta = 1000,
1213     //        left = 0,
1214     //        right = delta,
1215     //        offset = {x: 0, y: 0},
1216     //        lineStr = [];
1217
1218     //    var fontStyle = getComputedStyle(processFontStyle(this.font),
1219     //                                     this.element_);
1220
1221     //    var fontStyleString = buildStyle(fontStyle);
1222
1223     //    var elementStyle = this.element_.currentStyle;
1224     //    var textAlign = this.textAlign.toLowerCase();
1225     //    switch (textAlign) {
1226     //      case 'left':
1227     //      case 'center':
1228     //      case 'right':
1229     //        break;
1230     //      case 'end':
1231     //        textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
1232     //        break;
1233     //      case 'start':
1234     //        textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
1235     //        break;
1236     //      default:
1237     //        textAlign = 'left';
1238     //    }
1239
1240     //    // 1.75 is an arbitrary number, as there is no info about the text baseline
1241     //    switch (this.textBaseline) {
1242     //      case 'hanging':
1243     //      case 'top':
1244     //        offset.y = fontStyle.size / 1.75;
1245     //        break;
1246     //      case 'middle':
1247     //        break;
1248     //      default:
1249     //      case null:
1250     //      case 'alphabetic':
1251     //      case 'ideographic':
1252     //      case 'bottom':
1253     //        offset.y = -fontStyle.size / 2.25;
1254     //        break;
1255     //    }
1256
1257     //    switch(textAlign) {
1258     //      case 'right':
1259     //        left = delta;
1260     //        right = 0.05;
1261     //        break;
1262     //      case 'center':
1263     //        left = right = delta / 2;
1264     //        break;
1265     //    }
1266
1267     //    var d = getCoords(this, x + offset.x, y + offset.y);
1268
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;">');
1273
1274     //    if (stroke) {
1275     //      appendStroke(this, lineStr);
1276     //    } else {
1277     //      // TODO: Fix the min and max params.
1278     //      appendFill(this, lineStr, {x: -left, y: 0},
1279     //                 {x: right, y: fontStyle.size});
1280     //    }
1281
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';
1284
1285     //    var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
1286
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>');
1295
1296     //    this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
1297     //  };
1298
1299     //  contextPrototype.fillText = function(text, x, y, maxWidth) {
1300     //    this.drawText_(text, x, y, maxWidth, false);
1301     //  };
1302
1303     //  contextPrototype.strokeText = function(text, x, y, maxWidth) {
1304     //    this.drawText_(text, x, y, maxWidth, true);
1305     //  };
1306
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;
1314     //    }
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};
1321     //  };
1322
1323     /******** STUBS ********/
1324     //  contextPrototype.clip = function() {
1325     //    // TODO: Implement
1326     //  };
1327
1328     //  contextPrototype.arcTo = function() {
1329     //    // TODO: Implement
1330     //  };
1331
1332     //  contextPrototype.createPattern = function(image, repetition) {
1333     //    return new CanvasPattern_(image, repetition);
1334     //  };
1335
1336     //  // Gradient / Pattern Stubs
1337     //  function CanvasGradient_(aType) {
1338     //    this.type_ = aType;
1339     //    this.x0_ = 0;
1340     //    this.y0_ = 0;
1341     //    this.r0_ = 0;
1342     //    this.x1_ = 0;
1343     //    this.y1_ = 0;
1344     //    this.r1_ = 0;
1345     //    this.colors_ = [];
1346     //  }
1347
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});
1353     //  };
1354
1355     //  function CanvasPattern_(image, repetition) {
1356     //    assertImageIsValid(image);
1357     //    switch (repetition) {
1358     //      case 'repeat':
1359     //      case null:
1360     //      case '':
1361     //        this.repetition_ = 'repeat';
1362     //        break
1363     //      case 'repeat-x':
1364     //      case 'repeat-y':
1365     //      case 'no-repeat':
1366     //        this.repetition_ = repetition;
1367     //        break;
1368     //      default:
1369     //        throwException('SYNTAX_ERR');
1370     //    }
1371
1372     //    this.src_ = image.src;
1373     //    this.width_ = image.width;
1374     //    this.height_ = image.height;
1375     //  }
1376
1377     function throwException(s) {
1378       throw new DOMException_(s);
1379     }
1380
1381     //  function assertImageIsValid(img) {
1382     //    if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
1383     //      throwException('TYPE_MISMATCH_ERR');
1384     //    }
1385     //    if (img.readyState != 'complete') {
1386     //      throwException('INVALID_STATE_ERR');
1387     //    }
1388     //  }
1389
1390     function DOMException_(s) {
1391       this.code = this[s];
1392       this.message = s + ': DOM Exception ' + this.code;
1393     }
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;
1406     p.SYNTAX_ERR = 12;
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;
1412
1413     // set up externs
1414     G_vmlCanvasManager = G_vmlCanvasManager_;
1415     CanvasRenderingContext2D = CanvasRenderingContext2D_;
1416     //CanvasGradient = CanvasGradient_;
1417     //CanvasPattern = CanvasPattern_;
1418     DOMException = DOMException_;
1419   })();
1420
1421 } // if
1422
1423 /*! JsRender v1.0pre: http://github.com/BorisMoore/jsrender */
1424 /*
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.
1430  */
1431 // informal pre beta commit counter: 3
1432
1433 this.jsviews || this.jQuery && jQuery.views || (function( window, undefined ) {
1434
1435 //========================== Top-level vars ==========================
1436
1437 var versionNumber = "v1.0pre",
1438
1439         $, rTag, rTmplString, extend,
1440         sub = {},
1441         FALSE = false, TRUE = true,
1442         jQuery = window.jQuery,
1443
1444         rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|~([\w$]+)|#(view|([\w$]+))?)([\w$.]*?)(?:[.[]([\w$]+)\]?)?|(['"]).*\8)$/g,
1445         //                                 nil    object   helper    view  viewProperty pathTokens   leafToken     string
1446
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
1450
1451     rNewLine = /\r?\n/g,
1452         rUnescapeQuotes = /\\(['"])/g,
1453         rEscapeQuotes = /\\?(['"])/g,
1454         rBuildHash = /\x08(~)?([^\x08]+)\x08/g,
1455
1456         autoViewKey = 0,
1457         autoTmplName = 0,
1458         escapeMapForHtml = {
1459                 "&": "&amp;",
1460                 "<": "&lt;",
1461                 ">": "&gt;"
1462         },
1463         tmplAttr = "data-jsv-tmpl",
1464         fnDeclStr = "var j=j||" + (jQuery ? "jQuery." : "js") + "views,",
1465         htmlSpecialChar = /[\x00"&'<>]/g,
1466         slice = Array.prototype.slice,
1467
1468         render = {},
1469
1470         // jsviews object ($.views if jQuery is loaded)
1471         jsv = {
1472                 jsviews: versionNumber,
1473                 sub: sub, // subscription, e.g. JsViews integration
1474                 debugMode: TRUE,
1475                 err: function( e ) {
1476                         return jsv.debugMode ? ("<br/><b>Error:</b> <em> " + (e.message || e) + ". </em>") : '""';
1477                 },
1478                 tmplFn: tmplFn,
1479                 render: render,
1480                 templates: templates,
1481                 tags: tags,
1482                 helpers: helpers,
1483                 converters: converters,
1484                 View: View,
1485                 convert: convert,
1486                 delimiters: setDelimiters,
1487                 tag: renderTag
1488         };
1489
1490 //========================== Top-level functions ==========================
1491
1492 //===================
1493 // jsviews.delimiters
1494 //===================
1495
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
1505                 = secondOpenChar
1506                         //          tag    (followed by / space or })   or  colon     or  html or code
1507                 + "(?:(?:(\\w+(?=[\\/\\s" + firstCloseChar + "]))|(?:(\\w+)?(:)|(>)|(\\*)))"
1508                 //     params
1509                 + "\\s*((?:[^" + firstCloseChar + "]|" + firstCloseChar + "(?!" + secondCloseChar + "))*?)"
1510                 //  slash or closeBlock
1511                 + "(\\/)?|(?:\\/(\\w+)))"
1512         //  }}
1513         + firstCloseChar;
1514
1515         // Default rTag:    tag          converter colon  html  code     params         slash   closeBlock
1516         //          /{{(?:(?:(\w+(?=[\/\s\}]))|(?:(\w+)?(:)|(>)|(\*)))\s*((?:[^}]|}(?!\}))*?)(\/)?|(?:\/(\w+)))}}
1517
1518         //      /{{(?:(?:(\w+(?=[\/!\s\}!]))|(?:(\w+)?(:)|(>)|(\*)))((?:[^\}]|}(?!}))*?)(\/)?|(?:\/(\w+)))}}/g;
1519         rTag = new RegExp( firstOpenChar + rTag + secondCloseChar, "g" );
1520         rTmplString = new RegExp( "<.*>|" + openChars + ".*" + closeChars );
1521         return this;
1522 }
1523
1524 //=================
1525 // View.hlp
1526 //=================
1527
1528 function getHelper( helper ) {
1529         // Helper method called as view.hlp() from compiled template, for helper functions or template parameters ~foo
1530         var view = this,
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);
1535         };
1536 }
1537
1538 //=================
1539 // jsviews.convert
1540 //=================
1541
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;
1546 }
1547
1548 //=================
1549 // jsviews.tag
1550 //=================
1551
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 || {};
1556         var ret,
1557                 tmpl = tagObject.props.tmpl,
1558                 tmplTags = parentView.tmpl.tags,
1559                 nestedTemplates = parentView.tmpl.templates,
1560                 args = arguments,
1561                 tagFn = tmplTags && tmplTags[ tag ] || tags[ tag ];
1562
1563         if ( !tagFn ) {
1564                 return "";
1565         }
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;
1569         tagObject.tmpl =
1570                 "" + tmpl === tmpl // if a string
1571                         ? nestedTemplates && nestedTemplates[ tmpl ] || templates[ tmpl ] || templates( tmpl )
1572                         : tmpl;
1573
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);
1580         }
1581
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)
1584 }
1585
1586 //=================
1587 // View constructor
1588 //=================
1589
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 ) { ... }
1596                 self = {
1597                         tmpl: template,
1598                         path: path,
1599                         parent: parentView,
1600                         data: data,
1601                         ctx: context,
1602                         views: $.isArray( data ) ? [] : {},
1603                         hlp: getHelper
1604                 };
1605
1606         if ( $.isArray( views ))  {
1607                 views.splice(
1608                         self.index = index !== undefined
1609                                 ? index
1610                                 : views.length, 0, self
1611                 );
1612         } else {
1613                 views[ self.index = "_" + autoViewKey++ ] = self;
1614         }
1615         return self;
1616 }
1617
1618 //=================
1619 // Registration
1620 //=================
1621
1622 function addToStore( self, store, name, item, process ) {
1623         // Add item to named store such as templates, helpers, converters...
1624         var key, onStore;
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 ]);
1629                 }
1630                 return self;
1631         }
1632         if ( !name || item === undefined ) {
1633                 if ( process ) {
1634                         item = process( undefined, item || name );
1635                 }
1636         } else if ( "" + name === name ) { // name must be a string
1637                 if ( item === null ) {
1638                         // If item is null, delete this entry
1639                         delete store[name];
1640                 } else if ( item = process ? process( name, item ) : item ) {
1641                         store[ name ] = item;
1642                 }
1643         }
1644         if ( onStore = sub.onStoreItem ) {
1645                 // e.g. JsViews integration
1646                 onStore( store, name, item, process );
1647         }
1648         return item;
1649 }
1650
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.
1656
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 );
1660 }
1661
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.
1667
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 );
1671 }
1672
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 );
1680 }
1681
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 );
1689 }
1690
1691 //=================
1692 // renderContent
1693 //=================
1694
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,
1699                 props = {},
1700                 swapContent = index === TRUE,
1701                 self = this,
1702                 result = "";
1703
1704         if ( self.isTag ) {
1705                 // This is a call from renderTag
1706                 tmpl = self.tmpl;
1707                 context = context || self.ctx;
1708                 parentView = parentView || self.view;
1709                 path = path || self.path;
1710                 index = index || self.index;
1711                 props = self.props;
1712         } else {
1713                 tmpl = self.jquery && self[0] // This is a call $.fn.render
1714                         || self; // This is a call from tmpl.render
1715         }
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;
1723                 layout = TRUE;
1724         }
1725
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)
1729                 ? parentContext
1730                 : (parentContext
1731                         // if parentContext, make copy
1732                         ? ((parentContext = extend( {}, parentContext ), context)
1733                                 // If context, merge context with copied parentContext
1734                                 ? extend( parentContext, context )
1735                                 : parentContext)
1736                         // if no parentContext, use context, or default to {}
1737                         : context || {});
1738
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;
1743         }
1744         if ( !tmpl.fn ) {
1745                 tmpl = templates[ tmpl ] || templates( tmpl );
1746         }
1747         itemWrap = context.link && sub.onRenderItem;
1748         itemsWrap = context.link && sub.onRenderItems;
1749
1750         if ( tmpl ) {
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 );
1756
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;
1762                         }
1763                 } else {
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 ) : "";
1767                 }
1768                 parentView.topKey = newView.index;
1769                 return itemsWrap ? itemsWrap( result, path, newView.index, tmpl, props ) : result;
1770         }
1771         return ""; // No tmpl. Could throw...
1772 }
1773
1774 //===========================
1775 // Build and compile template
1776 //===========================
1777
1778 // Generate a reusable function that will serve to render a template against data
1779 // (Compile AST then build template function)
1780
1781 function syntaxError() {
1782         throw "Syntax error";
1783 }
1784
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,
1791                         debug: tmpl.debug
1792                 } : {},
1793                 nested = tmpl && tmpl.tmpls,
1794                 astTop = [],
1795                 loc = 0,
1796                 stack = [],
1797                 content = astTop,
1798                 current = [,,,astTop],
1799                 nestedIndex = 0;
1800
1801         //==== nested functions ====
1802         function pushPreceedingContent( shift ) {
1803                 shift -= loc;
1804                 if ( shift ) {
1805                         content.push( markup.substr( loc, shift ).replace( rNewLine, "\\n" ));
1806                 }
1807         }
1808
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 ]
1813                 if ( html ) {
1814                         colon = ":";
1815                         converter = "html";
1816                 }
1817                 var hash = "",
1818                         passedCtx = "",
1819                         block = !slash && !colon; // Block tag if not self-closing and not {{:}} or {{>}} (special case)
1820
1821                 //==== nested helper function ====
1822
1823                 tagName = tagName || colon;
1824                 pushPreceedingContent( index );
1825                 loc = index + all.length; // location marker - parsed up to here
1826                 if ( code ) {
1827                         if ( allowCode ) {
1828                                 content.push([ "*", params.replace( rUnescapeQuotes, "$1" ) ]);
1829                         }
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 ];
1835                                 block = TRUE;
1836                         }
1837                         params = (params
1838                                 ? parseParams( params, bind )
1839                                         .replace( rBuildHash, function( all, isCtx, keyValue ) {
1840                                                 if ( isCtx ) {
1841                                                         passedCtx += keyValue + ",";
1842                                                 } else {
1843                                                         hash += keyValue + ",";
1844                                                 }
1845                                                 return "";
1846                                         })
1847                                 : "");
1848                         hash = hash.slice( 0, -1 );
1849                         params = params.slice( 0, -1 );
1850                         newNode = [
1851                                 tagName,
1852                                 converter || "",
1853                                 params,
1854                                 block && [],
1855                                 "{" + (hash ? ("props:{" + hash + "},"): "") + "path:'" + params + "'" + (passedCtx ? ",ctx:{" + passedCtx.slice( 0, -1 ) + "}" : "") + "}"
1856                         ];
1857                         if ( block ) {
1858                                 stack.push( current );
1859                                 current = newNode;
1860                                 current[ 5 ] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag
1861                         }
1862                         content.push( newNode );
1863                 } else if ( closeBlock ) {
1864                         //if ( closeBlock !== current[ 0 ]) {
1865                         //      throw "unmatched close tag: /" + closeBlock + ". Expected /" + current[ 0 ];
1866                         //}
1867                         current[ 5 ] = markup.substring( current[ 5 ], index ); // contentMarkup for block tag
1868                         current = stack.pop();
1869                 }
1870                 if ( !current ) {
1871                         throw "Expected block tag";
1872                 }
1873                 content = current[ 3 ];
1874         }
1875         //==== /end of nested functions ====
1876
1877         markup = markup.replace( rEscapeQuotes, "\\$1" );
1878
1879         // Build the AST (abstract syntax tree) under astTop
1880         markup.replace( rTag, parseTag );
1881
1882         pushPreceedingContent( markup.length );
1883
1884         // Use the AST (astTop) to build the template function
1885         l = astTop.length;
1886         code = (l ? "" : '"";');
1887
1888         for ( i = 0; i < l; i++ ) {
1889                 // AST nodes: [ tagName, converter, params, content, hash, contentMarkup ]
1890                 node = astTop[ i ];
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 + '"+';
1895                 } else {
1896                         tag = node[ 0 ];
1897                         converter = node[ 1 ];
1898                         params = node[ 2 ];
1899                         content = node[ 3 ];
1900                         hash = node[ 4 ];
1901                         markup = node[ 5 ];
1902                         if ( content ) {
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 );
1908                         }
1909                         hasViewPath = hasViewPath || hash.indexOf( "view" ) > -1;
1910                         code += (tag === ":"
1911                                 ? (converter === "html"
1912                                         ? (hasEncoder = TRUE, "e(" + params)
1913                                         : converter
1914                                                 ? (hasConverter = TRUE, 'c("' + converter + '",view,' + params)
1915                                                 : (getsValue = TRUE, "((v=" + params + ')!=u?v:""')
1916                                 )
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))
1920                                         + ")+";
1921                 }
1922         }
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," : "")
1928                 + "ret; try{\n\n"
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);}"
1934         );
1935
1936         // Include only the var references that are needed in the code
1937         if ( tmpl ) {
1938                 tmpl.fn = code;
1939                 tmpl.useVw = hasConverter || hasViewPath || hasTag;
1940         }
1941         return code;
1942 }
1943
1944 function parseParams( params, bind ) {
1945         var named,
1946                 fnCall = {},
1947                 parenDepth = 0,
1948                 quoted = FALSE, // boolean for string content in double quotes
1949                 aposed = FALSE; // or in single quotes
1950
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 || "";
1960
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
1964                         if ( object ) {
1965                                 var ret = (helper
1966                                         ? 'view.hlp("' + helper + '")'
1967                                         : view
1968                                                 ? "view"
1969                                                 : "data")
1970                                 + (leafToken
1971                                         ? (viewProperty
1972                                                 ? "." + viewProperty
1973                                                 : helper
1974                                                         ? ""
1975                                                         : (view ? "" : "." + object)
1976                                                 ) + (pathTokens || "")
1977                                         : (leafToken = helper ? "" : view ? viewProperty || "" : object, ""));
1978
1979                                 if ( bind && prn !== "(" ) {
1980                                         ret = "b(" + ret + ',"' + leafToken + '")';
1981                                 }
1982                                 return ret + (leafToken ? "." + leafToken : "");
1983                         }
1984                         return all;
1985                 }
1986
1987                 if ( err ) {
1988                         syntaxError();
1989                 } else {
1990                         return (aposed
1991                                 // within single-quoted string
1992                                 ? (aposed = !apos, (aposed ? all : '"'))
1993                                 : quoted
1994                                         // within double-quoted string
1995                                         ? (quoted = !quot, (quoted ? all : '"'))
1996                                         :
1997                                 (
1998                                         (lftPrn
1999                                                         ? (parenDepth++, lftPrn)
2000                                                         : "")
2001                                         + (space
2002                                                 ? (parenDepth
2003                                                         ? ""
2004                                                         : named
2005                                                                 ? (named = FALSE, "\b")
2006                                                                 : ","
2007                                                 )
2008                                                 : eq
2009                                                         // named param
2010                                                         ? (parenDepth && syntaxError(), named = TRUE, '\b' + path + ':')
2011                                                         : path
2012                                                                 // path
2013                                                                 ? (path.replace( rPath, parsePath )
2014                                                                         + (prn
2015                                                                                 ? (fnCall[ ++parenDepth ] = TRUE, prn)
2016                                                                                 : operator)
2017                                                                 )
2018                                                                 : operator
2019                                                                         ? all
2020                                                                         : rtPrn
2021                                                                                 // function
2022                                                                                 ? ((fnCall[ parenDepth-- ] = FALSE, rtPrn)
2023                                                                                         + (prn
2024                                                                                                 ? (fnCall[ ++parenDepth ] = TRUE, prn)
2025                                                                                                 : "")
2026                                                                                 )
2027                                                                                 : comma
2028                                                                                         ? (fnCall[ parenDepth ] || syntaxError(), ",") // We don't allow top-level literal arrays or objects
2029                                                                                         : lftPrn0
2030                                                                                                 ? ""
2031                                                                                                 : (aposed = apos, quoted = quot, '"')
2032                                 ))
2033                         );
2034                 }
2035         }
2036         params = (params + " " ).replace( rParams, parseTokens );
2037         return params;
2038 }
2039
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;
2044
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
2049
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 )];
2057                                 if ( !value ) {
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;
2063                                 }
2064                         }
2065                         return value;
2066                 }
2067                 // If value is not a string or dom element, return undefined
2068         }
2069
2070         //==== Compile the template ====
2071         tmplOrMarkup = tmplOrMarkupFromStr( tmpl );
2072
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;
2077
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;
2084                 }
2085         }
2086         if ( tmplOrMarkup !== undefined ) {
2087                 if ( name && !parent ) {
2088                         render[ name ] = function() {
2089                                 return tmpl.render.apply( tmpl, arguments );
2090                         };
2091                 }
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);
2097                                 } else {
2098                                         tmpl = tmplOrMarkup;
2099                                 }
2100                         }
2101                 } else {
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 );
2107                 }
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 );
2113                         }
2114                 }
2115                 return tmpl;
2116         }
2117 }
2118 //==== /end of function compile ====
2119
2120 function TmplObject( markup, options, parent, index ) {
2121         // Template object constructor
2122
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 ] );
2128                 }
2129         }
2130
2131         options = options || {};
2132         var tmpl = {
2133                         markup: markup,
2134                         tmpls: [],
2135                         links: [],
2136                         render: renderContent
2137                 };
2138         if ( parent ) {
2139                 if ( parent.templates ) {
2140                         tmpl.templates = extend( extend( {}, parent.templates ), options.templates );
2141                 }
2142                 tmpl.parent = parent;
2143                 tmpl.name = parent.name + "[" + index + "]";
2144                 tmpl.index = index;
2145         }
2146
2147         extend( tmpl, options );
2148         if ( parent ) {
2149                 extendStore( "templates" );
2150                 extendStore( "tags" );
2151                 extendStore( "helpers" );
2152                 extendStore( "converters" );
2153         }
2154         return tmpl;
2155 }
2156
2157 //========================== Initialize ==========================
2158
2159 if ( jQuery ) {
2160         ////////////////////////////////////////////////////////////////////////////////////////////////
2161         // jQuery is loaded, so make $ the jQuery object
2162         $ = jQuery;
2163         $.templates = templates;
2164         $.render = render;
2165         $.views = jsv;
2166         $.fn.render = renderContent;
2167
2168 } else {
2169         ////////////////////////////////////////////////////////////////////////////////////////////////
2170         // jQuery is not loaded.
2171
2172         $ = window.jsviews = jsv;
2173         $.extend = function( target, source ) {
2174                 var name;
2175                 target =  target || {};
2176                 for ( name in source ) {
2177                         target[ name ] = source[ name ];
2178                 }
2179                 return target;
2180         };
2181
2182         $.isArray = Array && Array.isArray || function( obj ) {
2183                 return Object.prototype.toString.call( obj ) === "[object Array]";
2184         };
2185 }
2186
2187 extend = $.extend;
2188
2189 jsv.topView = { views: {}, tmpl: {}, hlp: getHelper, ctx: jsv.helpers };
2190
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 ) + ";");
2196 }
2197
2198 //========================== Register tags ==========================
2199
2200 tags({
2201         "if": function() {
2202                 var ifTag = this,
2203                         view = ifTag.view;
2204
2205                 view.onElse = function( tagObject, args ) {
2206                         var i = 0,
2207                                 l = args.length;
2208
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
2211                                 if ( i === l ) {
2212                                         return "";
2213                                 }
2214                         }
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
2221                 };
2222                 return view.onElse( this, arguments );
2223         },
2224         "else": function() {
2225                 var view = this.view;
2226                 return view.onElse ? view.onElse( this, arguments ) : "";
2227         },
2228         "for": function() {
2229                 var i,
2230                         self = this,
2231                         result = "",
2232                         args = arguments,
2233                         l = args.length;
2234                 if ( self.props.layout ) {
2235                         self.tmpl.layout = TRUE;
2236                 }
2237                 for ( i = 0; i < l; i++ ) {
2238                         result += self.renderContent( args[ i ]);
2239                 }
2240                 return result;
2241         },
2242         "=": function( value ) {
2243                 return value;
2244         },
2245         "*": function( value ) {
2246                 return value;
2247         }
2248 });
2249
2250 //========================== Register global helpers ==========================
2251
2252 //      helpers({ // Global helper functions
2253 //              // TODO add any useful built-in helper functions
2254 //      });
2255
2256 //========================== Register converters ==========================
2257
2258 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 ) : "";
2263         }
2264 });
2265
2266 //========================== Define default delimiters ==========================
2267 setDelimiters( "{{", "}}" );
2268
2269 })( this );
2270
2271 /*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
2272  * Licensed under the MIT License (LICENSE.txt).
2273  *
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
2277  *
2278  * Version: 3.0.6
2279  * 
2280  * Requires: 1.2.2+
2281  */
2282
2283 (function($) {
2284
2285 var types = ['DOMMouseScroll', 'mousewheel'];
2286
2287 if ($.event.fixHooks) {
2288     for ( var i=types.length; i; ) {
2289         $.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
2290     }
2291 }
2292
2293 $.event.special.mousewheel = {
2294     setup: function() {
2295         if ( this.addEventListener ) {
2296             for ( var i=types.length; i; ) {
2297                 this.addEventListener( types[--i], handler, false );
2298             }
2299         } else {
2300             this.onmousewheel = handler;
2301         }
2302     },
2303     
2304     teardown: function() {
2305         if ( this.removeEventListener ) {
2306             for ( var i=types.length; i; ) {
2307                 this.removeEventListener( types[--i], handler, false );
2308             }
2309         } else {
2310             this.onmousewheel = null;
2311         }
2312     }
2313 };
2314
2315 $.fn.extend({
2316     mousewheel: function(fn) {
2317         return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
2318     },
2319     
2320     unmousewheel: function(fn) {
2321         return this.unbind("mousewheel", fn);
2322     }
2323 });
2324
2325
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";
2330     
2331     // Old school scrollwheel delta
2332     if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; }
2333     if ( orgEvent.detail     ) { delta = -orgEvent.detail/3; }
2334     
2335     // New school multidimensional scroll (touchpads) deltas
2336     deltaY = delta;
2337     
2338     // Gecko
2339     if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
2340         deltaY = 0;
2341         deltaX = -1*delta;
2342     }
2343     
2344     // Webkit
2345     if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
2346     if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
2347     
2348     // Add event and delta to the front of the arguments
2349     args.unshift(event, delta, deltaX, deltaY);
2350     
2351     return ($.event.dispatch || $.event.handle).apply(this, args);
2352 }
2353
2354 })(jQuery);
2355
2356 /*!
2357  * jQuery UI Widget 1.8.18
2358  *
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
2362  *
2363  * http://docs.jquery.com/UI/Widget
2364  */
2365
2366 if ( ! $.widget ) {
2367
2368 (function( $, undefined ) {
2369
2370 // jQuery 1.4+
2371 if ( $.cleanData ) {
2372         var _cleanData = $.cleanData;
2373         $.cleanData = function( elems ) {
2374                 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
2375                         try {
2376                                 $( elem ).triggerHandler( "remove" );
2377                         // http://bugs.jquery.com/ticket/8235
2378                         } catch( e ) {}
2379                 }
2380                 _cleanData( elems );
2381         };
2382 } else {
2383         var _remove = $.fn.remove;
2384         $.fn.remove = function( selector, keepData ) {
2385                 return this.each(function() {
2386                         if ( !keepData ) {
2387                                 if ( !selector || $.filter( selector, [ this ] ).length ) {
2388                                         $( "*", this ).add( [ this ] ).each(function() {
2389                                                 try {
2390                                                         $( this ).triggerHandler( "remove" );
2391                                                 // http://bugs.jquery.com/ticket/8235
2392                                                 } catch( e ) {}
2393                                         });
2394                                 }
2395                         }
2396                         return _remove.call( $(this), selector, keepData );
2397                 });
2398         };
2399 }
2400
2401 $.widget = function( name, base, prototype ) {
2402         var namespace = name.split( "." )[ 0 ],
2403                 fullName;
2404         name = name.split( "." )[ 1 ];
2405         fullName = namespace + "-" + name;
2406
2407         if ( !prototype ) {
2408                 prototype = base;
2409                 base = $.Widget;
2410         }
2411
2412         // create selector for plugin
2413         $.expr[ ":" ][ fullName ] = function( elem ) {
2414                 return !!$.data( elem, name );
2415         };
2416
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 );
2422                 }
2423         };
2424
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
2428         // inheriting from
2429 //      $.each( basePrototype, function( key, val ) {
2430 //              if ( $.isPlainObject(val) ) {
2431 //                      basePrototype[ key ] = $.extend( {}, val );
2432 //              }
2433 //      });
2434         basePrototype.options = $.extend( true, {}, basePrototype.options );
2435         $[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
2436                 namespace: namespace,
2437                 widgetName: name,
2438                 widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
2439                 widgetBaseClass: fullName
2440         }, prototype );
2441
2442         $.widget.bridge( name, $[ namespace ][ name ] );
2443 };
2444
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 ),
2449                         returnValue = this;
2450
2451                 // allow multiple hashes to be passed on init
2452                 options = !isMethodCall && args.length ?
2453                         $.extend.apply( null, [ true, options ].concat(args) ) :
2454                         options;
2455
2456                 // prevent calls to internal methods
2457                 if ( isMethodCall && options.charAt( 0 ) === "_" ) {
2458                         return returnValue;
2459                 }
2460
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 ) :
2466                                                 instance;
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 + "'";
2471 //                              }
2472 //                              if ( !$.isFunction( instance[options] ) ) {
2473 //                                      throw "no such method '" + options + "' for " + name + " widget instance";
2474 //                              }
2475 //                              var methodValue = instance[ options ].apply( instance, args );
2476                                 if ( methodValue !== instance && methodValue !== undefined ) {
2477                                         returnValue = methodValue;
2478                                         return false;
2479                                 }
2480                         });
2481                 } else {
2482                         this.each(function() {
2483                                 var instance = $.data( this, name );
2484                                 if ( instance ) {
2485                                         instance.option( options || {} )._init();
2486                                 } else {
2487                                         $.data( this, name, new object( options, this ) );
2488                                 }
2489                         });
2490                 }
2491
2492                 return returnValue;
2493         };
2494 };
2495
2496 $.Widget = function( options, element ) {
2497         // allow instantiation without initializing for simple inheritance
2498         if ( arguments.length ) {
2499                 this._createWidget( options, element );
2500         }
2501 };
2502
2503 $.Widget.prototype = {
2504         widgetName: "widget",
2505         widgetEventPrefix: "",
2506         options: {
2507                 disabled: false
2508         },
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, {},
2515                         this.options,
2516                         this._getCreateOptions(),
2517                         options );
2518
2519                 var self = this;
2520                 this.element.bind( "remove." + this.widgetName, function() {
2521                         self.destroy();
2522                 });
2523
2524                 this._create();
2525                 this._trigger( "create" );
2526                 this._init();
2527         },
2528         _getCreateOptions: function() {
2529                 return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
2530         },
2531         _create: function() {},
2532         _init: function() {},
2533
2534         destroy: function() {
2535                 this.element
2536                         .unbind( "." + this.widgetName )
2537                         .removeData( this.widgetName );
2538                 this.widget()
2539                         .unbind( "." + this.widgetName )
2540                         .removeAttr( "aria-disabled" )
2541                         .removeClass(
2542                                 this.widgetBaseClass + "-disabled " +
2543                                 "ui-state-disabled" );
2544         },
2545
2546         widget: function() {
2547                 return this.element;
2548         },
2549
2550         option: function( key, value ) {
2551                 var options = key;
2552
2553                 if ( arguments.length === 0 ) {
2554                         // don't return a reference to the internal hash
2555                         return $.extend( {}, this.options );
2556                 }
2557
2558                 if  (typeof key === "string" ) {
2559                         if ( value === undefined ) {
2560                                 return this.options[ key ];
2561                         }
2562                         options = {};
2563                         options[ key ] = value;
2564                 }
2565
2566                 this._setOptions( options );
2567
2568                 return this;
2569         },
2570         _setOptions: function( options ) {
2571                 var self = this;
2572                 $.each( options, function( key, value ) {
2573                         self._setOption( key, value );
2574                 });
2575
2576                 return this;
2577         },
2578         _setOption: function( key, value ) {
2579                 this.options[ key ] = value;
2580
2581                 if ( key === "disabled" ) {
2582                         this.widget()
2583                                 [ value ? "addClass" : "removeClass"](
2584                                         this.widgetBaseClass + "-disabled" + " " +
2585                                         "ui-state-disabled" )
2586                                 .attr( "aria-disabled", value );
2587                 }
2588
2589                 return this;
2590         },
2591
2592         enable: function() {
2593                 return this._setOption( "disabled", false );
2594         },
2595         disable: function() {
2596                 return this._setOption( "disabled", true );
2597         },
2598
2599         _trigger: function( type, event, data ) {
2600                 var prop, orig,
2601                         callback = this.options[ type ];
2602
2603                 data = data || {};
2604                 event = $.Event( event );
2605                 event.type = ( type === this.widgetEventPrefix ?
2606                         type :
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 ];
2611
2612                 // copy original event properties over to the new event
2613                 orig = event.originalEvent;
2614                 if ( orig ) {
2615                         for ( prop in orig ) {
2616                                 if ( !( prop in event ) ) {
2617                                         event[ prop ] = orig[ prop ];
2618                                 }
2619                         }
2620                 }
2621
2622                 this.element.trigger( event, data );
2623
2624                 return !( $.isFunction(callback) &&
2625                         callback.call( this.element[0], event, data ) === false ||
2626                         event.isDefaultPrevented() );
2627         }
2628 };
2629
2630 })( jQuery );
2631
2632 };
2633
2634
2635 (function ($, window, undefined) {
2636   var pos_oo = Number.POSITIVE_INFINITY,
2637       neg_oo = Number.NEGATIVE_INFINITY;
2638
2639   $.geo = {
2640     //
2641     // utility functions
2642     //
2643
2644     _allCoordinates: function (geom) {
2645       // return array of all positions in all geometries of geom
2646       // not in JTS
2647       var geometries = this._flatten(geom),
2648           curGeom = 0,
2649           result = [];
2650
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]),
2656             i, j, k;
2657
2658         if (!isTriArray) {
2659           if (!isDblArray) {
2660             if (!isArray) {
2661               coordinates = [coordinates];
2662             }
2663             coordinates = [coordinates];
2664           }
2665           coordinates = [coordinates];
2666         }
2667
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]);
2672             }
2673           }
2674         }
2675       }
2676       return result;
2677     },
2678
2679     _isGeodetic: function( coords ) {
2680       // returns true if the first coordinate it can find is geodetic
2681
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 );
2685         } else {
2686           coords = coords[ 0 ];
2687         }
2688       }
2689
2690       return false;
2691     },
2692
2693     //
2694     // bbox functions
2695     //
2696
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 ) ) {
2702         wasGeodetic = true;
2703         bbox = $.geo.proj.fromGeodetic(bbox);
2704       }
2705
2706       var center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
2707       return wasGeodetic ? $.geo.proj.toGeodetic(center) : center;
2708     },
2709
2710     expandBy: function (bbox, dx, dy, _ignoreGeo /* Internal Use Only */) {
2711       var wasGeodetic = false;
2712       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2713         wasGeodetic = true;
2714         bbox = $.geo.proj.fromGeodetic(bbox);
2715       }
2716
2717       bbox = [bbox[0] - dx, bbox[1] - dy, bbox[2] + dx, bbox[3] + dy];
2718       return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2719     },
2720
2721     height: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
2722       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2723         bbox = $.geo.proj.fromGeodetic(bbox);
2724       }
2725
2726       return bbox[3] - bbox[1];
2727     },
2728
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];
2734     },
2735
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 ];
2741     },
2742
2743     include: function( bbox, value, _ignoreGeo /* Internal Use Only */ ) {
2744       // similar to Envelope.expandToInclude in JTS
2745       if ( !value || !$.isArray( value ) ) {
2746         return bbox;
2747       }
2748
2749       var wasGeodetic = false;
2750       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox || value ) ) {
2751         wasGeodetic = true;
2752       }
2753
2754       if ( !bbox ) {
2755         bbox = [ pos_oo, pos_oo, neg_oo, neg_oo ];
2756       } else if ( wasGeodetic ) {
2757         bbox = $.geo.proj.fromGeodetic( bbox );
2758       }
2759
2760       if ( value.length === 2 ) {
2761         value = [ value[ 0 ], value[ 1 ], value[ 0 ], value[ 1 ] ];
2762       }
2763
2764       value = $.geo.proj.fromGeodetic( value );
2765
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 ] );
2770
2771       return wasGeodetic ? $.geo.proj.toGeodetic( bbox ) : bbox;
2772     },
2773
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 ) ) {
2778         wasGeodetic = true;
2779         bbox = $.geo.proj.fromGeodetic(bbox);
2780       }
2781
2782       var polygon = {
2783         type: "Polygon",
2784         coordinates: [ [
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 ] ]
2790         ] ]
2791       };
2792
2793       if ( wasGeodetic ) {
2794         polygon.coordinates = $.geo.proj.toGeodetic( polygon.coordinates );
2795       }
2796
2797       return polygon;
2798     },
2799
2800     reaspect: function (bbox, ratio, _ignoreGeo /* Internal Use Only */ ) {
2801       // not in JTS
2802       var wasGeodetic = false;
2803       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2804         wasGeodetic = true;
2805         bbox = $.geo.proj.fromGeodetic(bbox);
2806       }
2807
2808       var width = this.width(bbox, true),
2809           height = this.height(bbox, true),
2810           center = this.center(bbox, true),
2811           dx, dy;
2812
2813       if (width !== 0 && height !== 0 && ratio > 0) {
2814         if (width / height > ratio) {
2815           dx = width / 2;
2816           dy = dx / ratio;
2817         } else {
2818           dy = height / 2;
2819           dx = dy * ratio;
2820         }
2821
2822         bbox = [center[0] - dx, center[1] - dy, center[0] + dx, center[1] + dy];
2823       }
2824
2825       return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2826     },
2827
2828     recenter: function( bbox, center, _ignoreGeo /* Internal Use Only */ ) {
2829       // not in JTS
2830       var wasGeodetic = false;
2831       if ( !_ignoreGeo && $.geo.proj ) {
2832         if ( this._isGeodetic( bbox ) ) {
2833           wasGeodetic = true;
2834           bbox = $.geo.proj.fromGeodetic(bbox);
2835         }
2836
2837         if ( this._isGeodetic( center ) ) {
2838           center = $.geo.proj.fromGeodetic(center);
2839         }
2840       }
2841
2842       var halfWidth = ( bbox[ 2 ] - bbox[ 0 ] ) / 2,
2843           halfHeight = ( bbox[ 3 ] - bbox[ 1 ] ) / 2;
2844
2845       bbox = [
2846         center[ 0 ] - halfWidth,
2847         center[ 1 ] - halfHeight,
2848         center[ 0 ] + halfWidth,
2849         center[ 1 ] + halfHeight
2850       ];
2851
2852       return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2853     },
2854
2855     scaleBy: function ( bbox, scale, _ignoreGeo /* Internal Use Only */ ) {
2856       // not in JTS
2857       var wasGeodetic = false;
2858       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2859         wasGeodetic = true;
2860         bbox = $.geo.proj.fromGeodetic(bbox);
2861       }
2862
2863       var c = this.center(bbox, true),
2864           dx = (bbox[2] - bbox[0]) * scale / 2,
2865           dy = (bbox[3] - bbox[1]) * scale / 2;
2866
2867       bbox = [c[0] - dx, c[1] - dy, c[0] + dx, c[1] + dy];
2868
2869       return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2870     },
2871
2872     width: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
2873       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2874         bbox = $.geo.proj.fromGeodetic(bbox);
2875       }
2876
2877       return bbox[2] - bbox[0];
2878     },
2879
2880     //
2881     // geometry functions
2882     //
2883
2884     // bbox (Geometry.getEnvelope in JTS)
2885
2886     bbox: function ( geom, _ignoreGeo /* Internal Use Only */ ) {
2887       var result, wasGeodetic = false;
2888       if ( !geom ) {
2889         return undefined;
2890       } else if ( geom.bbox ) {
2891         result = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom.bbox ) ) ? $.geo.proj.fromGeodetic( geom.bbox ) : geom.bbox;
2892       } else {
2893         result = [ pos_oo, pos_oo, neg_oo, neg_oo ];
2894
2895         var coordinates = this._allCoordinates( geom ),
2896             curCoord = 0;
2897
2898         if ( coordinates.length === 0 ) {
2899           return undefined;
2900         }
2901
2902         if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( coordinates ) ) {
2903           wasGeodetic = true;
2904           coordinates = $.geo.proj.fromGeodetic( coordinates );
2905         }
2906
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]);
2912         }
2913       }
2914
2915       return wasGeodetic ? $.geo.proj.toGeodetic(result) : result;
2916     },
2917
2918     // centroid
2919     
2920     centroid: function( geom, _ignoreGeo /* Internal Use Only */ ) {
2921       switch (geom.type) {
2922         case "Point":
2923           return $.extend({}, geom);
2924
2925         case "LineString":
2926         case "Polygon":
2927           var a = 0,
2928               c = [0, 0],
2929               coords = $.merge( [ ], geom.type == "Polygon" ? geom.coordinates[0] : geom.coordinates ),
2930               i = 1, j, n,
2931               bbox = [ pos_oo, pos_oo, neg_oo, neg_oo ];
2932
2933           var wasGeodetic = false;
2934           if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( coords ) ) {
2935             wasGeodetic = true;
2936             coords = $.geo.proj.fromGeodetic(coords);
2937           }
2938
2939           //if (coords[0][0] != coords[coords.length - 1][0] || coords[0][1] != coords[coords.length - 1][1]) {
2940           //  coords.push(coords[0]);
2941           //}
2942
2943           for (; i <= coords.length; i++) {
2944             j = i % coords.length;
2945
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]);
2950
2951             n = (coords[i - 1][0] * coords[j][1]) - (coords[j][0] * coords[i - 1][1]);
2952             a += n;
2953             c[0] += (coords[i - 1][0] + coords[j][0]) * n;
2954             c[1] += (coords[i - 1][1] + coords[j][1]) * n;
2955           }
2956
2957           if (a === 0) {
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 };
2962             } else {
2963               return undefined;
2964             }
2965           }
2966
2967           a *= 3;
2968           //c[0] /= a;
2969           //c[1] /= a;
2970
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 ] );
2973
2974           return { type: "Point", coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c };
2975       }
2976       return undefined;
2977     },
2978
2979     // contains
2980
2981     contains: function (geom1, geom2) {
2982       if (geom1.type != "Polygon") {
2983         return false;
2984       }
2985
2986       switch (geom2.type) {
2987         case "Point":
2988           return this._containsPolygonPoint(geom1.coordinates, geom2.coordinates);
2989
2990         case "LineString":
2991           return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates);
2992
2993         case "Polygon":
2994           return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates[0]);
2995
2996         default:
2997           return false;
2998       }
2999     },
3000
3001     _containsPolygonPoint: function (polygonCoordinates, pointCoordinate) {
3002       if (polygonCoordinates.length === 0 || polygonCoordinates[0].length < 4) {
3003         return false;
3004       }
3005
3006       var rayCross = 0,
3007           a = polygonCoordinates[0][0],
3008           i = 1,
3009           b,
3010           x;
3011
3012       for (; i < polygonCoordinates[0].length; i++) {
3013         b = polygonCoordinates[0][i];
3014
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]);
3017
3018           if (x > pointCoordinate[0]) {
3019             rayCross++;
3020           }
3021         }
3022
3023         a = b;
3024       }
3025
3026       return rayCross % 2 == 1;
3027     },
3028
3029     _containsPolygonLineString: function (polygonCoordinates, lineStringCoordinates) {
3030       for (var i = 0; i < lineStringCoordinates.length; i++) {
3031         if (!this._containsPolygonPoint(polygonCoordinates, lineStringCoordinates[i])) {
3032           return false;
3033         }
3034       }
3035       return true;
3036     },
3037
3038     // distance
3039
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;
3043
3044       switch (geom1.type) {
3045         case "Point":
3046           switch (geom2.type) {
3047             case "Point":
3048               return this._distancePointPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
3049             case "LineString":
3050               return this._distanceLineStringPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
3051             case "Polygon":
3052               return this._containsPolygonPoint(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
3053             default:
3054               return undefined;
3055           }
3056           break;
3057
3058         case "LineString":
3059           switch (geom2.type) {
3060             case "Point":
3061               return this._distanceLineStringPoint(geom1CoordinatesProjected, geom2CoordinatesProjected);
3062             case "LineString":
3063               return this._distanceLineStringLineString(geom1CoordinatesProjected, geom2CoordinatesProjected);
3064             case "Polygon":
3065               return this._containsPolygonLineString(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
3066             default:
3067               return undefined;
3068           }
3069           break;
3070
3071         case "Polygon":
3072           switch (geom2.type) {
3073             case "Point":
3074               return this._containsPolygonPoint(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
3075             case "LineString":
3076               return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
3077             case "Polygon":
3078               return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected[0]) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected[0]);
3079             default:
3080               return undefined;
3081           }
3082           break;
3083       }
3084     },
3085
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));
3090     },
3091
3092     _distanceLineStringPoint: function (lineStringCoordinates, pointCoordinate) {
3093       var minDist = pos_oo;
3094
3095       if (lineStringCoordinates.length > 0) {
3096         var a = lineStringCoordinates[0],
3097
3098             apx = pointCoordinate[0] - a[0],
3099             apy = pointCoordinate[1] - a[1];
3100
3101         if (lineStringCoordinates.length == 1) {
3102           return Math.sqrt(apx * apx + apy * apy);
3103         } else {
3104           for (var i = 1; i < lineStringCoordinates.length; i++) {
3105             var b = lineStringCoordinates[i],
3106
3107                 abx = b[0] - a[0],
3108                 aby = b[1] - a[1],
3109                 bpx = pointCoordinate[0] - b[0],
3110                 bpy = pointCoordinate[1] - b[1],
3111
3112                 d = this._distanceSegmentPoint(abx, aby, apx, apy, bpx, bpy);
3113
3114             if (d === 0) {
3115               return 0;
3116             }
3117
3118             if (d < minDist) {
3119               minDist = d;
3120             }
3121
3122             a = b;
3123             apx = bpx;
3124             apy = bpy;
3125           }
3126         }
3127       }
3128
3129       return Math.sqrt(minDist);
3130     },
3131
3132     _distanceSegmentPoint: function (abx, aby, apx, apy, bpx, bpy) {
3133       var dot1 = abx * apx + aby * apy;
3134
3135       if (dot1 <= 0) {
3136         return apx * apx + apy * apy;
3137       }
3138
3139       var dot2 = abx * abx + aby * aby;
3140
3141       if (dot1 >= dot2) {
3142         return bpx * bpx + bpy * bpy;
3143       }
3144
3145       return apx * apx + apy * apy - dot1 * dot1 / dot2;
3146     },
3147
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]));
3152       }
3153       return minDist;
3154     },
3155
3156     // buffer
3157
3158     _buffer: function( geom, distance, _ignoreGeo /* Internal Use Only */ ) {
3159       var wasGeodetic = false,
3160           coords = geom.coordinates;
3161
3162       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom.coordinates ) ) {
3163         wasGeodetic = true;
3164         coords = $.geo.proj.fromGeodetic( geom.coordinates );
3165       }
3166
3167       if ( geom.type === "Point" ) {
3168         var resultCoords = [],
3169             slices = 180,
3170             i = 0,
3171             a;
3172
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
3178           ] );
3179         }
3180
3181         return {
3182           type: "Polygon",
3183           coordinates: [ ( wasGeodetic ? $.geo.proj.toGeodetic( resultCoords ) : resultCoords ) ]
3184         };
3185       } else {
3186         return undefined;
3187       }
3188     },
3189
3190     
3191     //
3192     // feature
3193     //
3194
3195     _flatten: function (geom) {
3196       // return an array of all basic geometries
3197       // not in JTS
3198       var geometries = [],
3199           curGeom = 0;
3200       switch (geom.type) {
3201         case "Feature":
3202           $.merge(geometries, this._flatten(geom.geometry));
3203           break;
3204
3205         case "FeatureCollection":
3206           for (; curGeom < geom.features.length; curGeom++) {
3207             $.merge(geometries, this._flatten(geom.features[curGeom].geometry));
3208           }
3209           break;
3210
3211         case "GeometryCollection":
3212           for (; curGeom < geom.geometries.length; curGeom++) {
3213             $.merge(geometries, this._flatten(geom.geometries[curGeom]));
3214           }
3215           break;
3216
3217         default:
3218           geometries[0] = geom;
3219           break;
3220       }
3221       return geometries;
3222     },
3223
3224     length: function( geom, _ignoreGeo /* Internal Use Only */ ) {
3225       var sum = 0,
3226           lineStringCoordinates,
3227           i = 1, dx, dy;
3228
3229       switch ( geom.type ) {
3230         case "Point":
3231           return 0;
3232
3233         case "LineString":
3234           lineStringCoordinates = geom.coordinates;
3235           break;
3236
3237         case "Polygon":
3238           lineStringCoordinates = geom.coordinates[ 0 ];
3239           break;
3240       }
3241
3242       if ( lineStringCoordinates ) {
3243         if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( lineStringCoordinates ) ) {
3244           lineStringCoordinates = $.geo.proj.fromGeodetic( lineStringCoordinates );
3245         }
3246
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));
3251         }
3252
3253         return sum;
3254       }
3255
3256       // return undefined;
3257     },
3258
3259     area: function( geom, _ignoreGeo /* Internal Use Only */ ) {
3260       var sum = 0,
3261           polygonCoordinates,
3262           i = 1, j;
3263
3264       switch ( geom.type ) {
3265         case "Point":
3266         case "LineString":
3267           return 0;
3268
3269         case "Polygon":
3270           polygonCoordinates = geom.coordinates[ 0 ];
3271           break;
3272       }
3273
3274       if ( polygonCoordinates ) {
3275         if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( polygonCoordinates ) ) {
3276           polygonCoordinates = $.geo.proj.fromGeodetic( polygonCoordinates );
3277         }
3278
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;
3282         }
3283
3284         return Math.abs( sum );
3285       }
3286     },
3287
3288     pointAlong: function( geom, percentage, _ignoreGeo /* Internal Use Only */ ) {
3289       var totalLength = 0,
3290           previousPercentageSum = 0,
3291           percentageSum = 0,
3292           remainderPercentageSum,
3293           len,
3294           lineStringCoordinates,
3295           segmentLengths = [],
3296           i = 1, dx, dy,
3297           c, c0, c1,
3298           wasGeodetic = false;
3299
3300       switch ( geom.type ) {
3301         case "Point":
3302           return $.extend( { }, geom );
3303
3304         case "LineString":
3305           lineStringCoordinates = geom.coordinates;
3306           break;
3307
3308         case "Polygon":
3309           lineStringCoordinates = geom.coordinates[ 0 ];
3310           break;
3311       }
3312
3313       if ( lineStringCoordinates ) {
3314         if ( percentage === 0 ) {
3315           return {
3316             type: "Point",
3317             coordinates: [ lineStringCoordinates[ 0 ][ 0 ], lineStringCoordinates[ 0 ][ 1 ] ]
3318           };
3319         } else if ( percentage === 1 ) {
3320           i = lineStringCoordinates.length - 1;
3321           return {
3322             type: "Point",
3323             coordinates: [ lineStringCoordinates[ i ][ 0 ], lineStringCoordinates[ i ][ 1 ] ]
3324           };
3325         } else {
3326           if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( lineStringCoordinates ) ) {
3327             wasGeodetic = true;
3328             lineStringCoordinates = $.geo.proj.fromGeodetic( lineStringCoordinates );
3329           }
3330
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 );
3336             totalLength += len;
3337           }
3338
3339           for ( i = 0; i < segmentLengths.length && percentageSum < percentage; i++ ) {
3340             previousPercentageSum = percentageSum;
3341             percentageSum += ( segmentLengths[ i ] / totalLength );
3342           }
3343
3344           remainderPercentageSum = percentage - previousPercentageSum;
3345
3346           c0 = lineStringCoordinates[ i - 1 ];
3347           c1 = lineStringCoordinates[ i ];
3348
3349           c = [
3350             c0[ 0 ] + ( remainderPercentageSum * ( c1[ 0 ] - c0[ 0 ] ) ),
3351             c0[ 1 ] + ( remainderPercentageSum * ( c1[ 1 ] - c0[ 1 ] ) )
3352           ];
3353
3354           return {
3355             type: "Point",
3356             coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c
3357           };
3358         }
3359       }
3360     },
3361
3362     //
3363     // WKT functions
3364     //
3365
3366     _WKT: (function () {
3367       function pointToString(value) {
3368         return "POINT " + pointToUntaggedString(value.coordinates);
3369       }
3370
3371       function pointToUntaggedString(coordinates) {
3372         if (!(coordinates && coordinates.length)) {
3373           return "EMPTY";
3374         } else {
3375           return "(" + coordinates.join(" ") + ")";
3376         }
3377       }
3378
3379       function lineStringToString(value) {
3380         return "LINESTRING " + lineStringToUntaggedString(value.coordinates);
3381       }
3382
3383       function lineStringToUntaggedString(coordinates) {
3384         if (!(coordinates && coordinates.length)) {
3385           return "EMPTY";
3386         } else {
3387           var points = [];
3388
3389           for (var i = 0; i < coordinates.length; i++) {
3390             points.push(coordinates[i].join(" "));
3391           }
3392
3393           return "(" + points + ")";
3394         }
3395       }
3396
3397       function polygonToString(value) {
3398         return "POLYGON " + polygonToUntaggedString(value.coordinates);
3399       }
3400
3401       function polygonToUntaggedString(coordinates) {
3402         if (!(coordinates && coordinates.length)) {
3403           return "EMTPY";
3404         } else {
3405           var lineStrings = [];
3406
3407           for (var i = 0; i < coordinates.length; i++) {
3408             lineStrings.push(lineStringToUntaggedString(coordinates[i]));
3409           }
3410
3411           return "(" + lineStrings + ")";
3412         }
3413       }
3414
3415       function multiPointToString(value) {
3416         return "MULTIPOINT " + lineStringToUntaggedString(value.coordinates);
3417       }
3418
3419       function multiLineStringToString(value) {
3420         return "MULTILINSTRING " + polygonToUntaggedString(value.coordinates);
3421       }
3422
3423       function multiPolygonToString(value) {
3424         return "MULTIPOLYGON " + multiPolygonToUntaggedString(value.coordinates);
3425       }
3426
3427       function multiPolygonToUntaggedString(coordinates) {
3428         if (!(coordinates && coordinates.length)) {
3429           return "EMPTY";
3430         } else {
3431           var polygons = [];
3432           for (var i = 0; i < coordinates.length; i++) {
3433             polygons.push(polygonToUntaggedString(coordinates[i]));
3434           }
3435           return "(" + polygons + ")";
3436         }
3437       }
3438
3439       function geometryCollectionToString(value) {
3440         return "GEOMETRYCOLLECTION " + geometryCollectionToUntaggedString(value.geometries);
3441       }
3442
3443       function geometryCollectionToUntaggedString(geometries) {
3444         if (!(geometries && geometries.length)) {
3445           return "EMPTY";
3446         } else {
3447           var geometryText = [];
3448           for (var i = 0; i < geometries.length; i++) {
3449             geometryText.push(stringify(geometries[i]));
3450           }
3451           return "(" + geometries + ")";
3452         }
3453       }
3454
3455       function stringify(value) {
3456         if (!(value && value.type)) {
3457           return "";
3458         } else {
3459           switch (value.type) {
3460             case "Point":
3461               return pointToString(value);
3462
3463             case "LineString":
3464               return lineStringToString(value);
3465
3466             case "Polygon":
3467               return polygonToString(value);
3468
3469             case "MultiPoint":
3470               return multiPointToString(value);
3471
3472             case "MultiLineString":
3473               return multiLineStringToString(value);
3474
3475             case "MultiPolygon":
3476               return multiPolygonToString(value);
3477
3478             case "GeometryCollection":
3479               return geometryCollectionToString(value);
3480
3481             default:
3482               return "";
3483           }
3484         }
3485       }
3486
3487       function pointParseUntagged(wkt) {
3488         var pointString = wkt.match( /\(\s*([\d\.\-]+)\s+([\d\.\-]+)\s*\)/ );
3489         return pointString && pointString.length > 2 ? {
3490           type: "Point",
3491           coordinates: [
3492             parseFloat(pointString[1]),
3493             parseFloat(pointString[2])
3494           ]
3495         } : null;
3496       }
3497
3498       function lineStringParseUntagged(wkt) {
3499         var lineString = wkt.match( /\s*\((.*)\)/ ),
3500             coords = [],
3501             pointStrings,
3502             pointParts,
3503             i = 0;
3504
3505         if ( lineString && lineString.length > 1 ) {
3506           pointStrings = lineString[ 1 ].match( /[\d\.\-]+\s+[\d\.\-]+/g );
3507
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 ] ) ];
3511           }
3512
3513           return {
3514             type: "LineString",
3515             coordinates: coords
3516           };
3517         } else {
3518           return null;
3519         }
3520       }
3521
3522       function polygonParseUntagged(wkt) {
3523         var polygon = wkt.match( /\s*\(\s*\((.*)\)\s*\)/ ),
3524             coords = [],
3525             pointStrings,
3526             pointParts,
3527             i = 0;
3528
3529         if ( polygon && polygon.length > 1 ) {
3530           pointStrings = polygon[ 1 ].match( /[\d\.\-]+\s+[\d\.\-]+/g );
3531
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 ] ) ];
3535           }
3536
3537           return {
3538             type: "Polygon",
3539             coordinates: [ coords ]
3540           };
3541         } else {
3542           return null;
3543         }
3544       }
3545
3546       function multiPointParseUntagged(wkt) {
3547         var multiSomething;
3548
3549         if ( wkt.indexOf( "((" ) === -1 ) {
3550           multiSomething = lineStringParseUntagged( wkt );
3551         } else {
3552           multiSomething = multiLineStringParseUntagged( wkt );
3553           multiSomething.coordinates = $.geo._allCoordinates( multiSomething );
3554         }
3555
3556         multiSomething.type = "MultiPoint";
3557
3558         return multiSomething;
3559       }
3560
3561       function multiLineStringParseUntagged(wkt) {
3562         var lineStringsWkt = wkt.substr( 1, wkt.length - 2 ),
3563             lineStrings = lineStringsWkt.split( ")),((" ),
3564             i = 0,
3565             multiLineString = {
3566               type: "MultiLineString",
3567               coordinates: [ ]
3568             };
3569
3570         for ( ; i < lineStrings.length; i++ ) {
3571           multiLineString.coordinates.push( lineStringParseUntagged( lineStrings[ i ] ).coordinates );
3572         }
3573
3574         return multiLineString;
3575       }
3576
3577       function multiPolygonParseUntagged(wkt) {
3578         var polygonsWkt = wkt.substr( 1, wkt.length - 2 ),
3579             polygons = polygonsWkt.split( ")),((" ),
3580             i = 0,
3581             multiPolygon = {
3582               type: "MultiPolygon",
3583               coordinates: [ ]
3584             };
3585
3586         for ( ; i < polygons.length; i++ ) {
3587           multiPolygon.coordinates.push( polygonParseUntagged( polygons[ i ] ).coordinates );
3588         }
3589
3590         return multiPolygon;
3591       }
3592
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",
3598               geometries: [ ]
3599             },
3600             curGeom,
3601             i = 0, curStart = 0, curLen;
3602
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 ) );
3607             if ( curGeom ) {
3608               geometryCollection.geometries.push( curGeom );
3609             }
3610             curStart += curLen + 1;
3611           }
3612
3613           // one more
3614           curGeom = parse( geometriesWkt.substr( curStart ) );
3615           if ( curGeom ) {
3616             geometryCollection.geometries.push( curGeom );
3617           }
3618
3619           return geometryCollection;
3620         } else {
3621           return null;
3622         }
3623       }
3624
3625       function parse(wkt) {
3626         wkt = $.trim(wkt);
3627
3628         var typeIndex = wkt.indexOf( "(" ),
3629             untagged = wkt.substr( typeIndex  );
3630
3631         switch ($.trim(wkt.substr(0, typeIndex)).toUpperCase()) {
3632           case "POINT":
3633             return pointParseUntagged( untagged );
3634
3635           case "LINESTRING":
3636             return lineStringParseUntagged( untagged );
3637
3638           case "POLYGON":
3639             return polygonParseUntagged( untagged );
3640
3641           case "MULTIPOINT":
3642             return multiPointParseUntagged( untagged );
3643
3644           case "MULTILINESTRING":
3645             return multiLineStringParseUntagged( untagged );
3646
3647           case "MULTIPOLYGON":
3648             return multiPolygonParseUntagged( untagged );
3649
3650           case "GEOMETRYCOLLECTION":
3651             return geometryCollectionParseUntagged( untagged );
3652
3653           default:
3654             return null;
3655         }
3656       }
3657
3658       return {
3659         stringify: stringify,
3660
3661         parse: parse
3662       };
3663     }()),
3664
3665     //
3666     // projection functions
3667     //
3668
3669     proj: (function () {
3670       var halfPi = 1.5707963267948966192,
3671           quarterPi = 0.7853981633974483096,
3672           radiansPerDegree = 0.0174532925199432958,
3673           degreesPerRadian = 57.295779513082320877,
3674           semiMajorAxis = 6378137;
3675
3676       return {
3677         fromGeodeticPos: function (coordinate) {
3678           return [
3679             semiMajorAxis * coordinate[ 0 ] * radiansPerDegree,
3680             semiMajorAxis * Math.log(Math.tan(quarterPi + coordinate[ 1 ] * radiansPerDegree / 2))
3681           ];
3682         },
3683
3684         fromGeodetic: function ( coordinates ) {
3685           if ( ! $.geo._isGeodetic( coordinates ) ) {
3686             return coordinates;
3687           }
3688
3689           var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
3690               fromGeodeticPos = this.fromGeodeticPos;
3691
3692           if (!isMultiPointOrLineString && coordinates.length == 4) {
3693             // bbox
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 ] ];
3697           } else {
3698             // geometry
3699             var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
3700                 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
3701                 result = [ ],
3702                 i, j, k;
3703
3704             if (!isMultiPolygon) {
3705               if (!isMultiLineStringOrPolygon) {
3706                 if (!isMultiPointOrLineString) {
3707                   coordinates = [ coordinates ];
3708                 }
3709                 coordinates = [ coordinates ];
3710               }
3711               coordinates = [ coordinates ];
3712             }
3713
3714             for ( i = 0; i < coordinates.length; i++ ) {
3715               result[ 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 ]);
3720                 }
3721               }
3722             }
3723
3724             return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
3725           }
3726         },
3727
3728         toGeodeticPos: function (coordinate) {
3729           return [
3730             (coordinate[ 0 ] / semiMajorAxis) * degreesPerRadian,
3731             (halfPi - 2 * Math.atan(1 / Math.exp(coordinate[ 1 ] / semiMajorAxis))) * degreesPerRadian
3732           ];
3733         },
3734
3735         toGeodetic: function (coordinates) {
3736           if ( $.geo._isGeodetic( coordinates ) ) {
3737             return coordinates;
3738           }
3739
3740           var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
3741               toGeodeticPos = this.toGeodeticPos;
3742
3743           if (!isMultiPointOrLineString && coordinates.length == 4) {
3744             // bbox
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 ] ];
3748           } else {
3749             // geometry
3750             var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
3751                 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
3752                 result = [ ],
3753                 i, j, k;
3754
3755             if (!isMultiPolygon) {
3756               if (!isMultiLineStringOrPolygon) {
3757                 if (!isMultiPointOrLineString) {
3758                   coordinates = [ coordinates ];
3759                 }
3760                 coordinates = [ coordinates ];
3761               }
3762               coordinates = [ coordinates ];
3763             }
3764
3765             for ( i = 0; i < coordinates.length; i++ ) {
3766               result[ 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 ]);
3771                 }
3772               }
3773             }
3774
3775             return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
3776           }
3777         }
3778       };
3779     }()),
3780
3781     //
3782     // service types (defined in other files)
3783     //
3784
3785     _serviceTypes: {}
3786   };
3787 }(jQuery, this));
3788
3789 (function ($, undefined) {
3790   var _ieVersion = ( function () {
3791     var v = 5, div = document.createElement("div"), a = div.all || [];
3792     do {
3793       div.innerHTML = "<!--[if gt IE " + (++v) + "]><br><![endif]-->";
3794     } while ( a[0] );
3795     return v > 6 ? v : !v;
3796   }() );
3797
3798   $.widget("geo.geographics", {
3799     _$elem: undefined,
3800     _options: {},
3801     _trueCanvas: true,
3802     _trueDoubleBuffer: true,
3803
3804     _width: 0,
3805     _height: 0,
3806
3807     _$canvas: undefined,
3808     _context: undefined,
3809
3810     _$canvasSceneFront: undefined, //< if _trueCanvas, where canvas images get written (front buffer)
3811     _$canvasSceneBack: undefined, //< if _trueCanvas, where canvas images get written (back buffer)
3812     _timeoutEnd:  null,
3813     _requireFlip: false,
3814
3815     _blitcanvas: undefined,
3816     _blitcontext: undefined,
3817
3818     _$labelsContainerFront: undefined,
3819     _$labelsContainerBack: undefined,
3820     _labelsHtml: "",
3821
3822     options: {
3823       style: {
3824         borderRadius: "8px",
3825         color: "#7f0000",
3826         //fill: undefined,
3827         fillOpacity: 0.2,
3828         height: "8px",
3829         opacity: 1,
3830         //stroke: undefined,
3831         strokeOpacity: 1,
3832         strokeWidth: "2px",
3833         visibility: "visible",
3834         width: "8px"
3835       },
3836
3837       doubleBuffer: true
3838     },
3839
3840     _create: function () {
3841       this._$elem = this.element;
3842       this._options = this.options;
3843
3844       this._$elem.css( {
3845         webkitTransform: "translateZ(0)",
3846         display: "inline-block",
3847         overflow: "hidden",
3848         textAlign: "left"
3849       } );
3850
3851       if (this._$elem.css("position") == "static") {
3852         this._$elem.css("position", "relative");
3853       }
3854
3855       this._$elem.addClass( "geo-graphics" );
3856
3857       this._width = this._$elem.width();
3858       this._height = this._$elem.height();
3859
3860       if (!(this._width && this._height)) {
3861         this._width = parseInt(this._$elem.css("width"), 10);
3862         this._height = parseInt(this._$elem.css("height"), 10);
3863       }
3864
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 + '"';
3868
3869       this._blitcanvas = document.createElement( "canvas" );
3870
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>');
3875
3876         // test _trueDoubleBuffer
3877         this._blitcanvas.width = 1;
3878         this._blitcanvas.height = 1;
3879         this._trueDoubleBuffer = this._blitcanvas.toDataURL().length > 6;
3880
3881         if ( !(this._options.doubleBuffer && this._trueDoubleBuffer) ) {
3882           this._$elem.append( this._$canvas );
3883         }
3884
3885         this._context = this._$canvas[0].getContext("2d");
3886
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");
3891
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 + '" />');
3895
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');
3900
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 });
3904       }
3905
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>');
3909     },
3910
3911     _setOption: function (key, value) {
3912       if (key == "style") {
3913         value = $.extend({}, this._options.style, value);
3914       }
3915       $.Widget.prototype._setOption.apply(this, arguments);
3916     },
3917
3918     destroy: function () {
3919       $.Widget.prototype.destroy.apply(this, arguments);
3920       this._$elem.html("");
3921       this._$elem.removeClass( "geo-graphics" );
3922     },
3923
3924     clear: function () {
3925       this._context.clearRect(0, 0, this._width, this._height);
3926       this._labelsHtml = "";
3927
3928           //if ( this._options.doubleBuffer ) console.log("clear:_end " + $.now());
3929       this._end( );
3930     },
3931
3932     drawArc: function (coordinates, startAngle, sweepAngle, style) {
3933       style = this._getGraphicStyle(style);
3934
3935       if (style.visibility != "hidden" && style.opacity > 0 && style.widthValue > 0 && style.heightValue > 0) {
3936         var r = Math.min(style.widthValue, style.heightValue) / 2;
3937
3938         startAngle = (startAngle * Math.PI / 180);
3939         sweepAngle = (sweepAngle * Math.PI / 180);
3940
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);
3945         } else {
3946           this._context.scale(1, style.heightValue / style.widthValue);
3947         }
3948
3949         this._context.beginPath();
3950         this._context.arc(0, 0, r, startAngle, sweepAngle, false);
3951
3952         if (this._trueCanvas) {
3953           this._context.restore();
3954         }
3955
3956         if (style.doFill) {
3957           this._context.fillStyle = style.fill;
3958           this._context.globalAlpha = style.opacity * style.fillOpacity;
3959           this._context.fill();
3960         }
3961
3962         if (style.doStroke) {
3963           this._context.lineJoin = "round";
3964           this._context.lineWidth = style.strokeWidthValue;
3965           this._context.strokeStyle = style.stroke;
3966
3967           this._context.globalAlpha = style.opacity * style.strokeOpacity;
3968           this._context.stroke();
3969         }
3970
3971         if (!this._trueCanvas) {
3972           this._context.restore();
3973         }
3974       }
3975
3976           //if ( this._options.doubleBuffer ) console.log("drawArc:_end " + $.now());
3977       this._end( );
3978     },
3979
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();
3999
4000         if (style.doFill) {
4001           this._context.fillStyle = style.fill;
4002           this._context.globalAlpha = style.opacity * style.fillOpacity;
4003           this._context.fill();
4004         }
4005
4006         if (style.doStroke) {
4007           this._context.lineJoin = "round";
4008           this._context.lineWidth = style.strokeWidthValue;
4009           this._context.strokeStyle = style.stroke;
4010
4011           this._context.globalAlpha = style.opacity * style.strokeOpacity;
4012
4013           this._context.stroke();
4014         }
4015
4016           //if ( this._options.doubleBuffer ) console.log("drawPoint:_end " + $.now());
4017         this._end( );
4018       }
4019     },
4020
4021     drawLineString: function (coordinates, style) {
4022       this._drawLines([coordinates], false, style);
4023     },
4024
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 );
4029       } else {
4030         if ( !coordinates || !coordinates.length || coordinates[ 0 ].length < 3 ) {
4031           // this is not a Polygon or it doesn't have a proper outer ring
4032           return;
4033         }
4034
4035         style = this._getGraphicStyle(style);
4036
4037         var pixelBbox, i, j;
4038
4039         if ( style.visibility != "hidden" && style.opacity > 0 ) {
4040           this._blitcontext.clearRect(0, 0, this._width, this._height);
4041
4042           if ( style.doFill ) {
4043             if ( coordinates.length > 1 ) {
4044               // stencil inner rings
4045               this._blitcontext.globalCompositeOperation = "source-out";
4046               this._blitcontext.globalAlpha = 1;
4047
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 ] );
4053                 }
4054                 this._blitcontext.closePath();
4055
4056                 this._blitcontext.fill( );
4057               }
4058             }
4059           }
4060
4061           // path outer ring
4062           this._blitcontext.beginPath();
4063           this._blitcontext.moveTo( coordinates[ 0 ][ 0 ][ 0 ], coordinates[ 0 ][ 0 ][ 1 ] );
4064
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 ];
4066
4067           for ( i = 1; i < coordinates[ 0 ].length - 1; i++ ) {
4068             this._blitcontext.lineTo( coordinates[ 0 ][ i ][ 0 ], coordinates[ 0 ][ i ][ 1 ] );
4069
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 ] );
4074           }
4075
4076           this._blitcontext.closePath();
4077
4078           this._blitcontext.globalCompositeOperation = "source-out";
4079           if ( style.doFill ) {
4080             // fill outer ring
4081             this._blitcontext.fillStyle = style.fill;
4082             this._blitcontext.globalAlpha = style.opacity * style.fillOpacity;
4083             this._blitcontext.fill( );
4084           }
4085
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;
4092
4093             this._blitcontext.globalAlpha = style.opacity * style.strokeOpacity;
4094             this._blitcontext.stroke( );
4095
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 ] );
4103                 }
4104                 this._blitcontext.closePath();
4105
4106                 this._blitcontext.stroke( );
4107               }
4108             }
4109           }
4110
4111           // blit
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 );
4116
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 ] );
4119
4120           //if ( this._options.doubleBuffer ) console.log("drawPolygon:_end " + $.now());
4121             this._end( );
4122           }
4123         }
4124       }
4125     },
4126
4127     drawBbox: function (bbox, style) {
4128       this._drawLines([[
4129         [bbox[0], bbox[1]],
4130         [bbox[0], bbox[3]],
4131         [bbox[2], bbox[3]],
4132         [bbox[2], bbox[1]],
4133         [bbox[0], bbox[1]]
4134       ]], true, style);
4135     },
4136
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>';
4139     },
4140
4141     resize: function( ) {
4142       this._width = this._$elem.width();
4143       this._height = this._$elem.height();
4144
4145       if (!(this._width && this._height)) {
4146         this._width = parseInt(this._$elem.css("width"), 10);
4147         this._height = parseInt(this._$elem.css("height"), 10);
4148       }
4149
4150       if ( this._trueCanvas ) {
4151         this._$canvas[0].width = this._width;
4152         this._$canvas[0].height = this._height;
4153
4154         this._$canvasSceneFront.css( {
4155           width: this._width,
4156           height: this._height
4157         } );
4158
4159         this._$canvasSceneBack.css( {
4160           width: this._width,
4161           height: this._height
4162         } );
4163       } else {
4164         this._$canvas.css( {
4165           width: this._width,
4166           height: this._height
4167         } );
4168       }
4169
4170       this._$labelsContainerFront.css( {
4171         width: this._width,
4172         height: this._height
4173       } );
4174
4175       this._$labelsContainerBack.css( {
4176         width: this._width,
4177         height: this._height
4178       } );
4179     },
4180
4181     interactiveTransform: function( origin, scale ) {
4182       if ( this._timeoutEnd ) {
4183         clearTimeout( this._timeoutEnd );
4184         this._timeoutEnd = null;
4185       }
4186
4187       // hide labels for now until they are on the interactive div 
4188       //this._$labelsContainerFront.html("");
4189
4190       if ( this._trueCanvas ) {
4191         if ( this._options.doubleBuffer && this._trueDoubleBuffer ) {
4192
4193
4194           if ( this._requireFlip ) {
4195             var geographics = this;
4196
4197             var oldCanvasScene = geographics._$canvasSceneFront;
4198
4199             geographics._$canvasSceneFront = geographics._$canvasSceneBack.css( {
4200               left: 0,
4201               top: 0,
4202               width: geographics._width,
4203               height: geographics._height
4204             } ).prop( "src", geographics._$canvas[ 0 ].toDataURL( ) ).prependTo( geographics._$elem );
4205
4206             geographics._$canvasSceneBack = oldCanvasScene.detach();
4207
4208             geographics._requireFlip = false;
4209           }
4210
4211
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
4219           } );
4220         } else {
4221           this._context.clearRect(0, 0, this._width, this._height);
4222         }
4223       } else {
4224         this._context.clearRect(0, 0, this._width, this._height);
4225       }
4226
4227       // transform labels
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
4233       } );
4234     },
4235
4236     _end: function( ) {
4237       // end/finalize a scene
4238       if ( this._timeoutEnd ) {
4239         clearTimeout( this._timeoutEnd );
4240         this._timeoutEnd = null;
4241       }
4242
4243       this._requireFlip = true;
4244
4245       var geographics = this;
4246
4247       function endCallback( ) {
4248         if ( !geographics._timeoutEnd ) {
4249           // something has canceled the draw
4250           return;
4251         }
4252
4253         if ( geographics._trueCanvas && geographics._options.doubleBuffer && geographics._trueDoubleBuffer ) {
4254           //console.log("    endCallback...");
4255
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;
4261
4262             geographics._$canvasSceneFront = geographics._$canvasSceneBack.css( {
4263               left: 0,
4264               top: 0,
4265               width: geographics._width,
4266               height: geographics._height
4267             } ).prependTo( geographics._$elem );
4268
4269             geographics._$canvasSceneBack = oldCanvasScene.detach();
4270           } ).prop( "src", geographics._$canvas[ 0 ].toDataURL( ) );
4271         }
4272
4273
4274         geographics._$labelsContainerBack.html( geographics._labelsHtml );
4275
4276         var oldLabelsContainer = geographics._$labelsContainerFront;
4277
4278         geographics._$labelsContainerFront = geographics._$labelsContainerBack.css( {
4279           left: 0,
4280           top: 0,
4281           width: geographics._width,
4282           height: geographics._height
4283         } ).prependTo( geographics._$elem );
4284
4285         geographics._$labelsContainerBack = oldLabelsContainer.detach();
4286
4287
4288         geographics._timeoutEnd = null;
4289       }
4290
4291       //if ( this._options.doubleBuffer ) {
4292         this._timeoutEnd = setTimeout( endCallback, 20 );
4293       //} else {
4294         //geographics._$labelsContainerFront.html( geographics._labelsHtml );
4295       //}
4296     },
4297
4298     _getGraphicStyle: function (style) {
4299       function safeParse(value) {
4300         value = parseInt(value, 10);
4301         return (+value + '') === value ? +value : value;
4302       }
4303
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);
4313       return style;
4314     },
4315
4316     _drawLines: function (coordinates, close, style) {
4317       if (!coordinates || !coordinates.length || coordinates[0].length < 2) {
4318         return;
4319       }
4320
4321       var i, j;
4322       style = this._getGraphicStyle(style);
4323
4324       if (style.visibility != "hidden" && style.opacity > 0) {
4325         this._context.beginPath();
4326
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]);
4331           }
4332         }
4333
4334         if (close) {
4335           this._context.closePath();
4336         }
4337
4338         if (close && style.doFill) {
4339           this._context.fillStyle = style.fill;
4340           this._context.globalAlpha = style.opacity * style.fillOpacity;
4341           this._context.fill();
4342         }
4343
4344         if (style.doStroke) {
4345           this._context.lineCap = this._context.lineJoin = "round";
4346           this._context.lineWidth = style.strokeWidthValue;
4347           this._context.strokeStyle = style.stroke;
4348
4349           this._context.globalAlpha = style.opacity * style.strokeOpacity;
4350           this._context.stroke();
4351         }
4352
4353           //if ( this._options.doubleBuffer ) console.log("_drawLines:_end " + $.now());
4354         this._end( );
4355       }
4356     }
4357   });
4358 }(jQuery));
4359
4360
4361 (function ($, undefined) {
4362   var _widgetIdSeed = 0,
4363       _ieVersion = ( function () {
4364         var v = 5, div = document.createElement("div"), a = div.all || [];
4365         do {
4366           div.innerHTML = "<!--[if gt IE " + (++v) + "]><br><![endif]-->";
4367         } while ( a[0] );
4368         return v > 6 ? v : !v;
4369       }() ),
4370
4371       _defaultOptions = {
4372         bbox: [-180, -85, 180, 85],
4373         bboxMax: [-180, -85, 180, 85],
4374         center: [0, 0],
4375         cursors: {
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",
4378           zoom: "crosshair",
4379           dragBox: "crosshair",
4380           dragCircle: "crosshair",
4381           drawPoint: "crosshair",
4382           drawLineString: "crosshair",
4383           drawPolygon: "crosshair",
4384           measureLength: "crosshair",
4385           measureArea: "crosshair"
4386         },
4387         measureLabels: {
4388           length: "{{:length.toFixed( 2 )}} m",
4389           area: "{{:area.toFixed( 2 )}} sq m"
4390         },
4391         drawStyle: {},
4392         shapeStyle: {},
4393         mode: "pan",
4394         pannable: true,
4395         scroll: "default",
4396         shift: "default",
4397         services: [
4398             {
4399               "class": "osm",
4400               type: "tiled",
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";
4403               },
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'>"
4405             }
4406           ],
4407         tilingScheme: {
4408           tileWidth: 256,
4409           tileHeight: 256,
4410           levels: 18,
4411           basePixelSize: 156543.03392799936,
4412           origin: [-20037508.342787, 20037508.342787]
4413         },
4414         axisLayout: "map",
4415         zoom: 0,
4416         zoomMin: 0,
4417         zoomMax: Number.POSITIVE_INFINITY,
4418         pixelSize: 0
4419       };
4420
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
4425     _created: false,
4426     _createdGraphics: false,
4427     _widgetId: 0,
4428     _tmplLengthId: "",
4429     _tmplAreaId: "",
4430
4431     _contentBounds: {},
4432
4433     _$resizeContainer: undefined, //< all elements that should match _contentBounds' size
4434
4435     _$eventTarget: undefined,
4436     _$contentFrame: undefined,
4437     _$existingChildren: undefined,
4438     _$attrList: undefined,
4439     _$servicesContainer: undefined,
4440     _$shapesContainers: undefined, //< all shapesContainer divs (map only)
4441
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,
4447
4448     _dpi: 96,
4449
4450     _currentServices: [], //< internal copy
4451
4452     _center: undefined,
4453     _pixelSize: undefined,
4454     _centerMax: undefined,
4455     _pixelSizeMax: undefined,
4456
4457     _userGeodetic: true,
4458
4459     _centerInteractive: undefined,
4460     _pixelSizeInteractive: undefined,
4461     _timeoutInteractive: null,
4462     _triggerInteractive: false,
4463
4464     _timeoutRefreshShapes: null,
4465
4466     _loadCount: 0,
4467
4468     _wheelTimeout: null,
4469     _wheelLevel: 0,
4470
4471     _zoomFactor: 2, //< determines what a zoom level means
4472
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)
4475
4476     _mouseDown: undefined,
4477     _inOp: undefined,
4478     _toolPan: undefined,
4479     _shiftDown: undefined,
4480     _anchor: undefined,
4481     _current: undefined,
4482     _downDate: undefined,
4483     _moveDate: undefined,
4484     _clickDate: undefined,
4485     _lastMove: undefined,
4486     _lastDrag: undefined,
4487
4488     _windowHandler: null,
4489     _resizeTimeout: null,
4490
4491     _panning: undefined,
4492     _velocity: undefined,
4493     _friction: undefined,
4494
4495     _supportTouch: undefined,
4496     _softDblClick: undefined,
4497     _isTap: undefined,
4498     _isDbltap: undefined,
4499
4500     _isMultiTouch: undefined,
4501     _multiTouchAnchor: [], //< TouchList
4502     _multiTouchAnchorBbox: undefined, //< bbox
4503     _multiTouchCurrentBbox: undefined, //< bbox
4504
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
4507     _drawCoords: [],
4508
4509     _graphicShapes: [], //< an array of objects containing style object refs & GeoJSON object refs
4510
4511     _initOptions: {},
4512
4513     _options: {},
4514
4515     options: $.extend({}, _defaultOptions),
4516
4517     _createWidget: function (options, element) {
4518       this._$elem = $(element);
4519
4520       if (this._$elem.is(".geo-service")) {
4521         this._graphicShapes = [];
4522         $.Widget.prototype._createWidget.apply(this, arguments);
4523         return;
4524       }
4525
4526       this._widgetId = _widgetIdSeed++;
4527       this._tmplLengthId = "geoMeasureLength" + this._widgetId;
4528       this._tmplAreaId = "geoMeasureArea" + this._widgetId;
4529
4530       this._$elem.addClass("geo-map").css( {
4531         webkitTransform: "translateZ(0)"
4532       } );
4533         
4534
4535       this._initOptions = options || {};
4536
4537       this._forcePosition(this._$elem);
4538
4539       this._$elem.css("text-align", "left");
4540
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"]
4547       };
4548
4549       this._createChildren();
4550
4551       this._center = [ 0, 0 ];
4552       this._centerMax = [ 0, 0 ];
4553       this._centerInteractive = [ 0, 0 ];
4554
4555       this.options["pixelSize"] = this._pixelSize = this._pixelSizeMax = 156543.03392799936;
4556
4557       this._mouseDown =
4558           this._inOp =
4559           this._toolPan =
4560           this._shiftDown =
4561           this._panning =
4562           this._isTap =
4563           this._isDbltap = false;
4564
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 ];
4570
4571       this._friction = [0.8, 0.8];
4572
4573       this._downDate =
4574           this._moveDate =
4575           this._clickDate = 0;
4576
4577       this._drawPixels = [];
4578       this._drawCoords =  [];
4579       this._graphicShapes = [];
4580
4581
4582       $.Widget.prototype._createWidget.apply(this, arguments);
4583     },
4584
4585     _create: function () {
4586       this._options = this.options;
4587
4588       if (this._$elem.is(".geo-service")) {
4589         this._map = this._$elem.data( "geoMap" );
4590         this._$elem.data( "geoService", this );
4591         return;
4592       }
4593
4594       this._map = this;
4595
4596       this._supportTouch = "ontouchend" in document;
4597       this._softDblClick = this._supportTouch || _ieVersion == 7;
4598
4599       var geomap = this,
4600           touchStartEvent = this._supportTouch ? "touchstart" : "mousedown",
4601           touchStopEvent = this._supportTouch ? "touchend touchcancel" : "mouseup",
4602           touchMoveEvent = this._supportTouch ? "touchmove" : "mousemove";
4603
4604       $(document).keydown($.proxy(this._document_keydown, this));
4605
4606       this._$eventTarget.dblclick($.proxy(this._eventTarget_dblclick, this));
4607
4608       this._$eventTarget.bind(touchStartEvent, $.proxy(this._eventTarget_touchstart, this));
4609
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));
4613
4614       this._$eventTarget.mousewheel($.proxy(this._eventTarget_mousewheel, this));
4615
4616       this._windowHandler = function () {
4617         if (geomap._resizeTimeout) {
4618           clearTimeout(geomap._resizeTimeout);
4619         }
4620         geomap._resizeTimeout = setTimeout(function () {
4621           if (geomap._created) {
4622             geomap._$elem.geomap( "resize", true );
4623           }
4624         }, 500);
4625       };
4626
4627       $(window).resize(this._windowHandler);
4628
4629       this._$drawContainer.geographics({ style: this._initOptions.drawStyle || {}, doubleBuffer: false });
4630       this._options["drawStyle"] = this._$drawContainer.geographics("option", "style");
4631
4632       this._$shapesContainer.geographics( { style: this._initOptions.shapeStyle || { } } );
4633       this._createdGraphics = true;
4634
4635       this._options["shapeStyle"] = this._$shapesContainer.geographics("option", "style");
4636
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);
4641         }
4642
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 );
4646         }
4647         if (this._initOptions.bboxMax) {
4648           this._setOption("bboxMax", this._initOptions.bboxMax, false);
4649           this._setOption("bbox", this._initOptions.bboxMax, false);
4650         }
4651         if (this._initOptions.zoomMin !== undefined) {
4652           this._setOption("zoomMin", this._initOptions.zoomMin, false);
4653         }
4654         if (this._initOptions.zoomMax !== undefined) {
4655           this._setOption("zoomMax", this._initOptions.zoomMax, false);
4656         }
4657         if (this._initOptions.bbox) {
4658           this._setOption("bbox", this._initOptions.bbox, false);
4659         }
4660         if (this._initOptions.center) {
4661           this._setOption("center", this._initOptions.center, false);
4662         }
4663         if (this._initOptions.zoom !== undefined) {
4664           this._setOption("zoom", this._initOptions.zoom, false);
4665         }
4666       }
4667
4668       $.templates( this._tmplLengthId, this._options[ "measureLabels" ].length );
4669       $.templates( this._tmplAreaId, this._options[ "measureLabels" ].area );
4670
4671       this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
4672
4673       this._createServices();
4674       this._refresh();
4675
4676       this._created = true;
4677     },
4678
4679     _setOption: function (key, value, refresh) {
4680       if ( key == "pixelSize" ) {
4681         return;
4682       }
4683
4684       refresh = (refresh === undefined || refresh);
4685
4686       if ( this._$elem.is( ".geo-map" ) ) {
4687         this._panFinalize();
4688       }
4689
4690       var center, pixelSize, bbox, zoom;
4691
4692       switch (key) {
4693         case "bbox":
4694           if ( this._created ) {
4695             this._clearInteractiveTimeout( );
4696           }
4697
4698           this._userGeodetic = $.geo.proj && $.geo._isGeodetic( value );
4699           if ( this._userGeodetic ) {
4700             value = $.geo.proj.fromGeodetic( value );
4701           }
4702
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);
4705
4706           // clamp to zoom
4707           zoom = this._getZoom( center, pixelSize );
4708
4709           if ( this._options[ "tilingScheme" ] ) {
4710             pixelSize = this._getPixelSize( Math.min( Math.max( zoom, this._options[ "zoomMin" ] ), this._options[ "zoomMax" ] ) );
4711           } else {
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" ] );
4716             }
4717           }
4718
4719           if ( this._created ) {
4720             this._setInteractiveCenterAndSize( center, pixelSize );
4721             this._setInteractiveTimeout( false );
4722           } else {
4723             this._setCenterAndSize( center, pixelSize, false, refresh );
4724           }
4725
4726           value = this._getBbox( center, pixelSize );
4727           break;
4728
4729         case "bboxMax":
4730           this._userGeodetic = $.geo.proj && $.geo._isGeodetic( value );
4731           break;
4732
4733         case "center":
4734           if ( this._created ) {
4735             this._clearInteractiveTimeout( );
4736           }
4737
4738           this._userGeodetic = $.geo.proj && $.geo._isGeodetic( value );
4739           if ( this._userGeodetic ) {
4740             value = $.geo.proj.fromGeodetic( value );
4741           }
4742
4743           if ( this._created ) {
4744             this._setInteractiveCenterAndSize( value, this._pixelSizeInteractive );
4745             this._interactiveTransform( );
4746             this._setInteractiveTimeout( false );
4747           } else {
4748             this._setCenterAndSize( value, this._pixelSize, false, refresh );
4749           }
4750           break;
4751
4752         case "measureLabels":
4753           value = $.extend( this._options[ "measureLabels" ], value );
4754
4755
4756           $.templates( this._tmplLengthId, this._options[ "measureLabels" ].length );
4757           $.templates( this._tmplAreaId, this._options[ "measureLabels" ].area );
4758
4759           break;
4760
4761         case "drawStyle":
4762           if (this._$drawContainer) {
4763             this._$drawContainer.geographics("option", "style", value);
4764             value = this._$drawContainer.geographics("option", "style");
4765           }
4766           break;
4767
4768         case "shapeStyle":
4769           if ( this._$elem.is( ".geo-service" ) && !this._createdGraphics ) {
4770             this._createServiceGraphics( );
4771           }
4772
4773           if ( this._createdGraphics ) {
4774             this._$shapesContainer.geographics("option", "style", value);
4775             value = this._$shapesContainer.geographics("option", "style");
4776           }
4777           break;
4778
4779         case "mode":
4780           this._resetDrawing( );
4781           this._$eventTarget.css("cursor", this._options["cursors"][value]);
4782           break;
4783
4784         case "zoom":
4785           if ( this._created ) {
4786             this._setZoom(value, false, refresh);
4787           } else {
4788             value = Math.max( value, 0 );
4789             this._setCenterAndSize( this._center, this._getPixelSize( value ), false, refresh );
4790           }
4791           break;
4792       }
4793
4794       $.Widget.prototype._setOption.apply(this, arguments);
4795
4796       switch ( key ) {
4797         case "bbox":
4798         case "center":
4799           if ( this._userGeodetic ) {
4800             this._options[ "bbox" ] = $.geo.proj.toGeodetic( this._options[ "bbox" ] );
4801             this._options[ "center" ] = $.geo.proj.toGeodetic( this._center );
4802           }
4803           break;
4804
4805         case "tilingScheme":
4806           if ( value !== null ) {
4807             this._pixelSizeMax = this._getPixelSize( 0 );
4808             this._centerMax = [
4809               value.origin[ 0 ] + this._pixelSizeMax * value.tileWidth / 2,
4810               value.origin[ 1 ] + this._pixelSizeMax * value.tileHeight / 2
4811             ];
4812           }
4813           break;
4814
4815         case "bboxMax":
4816           if ( $.geo.proj && $.geo._isGeodetic( value ) ) {
4817             bbox = $.geo.proj.fromGeodetic( value );
4818           } else {
4819             bbox = value;
4820           }
4821
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 );
4824           break;
4825
4826         case "services":
4827           this._createServices();
4828           if (refresh) {
4829             this._refresh();
4830             this._refreshAllShapes();
4831           }
4832           break;
4833
4834         case "shapeStyle":
4835           if ( refresh && this._createdGraphics ) {
4836             this._$shapesContainer.geographics("clear");
4837             this._refreshShapes( this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes );
4838           }
4839           break;
4840       }
4841     },
4842
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;
4849         }
4850       } else {
4851         clearTimeout( this._timeoutInteractive );
4852         this._timeoutInteractive = null;
4853
4854         this._created = false;
4855
4856         $(window).unbind("resize", this._windowHandler);
4857
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]);
4861         }
4862
4863         this._$shapesContainer.geographics("destroy");
4864         this._$shapesContainer = undefined;
4865         this._createdGraphics = false;
4866
4867         this._$drawContainer.geographics("destroy");
4868         this._$drawContainer = undefined;
4869
4870         this._$existingChildren.detach();
4871         this._$elem.html("");
4872         this._$elem.append(this._$existingChildren);
4873         this._$elem.removeClass("geo-map");
4874       }
4875
4876       $.Widget.prototype.destroy.apply(this, arguments);
4877     },
4878
4879     toMap: function (p) {
4880       p = this._toMap(p);
4881       return this._userGeodetic ? $.geo.proj.toGeodetic(p) : p;
4882     },
4883
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 );
4886     },
4887
4888     opacity: function ( value, _serviceContainer ) {
4889       if ( this._$elem.is( ".geo-service" ) ) {
4890         this._$elem.closest( ".geo-map" ).geomap( "opacity", value, this._$elem );
4891       } else {
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;
4897
4898               // update the original service object's style property
4899               service.serviceObject.style = $.extend( { }, service.serviceObject.style, service.style );
4900
4901               $.geo[ "_serviceTypes" ][ service.type ].opacity( this, service );
4902             }
4903           }
4904         }
4905       }
4906     },
4907
4908     toggle: function ( value, _serviceContainer ) {
4909       if ( this._$elem.is( ".geo-service" ) ) {
4910         this._$elem.closest( ".geo-map" ).geomap( "toggle", value, this._$elem );
4911       } else {
4912
4913         for ( var i = 0; i < this._currentServices.length; i++ ) {
4914           var service = this._currentServices[ i ];
4915
4916           if ( !_serviceContainer || service.serviceContainer[ 0 ] == _serviceContainer[ 0 ] ) {
4917             if ( value === undefined ) {
4918               // toggle visibility
4919               value = ( service.style.visibility !== "visible" );
4920             }
4921
4922             service.style.visibility = ( value ? "visible" : "hidden" );
4923
4924             // update the original service object's style property
4925             service.serviceObject.style = $.extend( { }, service.serviceObject.style, service.style );
4926
4927             service.serviceContainer.toggle( value );
4928
4929             if ( value ) {
4930               $.geo[ "_serviceTypes" ][ service.type ].refresh( this, service );
4931             }
4932           }
4933         }
4934       }
4935     },
4936
4937     zoom: function (numberOfLevels) {
4938       if (numberOfLevels !== null) {
4939         this._setZoom(this._options["zoom"] + numberOfLevels, false, true);
4940       }
4941     },
4942
4943     refresh: function ( force, _serviceContainer ) {
4944       if ( this._$elem.is( ".geo-service" ) ) {
4945         this._$elem.closest( ".geo-map" ).geomap( "refresh", force, this._$elem );
4946       } else {
4947         this._refresh( force, _serviceContainer );
4948         this._refreshAllShapes( );
4949       }
4950     },
4951
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,
4956           i;
4957
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"]
4963       };
4964
4965       this._$resizeContainer.css( {
4966         width: size["width"],
4967         height: size["height"]
4968       } );
4969
4970       for (i = 0; i < this._currentServices.length; i++) {
4971         $.geo["_serviceTypes"][this._currentServices[i].type].resize(this, this._currentServices[i]);
4972       }
4973
4974       this._$elem.find( ".geo-graphics" ).css( {
4975         width: size["width"],
4976         height: size["height"]
4977       }).geographics( "resize" );
4978
4979       for (i = 0; i < this._drawPixels.length; i++) {
4980         this._drawPixels[i][0] += dx;
4981         this._drawPixels[i][1] += dy;
4982       }
4983
4984       this._setCenterAndSize(this._center, this._pixelSize, _trigger, true);
4985     },
4986
4987     append: function ( shape, style, label, refresh ) {
4988       if ( shape && ( $.isPlainObject( shape ) || ( $.isArray( shape ) && shape.length > 0 ) ) ) {
4989         if ( !this._createdGraphics ) {
4990           this._createServiceGraphics( );
4991         }
4992
4993         var shapes, arg, i, realStyle, realLabel, realRefresh;
4994
4995         if ( $.isArray( shape ) ) {
4996           shapes = shape;
4997         } else if ( shape.type == "FeatureCollection" ) {
4998           shapes = shape.features;
4999         } else {
5000           shapes = [ shape ];
5001         }
5002
5003         for ( i = 1; i < arguments.length; i++ ) {
5004           arg = arguments[ i ];
5005
5006           if ( typeof arg === "object" ) {
5007             realStyle = arg;
5008           } else if ( typeof arg === "number" || typeof arg === "string" ) {
5009             realLabel = arg;
5010           } else if ( typeof arg === "boolean" ) {
5011             realRefresh = arg;
5012           }
5013         }
5014
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 );
5020             }
5021             $.data( shapes[ i ], "geoBbox", bbox );
5022           }
5023
5024           this._graphicShapes.push( {
5025             shape: shapes[ i ],
5026             style: realStyle,
5027             label: realLabel
5028           } );
5029         }
5030
5031         if ( realRefresh === undefined || realRefresh ) {
5032           if ( this._$elem.is( ".geo-service" ) ) {
5033             this._refresh( false, this._$elem );
5034           } else {
5035             this._refresh( );
5036           }
5037           this._refreshAllShapes( );
5038         }
5039       }
5040     },
5041
5042     empty: function ( refresh ) {
5043       for ( var i = 0; i < this._graphicShapes.length; i++ ) {
5044         $.removeData( this._graphicShapes[ i ].shape, "geoBbox" );
5045       }
5046
5047       this._graphicShapes = [];
5048
5049       if ( refresh === undefined || refresh ) {
5050         if ( this._$elem.is( ".geo-service" ) ) {
5051           this._refresh( false, this._$elem );
5052         } else {
5053           this._refresh( );
5054         }
5055         this._refreshAllShapes( );
5056       }
5057     },
5058
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,
5063           result = [],
5064           graphicShape,
5065           geometries,
5066           curGeom,
5067           i = 0;
5068
5069       for ( ; i < this._graphicShapes.length; i++ ) {
5070         graphicShape = this._graphicShapes[ i ];
5071
5072         if ( isPoint ) {
5073           if ( graphicShape.shape.type == "Point" ) {
5074             if ( $.geo.distance( graphicShape.shape, selector ) <= mapTol ) {
5075               result.push( graphicShape.shape );
5076             }
5077           } else {
5078             var bbox = $.data( graphicShape.shape, "geoBbox" ),
5079                 bboxPolygon = {
5080                   type: "Polygon",
5081                   coordinates: [ [
5082                     [bbox[0], bbox[1]],
5083                     [bbox[0], bbox[3]],
5084                     [bbox[2], bbox[3]],
5085                     [bbox[2], bbox[1]],
5086                     [bbox[0], bbox[1]]
5087                   ] ]
5088                 },
5089                 projectedPoint = {
5090                   type: "Point",
5091                   coordinates: $.geo.proj && $.geo._isGeodetic( selector.coordinates ) ? $.geo.proj.fromGeodetic( selector.coordinates ) : selector.coordinates
5092                 };
5093
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 );
5099                   break;
5100                 }
5101               }
5102             }
5103           }
5104         } else {
5105           result.push( graphicShape.shape );
5106         }
5107       }
5108
5109       if ( this._$elem.is( ".geo-map" ) ) {
5110         this._$elem.find( ".geo-service" ).each( function( ) {
5111           result = $.merge( result, $( this ).geomap( "find", selector, pixelTolerance ) );
5112         } );
5113       }
5114
5115       return result;
5116     },
5117
5118     remove: function ( shape, refresh ) {
5119       if ( shape && ( $.isPlainObject( shape ) || ( $.isArray( shape ) && shape.length > 0 ) ) ) {
5120         var shapes = $.isArray( shape ) ? shape : [ shape ],
5121             rest;
5122
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 );
5129             i--;
5130           }
5131         }
5132
5133         if ( refresh === undefined || refresh ) {
5134           if ( this._$elem.is( ".geo-service" ) ) {
5135             this._refresh( false, this._$elem );
5136           } else {
5137             this._refresh( );
5138           }
5139           this._refreshAllShapes( );
5140         }
5141       }
5142     },
5143
5144     _getBbox: function (center, pixelSize) {
5145       center = center || this._center;
5146       pixelSize = pixelSize || this._pixelSize;
5147
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 ];
5152     },
5153
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 );
5158
5159       // clamp to zoom
5160       if ( this._options[ "tilingScheme" ] ) {
5161         pixelSize = this._getPixelSize( Math.min( Math.max( zoom, this._options[ "zoomMin" ] ), this._options[ "zoomMax" ] ) );
5162       } else {
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" ] );
5167         }
5168       }
5169
5170       this._setInteractiveCenterAndSize( center, pixelSize );
5171       this._interactiveTransform( );
5172     },
5173
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];
5179     },
5180
5181     _getCenter: function () {
5182       return this._center;
5183     },
5184
5185     _getContentBounds: function () {
5186       return this._contentBounds;
5187     },
5188
5189     _getServicesContainer: function () {
5190       return this._$servicesContainer;
5191     },
5192
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;
5198
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,
5204               i = levels - 1;
5205
5206           for ( ; i >= 0; i-- ) {
5207             if ( Math.floor( tilingScheme.pixelSizes[ i ] * 1000 ) >= roundedPixelSize ) {
5208               return i;
5209             }
5210           }
5211
5212           return 0;
5213         } else {
5214           return Math.round( Math.log( tilingScheme.basePixelSize / pixelSize) / Math.log( 2 ) );
5215         }
5216       } else {
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);
5220
5221         return Math.round( Math.log($.geo.width(bboxMax, true) / $.geo.width(bbox, true)) / Math.log(this._zoomFactor) );
5222       }
5223     },
5224
5225     _setZoom: function ( value, trigger, refresh ) {
5226       // set the map widget's zoom, taking zoomMin and zoomMax into account
5227       this._clearInteractiveTimeout( );
5228
5229       value = Math.min( Math.max( value, this._options[ "zoomMin" ] ), this._options[ "zoomMax" ] );
5230
5231       this._setInteractiveCenterAndSize( this._centerInteractive, this._getPixelSize( value ) );
5232       this._interactiveTransform( );
5233       this._setInteractiveTimeout( trigger );
5234     },
5235
5236     _createChildren: function () {
5237       this._$existingChildren = this._$elem.children();
5238
5239       this._forcePosition(this._$existingChildren);
5240
5241       this._$existingChildren.detach().css( {
5242         mozUserSelect: "none"
5243       } );
5244
5245
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;";
5248
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');
5251
5252       this._$contentFrame.append('<div class="geo-services-container" style="' + contentPosCss + contentSizeCss + '"></div>');
5253       this._$servicesContainer = this._$contentFrame.children(':last');
5254
5255       this._$contentFrame.append('<div class="geo-shapes-container" style="' + contentPosCss + contentSizeCss + '"></div>');
5256       this._$shapesContainer = this._$contentFrame.children(':last');
5257
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" );
5260
5261       this._$contentFrame.append('<div class="geo-draw-container" style="' + contentPosCss + contentSizeCss + '"></div>');
5262       this._$drawContainer = this._$contentFrame.children(':last');
5263
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();
5267
5268       this._$panContainer = $( [ this._$shapesContainer[ 0 ], this._$drawContainer[ 0 ], this._$measureContainer[ 0 ] ] );
5269
5270       this._$resizeContainer = $( [ this._$contentFrame[ 0 ], this._$servicesContainer[ 0 ], this._$eventTarget[ 0 ], this._$measureContainer[ 0 ] ] ); 
5271
5272       this._$contentFrame.append(this._$existingChildren);
5273
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>' );
5276       }
5277     },
5278
5279     _createServices: function () {
5280       var service, i;
5281
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 ] );
5285       }
5286
5287       this._currentServices = [ ];
5288       this._$servicesContainer.html( "" );
5289       this._$attrList.html( "" );
5290
5291       for ( i = 0; i < this._options[ "services" ].length; i++ ) {
5292         service = this._currentServices[ i ] = $.extend( { }, this._options[ "services" ][ i ] );
5293
5294         // keep a reference to the original
5295         service.serviceObject = this._options[ "services" ][ i ];
5296
5297         // default the service style property on our copy
5298         service.style = $.extend( {
5299                           visibility: "visible",
5300                           opacity: 1
5301                         }, service.style );
5302
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>',
5306             servicesContainer;
5307
5308         this._$servicesContainer.append( scHtml );
5309         serviceContainer = this._$servicesContainer.children( ":last" );
5310         service.serviceContainer = serviceContainer;
5311         
5312         $.geo[ "_serviceTypes" ][ service.type ].create( this, serviceContainer, service, i );
5313
5314         serviceContainer.data( "geoMap", this ).geomap();
5315
5316         if ( service.attr ) {
5317           this._$attrList.append( '<li>' + service.attr + '</li>' );
5318         }
5319       }
5320
5321       // start with our map-level shapesContainer
5322       this._$shapesContainers = this._$shapesContainer;
5323
5324       this._$attrList.find( "a" ).css( {
5325         position: "relative",
5326         zIndex: 100
5327       } );
5328     },
5329
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');
5335
5336       this._map._$shapesContainers = this._map._$shapesContainers.add( this._$shapesContainer );
5337
5338       this._$shapesContainer.geographics( );
5339       this._createdGraphics = true;
5340
5341       this._options["shapeStyle"] = this._$shapesContainer.geographics("option", "style");
5342     },
5343
5344     _refreshDrawing: function ( ) {
5345       this._$drawContainer.geographics("clear");
5346
5347       if ( this._drawPixels.length > 0 ) {
5348         var mode = this._options[ "mode" ],
5349             pixels = this._drawPixels,
5350             coords = this._drawCoords,
5351             label,
5352             labelShape,
5353             labelPixel,
5354             widthOver,
5355             heightOver;
5356
5357         switch ( mode ) {
5358           case "measureLength":
5359             mode = "drawLineString";
5360             labelShape = {
5361               type: "LineString",
5362               coordinates: coords
5363             };
5364             label = $.render[ this._tmplLengthId ]( { length: $.geo.length( labelShape, true ) } );
5365             labelPixel = $.merge( [], pixels[ pixels.length - 1 ] );
5366             break;
5367
5368           case "measureArea":
5369             mode = "drawPolygon";
5370
5371             labelShape = {
5372               type: "Polygon",
5373               coordinates: [ $.merge( [ ], coords ) ]
5374             };
5375             labelShape.coordinates[ 0 ].push( coords[ 0 ] );
5376
5377             label = $.render[ this._tmplAreaId ]( { area: $.geo.area( labelShape, true ) } );
5378             labelPixel = this._toPixel( $.geo.centroid( labelShape ).coordinates );
5379             pixels = [ pixels ];
5380             break;
5381
5382           case "drawPolygon":
5383             pixels = [ pixels ];
5384             break;
5385         }
5386
5387         this._$drawContainer.geographics( mode, pixels );
5388         
5389         if ( label ) {
5390           this._$measureLabel.html( label );
5391
5392           widthOver = this._contentBounds.width - ( this._$measureLabel.outerWidth( true ) + labelPixel[ 0 ] );
5393           heightOver = this._contentBounds.height - ( this._$measureLabel.outerHeight( true ) + labelPixel[ 1 ] );
5394
5395           if ( widthOver < 0 ) {
5396             labelPixel[ 0 ] += widthOver;
5397           }
5398
5399           if ( heightOver < 0 ) {
5400             labelPixel[ 1 ] += heightOver;
5401           }
5402
5403           this._$measureLabel.css( {
5404             left: Math.max( labelPixel[ 0 ], 0 ),
5405             top: Math.max( labelPixel[ 1 ], 0 )
5406           } ).show();
5407         }
5408       }
5409     },
5410
5411     _resetDrawing: function () {
5412       this._drawPixels = [];
5413       this._drawCoords = [];
5414       this._$drawContainer.geographics("clear");
5415       this._$measureLabel.hide();
5416     },
5417
5418     _refreshAllShapes: function ( ) {
5419       this._timeoutRefreshShapes = null;
5420
5421       var service,
5422           geoService,
5423           i = 0;
5424
5425       for ( ; i < this._currentServices.length; i++ ) {
5426         service = this._currentServices[ i ];
5427         geoService = service.serviceContainer.data( "geoService" );
5428
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 );
5433           }
5434         }
5435       }
5436
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 );
5441         }
5442       }
5443     },
5444
5445
5446     _refreshShapes: function (geographics, shapes, styles, labels, center, pixelSize) {
5447       var i, mgi,
5448           shape,
5449           shapeBbox,
5450           style,
5451           label,
5452           hasLabel,
5453           labelPixel,
5454           bbox = this._map._getBbox(center, pixelSize);
5455
5456       /*
5457       if ( shapes.length > 0 ) {
5458         console.log( "_refreshShapes " + $.now() );
5459       }
5460       */
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");
5465
5466         if ( shapeBbox && $.geo._bboxDisjoint( bbox, shapeBbox ) ) {
5467           continue;
5468         }
5469
5470         style = $.isArray(styles) ? styles[i].style : styles;
5471         label = $.isArray(labels) ? labels[i].label : labels;
5472         hasLabel = ( label !== undefined );
5473         labelPixel = undefined;
5474
5475         switch (shape.type) {
5476           case "Point":
5477             labelPixel = this._map.toPixel( shape.coordinates, center, pixelSize );
5478             this._$shapesContainer.geographics("drawPoint", labelPixel, style);
5479             break;
5480           case "LineString":
5481             this._$shapesContainer.geographics("drawLineString", this._map.toPixel(shape.coordinates, center, pixelSize), style);
5482             if ( hasLabel ) {
5483               labelPixel = this._map.toPixel( $.geo.pointAlong( shape, 0.5 ).coordinates, center, pixelSize );
5484             }
5485             break;
5486           case "Polygon":
5487             this._$shapesContainer.geographics("drawPolygon", this._map.toPixel(shape.coordinates, center, pixelSize), style);
5488             if ( hasLabel ) {
5489               labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
5490             }
5491             break;
5492           case "MultiPoint":
5493             for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
5494               this._$shapesContainer.geographics("drawPoint", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
5495             }
5496             if ( hasLabel ) {
5497               labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
5498             }
5499             break;
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);
5503             }
5504             if ( hasLabel ) {
5505               labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
5506             }
5507             break;
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);
5511             }
5512             if ( hasLabel ) {
5513               labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
5514             }
5515             break;
5516
5517           case "GeometryCollection":
5518             this._refreshShapes(geographics, shape.geometries, style, label, center, pixelSize);
5519             break;
5520         }
5521
5522         if ( hasLabel && labelPixel ) {
5523           this._$shapesContainer.geographics( "drawLabel", labelPixel, label );
5524         }
5525       }
5526     },
5527
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;
5533
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) };
5538         }
5539         sizeContainer = sizeContainer.parent();
5540       }
5541       return size;
5542     },
5543
5544     _forcePosition: function (elem) {
5545       var cssPosition = elem.css("position");
5546       if (cssPosition != "relative" && cssPosition != "absolute" && cssPosition != "fixed") {
5547         elem.css("position", "relative");
5548       }
5549     },
5550
5551     _getPixelSize: function ( zoom ) {
5552       var tilingScheme = this._options["tilingScheme"];
5553       if (tilingScheme !== null) {
5554         if (zoom === 0) {
5555           return tilingScheme.pixelSizes ? tilingScheme.pixelSizes[0] : tilingScheme.basePixelSize;
5556         }
5557
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);
5562
5563         if ( tilingScheme.pixelSizes ) {
5564           return tilingScheme.pixelSizes[zoom];
5565         } else {
5566           return tilingScheme.basePixelSize / Math.pow(2, zoom);
5567         }
5568       } else {
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 );
5571       }
5572     },
5573
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);
5579
5580       // clamp to zoom
5581       if ( full && this._options[ "tilingScheme" ] ) {
5582         pixelSize = this._getPixelSize( Math.min( Math.max( zoom, this._options[ "zoomMin" ] ), this._options[ "zoomMax" ] ) );
5583       } else {
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;
5588         }
5589       }
5590
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]];
5595
5596       return { pixelSize: pixelSize, center: scaleCenter };
5597     },
5598
5599     _mouseWheelFinish: function ( refresh ) {
5600       this._wheelTimeout = null;
5601
5602       if (this._wheelLevel !== 0) {
5603         var wheelCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, this._options[ "tilingScheme" ] !== null );
5604
5605         this._wheelLevel = 0;
5606       } else if ( refresh ) {
5607         this._refresh();
5608         this._refreshAllShapes( );
5609       }
5610     },
5611
5612     _panFinalize: function () {
5613       if (this._panning) {
5614         this._velocity = [0, 0];
5615
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;
5621
5622         this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
5623
5624         this._inOp = false;
5625         this._anchor = this._current;
5626         this._mouseDown = this._toolPan = this._panning = false;
5627       }
5628     },
5629
5630     _panMove: function () {
5631       if ( ! this._options[ "pannable" ] ) {
5632         return;
5633       }
5634
5635       var dx = this._current[0] - this._lastDrag[0],
5636           dy = this._current[1] - this._lastDrag[1],
5637           i = 0,
5638           service,
5639           translateObj;
5640
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"]);
5645         }
5646
5647         if (this._mouseDown) {
5648           this._velocity = [dx, dy];
5649         }
5650
5651         if (dx !== 0 || dy !== 0) {
5652           this._panning = true;
5653           this._lastDrag = this._current;
5654
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( );
5659         }
5660       }
5661     },
5662
5663     _clearInteractiveTimeout: function() {
5664       if ( this._timeoutRefreshShapes ) {
5665         clearTimeout( this._timeoutRefreshShapes );
5666         this._timeoutRefreshShapes = null;
5667       }
5668
5669       if ( this._timeoutInteractive ) {
5670         clearTimeout( this._timeoutInteractive );
5671         this._timeoutInteractive = null;
5672         return true;
5673       } else {
5674         this._centerInteractive[ 0 ] = this._center[ 0 ];
5675         this._centerInteractive[ 1 ] = this._center[ 1 ];
5676         this._pixelSizeInteractive = this._pixelSize;
5677         return false;
5678       }
5679     },
5680
5681     _interactiveTransform: function( ) {
5682       var mapWidth = this._contentBounds[ "width" ],
5683           mapHeight = this._contentBounds[ "height" ],
5684
5685           halfWidth = mapWidth / 2,
5686           halfHeight = mapHeight / 2,
5687
5688           bbox = [ this._centerInteractive[ 0 ] - halfWidth, this._centerInteractive[ 1 ] - halfHeight, this._centerInteractive[ 0 ] + halfWidth, this._centerInteractive[ 1 ] + halfHeight ];
5689
5690       var scalePixelSize = this._pixelSize,
5691           scaleRatio = scalePixelSize / this._pixelSizeInteractive;
5692           
5693       if ( scalePixelSize > 0 ) {
5694         scaleRatio = Math.round(scaleRatio * 1000) / 1000;
5695
5696         var oldMapOrigin = this._toMap( [ 0, 0 ] ),
5697             newPixelPoint = this._toPixel( oldMapOrigin, this._centerInteractive, this._pixelSizeInteractive );
5698
5699
5700         this._$shapesContainers.geographics("interactiveTransform", newPixelPoint, scaleRatio);
5701
5702         /*
5703         $scaleContainer.css( {
5704           left: Math.round( newPixelPoint[ 0 ] ),
5705           top: Math.round( newPixelPoint[ 1 ] ),
5706           width: mapWidth * scaleRatio,
5707           height: mapHeight * scaleRatio
5708         } );
5709         */
5710         
5711       }
5712
5713
5714
5715
5716
5717
5718
5719
5720
5721
5722
5723
5724
5725
5726
5727
5728
5729
5730
5731
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 );
5735       }
5736
5737       if (this._drawCoords.length > 0) {
5738         this._drawPixels = this._toPixel( this._drawCoords, this._centerInteractive, this._pixelSizeInteractive );
5739         this._refreshDrawing();
5740       }
5741     },
5742
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;
5750
5751         this._timeoutRefreshShapes = setTimeout( $.proxy( this._refreshAllShapes, this ), 128 );
5752       }
5753     },
5754
5755     _setInteractiveTimeout: function( trigger ) {
5756       this._timeoutInteractive = setTimeout( $.proxy( this._interactiveTimeout, this ), 128 );
5757       this._triggerInteractive |= trigger;
5758     },
5759
5760     _refresh: function ( force, _serviceContainer ) {
5761       var service,
5762           i = 0;
5763
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 );
5768         }
5769       }
5770     },
5771
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;
5779
5780       if ( this._userGeodetic ) {
5781         this._options["bbox"] = $.geo.proj.toGeodetic( this._getBbox( center, pixelSize ) );
5782         this._options["center"] = $.geo.proj.toGeodetic( center );
5783       } else {
5784         this._options["bbox"] = this._getBbox( center, pixelSize );
5785         this._options["center"][ 0 ] = center[ 0 ];
5786         this._options["center"][ 1 ] = center[ 1 ];
5787       }
5788
5789       this._options["pixelSize"] = pixelSize;
5790       this._options["zoom"] = this._getZoom( center, pixelSize );
5791     },
5792
5793     _setCenterAndSize: function (center, pixelSize, trigger, refresh) {
5794       if ( ! $.isArray( center ) || center.length != 2 || typeof center[ 0 ] !== "number" || typeof center[ 1 ] !== "number" ) {
5795         return;
5796       }
5797
5798       // the final call during any extent change
5799       // only called by timeoutInteractive & resize
5800       // clamp to zoom
5801       var zoom = this._getZoom( center, pixelSize );
5802
5803       if ( this._options[ "tilingScheme" ] ) {
5804         this._pixelSizeInteractive = pixelSize = this._getPixelSize( Math.min( Math.max( zoom, this._options[ "zoomMin" ] ), this._options[ "zoomMax" ] ) );
5805       } else {
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" ] );
5810         }
5811       }
5812
5813       this._center[ 0 ] = center[ 0 ];
5814       this._center[ 1 ] = center[ 1 ];
5815       this._options["pixelSize"] = this._pixelSize = pixelSize;
5816
5817       if ( this._userGeodetic ) {
5818         this._options["bbox"] = $.geo.proj.toGeodetic( this._getBbox() );
5819         this._options["center"] = $.geo.proj.toGeodetic( this._center );
5820       } else {
5821         this._options["bbox"] = this._getBbox();
5822         this._options["center"] = $.merge( [ ], center );
5823       }
5824
5825       this._options["zoom"] = zoom;
5826
5827       if (trigger) {
5828         this._trigger("bboxchange", window.event, { bbox: $.merge( [ ], this._options["bbox"] ) });
5829       }
5830
5831       if (refresh) {
5832         this._refresh();
5833         this._refreshAllShapes( );
5834         this._refreshDrawing();
5835       }
5836     },
5837
5838     _requestQueued: function ( ) {
5839       if ( this._loadCount === 0 ) {
5840         this._trigger( "loadstart", window.event );
5841       }
5842       this._loadCount++;
5843     },
5844
5845     _requestComplete: function ( ) {
5846       this._loadCount--;
5847       if ( this._loadCount <= 0 ) {
5848         this._loadCount = 0;
5849         this._trigger( "loadend", window.event );
5850       }
5851     },
5852
5853     _toMap: function (p, center, pixelSize) {
5854       // ignores $.geo.proj
5855
5856       center = center || this._center;
5857       pixelSize = pixelSize || this._pixelSize;
5858
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,
5869           yOffset,
5870           image = this._options[ "axisLayout" ] === "image",
5871           result = [],
5872           i, j, k;
5873
5874       if ( !isMultiPolygon ) {
5875         if ( !isMultiLineStringOrPolygon ) {
5876           if ( !isMultiPointOrLineString ) {
5877             p = [ p ];
5878           }
5879           p = [ p ];
5880         }
5881         p = [ p ];
5882       }
5883
5884       for ( i = 0; i < p.length; i++ ) {
5885         result[ 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
5893             ];
5894           }
5895         }
5896       }
5897
5898       return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
5899     },
5900
5901     _toPixel: function (p, center, pixelSize) {
5902       // ignores $.geo.proj
5903
5904       center = center || this._center;
5905       pixelSize = pixelSize || this._pixelSize;
5906
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,
5920           result = [ ],
5921           i, j, k;
5922
5923       if ( !isMultiPolygon ) {
5924         if ( !isMultiLineStringOrPolygon ) {
5925           if ( !isMultiPointOrLineString ) {
5926             p = [ p ];
5927           }
5928           p = [ p ];
5929         }
5930         p = [ p ];
5931       }
5932
5933       for ( i = 0; i < p.length; i++ ) {
5934         result[ 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 )
5941             ];
5942           }
5943         }
5944       }
5945
5946       return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
5947     },
5948
5949     _document_keydown: function (e) {
5950       var len = this._drawCoords.length;
5951       if (len > 0 && e.which == 27) {
5952         if (len <= 2) {
5953           this._resetDrawing();
5954           this._inOp = false;
5955         } else {
5956           this._drawCoords[len - 2] = $.merge( [], this._drawCoords[ len - 1 ] );
5957           this._drawPixels[len - 2] = $.merge( [], this._drawPixels[ len - 1 ] );
5958
5959           this._drawCoords.length--;
5960           this._drawPixels.length--;
5961
5962           this._refreshDrawing();
5963         }
5964       }
5965     },
5966
5967     _eventTarget_dblclick_zoom: function(e) {
5968       var doInteractiveTimeout = this._clearInteractiveTimeout( );
5969
5970       this._trigger("dblclick", e, { type: "Point", coordinates: this._toMap(this._current, this._centerInteractive, this._pixelSizeInteractive ) });
5971
5972       if (!e.isDefaultPrevented()) {
5973         var centerAndSize = this._getZoomCenterAndSize(this._current, 1, true );
5974
5975         this._setInteractiveCenterAndSize( centerAndSize.center, centerAndSize.pixelSize );
5976         this._interactiveTransform( );
5977
5978         doInteractiveTimeout = true;
5979       }
5980
5981       if ( doInteractiveTimeout ) {
5982         this._setInteractiveTimeout( true );
5983       }
5984     },
5985
5986     _eventTarget_dblclick: function (e) {
5987       if ( this._options[ "mode" ] === "static" ) {
5988         return;
5989       }
5990
5991       if (this._drawTimeout) {
5992         window.clearTimeout(this._drawTimeout);
5993         this._drawTimeout = null;
5994       }
5995
5996       var offset = $(e.currentTarget).offset();
5997
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, {
6005                 type: "LineString",
6006                 coordinates: this._userGeodetic ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords
6007               } );
6008           } else {
6009             this._eventTarget_dblclick_zoom(e);
6010           }
6011           this._resetDrawing();
6012           break;
6013
6014         case "drawPolygon":
6015         case "measureArea":
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;
6019             if (endIndex > 2) {
6020               this._drawCoords[endIndex] = $.merge( [], this._drawCoords[0] );
6021               this._trigger( "shape", e, {
6022                 type: "Polygon",
6023                 coordinates: [ this._userGeodetic ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords ]
6024               } );
6025             }
6026           } else {
6027             this._eventTarget_dblclick_zoom(e);
6028           }
6029           this._resetDrawing();
6030           break;
6031
6032         default:
6033           this._eventTarget_dblclick_zoom(e);
6034           break;
6035       }
6036
6037       this._inOp = false;
6038     },
6039
6040     _eventTarget_touchstart: function (e) {
6041       var mode = this._options[ "mode" ],
6042           shift = this._options[ "shift" ],
6043           defaultShift = ( mode === "dragBox" ? "dragBox" : "zoom" );
6044
6045       if ( mode === "static" ) {
6046         return;
6047       }
6048
6049       if ( !this._supportTouch && e.which != 1 ) {
6050         return;
6051       }
6052
6053       var doInteractiveTimeout = this._clearInteractiveTimeout( );
6054
6055       var offset = $(e.currentTarget).offset(),
6056           touches = e.originalEvent.changedTouches;
6057
6058       if ( this._supportTouch ) {
6059         this._multiTouchAnchor = $.merge( [ ], touches );
6060
6061         this._isMultiTouch = this._multiTouchAnchor.length > 1;
6062
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
6069           ];
6070
6071           this._multiTouchAnchorBbox = $.merge( [ ], this._multiTouchCurrentBbox );
6072
6073           this._current = $.geo.center( this._multiTouchCurrentBbox, true );
6074         } else {
6075           this._multiTouchCurrentBbox = [
6076             touches[0].pageX - offset.left,
6077             touches[0].pageY - offset.top,
6078             NaN,
6079             NaN
6080           ];
6081
6082           this._current = [ touches[0].pageX - offset.left, touches[0].pageY - offset.top ];
6083         }
6084       } else {
6085         this._current = [e.pageX - offset.left, e.pageY - offset.top];
6086       }
6087
6088       if (this._softDblClick) {
6089         var downDate = $.now();
6090         if (downDate - this._downDate < 750) {
6091           if (this._isTap) {
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));
6095             if (distance > 8) {
6096               this._isTap = false;
6097             } else {
6098               this._current = $.merge( [ ], this._anchor );
6099             }
6100           }
6101
6102           if (this._isDbltap) {
6103             this._isDbltap = false;
6104           } else {
6105             this._isDbltap = this._isTap;
6106           }
6107         } else {
6108           this._isDbltap = false;
6109         }
6110         this._isTap = true;
6111         this._downDate = downDate;
6112       }
6113
6114       this._mouseDown = true;
6115       this._anchor = $.merge( [ ], this._current );
6116
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" ) ) {
6121         this._inOp = true;
6122
6123         if ( mode !== "zoom" && mode !== "dragBox" && mode !== "dragCircle" ) {
6124           this._lastDrag = this._current;
6125
6126           if (e.currentTarget.setCapture) {
6127             e.currentTarget.setCapture();
6128           }
6129         }
6130       }
6131
6132       e.preventDefault();
6133
6134       if ( doInteractiveTimeout ) {
6135         this._setInteractiveTimeout( true );
6136       }
6137
6138       return false;
6139     },
6140
6141     _dragTarget_touchmove: function (e) {
6142       if ( this._options[ "mode" ] === "static" ) {
6143         return;
6144       }
6145
6146       var doInteractiveTimeout = false;
6147       if ( this._mouseDown ) {
6148         doInteractiveTimeout = this._clearInteractiveTimeout( );
6149       }
6150
6151       var offset = this._$eventTarget.offset(),
6152           drawCoordsLen = this._drawCoords.length,
6153           touches = e.originalEvent.changedTouches,
6154           current,
6155           service,
6156           i = 0;
6157
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;
6164
6165           this._multiTouchAnchor.push( touches[ 0 ] );
6166
6167
6168
6169
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
6175           ];
6176
6177           this._multiTouchAnchorBbox = $.merge( [ ], this._multiTouchCurrentBbox );
6178
6179           this._mouseDown = true;
6180           this._anchor = this._current = $.geo.center( this._multiTouchCurrentBbox, true );
6181
6182
6183           if ( doInteractiveTimeout ) {
6184             this._setInteractiveTimeout( true );
6185           }
6186           return false;
6187         }
6188
6189         if ( this._isMultiTouch ) {
6190
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;
6198             }
6199           }
6200
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 ] ] );
6203
6204           current = $.geo.center( this._multiTouchCurrentBbox, true );
6205
6206           var wheelLevel = ( ( currentDistance - anchorDistance ) / anchorDistance );
6207
6208           if ( wheelLevel > 0 ) {
6209             wheelLevel *= 5;
6210           } else {
6211             wheelLevel *= 10;
6212           }
6213
6214           var delta = wheelLevel - this._wheelLevel;
6215
6216           this._wheelLevel = wheelLevel;
6217
6218           var pinchCenterAndSize = this._getZoomCenterAndSize( this._anchor, delta, false );
6219
6220           this._setInteractiveCenterAndSize( pinchCenterAndSize.center, pinchCenterAndSize.pixelSize );
6221           this._interactiveTransform( );
6222
6223           doInteractiveTimeout = true;
6224
6225           current = $.geo.center( this._multiTouchCurrentBbox, true );
6226         } else {
6227           current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
6228         }
6229       } else {
6230         current = [e.pageX - offset.left, e.pageY - offset.top];
6231       }
6232
6233       if (current[0] === this._lastMove[0] && current[1] === this._lastMove[1]) {
6234         if ( this._inOp ) {
6235           e.preventDefault();
6236           if ( doInteractiveTimeout ) {
6237             this._setInteractiveTimeout( true );
6238           }
6239           return false;
6240         }
6241       }
6242
6243       if ( _ieVersion == 7 ) {
6244         this._isDbltap = this._isTap = false;
6245       }
6246
6247       if (this._mouseDown) {
6248         this._current = current;
6249         this._moveDate = $.now();
6250       }
6251
6252       if ( this._isMultiTouch ) {
6253         e.preventDefault( );
6254         this._isDbltap = this._isTap = false;
6255         if ( doInteractiveTimeout ) {
6256           this._setInteractiveTimeout( true );
6257         }
6258         return false;
6259       }
6260
6261       var mode = this._options["mode"],
6262           shift = this._options[ "shift" ],
6263           defaultShift = ( mode === "dragBox" ? "dragBox" : "zoom" ),
6264           dx, dy, circleSize;
6265
6266       if ( this._shiftDown ) {
6267         mode = ( shift === "default" ? defaultShift : shift );
6268       }
6269
6270       switch (mode) {
6271         case "zoom":
6272         case "dragBox":
6273           if ( this._mouseDown ) {
6274             this._$drawContainer.geographics( "clear" );
6275             this._$drawContainer.geographics( "drawBbox", [
6276               this._anchor[ 0 ],
6277               this._anchor[ 1 ],
6278               current[ 0 ],
6279               current[ 1 ]
6280             ] );
6281           } else {
6282             this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
6283           }
6284           break;
6285
6286         case "dragCircle":
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;
6292
6293             // not part of _refreshDrawing
6294             this._$drawContainer.geographics( "clear" );
6295             this._$drawContainer.geographics( "drawArc", this._anchor, 0, 360, {
6296               width: circleSize,
6297               height: circleSize
6298             } );
6299           } else {
6300             this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
6301           }
6302           break;
6303
6304         case "drawLineString":
6305         case "drawPolygon":
6306         case "measureLength":
6307         case "measureArea":
6308           if (this._mouseDown || this._toolPan) {
6309             this._panMove();
6310             doInteractiveTimeout = true;
6311           } else {
6312             if (drawCoordsLen > 0) {
6313               this._drawCoords[drawCoordsLen - 1] = this._toMap( current, this._centerInteractive, this._pixelSizeInteractive );
6314               this._drawPixels[drawCoordsLen - 1] = current;
6315
6316               this._refreshDrawing();
6317             }
6318
6319             this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
6320           }
6321           break;
6322
6323         default:
6324           if (this._mouseDown || this._toolPan) {
6325             this._panMove();
6326             doInteractiveTimeout = true;
6327           } else {
6328             this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
6329           }
6330           break;
6331       }
6332
6333       this._lastMove = current;
6334
6335       if ( doInteractiveTimeout ) {
6336         this._setInteractiveTimeout( true );
6337       }
6338
6339       if ( this._inOp ) {
6340         e.preventDefault();
6341         return false;
6342       }
6343     },
6344
6345     _dragTarget_touchstop: function (e) {
6346       if ( this._options[ "mode" ] === "static" ) {
6347         return;
6348       }
6349
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);
6355         } else {
6356           // Chrome & Firefox trigger a rogue mouseup event when doing a dblclick maximize in Windows(/Linux?)
6357           // ignore it
6358           return;
6359         }
6360       }
6361
6362       var doInteractiveTimeout = this._clearInteractiveTimeout( );
6363
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,
6371           dx, dy,
6372           coordBuffer,
6373           triggerShape;
6374
6375       if ( this._shiftDown ) {
6376         mode = ( shift === "default" ? defaultShift : shift );
6377       }
6378
6379       if (this._supportTouch) {
6380         current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
6381         this._multiTouchAnchor = [];
6382         this._inOp = false;
6383       } else {
6384         current = [e.pageX - offset.left, e.pageY - offset.top];
6385       }
6386
6387       if (this._softDblClick) {
6388         if (this._isTap) {
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 );
6393           }
6394         }
6395       }
6396
6397       dx = current[0] - this._anchor[0];
6398       dy = current[1] - this._anchor[1];
6399
6400       this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
6401
6402       this._shiftDown = this._mouseDown = this._toolPan = false;
6403
6404       if ( this._isMultiTouch ) {
6405         e.preventDefault( );
6406         this._isMultiTouch = false;
6407
6408         this._wheelLevel = 0;
6409
6410         if ( doInteractiveTimeout ) {
6411           this._setInteractiveTimeout( true );
6412         }
6413         return;
6414       }
6415
6416       if (document.releaseCapture) {
6417         document.releaseCapture();
6418       }
6419
6420       if (mouseWasDown) {
6421         clickDate = $.now();
6422         this._current = current;
6423
6424         switch ( mode ) {
6425           case "zoom":
6426           case "dragBox":
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 ] )
6432                     ], [
6433                       Math.max( this._anchor[ 0 ], current[ 0 ] ),
6434                       Math.min( this._anchor[ 1 ], current[ 1 ] )
6435                     ]
6436                   ] ),
6437                   bbox = [
6438                     bboxCoords[0][0],
6439                     bboxCoords[0][1],
6440                     bboxCoords[1][0],
6441                     bboxCoords[1][1]
6442                   ];
6443
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 );
6447                 }
6448
6449                 this._setBbox(bbox, true, true);
6450                 doInteractiveTimeout = true;
6451               } else {
6452                 triggerShape = $.geo.polygonize( bbox, true );
6453                 triggerShape.bbox = bbox;
6454
6455                 if ( this._userGeodetic ) {
6456                   triggerShape.coordinates = $.geo.proj.toGeodetic( triggerShape.coordinates );
6457                   triggerShape.bbox = $.geo.proj.toGeodetic( triggerShape.bbox );
6458                 }
6459                 this._trigger( "shape", e, triggerShape );
6460               }
6461             } else {
6462               if ( mode === "dragBox" ) {
6463                 coordBuffer = this._toMap( current );
6464
6465                 triggerShape = {
6466                   type: "Point",
6467                   coordinates: [ coordBuffer[ 0 ], coordBuffer[ 1 ] ],
6468                   bbox: [ coordBuffer[ 0 ], coordBuffer[ 1 ], coordBuffer[ 0 ], coordBuffer[ 1 ] ]
6469                 };
6470
6471                 if ( this._userGeodetic ) {
6472                   triggerShape.coordinates = $.geo.proj.toGeodetic( triggerShape.coordinates );
6473                   triggerShape.bbox = $.geo.proj.toGeodetic( triggerShape.bbox );
6474                 }
6475
6476                 this._trigger( "shape", e, triggerShape );
6477               }
6478             }
6479
6480             this._resetDrawing();
6481             break;
6482
6483           case "dragCircle":
6484             if ( dx !== 0 || dy !== 0 ) {
6485               var image = this._options[ "axisLayout" ] === "image",
6486                   d = Math.sqrt( ( dx * dx) + ( dy * dy ) ),
6487                   n = 180,
6488                   a;
6489
6490               this._drawPixels.length = n + 1;
6491
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
6497                 ];
6498               }
6499
6500               this._drawPixels[ n ] = [
6501                 this._drawPixels[ 0 ][ 0 ],
6502                 this._drawPixels[ 0 ][ 1 ]
6503               ];
6504
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 ) ]
6509               ] );
6510
6511               triggerShape = {
6512                 type: "Polygon",
6513                 coordinates: [ this._toMap( this._drawPixels ) ],
6514                 bbox: [ coordBuffer[ 0 ][ 0 ], coordBuffer[ 0 ][ 1 ], coordBuffer[ 1 ][ 0 ], coordBuffer[ 1 ][ 1 ] ]
6515               };
6516
6517               if ( this._userGeodetic ) {
6518                 triggerShape.coordinates = $.geo.proj.toGeodetic( triggerShape.coordinates );
6519                 triggerShape.bbox = $.geo.proj.toGeodetic( triggerShape.bbox );
6520               }
6521
6522               this._trigger( "shape", e, triggerShape );
6523
6524               this._resetDrawing();
6525             } else {
6526               coordBuffer = this._toMap( current );
6527
6528               triggerShape = {
6529                 type: "Point",
6530                 coordinates: [ coordBuffer[ 0 ], coordBuffer[ 1 ] ],
6531                 bbox: [ coordBuffer[ 0 ], coordBuffer[ 1 ], coordBuffer[ 0 ], coordBuffer[ 1 ] ]
6532               };
6533
6534               if ( this._userGeodetic ) {
6535                 triggerShape.coordinates = $.geo.proj.toGeodetic( triggerShape.coordinates );
6536                 triggerShape.bbox = $.geo.proj.toGeodetic( triggerShape.bbox );
6537               }
6538
6539               this._trigger( "shape", e, triggerShape );
6540             }
6541             break;
6542
6543           case "drawPoint":
6544             if (this._drawTimeout) {
6545               window.clearTimeout(this._drawTimeout);
6546               this._drawTimeout = null;
6547             }
6548
6549             if (wasToolPan) {
6550               this._panFinalize();
6551             } else {
6552               if (clickDate - this._clickDate > 100) {
6553                 var geomap = this;
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;
6559                   }
6560                 }, 250);
6561               }
6562             }
6563             break;
6564
6565           case "drawLineString":
6566           case "drawPolygon":
6567           case "measureLength":
6568           case "measureArea":
6569             if (wasToolPan) {
6570               this._panFinalize();
6571             } else {
6572               i = (this._drawCoords.length === 0 ? 0 : this._drawCoords.length - 1);
6573
6574               this._drawCoords[i] = this._toMap(current);
6575               this._drawPixels[i] = current;
6576
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;
6581               }
6582
6583               this._refreshDrawing();
6584             }
6585             break;
6586
6587           default:
6588             if (wasToolPan) {
6589               this._panFinalize();
6590             } else {
6591               if (clickDate - this._clickDate > 100) {
6592                 this._trigger("click", e, { type: "Point", coordinates: this.toMap(current) });
6593                 this._inOp = false;
6594               }
6595             }
6596             break;
6597         }
6598
6599         this._clickDate = clickDate;
6600
6601         if (this._softDblClick && this._isDbltap) {
6602           this._isDbltap = this._isTap = false;
6603           if ( doInteractiveTimeout ) {
6604             this._setInteractiveTimeout( true );
6605           }
6606           this._$eventTarget.trigger("dblclick", e);
6607           return false;
6608         }
6609       }
6610
6611       if ( doInteractiveTimeout ) {
6612         this._setInteractiveTimeout( true );
6613       }
6614
6615       if ( this._inOp ) {
6616         e.preventDefault();
6617         return false;
6618       }
6619     },
6620
6621     _eventTarget_mousewheel: function (e, delta) {
6622       if ( this._options[ "mode" ] === "static" || this._options[ "scroll" ] === "off" ) {
6623         return;
6624       }
6625
6626       e.preventDefault();
6627
6628       if ( this._mouseDown ) {
6629         return false;
6630       }
6631
6632       if (delta !== 0) {
6633         this._clearInteractiveTimeout( );
6634
6635         if ( delta > 0 ) {
6636           delta = Math.ceil( delta );
6637         } else { 
6638           delta = Math.floor( delta );
6639         }
6640
6641         var offset = $(e.currentTarget).offset();
6642         this._anchor = [e.pageX - offset.left, e.pageY - offset.top];
6643
6644         var wheelCenterAndSize = this._getZoomCenterAndSize( this._anchor, delta, this._options[ "tilingScheme" ] !== null ),
6645             service,
6646             i = 0;
6647
6648         this._setInteractiveCenterAndSize( wheelCenterAndSize.center, wheelCenterAndSize.pixelSize );
6649         this._interactiveTransform( );
6650
6651         this._setInteractiveTimeout( true );
6652       }
6653
6654       return false;
6655     }
6656   }
6657   );
6658 }(jQuery));
6659
6660
6661 (function ($, undefined) {
6662   $.geo._serviceTypes.tiled = (function () {
6663     return {
6664       create: function (map, serviceContainer, service, index) {
6665         var serviceState = $.data(service, "geoServiceState");
6666
6667         if ( !serviceState ) {
6668           serviceState = {
6669             loadCount: 0,
6670             reloadTiles: false
6671           };
6672
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>';
6674
6675           serviceContainer.append(scHtml);
6676
6677           serviceState.serviceContainer = serviceContainer.children( ":last" );
6678
6679           $.data(service, "geoServiceState", serviceState);
6680         }
6681
6682         return serviceState.serviceContainer;
6683       },
6684
6685       destroy: function (map, serviceContainer, service) {
6686         var serviceState = $.data(service, "geoServiceState");
6687
6688         serviceState.serviceContainer.remove();
6689
6690         $.removeData(service, "geoServiceState");
6691       },
6692
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" ];
6697
6698         if ( serviceState ) {
6699           this._cancelUnloaded( map, service );
6700
6701           serviceState.serviceContainer.children( ).each( function ( i ) {
6702             var $scaleContainer = $(this),
6703                 scalePixelSize = $scaleContainer.data("pixelSize"),
6704                 scaleRatio = scalePixelSize / pixelSize;
6705
6706             if ( scalePixelSize > 0 ) {
6707               scaleRatio = Math.round(scaleRatio * 1000) / 1000;
6708
6709               var oldMapCoord = $scaleContainer.data("scaleOrigin"),
6710                   newPixelPoint = map._toPixel(oldMapCoord, center, pixelSize);
6711
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
6717               } );
6718
6719               /*
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')" );
6723                 } );
6724               }
6725               */
6726             }
6727           });
6728         }
6729       },
6730
6731       refresh: function (map, service, force) {
6732         //console.log( "tiled.refresh( " + map._center.join( ", " ) + ", " + map._pixelSize + ")" );
6733         var serviceState = $.data( service, "geoServiceState" );
6734
6735         this._cancelUnloaded(map, service);
6736
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;
6740         }
6741
6742         if ( serviceState && service && service.style.visibility === "visible" && !( serviceState.serviceContainer.is( ":hidden" ) ) ) {
6743           var bbox = map._getBbox(),
6744               pixelSize = map._pixelSize,
6745
6746               serviceObj = this,
6747               $serviceContainer = serviceState.serviceContainer,
6748
6749               contentBounds = map._getContentBounds(),
6750               mapWidth = contentBounds["width"],
6751               mapHeight = contentBounds["height"],
6752
6753               image = map.options[ "axisLayout" ] === "image",
6754               ySign = image ? +1 : -1,
6755
6756               tilingScheme = map.options["tilingScheme"],
6757               tileWidth = tilingScheme.tileWidth,
6758               tileHeight = tilingScheme.tileHeight,
6759
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) ),
6764
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,
6770
6771               fullXMinX = tilingScheme.origin[0] + (fullXAtScale * tileWidth) * pixelSize,
6772               fullYMinOrMaxY = tilingScheme.origin[1] + ySign * (fullYAtScale * tileHeight) * pixelSize,
6773
6774               serviceLeft = Math.round((fullXMinX - bbox[0]) / pixelSize),
6775               serviceTop = Math.round( ( image ? fullYMinOrMaxY - bbox[1] : bbox[3] - fullYMinOrMaxY ) / pixelSize),
6776
6777               scaleContainers = $serviceContainer.children().show(),
6778               scaleContainer = scaleContainers.filter("[data-pixel-size='" + pixelSize + "']").appendTo($serviceContainer),
6779
6780               opacity = service.style.opacity,
6781
6782               x, y,
6783
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 );
6788               },
6789
6790               loadImageDeferredFail = function( ) {
6791                 $.data( this, "img" ).remove( );
6792                 serviceState.loadCount--;
6793                 map._requestComplete();
6794               };
6795
6796           if (serviceState.reloadTiles) {
6797             scaleContainers.find("img").attr("data-dirty", "true");
6798           }
6799
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) ] ) );
6803           } else {
6804             scaleContainer.css({
6805               left: (serviceLeft % tileWidth) + "px",
6806               top: (serviceTop % tileHeight) + "px"
6807             }).data("scaleOrigin", map._toMap( [ (serviceLeft % tileWidth), (serviceTop % tileHeight) ] ) );
6808
6809             scaleContainer.children().each(function (i) {
6810               var 
6811               $img = $(this),
6812               tile = $img.attr("data-tile").split(",");
6813
6814               $img.css({
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) + "%"
6817               });
6818
6819               if (opacity < 1) {
6820                 $img.fadeTo(0, opacity);
6821               }
6822             });
6823           }
6824
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");
6829
6830               if ($img.size() === 0 || serviceState.reloadTiles) {
6831                 var bottomLeft = [
6832                       tilingScheme.origin[0] + (x * tileWidth) * pixelSize,
6833                       tilingScheme.origin[1] + ySign * (y * tileHeight) * pixelSize
6834                     ],
6835
6836                     topRight = [
6837                       tilingScheme.origin[0] + ((x + 1) * tileWidth - 1) * pixelSize,
6838                       tilingScheme.origin[1] + ySign * ((y + 1) * tileHeight - 1) * pixelSize
6839                     ],
6840
6841                     tileBbox = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]],
6842
6843                     urlProp = ( service.hasOwnProperty( "src" ) ? "src" : "getUrl" ),
6844                     urlArgs = {
6845                       bbox: tileBbox,
6846                       width: tileWidth,
6847                       height: tileHeight,
6848                       zoom: map._getZoom(),
6849                       tile: {
6850                         row: y,
6851                         column: x
6852                       },
6853                       index: Math.abs(y + x)
6854                     },
6855                     isFunc = $.isFunction( service[ urlProp ] ),
6856                     imageUrl;
6857
6858                 if ( isFunc ) {
6859                   imageUrl = service[ urlProp ]( urlArgs );
6860                 } else {
6861                   $.templates( "geoSrc", service[ urlProp ] );
6862                   imageUrl = $.render[ "geoSrc" ]( urlArgs );
6863                 }
6864
6865                 serviceState.loadCount++;
6866                 map._requestQueued();
6867
6868                 if (serviceState.reloadTiles && $img.size() > 0) {
6869                   $img.attr("src", imageUrl);
6870                 } else {
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) + "%; ";
6874
6875                   imgMarkup += "width: 100%; height: 100%;";
6876
6877                   // #newpanzoom
6878                   /*
6879                   if ($("body")[0].filters === undefined) {
6880                     imgMarkup += "width: 100%; height: 100%;";
6881                   }
6882                   */
6883
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 + "' />";
6885
6886                   scaleContainer.append(imgMarkup);
6887                   $img = scaleContainer.children(":last");
6888                 }
6889
6890                 if ( typeof imageUrl === "string" ) {
6891                   serviceObj._loadImage( $img, imageUrl, pixelSize, map, serviceState, opacity );
6892                 } else if ( imageUrl ) {
6893                   // assume Deferred
6894                   $.data( imageUrl, "img", $img );
6895                   imageUrl.done( loadImageDeferredDone ).fail( loadImageDeferredFail );
6896                 } else {
6897                   $img.remove( );
6898                 }
6899               }
6900             }
6901           }
6902
6903           scaleContainers.find("[data-dirty]").remove();
6904           serviceState.reloadTiles = false;
6905         }
6906       },
6907
6908       resize: function (map, service) {
6909       },
6910
6911       opacity: function ( map, service ) {
6912         var serviceState = $.data( service, "geoServiceState" );
6913         serviceState.serviceContainer.find( "img" ).stop( true ).fadeTo( "fast", service.style.opacity );
6914       },
6915
6916       toggle: function ( map, service ) {
6917         var serviceState = $.data( service, "geoServiceState" );
6918         serviceState.serviceContainer.css( "display", service.style.visibility === "visible" ? "block" : "none" );
6919       },
6920
6921       _cancelUnloaded: function (map, service) {
6922         var serviceState = $.data( service, "geoServiceState" );
6923
6924         if (serviceState && serviceState.loadCount > 0) {
6925           serviceState.serviceContainer.find("img:hidden").remove();
6926           while (serviceState.loadCount > 0) {
6927             serviceState.loadCount--;
6928             map._requestComplete();
6929           }
6930         }
6931       },
6932
6933       _loadImage: function ( $img, url, pixelSize, map, serviceState, opacity ) {
6934         var serviceContainer = serviceState.serviceContainer;
6935
6936         $img.load(function (e) {
6937           if (opacity < 1) {
6938             $(e.target).fadeTo(0, opacity);
6939           } else {
6940             $(e.target).show();
6941           }
6942
6943           serviceState.loadCount--;
6944           map._requestComplete();
6945
6946           if (serviceState.loadCount <= 0) {
6947             serviceContainer.children(":not([data-pixel-size='" + pixelSize + "'])").remove();
6948             serviceState.loadCount = 0;
6949           }
6950         }).error(function (e) {
6951           $(e.target).remove();
6952           serviceState.loadCount--;
6953           map._requestComplete();
6954
6955           if (serviceState.loadCount <= 0) {
6956             serviceContainer.children(":not([data-pixel-size='" + pixelSize + "'])").remove();
6957             serviceState.loadCount = 0;
6958           }
6959         }).attr("src", url);
6960       }
6961     };
6962   }());
6963 }(jQuery));
6964
6965 (function ($, undefined) {
6966   $.geo._serviceTypes.shingled = (function () {
6967     return {
6968       create: function (map, serviceContainer, service, index) {
6969         var serviceState = $.data(service, "geoServiceState");
6970
6971         if ( !serviceState ) {
6972           serviceState = {
6973             loadCount: 0
6974           };
6975
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>';
6977
6978           serviceContainer.append(scHtml);
6979
6980           serviceState.serviceContainer = serviceContainer.children(":last");
6981           $.data(service, "geoServiceState", serviceState);
6982         }
6983
6984         return serviceState.serviceContainer;
6985       },
6986
6987       destroy: function (map, serviceContainer, service) {
6988         var serviceState = $.data(service, "geoServiceState");
6989
6990         serviceState.serviceContainer.remove();
6991
6992         $.removeData(service, "geoServiceState");
6993       },
6994
6995       interactiveTransform: function ( map, service, center, pixelSize ) {
6996         var serviceState = $.data( service, "geoServiceState" ),
6997
6998             contentBounds = map._getContentBounds(),
6999             mapWidth = contentBounds[ "width" ],
7000             mapHeight = contentBounds[ "height" ],
7001
7002             halfWidth = mapWidth / 2,
7003             halfHeight = mapHeight / 2,
7004
7005             bbox = [ center[ 0 ] - halfWidth, center[ 1 ] - halfHeight, center[ 0 ] + halfWidth, center[ 1 ] + halfHeight ];
7006
7007         if ( serviceState ) {
7008           this._cancelUnloaded( map, service );
7009
7010           serviceState.serviceContainer.children( ).each( function ( i ) {
7011             var $scaleContainer = $(this),
7012                 scalePixelSize = $scaleContainer.data( "pixelSize" ),
7013                 scaleRatio = scalePixelSize / pixelSize;
7014                 
7015             if ( scalePixelSize > 0 ) {
7016               scaleRatio = Math.round(scaleRatio * 1000) / 1000;
7017
7018               var oldMapOrigin = $scaleContainer.data( "origin" ),
7019                   newPixelPoint = map._toPixel( oldMapOrigin, center, pixelSize );
7020
7021               $scaleContainer.css( {
7022                 left: Math.round( newPixelPoint[ 0 ] ),
7023                 top: Math.round( newPixelPoint[ 1 ] ),
7024                 width: mapWidth * scaleRatio,
7025                 height: mapHeight * scaleRatio
7026               } );
7027             }
7028           });
7029         }
7030       },
7031
7032       refresh: function (map, service) {
7033         var serviceState = $.data(service, "geoServiceState");
7034
7035         this._cancelUnloaded(map, service);
7036
7037         if ( serviceState && service && service.style.visibility === "visible" && !( serviceState.serviceContainer.is( ":hidden" ) ) ) {
7038
7039           var bbox = map._getBbox(),
7040               pixelSize = map._pixelSize,
7041
7042               serviceObj = this,
7043               serviceContainer = serviceState.serviceContainer,
7044
7045               contentBounds = map._getContentBounds(),
7046               mapWidth = contentBounds["width"],
7047               mapHeight = contentBounds["height"],
7048
7049               scaleContainer = serviceContainer.children('[data-pixel-size="' + pixelSize + '"]'),
7050
7051               opacity = service.style.opacity,
7052
7053               $img;
7054
7055           if (opacity < 1) {
7056             serviceContainer.find("img").attr("data-keep-alive", "0");
7057           }
7058
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");
7062           }
7063
7064           var urlProp = ( service.hasOwnProperty("src") ? "src" : "getUrl" ),
7065               urlArgs = {
7066                 bbox: bbox,
7067                 width: mapWidth,
7068                 height: mapHeight,
7069                 zoom: map._getZoom(),
7070                 tile: null,
7071                 index: 0
7072               },
7073               isFunc = $.isFunction( service[ urlProp ] ),
7074               imageUrl,
7075               imagePos = scaleContainer.position( );
7076
7077           imagePos.left = - ( imagePos.left );
7078           imagePos.top = - ( imagePos.top );
7079
7080           if ( isFunc ) {
7081             imageUrl = service[ urlProp ]( urlArgs );
7082           } else {
7083             $.templates( "geoSrc", service[ urlProp ] );
7084             imageUrl = $.render[ "geoSrc" ]( urlArgs );
7085           }
7086
7087           serviceState.loadCount++;
7088           map._requestQueued();
7089
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);
7092
7093           if ( typeof imageUrl === "string" ) {
7094             serviceObj._loadImage( $img, imageUrl, pixelSize, map, serviceState, opacity );
7095           } else {
7096             // assume Deferred
7097             imageUrl.done( function( url ) {
7098               serviceObj._loadImage( $img, url, pixelSize, map, serviceState, opacity );
7099             } ).fail( function( ) {
7100               $img.remove( );
7101               serviceState.loadCount--;
7102               map._requestComplete();
7103             } );
7104           }
7105
7106         }
7107       },
7108
7109       resize: function (map, service) {
7110         var serviceState = $.data(service, "geoServiceState");
7111
7112         if ( serviceState && service && service.style.visibility === "visible" ) {
7113           this._cancelUnloaded(map, service);
7114
7115           var serviceContainer = serviceState.serviceContainer,
7116
7117               contentBounds = map._getContentBounds(),
7118               mapWidth = contentBounds["width"],
7119               mapHeight = contentBounds["height"],
7120
7121               scaleContainers = serviceContainer.children();
7122
7123           scaleContainers.attr("data-pixel-size", "0");
7124
7125           scaleContainers.each( function ( i ) {
7126             var $scaleContainer = $(this),
7127                 position = $scaleContainer.position( );
7128
7129             var oldMapOrigin = $scaleContainer.data( "origin" ),
7130                 newPixelPoint = map._toPixel( oldMapOrigin );
7131
7132             $scaleContainer.css( {
7133               left: position.left + ( mapWidth - $scaleContainer.width( ) ) / 2,
7134               top: position.top + ( mapHeight - $scaleContainer.height( ) ) / 2
7135             } );
7136
7137           } );
7138             
7139
7140           /*
7141           scaleContainer.css({
7142             left: halfWidth + 'px',
7143             top: halfHeight + 'px'
7144           });
7145           */
7146         }
7147       },
7148
7149       opacity: function ( map, service ) {
7150         var serviceState = $.data( service, "geoServiceState" );
7151         serviceState.serviceContainer.find( "img" ).stop( true ).fadeTo( "fast", service.style.opacity );
7152       },
7153
7154       toggle: function (map, service) {
7155         var serviceState = $.data(service, "geoServiceState");
7156         serviceState.serviceContainer.css("display", service.style.visibility === "visible" ? "block" : "none");
7157       },
7158
7159       _cancelUnloaded: function (map, service) {
7160         var serviceState = $.data(service, "geoServiceState");
7161
7162         if (serviceState && serviceState.loadCount > 0) {
7163           serviceState.serviceContainer.find("img:hidden").remove();
7164           while (serviceState.loadCount > 0) {
7165             serviceState.loadCount--;
7166             map._requestComplete();
7167           }
7168         }
7169       },
7170
7171       _loadImage: function ( $img, url, pixelSize, map, serviceState, opacity ) {
7172         var serviceContainer = serviceState.serviceContainer;
7173
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
7177             return;
7178           }
7179
7180           if (opacity < 1) {
7181             $(e.target).fadeTo(0, opacity);
7182           } else {
7183             $(e.target).show();
7184           }
7185
7186           serviceState.loadCount--;
7187           map._requestComplete();
7188
7189           if (serviceState.loadCount <= 0) {
7190             // #newpanzoom
7191             serviceContainer.children(':not([data-pixel-size="' + pixelSize + '"])').remove();
7192
7193             serviceContainer.find( "img[data-keep-alive]" ).remove( );
7194
7195             serviceState.loadCount = 0;
7196           }
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
7200             return;
7201           }
7202
7203           $(e.target).remove();
7204           serviceState.loadCount--;
7205           map._requestComplete();
7206
7207           if (serviceState.loadCount <= 0) {
7208             serviceContainer.children(":not([data-pixel-size='" + pixelSize + "'])").remove();
7209             serviceState.loadCount = 0;
7210           }
7211         }).attr("src", url);
7212       }
7213     };
7214   }());
7215 }(jQuery));