322325334e6d2473ef9ca5a956f8bdd2430e77c5
[framework/web/web-ui-fw.git] / libs / js / jquery-geo-1.0a4 / docs / jquery.geo-test.js
1 // excanvas
2 // Copyright 2006 Google Inc.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //   http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15
16 /* 
17  * AppGeo/geo 
18  * (c) 2007-2011, Applied Geographics, Inc. All rights reserved. 
19  * Dual licensed under the MIT or GPL Version 2 licenses. 
20  * http://jquery.org/license 
21  */ 
22  
23
24 // Copyright 2006 Google Inc.
25 //
26 // Licensed under the Apache License, Version 2.0 (the "License");
27 // you may not use this file except in compliance with the License.
28 // You may obtain a copy of the License at
29 //
30 //   http://www.apache.org/licenses/LICENSE-2.0
31 //
32 // Unless required by applicable law or agreed to in writing, software
33 // distributed under the License is distributed on an "AS IS" BASIS,
34 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
35 // See the License for the specific language governing permissions and
36 // limitations under the License.
37
38
39 // Known Issues:
40 //
41 // * Patterns only support repeat.
42 // * Radial gradient are not implemented. The VML version of these look very
43 //   different from the canvas one.
44 // * Clipping paths are not implemented.
45 // * Coordsize. The width and height attribute have higher priority than the
46 //   width and height style values which isn't correct.
47 // * Painting mode isn't implemented.
48 // * Canvas width/height should is using content-box by default. IE in
49 //   Quirks mode will draw the canvas using border-box. Either change your
50 //   doctype to HTML5
51 //   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
52 //   or use Box Sizing Behavior from WebFX
53 //   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
54 // * Non uniform scaling does not correctly scale strokes.
55 // * Optimize. There is always room for speed improvements.
56
57 // Only add this code if we do not already have a canvas implementation
58 if (!document.createElement('canvas').getContext) {
59
60   (function () {
61
62     // alias some functions to make (compiled) code shorter
63     var m = Math;
64     var mr = m.round;
65     var ms = m.sin;
66     var mc = m.cos;
67     var abs = m.abs;
68     var sqrt = m.sqrt;
69
70     // this is used for sub pixel precision
71     var Z = 10;
72     var Z2 = Z / 2;
73
74     var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
75
76     /**
77     * This funtion is assigned to the <canvas> elements as element.getContext().
78     * @this {HTMLElement}
79     * @return {CanvasRenderingContext2D_}
80     */
81     function getContext() {
82       return this.context_ ||
83         (this.context_ = new CanvasRenderingContext2D_(this));
84     }
85
86     var slice = Array.prototype.slice;
87
88     /**
89     * Binds a function to an object. The returned function will always use the
90     * passed in {@code obj} as {@code this}.
91     *
92     * Example:
93     *
94     *   g = bind(f, obj, a, b)
95     *   g(c, d) // will do f.call(obj, a, b, c, d)
96     *
97     * @param {Function} f The function to bind the object to
98     * @param {Object} obj The object that should act as this when the function
99     *     is called
100     * @param {*} var_args Rest arguments that will be used as the initial
101     *     arguments when the function is called
102     * @return {Function} A new function that has bound this
103     */
104     function bind(f, obj, var_args) {
105       var a = slice.call(arguments, 2);
106       return function () {
107         return f.apply(obj, a.concat(slice.call(arguments)));
108       };
109     }
110
111     function encodeHtmlAttribute(s) {
112       return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
113     }
114
115     function addNamespace(doc, prefix, urn) {
116       if (!doc.namespaces[prefix]) {
117         doc.namespaces.add(prefix, urn, '#default#VML');
118       }
119     }
120
121     function addNamespacesAndStylesheet(doc) {
122       addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
123       addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
124
125       // Setup default CSS.  Only add one style sheet per document
126       if (!doc.styleSheets['ex_canvas_']) {
127         var ss = doc.createStyleSheet();
128         ss.owningElement.id = 'ex_canvas_';
129         ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
130         // default size is 300x150 in Gecko and Opera
131           'text-align:left;width:300px;height:150px}';
132       }
133     }
134
135     // Add namespaces and stylesheet at startup.
136     addNamespacesAndStylesheet(document);
137
138     var G_vmlCanvasManager_ = {
139       init: function (opt_doc) {
140         var doc = opt_doc || document;
141         // Create a dummy element so that IE will allow canvas elements to be
142         // recognized.
143         doc.createElement('canvas');
144         doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
145       },
146
147       init_: function (doc) {
148         // find all canvas elements
149         var els = doc.getElementsByTagName('canvas');
150         for (var i = 0; i < els.length; i++) {
151           this.initElement(els[i]);
152         }
153       },
154
155       /**
156       * Public initializes a canvas element so that it can be used as canvas
157       * element from now on. This is called automatically before the page is
158       * loaded but if you are creating elements using createElement you need to
159       * make sure this is called on the element.
160       * @param {HTMLElement} el The canvas element to initialize.
161       * @return {HTMLElement} the element that was created.
162       */
163       initElement: function (el) {
164         if (!el.getContext) {
165           el.getContext = getContext;
166
167           // Add namespaces and stylesheet to document of the element.
168           addNamespacesAndStylesheet(el.ownerDocument);
169
170           // Remove fallback content. There is no way to hide text nodes so we
171           // just remove all childNodes. We could hide all elements and remove
172           // text nodes but who really cares about the fallback content.
173           el.innerHTML = '';
174
175           // do not use inline function because that will leak memory
176           el.attachEvent('onpropertychange', onPropertyChange);
177           el.attachEvent('onresize', onResize);
178
179           var attrs = el.attributes;
180           if (attrs.width && attrs.width.specified) {
181             // TODO: use runtimeStyle and coordsize
182             // el.getContext().setWidth_(attrs.width.nodeValue);
183             el.style.width = attrs.width.nodeValue + 'px';
184           } else {
185             el.width = el.clientWidth;
186           }
187           if (attrs.height && attrs.height.specified) {
188             // TODO: use runtimeStyle and coordsize
189             // el.getContext().setHeight_(attrs.height.nodeValue);
190             el.style.height = attrs.height.nodeValue + 'px';
191           } else {
192             el.height = el.clientHeight;
193           }
194           //el.getContext().setCoordsize_()
195         }
196         return el;
197       }
198     };
199
200     function onPropertyChange(e) {
201       var el = e.srcElement;
202
203       switch (e.propertyName) {
204         case 'width':
205           el.getContext().clearRect();
206           el.style.width = el.attributes.width.nodeValue + 'px';
207           // In IE8 this does not trigger onresize.
208           el.firstChild.style.width = el.clientWidth + 'px';
209           break;
210         case 'height':
211           el.getContext().clearRect();
212           el.style.height = el.attributes.height.nodeValue + 'px';
213           el.firstChild.style.height = el.clientHeight + 'px';
214           break;
215       }
216     }
217
218     function onResize(e) {
219       var el = e.srcElement;
220       if (el.firstChild) {
221         el.firstChild.style.width = el.clientWidth + 'px';
222         el.firstChild.style.height = el.clientHeight + 'px';
223       }
224     }
225
226     G_vmlCanvasManager_.init();
227
228     // precompute "00" to "FF"
229     var decToHex = [];
230     for (var i = 0; i < 16; i++) {
231       for (var j = 0; j < 16; j++) {
232         decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
233       }
234     }
235
236     function createMatrixIdentity() {
237       return [
238       [1, 0, 0],
239       [0, 1, 0],
240       [0, 0, 1]
241     ];
242     }
243
244     function matrixMultiply(m1, m2) {
245       var result = createMatrixIdentity();
246
247       for (var x = 0; x < 3; x++) {
248         for (var y = 0; y < 3; y++) {
249           var sum = 0;
250
251           for (var z = 0; z < 3; z++) {
252             sum += m1[x][z] * m2[z][y];
253           }
254
255           result[x][y] = sum;
256         }
257       }
258       return result;
259     }
260
261     function copyState(o1, o2) {
262       o2.fillStyle     = o1.fillStyle;
263       o2.lineCap       = o1.lineCap;
264       o2.lineJoin      = o1.lineJoin;
265       o2.lineWidth     = o1.lineWidth;
266       o2.miterLimit    = o1.miterLimit;
267       o2.shadowBlur    = o1.shadowBlur;
268       o2.shadowColor   = o1.shadowColor;
269       o2.shadowOffsetX = o1.shadowOffsetX;
270       o2.shadowOffsetY = o1.shadowOffsetY;
271       o2.strokeStyle   = o1.strokeStyle;
272       o2.globalAlpha   = o1.globalAlpha;
273       o2.font          = o1.font;
274       o2.textAlign     = o1.textAlign;
275       o2.textBaseline  = o1.textBaseline;
276       o2.arcScaleX_    = o1.arcScaleX_;
277       o2.arcScaleY_    = o1.arcScaleY_;
278       o2.lineScale_    = o1.lineScale_;
279     }
280
281     //  var colorData = {
282     //    aliceblue: '#F0F8FF',
283     //    antiquewhite: '#FAEBD7',
284     //    aquamarine: '#7FFFD4',
285     //    azure: '#F0FFFF',
286     //    beige: '#F5F5DC',
287     //    bisque: '#FFE4C4',
288     //    black: '#000000',
289     //    blanchedalmond: '#FFEBCD',
290     //    blueviolet: '#8A2BE2',
291     //    brown: '#A52A2A',
292     //    burlywood: '#DEB887',
293     //    cadetblue: '#5F9EA0',
294     //    chartreuse: '#7FFF00',
295     //    chocolate: '#D2691E',
296     //    coral: '#FF7F50',
297     //    cornflowerblue: '#6495ED',
298     //    cornsilk: '#FFF8DC',
299     //    crimson: '#DC143C',
300     //    cyan: '#00FFFF',
301     //    darkblue: '#00008B',
302     //    darkcyan: '#008B8B',
303     //    darkgoldenrod: '#B8860B',
304     //    darkgray: '#A9A9A9',
305     //    darkgreen: '#006400',
306     //    darkgrey: '#A9A9A9',
307     //    darkkhaki: '#BDB76B',
308     //    darkmagenta: '#8B008B',
309     //    darkolivegreen: '#556B2F',
310     //    darkorange: '#FF8C00',
311     //    darkorchid: '#9932CC',
312     //    darkred: '#8B0000',
313     //    darksalmon: '#E9967A',
314     //    darkseagreen: '#8FBC8F',
315     //    darkslateblue: '#483D8B',
316     //    darkslategray: '#2F4F4F',
317     //    darkslategrey: '#2F4F4F',
318     //    darkturquoise: '#00CED1',
319     //    darkviolet: '#9400D3',
320     //    deeppink: '#FF1493',
321     //    deepskyblue: '#00BFFF',
322     //    dimgray: '#696969',
323     //    dimgrey: '#696969',
324     //    dodgerblue: '#1E90FF',
325     //    firebrick: '#B22222',
326     //    floralwhite: '#FFFAF0',
327     //    forestgreen: '#228B22',
328     //    gainsboro: '#DCDCDC',
329     //    ghostwhite: '#F8F8FF',
330     //    gold: '#FFD700',
331     //    goldenrod: '#DAA520',
332     //    grey: '#808080',
333     //    greenyellow: '#ADFF2F',
334     //    honeydew: '#F0FFF0',
335     //    hotpink: '#FF69B4',
336     //    indianred: '#CD5C5C',
337     //    indigo: '#4B0082',
338     //    ivory: '#FFFFF0',
339     //    khaki: '#F0E68C',
340     //    lavender: '#E6E6FA',
341     //    lavenderblush: '#FFF0F5',
342     //    lawngreen: '#7CFC00',
343     //    lemonchiffon: '#FFFACD',
344     //    lightblue: '#ADD8E6',
345     //    lightcoral: '#F08080',
346     //    lightcyan: '#E0FFFF',
347     //    lightgoldenrodyellow: '#FAFAD2',
348     //    lightgreen: '#90EE90',
349     //    lightgrey: '#D3D3D3',
350     //    lightpink: '#FFB6C1',
351     //    lightsalmon: '#FFA07A',
352     //    lightseagreen: '#20B2AA',
353     //    lightskyblue: '#87CEFA',
354     //    lightslategray: '#778899',
355     //    lightslategrey: '#778899',
356     //    lightsteelblue: '#B0C4DE',
357     //    lightyellow: '#FFFFE0',
358     //    limegreen: '#32CD32',
359     //    linen: '#FAF0E6',
360     //    magenta: '#FF00FF',
361     //    mediumaquamarine: '#66CDAA',
362     //    mediumblue: '#0000CD',
363     //    mediumorchid: '#BA55D3',
364     //    mediumpurple: '#9370DB',
365     //    mediumseagreen: '#3CB371',
366     //    mediumslateblue: '#7B68EE',
367     //    mediumspringgreen: '#00FA9A',
368     //    mediumturquoise: '#48D1CC',
369     //    mediumvioletred: '#C71585',
370     //    midnightblue: '#191970',
371     //    mintcream: '#F5FFFA',
372     //    mistyrose: '#FFE4E1',
373     //    moccasin: '#FFE4B5',
374     //    navajowhite: '#FFDEAD',
375     //    oldlace: '#FDF5E6',
376     //    olivedrab: '#6B8E23',
377     //    orange: '#FFA500',
378     //    orangered: '#FF4500',
379     //    orchid: '#DA70D6',
380     //    palegoldenrod: '#EEE8AA',
381     //    palegreen: '#98FB98',
382     //    paleturquoise: '#AFEEEE',
383     //    palevioletred: '#DB7093',
384     //    papayawhip: '#FFEFD5',
385     //    peachpuff: '#FFDAB9',
386     //    peru: '#CD853F',
387     //    pink: '#FFC0CB',
388     //    plum: '#DDA0DD',
389     //    powderblue: '#B0E0E6',
390     //    rosybrown: '#BC8F8F',
391     //    royalblue: '#4169E1',
392     //    saddlebrown: '#8B4513',
393     //    salmon: '#FA8072',
394     //    sandybrown: '#F4A460',
395     //    seagreen: '#2E8B57',
396     //    seashell: '#FFF5EE',
397     //    sienna: '#A0522D',
398     //    skyblue: '#87CEEB',
399     //    slateblue: '#6A5ACD',
400     //    slategray: '#708090',
401     //    slategrey: '#708090',
402     //    snow: '#FFFAFA',
403     //    springgreen: '#00FF7F',
404     //    steelblue: '#4682B4',
405     //    tan: '#D2B48C',
406     //    thistle: '#D8BFD8',
407     //    tomato: '#FF6347',
408     //    turquoise: '#40E0D0',
409     //    violet: '#EE82EE',
410     //    wheat: '#F5DEB3',
411     //    whitesmoke: '#F5F5F5',
412     //    yellowgreen: '#9ACD32'
413     //  };
414
415
416     function getRgbHslContent(styleString) {
417       var start = styleString.indexOf('(', 3);
418       var end = styleString.indexOf(')', start + 1);
419       var parts = styleString.substring(start + 1, end).split(',');
420       // add alpha if needed
421       if (parts.length != 4 || styleString.charAt(3) != 'a') {
422         parts[3] = 1;
423       }
424       return parts;
425     }
426
427     function percent(s) {
428       return parseFloat(s) / 100;
429     }
430
431     function clamp(v, min, max) {
432       return Math.min(max, Math.max(min, v));
433     }
434
435     function hslToRgb(parts) {
436       var r, g, b, h, s, l;
437       h = parseFloat(parts[0]) / 360 % 360;
438       if (h < 0)
439         h++;
440       s = clamp(percent(parts[1]), 0, 1);
441       l = clamp(percent(parts[2]), 0, 1);
442       if (s == 0) {
443         r = g = b = l; // achromatic
444       } else {
445         var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
446         var p = 2 * l - q;
447         r = hueToRgb(p, q, h + 1 / 3);
448         g = hueToRgb(p, q, h);
449         b = hueToRgb(p, q, h - 1 / 3);
450       }
451
452       return '#' + decToHex[Math.floor(r * 255)] +
453         decToHex[Math.floor(g * 255)] +
454         decToHex[Math.floor(b * 255)];
455     }
456
457     function hueToRgb(m1, m2, h) {
458       if (h < 0)
459         h++;
460       if (h > 1)
461         h--;
462
463       if (6 * h < 1)
464         return m1 + (m2 - m1) * 6 * h;
465       else if (2 * h < 1)
466         return m2;
467       else if (3 * h < 2)
468         return m1 + (m2 - m1) * (2 / 3 - h) * 6;
469       else
470         return m1;
471     }
472
473     var processStyleCache = {};
474
475     function processStyle(styleString) {
476       if (styleString in processStyleCache) {
477         return processStyleCache[styleString];
478       }
479
480       var str, alpha = 1;
481
482       styleString = String(styleString);
483       if (styleString.charAt(0) == '#') {
484         str = styleString;
485       } else if (/^rgb/.test(styleString)) {
486         var parts = getRgbHslContent(styleString);
487         var str = '#', n;
488         for (var i = 0; i < 3; i++) {
489           if (parts[i].indexOf('%') != -1) {
490             n = Math.floor(percent(parts[i]) * 255);
491           } else {
492             n = +parts[i];
493           }
494           str += decToHex[clamp(n, 0, 255)];
495         }
496         alpha = +parts[3];
497       } else if (/^hsl/.test(styleString)) {
498         var parts = getRgbHslContent(styleString);
499         str = hslToRgb(parts);
500         alpha = parts[3];
501       } else {
502         str = /*colorData[styleString] ||*/styleString;
503       }
504       return processStyleCache[styleString] = { color: str, alpha: alpha };
505     }
506
507     var DEFAULT_STYLE = {
508       style: 'normal',
509       variant: 'normal',
510       weight: 'normal',
511       size: 10,
512       family: 'sans-serif'
513     };
514
515     // Internal text style cache
516     //  var fontStyleCache = {};
517
518     //  function processFontStyle(styleString) {
519     //    if (fontStyleCache[styleString]) {
520     //      return fontStyleCache[styleString];
521     //    }
522
523     //    var el = document.createElement('div');
524     //    var style = el.style;
525     //    try {
526     //      style.font = styleString;
527     //    } catch (ex) {
528     //      // Ignore failures to set to invalid font.
529     //    }
530
531     //    return fontStyleCache[styleString] = {
532     //      style: style.fontStyle || DEFAULT_STYLE.style,
533     //      variant: style.fontVariant || DEFAULT_STYLE.variant,
534     //      weight: style.fontWeight || DEFAULT_STYLE.weight,
535     //      size: style.fontSize || DEFAULT_STYLE.size,
536     //      family: style.fontFamily || DEFAULT_STYLE.family
537     //    };
538     //  }
539
540     //  function getComputedStyle(style, element) {
541     //    var computedStyle = {};
542
543     //    for (var p in style) {
544     //      computedStyle[p] = style[p];
545     //    }
546
547     //    // Compute the size
548     //    var canvasFontSize = parseFloat(element.currentStyle.fontSize),
549     //        fontSize = parseFloat(style.size);
550
551     //    if (typeof style.size == 'number') {
552     //      computedStyle.size = style.size;
553     //    } else if (style.size.indexOf('px') != -1) {
554     //      computedStyle.size = fontSize;
555     //    } else if (style.size.indexOf('em') != -1) {
556     //      computedStyle.size = canvasFontSize * fontSize;
557     //    } else if(style.size.indexOf('%') != -1) {
558     //      computedStyle.size = (canvasFontSize / 100) * fontSize;
559     //    } else if (style.size.indexOf('pt') != -1) {
560     //      computedStyle.size = fontSize / .75;
561     //    } else {
562     //      computedStyle.size = canvasFontSize;
563     //    }
564
565     //    // Different scaling between normal text and VML text. This was found using
566     //    // trial and error to get the same size as non VML text.
567     //    computedStyle.size *= 0.981;
568
569     //    return computedStyle;
570     //  }
571
572     //  function buildStyle(style) {
573     //    return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
574     //        style.size + 'px ' + style.family;
575     //  }
576
577     var lineCapMap = {
578       'butt': 'flat',
579       'round': 'round'
580     };
581
582     function processLineCap(lineCap) {
583       return lineCapMap[lineCap] || 'square';
584     }
585
586     /**
587     * This class implements CanvasRenderingContext2D interface as described by
588     * the WHATWG.
589     * @param {HTMLElement} canvasElement The element that the 2D context should
590     * be associated with
591     */
592     function CanvasRenderingContext2D_(canvasElement) {
593       this.m_ = createMatrixIdentity();
594
595       this.mStack_ = [];
596       this.aStack_ = [];
597       this.currentPath_ = [];
598
599       // Canvas context properties
600       this.strokeStyle = '#000';
601       this.fillStyle = '#000';
602
603       this.lineWidth = 1;
604       this.lineJoin = 'miter';
605       this.lineCap = 'butt';
606       this.miterLimit = Z * 1;
607       this.globalAlpha = 1;
608       //this.font = '10px sans-serif';
609       //this.textAlign = 'left';
610       //this.textBaseline = 'alphabetic';
611       this.canvas = canvasElement;
612
613       var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' +
614         canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
615       var el = canvasElement.ownerDocument.createElement('div');
616       el.style.cssText = cssText;
617       canvasElement.appendChild(el);
618
619       var overlayEl = el.cloneNode(false);
620       // Use a non transparent background.
621       overlayEl.style.backgroundColor = 'red';
622       overlayEl.style.filter = 'alpha(opacity=0)';
623       canvasElement.appendChild(overlayEl);
624
625       this.element_ = el;
626       this.arcScaleX_ = 1;
627       this.arcScaleY_ = 1;
628       this.lineScale_ = 1;
629     }
630
631     var contextPrototype = CanvasRenderingContext2D_.prototype;
632     contextPrototype.clearRect = function () {
633       if (this.textMeasureEl_) {
634         this.textMeasureEl_.removeNode(true);
635         this.textMeasureEl_ = null;
636       }
637       this.element_.innerHTML = '';
638     };
639
640     contextPrototype.beginPath = function () {
641       // TODO: Branch current matrix so that save/restore has no effect
642       //       as per safari docs.
643       this.currentPath_ = [];
644     };
645
646     contextPrototype.moveTo = function (aX, aY) {
647       var p = getCoords(this, aX, aY);
648       this.currentPath_.push({ type: 'moveTo', x: p.x, y: p.y });
649       this.currentX_ = p.x;
650       this.currentY_ = p.y;
651     };
652
653     contextPrototype.lineTo = function (aX, aY) {
654       var p = getCoords(this, aX, aY);
655       this.currentPath_.push({ type: 'lineTo', x: p.x, y: p.y });
656
657       this.currentX_ = p.x;
658       this.currentY_ = p.y;
659     };
660
661     contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
662                                               aCP2x, aCP2y,
663                                               aX, aY) {
664       var p = getCoords(this, aX, aY);
665       var cp1 = getCoords(this, aCP1x, aCP1y);
666       var cp2 = getCoords(this, aCP2x, aCP2y);
667       bezierCurveTo(this, cp1, cp2, p);
668     };
669
670     // Helper function that takes the already fixed cordinates.
671     function bezierCurveTo(self, cp1, cp2, p) {
672       self.currentPath_.push({
673         type: 'bezierCurveTo',
674         cp1x: cp1.x,
675         cp1y: cp1.y,
676         cp2x: cp2.x,
677         cp2y: cp2.y,
678         x: p.x,
679         y: p.y
680       });
681       self.currentX_ = p.x;
682       self.currentY_ = p.y;
683     }
684
685     contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
686       // the following is lifted almost directly from
687       // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
688
689       var cp = getCoords(this, aCPx, aCPy);
690       var p = getCoords(this, aX, aY);
691
692       var cp1 = {
693         x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
694         y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
695       };
696       var cp2 = {
697         x: cp1.x + (p.x - this.currentX_) / 3.0,
698         y: cp1.y + (p.y - this.currentY_) / 3.0
699       };
700
701       bezierCurveTo(this, cp1, cp2, p);
702     };
703
704     contextPrototype.arc = function (aX, aY, aRadius,
705                                   aStartAngle, aEndAngle, aClockwise) {
706       aRadius *= Z;
707       var arcType = aClockwise ? 'at' : 'wa';
708
709       var xStart = aX + mc(aStartAngle) * aRadius - Z2;
710       var yStart = aY + ms(aStartAngle) * aRadius - Z2;
711
712       var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
713       var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
714
715       // IE won't render arches drawn counter clockwise if xStart == xEnd.
716       if (xStart == xEnd && !aClockwise) {
717         xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
718         // that can be represented in binary
719       }
720
721       var p = getCoords(this, aX, aY);
722       var pStart = getCoords(this, xStart, yStart);
723       var pEnd = getCoords(this, xEnd, yEnd);
724
725       this.currentPath_.push({ type: arcType,
726         x: p.x,
727         y: p.y,
728         radius: aRadius,
729         xStart: pStart.x,
730         yStart: pStart.y,
731         xEnd: pEnd.x,
732         yEnd: pEnd.y
733       });
734
735     };
736
737     //  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
738     //    this.moveTo(aX, aY);
739     //    this.lineTo(aX + aWidth, aY);
740     //    this.lineTo(aX + aWidth, aY + aHeight);
741     //    this.lineTo(aX, aY + aHeight);
742     //    this.closePath();
743     //  };
744
745     //  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
746     //    var oldPath = this.currentPath_;
747     //    this.beginPath();
748
749     //    this.moveTo(aX, aY);
750     //    this.lineTo(aX + aWidth, aY);
751     //    this.lineTo(aX + aWidth, aY + aHeight);
752     //    this.lineTo(aX, aY + aHeight);
753     //    this.closePath();
754     //    this.stroke();
755
756     //    this.currentPath_ = oldPath;
757     //  };
758
759     //  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
760     //    var oldPath = this.currentPath_;
761     //    this.beginPath();
762
763     //    this.moveTo(aX, aY);
764     //    this.lineTo(aX + aWidth, aY);
765     //    this.lineTo(aX + aWidth, aY + aHeight);
766     //    this.lineTo(aX, aY + aHeight);
767     //    this.closePath();
768     //    this.fill();
769
770     //    this.currentPath_ = oldPath;
771     //  };
772
773     //  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
774     //    var gradient = new CanvasGradient_('gradient');
775     //    gradient.x0_ = aX0;
776     //    gradient.y0_ = aY0;
777     //    gradient.x1_ = aX1;
778     //    gradient.y1_ = aY1;
779     //    return gradient;
780     //  };
781
782     //  contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
783     //                                                   aX1, aY1, aR1) {
784     //    var gradient = new CanvasGradient_('gradientradial');
785     //    gradient.x0_ = aX0;
786     //    gradient.y0_ = aY0;
787     //    gradient.r0_ = aR0;
788     //    gradient.x1_ = aX1;
789     //    gradient.y1_ = aY1;
790     //    gradient.r1_ = aR1;
791     //    return gradient;
792     //  };
793
794     //  contextPrototype.drawImage = function(image, var_args) {
795     //    var dx, dy, dw, dh, sx, sy, sw, sh;
796
797     //    // to find the original width we overide the width and height
798     //    var oldRuntimeWidth = image.runtimeStyle.width;
799     //    var oldRuntimeHeight = image.runtimeStyle.height;
800     //    image.runtimeStyle.width = 'auto';
801     //    image.runtimeStyle.height = 'auto';
802
803     //    // get the original size
804     //    var w = image.width;
805     //    var h = image.height;
806
807     //    // and remove overides
808     //    image.runtimeStyle.width = oldRuntimeWidth;
809     //    image.runtimeStyle.height = oldRuntimeHeight;
810
811     //    if (arguments.length == 3) {
812     //      dx = arguments[1];
813     //      dy = arguments[2];
814     //      sx = sy = 0;
815     //      sw = dw = w;
816     //      sh = dh = h;
817     //    } else if (arguments.length == 5) {
818     //      dx = arguments[1];
819     //      dy = arguments[2];
820     //      dw = arguments[3];
821     //      dh = arguments[4];
822     //      sx = sy = 0;
823     //      sw = w;
824     //      sh = h;
825     //    } else if (arguments.length == 9) {
826     //      sx = arguments[1];
827     //      sy = arguments[2];
828     //      sw = arguments[3];
829     //      sh = arguments[4];
830     //      dx = arguments[5];
831     //      dy = arguments[6];
832     //      dw = arguments[7];
833     //      dh = arguments[8];
834     //    } else {
835     //      throw Error('Invalid number of arguments');
836     //    }
837
838     //    var d = getCoords(this, dx, dy);
839
840     //    var w2 = sw / 2;
841     //    var h2 = sh / 2;
842
843     //    var vmlStr = [];
844
845     //    var W = 10;
846     //    var H = 10;
847
848     //    // For some reason that I've now forgotten, using divs didn't work
849     //    vmlStr.push(' <g_vml_:group',
850     //                ' coordsize="', Z * W, ',', Z * H, '"',
851     //                ' coordorigin="0,0"' ,
852     //                ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
853
854     //    // If filters are necessary (rotation exists), create them
855     //    // filters are bog-slow, so only create them if abbsolutely necessary
856     //    // The following check doesn't account for skews (which don't exist
857     //    // in the canvas spec (yet) anyway.
858
859     //    if (this.m_[0][0] != 1 || this.m_[0][1] ||
860     //        this.m_[1][1] != 1 || this.m_[1][0]) {
861     //      var filter = [];
862
863     //      // Note the 12/21 reversal
864     //      filter.push('M11=', this.m_[0][0], ',',
865     //                  'M12=', this.m_[1][0], ',',
866     //                  'M21=', this.m_[0][1], ',',
867     //                  'M22=', this.m_[1][1], ',',
868     //                  'Dx=', mr(d.x / Z), ',',
869     //                  'Dy=', mr(d.y / Z), '');
870
871     //      // Bounding box calculation (need to minimize displayed area so that
872     //      // filters don't waste time on unused pixels.
873     //      var max = d;
874     //      var c2 = getCoords(this, dx + dw, dy);
875     //      var c3 = getCoords(this, dx, dy + dh);
876     //      var c4 = getCoords(this, dx + dw, dy + dh);
877
878     //      max.x = m.max(max.x, c2.x, c3.x, c4.x);
879     //      max.y = m.max(max.y, c2.y, c3.y, c4.y);
880
881     //      vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
882     //                  'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
883     //                  filter.join(''), ", sizingmethod='clip');");
884
885     //    } else {
886     //      vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
887     //    }
888
889     //    vmlStr.push(' ">' ,
890     //                '<g_vml_:image src="', image.src, '"',
891     //                ' style="width:', Z * dw, 'px;',
892     //                ' height:', Z * dh, 'px"',
893     //                ' cropleft="', sx / w, '"',
894     //                ' croptop="', sy / h, '"',
895     //                ' cropright="', (w - sx - sw) / w, '"',
896     //                ' cropbottom="', (h - sy - sh) / h, '"',
897     //                ' />',
898     //                '</g_vml_:group>');
899
900     //    this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
901     //  };
902
903     contextPrototype.stroke = function (aFill) {
904       var lineStr = [];
905       var lineOpen = false;
906
907       var W = 10;
908       var H = 10;
909
910       lineStr.push('<g_vml_:shape',
911                  ' filled="', !!aFill, '"',
912                  ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
913                  ' coordorigin="0,0"',
914                  ' coordsize="', Z * W, ',', Z * H, '"',
915                  ' stroked="', !aFill, '"',
916                  ' path="');
917
918       var newSeq = false;
919       var min = { x: null, y: null };
920       var max = { x: null, y: null };
921
922       for (var i = 0; i < this.currentPath_.length; i++) {
923         var p = this.currentPath_[i];
924         var c;
925
926         switch (p.type) {
927           case 'moveTo':
928             c = p;
929             lineStr.push(' m ', mr(p.x), ',', mr(p.y));
930             break;
931           case 'lineTo':
932             lineStr.push(' l ', mr(p.x), ',', mr(p.y));
933             break;
934           case 'close':
935             lineStr.push(' x ');
936             p = null;
937             break;
938           case 'bezierCurveTo':
939             lineStr.push(' c ',
940                        mr(p.cp1x), ',', mr(p.cp1y), ',',
941                        mr(p.cp2x), ',', mr(p.cp2y), ',',
942                        mr(p.x), ',', mr(p.y));
943             break;
944           case 'at':
945           case 'wa':
946             lineStr.push(' ', p.type, ' ',
947                        mr(p.x - this.arcScaleX_ * p.radius), ',',
948                        mr(p.y - this.arcScaleY_ * p.radius), ' ',
949                        mr(p.x + this.arcScaleX_ * p.radius), ',',
950                        mr(p.y + this.arcScaleY_ * p.radius), ' ',
951                        mr(p.xStart), ',', mr(p.yStart), ' ',
952                        mr(p.xEnd), ',', mr(p.yEnd));
953             break;
954         }
955
956
957         // TODO: Following is broken for curves due to
958         //       move to proper paths.
959
960         // Figure out dimensions so we can do gradient fills
961         // properly
962         if (p) {
963           if (min.x == null || p.x < min.x) {
964             min.x = p.x;
965           }
966           if (max.x == null || p.x > max.x) {
967             max.x = p.x;
968           }
969           if (min.y == null || p.y < min.y) {
970             min.y = p.y;
971           }
972           if (max.y == null || p.y > max.y) {
973             max.y = p.y;
974           }
975         }
976       }
977       lineStr.push(' ">');
978
979       if (!aFill) {
980         appendStroke(this, lineStr);
981       } else {
982         appendFill(this, lineStr, min, max);
983       }
984
985       lineStr.push('</g_vml_:shape>');
986
987       this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
988     };
989
990     function appendStroke(ctx, lineStr) {
991       var a = processStyle(ctx.strokeStyle);
992       var color = a.color;
993       var opacity = a.alpha * ctx.globalAlpha;
994       var lineWidth = ctx.lineScale_ * ctx.lineWidth;
995
996       // VML cannot correctly render a line if the width is less than 1px.
997       // In that case, we dilute the color to make the line look thinner.
998       if (lineWidth < 1) {
999         opacity *= lineWidth;
1000       }
1001
1002       lineStr.push(
1003       '<g_vml_:stroke',
1004       ' opacity="', opacity, '"',
1005       ' joinstyle="', ctx.lineJoin, '"',
1006       ' miterlimit="', ctx.miterLimit, '"',
1007       ' endcap="', processLineCap(ctx.lineCap), '"',
1008       ' weight="', lineWidth, 'px"',
1009       ' color="', color, '" />'
1010     );
1011     }
1012
1013     function appendFill(ctx, lineStr, min, max) {
1014       var fillStyle = ctx.fillStyle;
1015       var arcScaleX = ctx.arcScaleX_;
1016       var arcScaleY = ctx.arcScaleY_;
1017       var width = max.x - min.x;
1018       var height = max.y - min.y;
1019       //    if (fillStyle instanceof CanvasGradient_) {
1020       //      // TODO: Gradients transformed with the transformation matrix.
1021       //      var angle = 0;
1022       //      var focus = {x: 0, y: 0};
1023
1024       //      // additional offset
1025       //      var shift = 0;
1026       //      // scale factor for offset
1027       //      var expansion = 1;
1028
1029       //      if (fillStyle.type_ == 'gradient') {
1030       //        var x0 = fillStyle.x0_ / arcScaleX;
1031       //        var y0 = fillStyle.y0_ / arcScaleY;
1032       //        var x1 = fillStyle.x1_ / arcScaleX;
1033       //        var y1 = fillStyle.y1_ / arcScaleY;
1034       //        var p0 = getCoords(ctx, x0, y0);
1035       //        var p1 = getCoords(ctx, x1, y1);
1036       //        var dx = p1.x - p0.x;
1037       //        var dy = p1.y - p0.y;
1038       //        angle = Math.atan2(dx, dy) * 180 / Math.PI;
1039
1040       //        // The angle should be a non-negative number.
1041       //        if (angle < 0) {
1042       //          angle += 360;
1043       //        }
1044
1045       //        // Very small angles produce an unexpected result because they are
1046       //        // converted to a scientific notation string.
1047       //        if (angle < 1e-6) {
1048       //          angle = 0;
1049       //        }
1050       //      } else {
1051       //        var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
1052       //        focus = {
1053       //          x: (p0.x - min.x) / width,
1054       //          y: (p0.y - min.y) / height
1055       //        };
1056
1057       //        width  /= arcScaleX * Z;
1058       //        height /= arcScaleY * Z;
1059       //        var dimension = m.max(width, height);
1060       //        shift = 2 * fillStyle.r0_ / dimension;
1061       //        expansion = 2 * fillStyle.r1_ / dimension - shift;
1062       //      }
1063
1064       //      // We need to sort the color stops in ascending order by offset,
1065       //      // otherwise IE won't interpret it correctly.
1066       //      var stops = fillStyle.colors_;
1067       //      stops.sort(function(cs1, cs2) {
1068       //        return cs1.offset - cs2.offset;
1069       //      });
1070
1071       //      var length = stops.length;
1072       //      var color1 = stops[0].color;
1073       //      var color2 = stops[length - 1].color;
1074       //      var opacity1 = stops[0].alpha * ctx.globalAlpha;
1075       //      var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
1076
1077       //      var colors = [];
1078       //      for (var i = 0; i < length; i++) {
1079       //        var stop = stops[i];
1080       //        colors.push(stop.offset * expansion + shift + ' ' + stop.color);
1081       //      }
1082
1083       //      // When colors attribute is used, the meanings of opacity and o:opacity2
1084       //      // are reversed.
1085       //      lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
1086       //                   ' method="none" focus="100%"',
1087       //                   ' color="', color1, '"',
1088       //                   ' color2="', color2, '"',
1089       //                   ' colors="', colors.join(','), '"',
1090       //                   ' opacity="', opacity2, '"',
1091       //                   ' g_o_:opacity2="', opacity1, '"',
1092       //                   ' angle="', angle, '"',
1093       //                   ' focusposition="', focus.x, ',', focus.y, '" />');
1094       //    } else if (fillStyle instanceof CanvasPattern_) {
1095       //      if (width && height) {
1096       //        var deltaLeft = -min.x;
1097       //        var deltaTop = -min.y;
1098       //        lineStr.push('<g_vml_:fill',
1099       //                     ' position="',
1100       //                     deltaLeft / width * arcScaleX * arcScaleX, ',',
1101       //                     deltaTop / height * arcScaleY * arcScaleY, '"',
1102       //                     ' type="tile"',
1103       //                     // TODO: Figure out the correct size to fit the scale.
1104       //                     //' size="', w, 'px ', h, 'px"',
1105       //                     ' src="', fillStyle.src_, '" />');
1106       //       }
1107       //    } else {
1108       var a = processStyle(ctx.fillStyle);
1109       var color = a.color;
1110       var opacity = a.alpha * ctx.globalAlpha;
1111       lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
1112                    '" />');
1113       //     }
1114     }
1115
1116     contextPrototype.fill = function () {
1117       this.stroke(true);
1118     };
1119
1120     contextPrototype.closePath = function () {
1121       this.currentPath_.push({ type: 'close' });
1122     };
1123
1124     function getCoords(ctx, aX, aY) {
1125       var m = ctx.m_;
1126       return {
1127         x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
1128         y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
1129       };
1130     };
1131
1132     contextPrototype.save = function () {
1133       var o = {};
1134       copyState(this, o);
1135       this.aStack_.push(o);
1136       this.mStack_.push(this.m_);
1137       this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
1138     };
1139
1140     contextPrototype.restore = function () {
1141       if (this.aStack_.length) {
1142         copyState(this.aStack_.pop(), this);
1143         this.m_ = this.mStack_.pop();
1144       }
1145     };
1146
1147     function matrixIsFinite(m) {
1148       return isFinite(m[0][0]) && isFinite(m[0][1]) &&
1149         isFinite(m[1][0]) && isFinite(m[1][1]) &&
1150         isFinite(m[2][0]) && isFinite(m[2][1]);
1151     }
1152
1153     function setM(ctx, m, updateLineScale) {
1154       if (!matrixIsFinite(m)) {
1155         return;
1156       }
1157       ctx.m_ = m;
1158
1159       if (updateLineScale) {
1160         // Get the line scale.
1161         // Determinant of this.m_ means how much the area is enlarged by the
1162         // transformation. So its square root can be used as a scale factor
1163         // for width.
1164         var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
1165         ctx.lineScale_ = sqrt(abs(det));
1166       }
1167     }
1168
1169     contextPrototype.translate = function (aX, aY) {
1170       var m1 = [
1171       [1, 0, 0],
1172       [0, 1, 0],
1173       [aX, aY, 1]
1174     ];
1175
1176       setM(this, matrixMultiply(m1, this.m_), false);
1177     };
1178
1179     //  contextPrototype.rotate = function(aRot) {
1180     //    var c = mc(aRot);
1181     //    var s = ms(aRot);
1182
1183     //    var m1 = [
1184     //      [c,  s, 0],
1185     //      [-s, c, 0],
1186     //      [0,  0, 1]
1187     //    ];
1188
1189     //    setM(this, matrixMultiply(m1, this.m_), false);
1190     //  };
1191
1192     contextPrototype.scale = function (aX, aY) {
1193       this.arcScaleX_ *= aX;
1194       this.arcScaleY_ *= aY;
1195       var m1 = [
1196       [aX, 0, 0],
1197       [0, aY, 0],
1198       [0, 0, 1]
1199     ];
1200
1201       setM(this, matrixMultiply(m1, this.m_), true);
1202     };
1203
1204     //  contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
1205     //    var m1 = [
1206     //      [m11, m12, 0],
1207     //      [m21, m22, 0],
1208     //      [dx,  dy,  1]
1209     //    ];
1210
1211     //    setM(this, matrixMultiply(m1, this.m_), true);
1212     //  };
1213
1214     //  contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
1215     //    var m = [
1216     //      [m11, m12, 0],
1217     //      [m21, m22, 0],
1218     //      [dx,  dy,  1]
1219     //    ];
1220
1221     //    setM(this, m, true);
1222     //  };
1223
1224     /**
1225     * The text drawing function.
1226     * The maxWidth argument isn't taken in account, since no browser supports
1227     * it yet.
1228     */
1229     //  contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
1230     //    var m = this.m_,
1231     //        delta = 1000,
1232     //        left = 0,
1233     //        right = delta,
1234     //        offset = {x: 0, y: 0},
1235     //        lineStr = [];
1236
1237     //    var fontStyle = getComputedStyle(processFontStyle(this.font),
1238     //                                     this.element_);
1239
1240     //    var fontStyleString = buildStyle(fontStyle);
1241
1242     //    var elementStyle = this.element_.currentStyle;
1243     //    var textAlign = this.textAlign.toLowerCase();
1244     //    switch (textAlign) {
1245     //      case 'left':
1246     //      case 'center':
1247     //      case 'right':
1248     //        break;
1249     //      case 'end':
1250     //        textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
1251     //        break;
1252     //      case 'start':
1253     //        textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
1254     //        break;
1255     //      default:
1256     //        textAlign = 'left';
1257     //    }
1258
1259     //    // 1.75 is an arbitrary number, as there is no info about the text baseline
1260     //    switch (this.textBaseline) {
1261     //      case 'hanging':
1262     //      case 'top':
1263     //        offset.y = fontStyle.size / 1.75;
1264     //        break;
1265     //      case 'middle':
1266     //        break;
1267     //      default:
1268     //      case null:
1269     //      case 'alphabetic':
1270     //      case 'ideographic':
1271     //      case 'bottom':
1272     //        offset.y = -fontStyle.size / 2.25;
1273     //        break;
1274     //    }
1275
1276     //    switch(textAlign) {
1277     //      case 'right':
1278     //        left = delta;
1279     //        right = 0.05;
1280     //        break;
1281     //      case 'center':
1282     //        left = right = delta / 2;
1283     //        break;
1284     //    }
1285
1286     //    var d = getCoords(this, x + offset.x, y + offset.y);
1287
1288     //    lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ',
1289     //                 ' coordsize="100 100" coordorigin="0 0"',
1290     //                 ' filled="', !stroke, '" stroked="', !!stroke,
1291     //                 '" style="position:absolute;width:1px;height:1px;">');
1292
1293     //    if (stroke) {
1294     //      appendStroke(this, lineStr);
1295     //    } else {
1296     //      // TODO: Fix the min and max params.
1297     //      appendFill(this, lineStr, {x: -left, y: 0},
1298     //                 {x: right, y: fontStyle.size});
1299     //    }
1300
1301     //    var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
1302     //                m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
1303
1304     //    var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
1305
1306     //    lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ',
1307     //                 ' offset="', skewOffset, '" origin="', left ,' 0" />',
1308     //                 '<g_vml_:path textpathok="true" />',
1309     //                 '<g_vml_:textpath on="true" string="',
1310     //                 encodeHtmlAttribute(text),
1311     //                 '" style="v-text-align:', textAlign,
1312     //                 ';font:', encodeHtmlAttribute(fontStyleString),
1313     //                 '" /></g_vml_:line>');
1314
1315     //    this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
1316     //  };
1317
1318     //  contextPrototype.fillText = function(text, x, y, maxWidth) {
1319     //    this.drawText_(text, x, y, maxWidth, false);
1320     //  };
1321
1322     //  contextPrototype.strokeText = function(text, x, y, maxWidth) {
1323     //    this.drawText_(text, x, y, maxWidth, true);
1324     //  };
1325
1326     //  contextPrototype.measureText = function(text) {
1327     //    if (!this.textMeasureEl_) {
1328     //      var s = '<span style="position:absolute;' +
1329     //          'top:-20000px;left:0;padding:0;margin:0;border:none;' +
1330     //          'white-space:pre;"></span>';
1331     //      this.element_.insertAdjacentHTML('beforeEnd', s);
1332     //      this.textMeasureEl_ = this.element_.lastChild;
1333     //    }
1334     //    var doc = this.element_.ownerDocument;
1335     //    this.textMeasureEl_.innerHTML = '';
1336     //    this.textMeasureEl_.style.font = this.font;
1337     //    // Don't use innerHTML or innerText because they allow markup/whitespace.
1338     //    this.textMeasureEl_.appendChild(doc.createTextNode(text));
1339     //    return {width: this.textMeasureEl_.offsetWidth};
1340     //  };
1341
1342     /******** STUBS ********/
1343     //  contextPrototype.clip = function() {
1344     //    // TODO: Implement
1345     //  };
1346
1347     //  contextPrototype.arcTo = function() {
1348     //    // TODO: Implement
1349     //  };
1350
1351     //  contextPrototype.createPattern = function(image, repetition) {
1352     //    return new CanvasPattern_(image, repetition);
1353     //  };
1354
1355     //  // Gradient / Pattern Stubs
1356     //  function CanvasGradient_(aType) {
1357     //    this.type_ = aType;
1358     //    this.x0_ = 0;
1359     //    this.y0_ = 0;
1360     //    this.r0_ = 0;
1361     //    this.x1_ = 0;
1362     //    this.y1_ = 0;
1363     //    this.r1_ = 0;
1364     //    this.colors_ = [];
1365     //  }
1366
1367     //  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
1368     //    aColor = processStyle(aColor);
1369     //    this.colors_.push({offset: aOffset,
1370     //                       color: aColor.color,
1371     //                       alpha: aColor.alpha});
1372     //  };
1373
1374     //  function CanvasPattern_(image, repetition) {
1375     //    assertImageIsValid(image);
1376     //    switch (repetition) {
1377     //      case 'repeat':
1378     //      case null:
1379     //      case '':
1380     //        this.repetition_ = 'repeat';
1381     //        break
1382     //      case 'repeat-x':
1383     //      case 'repeat-y':
1384     //      case 'no-repeat':
1385     //        this.repetition_ = repetition;
1386     //        break;
1387     //      default:
1388     //        throwException('SYNTAX_ERR');
1389     //    }
1390
1391     //    this.src_ = image.src;
1392     //    this.width_ = image.width;
1393     //    this.height_ = image.height;
1394     //  }
1395
1396     function throwException(s) {
1397       throw new DOMException_(s);
1398     }
1399
1400     //  function assertImageIsValid(img) {
1401     //    if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
1402     //      throwException('TYPE_MISMATCH_ERR');
1403     //    }
1404     //    if (img.readyState != 'complete') {
1405     //      throwException('INVALID_STATE_ERR');
1406     //    }
1407     //  }
1408
1409     function DOMException_(s) {
1410       this.code = this[s];
1411       this.message = s + ': DOM Exception ' + this.code;
1412     }
1413     var p = DOMException_.prototype = new Error;
1414     p.INDEX_SIZE_ERR = 1;
1415     p.DOMSTRING_SIZE_ERR = 2;
1416     p.HIERARCHY_REQUEST_ERR = 3;
1417     p.WRONG_DOCUMENT_ERR = 4;
1418     p.INVALID_CHARACTER_ERR = 5;
1419     p.NO_DATA_ALLOWED_ERR = 6;
1420     p.NO_MODIFICATION_ALLOWED_ERR = 7;
1421     p.NOT_FOUND_ERR = 8;
1422     p.NOT_SUPPORTED_ERR = 9;
1423     p.INUSE_ATTRIBUTE_ERR = 10;
1424     p.INVALID_STATE_ERR = 11;
1425     p.SYNTAX_ERR = 12;
1426     p.INVALID_MODIFICATION_ERR = 13;
1427     p.NAMESPACE_ERR = 14;
1428     p.INVALID_ACCESS_ERR = 15;
1429     p.VALIDATION_ERR = 16;
1430     p.TYPE_MISMATCH_ERR = 17;
1431
1432     // set up externs
1433     G_vmlCanvasManager = G_vmlCanvasManager_;
1434     CanvasRenderingContext2D = CanvasRenderingContext2D_;
1435     //CanvasGradient = CanvasGradient_;
1436     //CanvasPattern = CanvasPattern_;
1437     DOMException = DOMException_;
1438   })();
1439
1440 } // if
1441 /*!
1442  * jQuery UI Widget @VERSION
1443  *
1444  * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
1445  * Dual licensed under the MIT or GPL Version 2 licenses.
1446  * http://jquery.org/license
1447  *
1448  * http://docs.jquery.com/UI/Widget
1449  */
1450
1451 if ( ! $.widget ) {
1452
1453 (function( $, undefined ) {
1454
1455 // jQuery 1.4+
1456 if ( $.cleanData ) {
1457         var _cleanData = $.cleanData;
1458         $.cleanData = function( elems ) {
1459                 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
1460                         try {
1461                                 $( elem ).triggerHandler( "remove" );
1462                         // http://bugs.jquery.com/ticket/8235
1463                         } catch( e ) {}
1464                 }
1465                 _cleanData( elems );
1466         };
1467 } else {
1468         var _remove = $.fn.remove;
1469         $.fn.remove = function( selector, keepData ) {
1470                 return this.each(function() {
1471                         if ( !keepData ) {
1472                                 if ( !selector || $.filter( selector, [ this ] ).length ) {
1473                                         $( "*", this ).add( [ this ] ).each(function() {
1474                                                 try {
1475                                                         $( this ).triggerHandler( "remove" );
1476                                                 // http://bugs.jquery.com/ticket/8235
1477                                                 } catch( e ) {}
1478                                         });
1479                                 }
1480                         }
1481                         return _remove.call( $(this), selector, keepData );
1482                 });
1483         };
1484 }
1485
1486 $.widget = function( name, base, prototype ) {
1487         var namespace = name.split( "." )[ 0 ],
1488                 fullName;
1489         name = name.split( "." )[ 1 ];
1490         fullName = namespace + "-" + name;
1491
1492         if ( !prototype ) {
1493                 prototype = base;
1494                 base = $.Widget;
1495         }
1496
1497         // create selector for plugin
1498         $.expr[ ":" ][ fullName ] = function( elem ) {
1499                 return !!$.data( elem, name );
1500         };
1501
1502         $[ namespace ] = $[ namespace ] || {};
1503         $[ namespace ][ name ] = function( options, element ) {
1504                 // allow instantiation without initializing for simple inheritance
1505                 if ( arguments.length ) {
1506                         this._createWidget( options, element );
1507                 }
1508         };
1509
1510         var basePrototype = new base();
1511         // we need to make the options hash a property directly on the new instance
1512         // otherwise we'll modify the options hash on the prototype that we're
1513         // inheriting from
1514 //      $.each( basePrototype, function( key, val ) {
1515 //              if ( $.isPlainObject(val) ) {
1516 //                      basePrototype[ key ] = $.extend( {}, val );
1517 //              }
1518 //      });
1519         basePrototype.options = $.extend( true, {}, basePrototype.options );
1520         $[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
1521                 namespace: namespace,
1522                 widgetName: name,
1523                 widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
1524                 widgetBaseClass: fullName
1525         }, prototype );
1526
1527         $.widget.bridge( name, $[ namespace ][ name ] );
1528 };
1529
1530 $.widget.bridge = function( name, object ) {
1531         $.fn[ name ] = function( options ) {
1532                 var isMethodCall = typeof options === "string",
1533                         args = Array.prototype.slice.call( arguments, 1 ),
1534                         returnValue = this;
1535
1536                 // allow multiple hashes to be passed on init
1537                 options = !isMethodCall && args.length ?
1538                         $.extend.apply( null, [ true, options ].concat(args) ) :
1539                         options;
1540
1541                 // prevent calls to internal methods
1542                 if ( isMethodCall && options.charAt( 0 ) === "_" ) {
1543                         return returnValue;
1544                 }
1545
1546                 if ( isMethodCall ) {
1547                         this.each(function() {
1548                                 var instance = $.data( this, name ),
1549                                         methodValue = instance && $.isFunction( instance[options] ) ?
1550                                                 instance[ options ].apply( instance, args ) :
1551                                                 instance;
1552                                 // TODO: add this back in 1.9 and use $.error() (see #5972)
1553 //                              if ( !instance ) {
1554 //                                      throw "cannot call methods on " + name + " prior to initialization; " +
1555 //                                              "attempted to call method '" + options + "'";
1556 //                              }
1557 //                              if ( !$.isFunction( instance[options] ) ) {
1558 //                                      throw "no such method '" + options + "' for " + name + " widget instance";
1559 //                              }
1560 //                              var methodValue = instance[ options ].apply( instance, args );
1561                                 if ( methodValue !== instance && methodValue !== undefined ) {
1562                                         returnValue = methodValue;
1563                                         return false;
1564                                 }
1565                         });
1566                 } else {
1567                         this.each(function() {
1568                                 var instance = $.data( this, name );
1569                                 if ( instance ) {
1570                                         instance.option( options || {} )._init();
1571                                 } else {
1572                                         $.data( this, name, new object( options, this ) );
1573                                 }
1574                         });
1575                 }
1576
1577                 return returnValue;
1578         };
1579 };
1580
1581 $.Widget = function( options, element ) {
1582         // allow instantiation without initializing for simple inheritance
1583         if ( arguments.length ) {
1584                 this._createWidget( options, element );
1585         }
1586 };
1587
1588 $.Widget.prototype = {
1589         widgetName: "widget",
1590         widgetEventPrefix: "",
1591         options: {
1592                 disabled: false
1593         },
1594         _createWidget: function( options, element ) {
1595                 // $.widget.bridge stores the plugin instance, but we do it anyway
1596                 // so that it's stored even before the _create function runs
1597                 $.data( element, this.widgetName, this );
1598                 this.element = $( element );
1599                 this.options = $.extend( true, {},
1600                         this.options,
1601                         this._getCreateOptions(),
1602                         options );
1603
1604                 var self = this;
1605                 this.element.bind( "remove." + this.widgetName, function() {
1606                         self.destroy();
1607                 });
1608
1609                 this._create();
1610                 this._trigger( "create" );
1611                 this._init();
1612         },
1613         _getCreateOptions: function() {
1614                 return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
1615         },
1616         _create: function() {},
1617         _init: function() {},
1618
1619         destroy: function() {
1620                 this.element
1621                         .unbind( "." + this.widgetName )
1622                         .removeData( this.widgetName );
1623                 this.widget()
1624                         .unbind( "." + this.widgetName )
1625                         .removeAttr( "aria-disabled" )
1626                         .removeClass(
1627                                 this.widgetBaseClass + "-disabled " +
1628                                 "ui-state-disabled" );
1629         },
1630
1631         widget: function() {
1632                 return this.element;
1633         },
1634
1635         option: function( key, value ) {
1636                 var options = key;
1637
1638                 if ( arguments.length === 0 ) {
1639                         // don't return a reference to the internal hash
1640                         return $.extend( {}, this.options );
1641                 }
1642
1643                 if  (typeof key === "string" ) {
1644                         if ( value === undefined ) {
1645                                 return this.options[ key ];
1646                         }
1647                         options = {};
1648                         options[ key ] = value;
1649                 }
1650
1651                 this._setOptions( options );
1652
1653                 return this;
1654         },
1655         _setOptions: function( options ) {
1656                 var self = this;
1657                 $.each( options, function( key, value ) {
1658                         self._setOption( key, value );
1659                 });
1660
1661                 return this;
1662         },
1663         _setOption: function( key, value ) {
1664                 this.options[ key ] = value;
1665
1666                 if ( key === "disabled" ) {
1667                         this.widget()
1668                                 [ value ? "addClass" : "removeClass"](
1669                                         this.widgetBaseClass + "-disabled" + " " +
1670                                         "ui-state-disabled" )
1671                                 .attr( "aria-disabled", value );
1672                 }
1673
1674                 return this;
1675         },
1676
1677         enable: function() {
1678                 return this._setOption( "disabled", false );
1679         },
1680         disable: function() {
1681                 return this._setOption( "disabled", true );
1682         },
1683
1684         _trigger: function( type, event, data ) {
1685                 var prop, orig,
1686                         callback = this.options[ type ];
1687
1688                 data = data || {};
1689                 event = $.Event( event );
1690                 event.type = ( type === this.widgetEventPrefix ?
1691                         type :
1692                         this.widgetEventPrefix + type ).toLowerCase();
1693                 // the original event may come from any element
1694                 // so we need to reset the target on the new event
1695                 event.target = this.element[ 0 ];
1696
1697                 // copy original event properties over to the new event
1698                 orig = event.originalEvent;
1699                 if ( orig ) {
1700                         for ( prop in orig ) {
1701                                 if ( !( prop in event ) ) {
1702                                         event[ prop ] = orig[ prop ];
1703                                 }
1704                         }
1705                 }
1706
1707                 this.element.trigger( event, data );
1708
1709                 return !( $.isFunction(callback) &&
1710                         callback.call( this.element[0], event, data ) === false ||
1711                         event.isDefaultPrevented() );
1712         }
1713 };
1714
1715 })( jQuery );
1716
1717 }
1718
1719 /*! JsRender v1.0pre - (jsrender.js version: does not require jQuery): http://github.com/BorisMoore/jsrender */
1720 /*
1721  * Optimized version of jQuery Templates, fosr rendering to string, using 'codeless' markup.
1722  *
1723  * Copyright 2011, Boris Moore
1724  * Released under the MIT License.
1725  */
1726 window.JsViews || window.jQuery && jQuery.views || (function( window, undefined ) {
1727
1728 var $, _$, JsViews, viewsNs, tmplEncode, render, rTag, registerTags, registerHelpers, extend,
1729         FALSE = false, TRUE = true,
1730         jQuery = window.jQuery, document = window.document,
1731         htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,
1732         rPath = /^(true|false|null|[\d\.]+)|(\w+|\$(view|data|ctx|(\w+)))([\w\.]*)|((['"])(?:\\\1|.)*\7)$/g,
1733         rParams = /(\$?[\w\.\[\]]+)(?:(\()|\s*(===|!==|==|!=|<|>|<=|>=)\s*|\s*(\=)\s*)?|(\,\s*)|\\?(\')|\\?(\")|(\))|(\s+)/g,
1734         rNewLine = /\r?\n/g,
1735         rUnescapeQuotes = /\\(['"])/g,
1736         rEscapeQuotes = /\\?(['"])/g,
1737         rBuildHash = /\x08([^\x08]+)\x08/g,
1738         autoName = 0,
1739         escapeMapForHtml = {
1740                 "&": "&amp;",
1741                 "<": "&lt;",
1742                 ">": "&gt;"
1743         },
1744         htmlSpecialChar = /[\x00"&'<>]/g,
1745         slice = Array.prototype.slice;
1746
1747 if ( jQuery ) {
1748
1749         ////////////////////////////////////////////////////////////////////////////////////////////////
1750         // jQuery is loaded, so make $ the jQuery object
1751         $ = jQuery;
1752
1753         $.fn.extend({
1754                 // Use first wrapped element as template markup.
1755                 // Return string obtained by rendering the template against data.
1756                 render: function( data, context, parentView, path ) {
1757                         return render( data, this[0], context, parentView, path );
1758                 },
1759
1760                 // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template.
1761                 template: function( name, context ) {
1762                         return $.template( name, this[0], context );
1763                 }
1764         });
1765
1766 } else {
1767
1768         ////////////////////////////////////////////////////////////////////////////////////////////////
1769         // jQuery is not loaded. Make $ the JsViews object
1770
1771         // Map over the $ in case of overwrite
1772         _$ = window.$;
1773
1774         window.JsViews = JsViews = window.$ = $ = {
1775                 extend: function( target, source ) {
1776                         var name;
1777                         for ( name in source ) {
1778                                 target[ name ] = source[ name ];
1779                         }
1780                         return target;
1781                 },
1782                 isArray: Array.isArray || function( obj ) {
1783                         return Object.prototype.toString.call( obj ) === "[object Array]";
1784                 },
1785                 noConflict: function() {
1786                         if ( window.$ === JsViews ) {
1787                                 window.$ = _$;
1788                         }
1789                         return JsViews;
1790                 }
1791         };
1792 }
1793
1794 extend = $.extend;
1795
1796 //=================
1797 // View constructor
1798 //=================
1799
1800 function View( context, path, parentView, data, template ) {
1801         // Returns a view data structure for a new rendered instance of a template.
1802         // The content field is a hierarchical array of strings and nested views.
1803
1804         parentView = parentView || { viewsCount:0, ctx: viewsNs.helpers };
1805
1806         var parentContext = parentView && parentView.ctx;
1807
1808         return {
1809                 jsViews: "v1.0pre",
1810                 path: path || "",
1811                 // inherit context from parentView, merged with new context.
1812                 itemNumber: ++parentView.viewsCount || 1,
1813                 viewsCount: 0,
1814                 tmpl: template,
1815                 data: data || parentView.data || {},
1816                 // Set additional context on this view (which will modify the context inherited from the parent, and be inherited by child views)
1817                 ctx : context && context === parentContext
1818                         ? parentContext
1819                         : (parentContext ? extend( extend( {}, parentContext ), context ) : context||{}), 
1820                         // If no jQuery, extend does not support chained copies - so limit to two parameters
1821                 parent: parentView
1822         };
1823 }
1824 extend( $, {
1825         views: viewsNs = {
1826                 templates: {},
1827                 tags: {
1828                         "if": function() {
1829                                 var ifTag = this,
1830                                         view = ifTag._view;
1831                                 view.onElse = function( presenter, args ) {
1832                                         var i = 0,
1833                                                 l = args.length;
1834                                         while ( l && !args[ i++ ]) {
1835                                                 // Only render content if args.length === 0 (i.e. this is an else with no condition) or if a condition argument is truey
1836                                                 if ( i === l ) {
1837                                                         return "";
1838                                                 }
1839                                         }
1840                                         view.onElse = undefined; // If condition satisfied, so won't run 'else'.
1841                                         return render( view.data, presenter.tmpl, view.ctx, view);
1842                                 };
1843                                 return view.onElse( this, arguments );
1844                         },
1845                         "else": function() {
1846                                 var view = this._view;
1847                                 return view.onElse ? view.onElse( this, arguments ) : "";
1848                         },
1849                         each: function() {
1850                                 var i, 
1851                                         self = this,
1852                                         result = "",
1853                                         args = arguments,
1854                                         l = args.length,
1855                                         content = self.tmpl,
1856                                         view = self._view;
1857                                 for ( i = 0; i < l; i++ ) {
1858                                         result += args[ i ] ? render( args[ i ], content, self.ctx || view.ctx, view, self._path, self._ctor ) : "";
1859                                 }
1860                                 return l ? result 
1861                                         // If no data parameter, use the current $data from view, and render once
1862                                         :  result + render( view.data, content, view.ctx, view, self._path, self.tag );
1863                         },
1864                         "=": function( value ) {
1865                                 return value;
1866                         },
1867                         "*": function( value ) {
1868                                 return value;
1869                         }
1870                 },
1871                 helpers: {
1872                         not: function( value ) {
1873                                 return !value;
1874                         }
1875                 },
1876                 allowCode: FALSE,
1877                 debugMode: TRUE,
1878                 err: function( e ) {
1879                         return viewsNs.debugMode ? ("<br/><b>Error:</b> <em> " + (e.message || e) + ". </em>"): '""';
1880                 },
1881
1882 //===============
1883 // setDelimiters
1884 //===============
1885
1886                 setDelimiters: function( openTag, closeTag ) {
1887                         // Set or modify the delimiter characters for tags: "{{" and "}}"
1888                         var firstCloseChar = closeTag.charAt( 0 ),
1889                                 secondCloseChar = closeTag.charAt( 1 );
1890                         openTag = "\\" + openTag.charAt( 0 ) + "\\" + openTag.charAt( 1 );
1891                         closeTag = "\\" + firstCloseChar + "\\" + secondCloseChar;
1892
1893                         // Build regex with new delimiters
1894                         //           {{
1895                         rTag = openTag
1896                                 //       #      tag    (followed by space,! or })             or equals or  code
1897                                 + "(?:(?:(\\#)?(\\w+(?=[!\\s\\" + firstCloseChar + "]))" + "|(?:(\\=)|(\\*)))"
1898                                 //     params
1899                                 + "\\s*((?:[^\\" + firstCloseChar + "]|\\" + firstCloseChar + "(?!\\" + secondCloseChar + "))*?)"
1900                                 //   encoding
1901                                 + "(!(\\w*))?"
1902                                 //        closeBlock
1903                                 + "|(?:\\/([\\w\\$\\.\\[\\]]+)))"
1904                         //  }}
1905                         + closeTag;
1906
1907                         // Default rTag:     #    tag              equals code        params         encoding    closeBlock
1908                         //      /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g;
1909
1910                         rTag = new RegExp( rTag, "g" );
1911                 },
1912
1913
1914 //===============
1915 // registerTags
1916 //===============
1917
1918                 // Register declarative tag.
1919                 registerTags: registerTags = function( name, tagFn ) {
1920                         var key;
1921                         if ( typeof name === "object" ) {
1922                                 for ( key in name ) {
1923                                         registerTags( key, name[ key ]);
1924                                 }
1925                         } else {
1926                                 // Simple single property case.
1927                                 viewsNs.tags[ name ] = tagFn;
1928                         }
1929                         return this;
1930                 },
1931
1932 //===============
1933 // registerHelpers
1934 //===============
1935
1936                 // Register helper function for use in markup.
1937                 registerHelpers: registerHelpers = function( name, helper ) {
1938                         if ( typeof name === "object" ) {
1939                                 // Object representation where property name is path and property value is value.
1940                                 // TODO: We've discussed an "objectchange" event to capture all N property updates here. See TODO note above about propertyChanges.
1941                                 var key;
1942                                 for ( key in name ) {
1943                                         registerHelpers( key, name[ key ]);
1944                                 }
1945                         } else {
1946                                 // Simple single property case.
1947                                 viewsNs.helpers[ name ] = helper;
1948                         }
1949                         return this;
1950                 },
1951
1952 //===============
1953 // tmpl.encode
1954 //===============
1955
1956                 encode: function( encoding, text ) {
1957                         return text
1958                                 ? ( tmplEncode[ encoding || "html" ] || tmplEncode.html)( text ) // HTML encoding is the default
1959                                 : "";
1960                 },
1961
1962                 encoders: tmplEncode = {
1963                         "none": function( text ) {
1964                                 return text;
1965                         },
1966                         "html": function( text ) {
1967                                 // HTML encoding helper: Replace < > & and ' and " by corresponding entities.
1968                                 // Implementation, from Mike Samuel <msamuel@google.com>
1969                                 return String( text ).replace( htmlSpecialChar, replacerForHtml );
1970                         }
1971                         //TODO add URL encoding, and perhaps other encoding helpers...
1972                 },
1973
1974 //===============
1975 // renderTag
1976 //===============
1977
1978                 renderTag: function( tag, view, encode, content, tagProperties ) {
1979                         // This is a tag call, with arguments: "tag", view, encode, content, presenter [, params...]
1980                         var ret, ctx, name,
1981                                 args = arguments,
1982                                 presenters = viewsNs.presenters;
1983                                 hash = tagProperties._hash,
1984                                 tagFn = viewsNs.tags[ tag ];
1985
1986                         if ( !tagFn ) {
1987                                 return "";
1988                         }
1989                         
1990                         content = content && view.tmpl.nested[ content - 1 ];
1991                         tagProperties.tmpl = tagProperties.tmpl || content || undefined;
1992                         // Set the tmpl property to the content of the block tag, unless set as an override property on the tag
1993                 
1994                         if ( presenters && presenters[ tag ]) {
1995                                 ctx = extend( extend( {}, tagProperties.ctx ), tagProperties );  
1996                                 delete ctx.ctx;  
1997                                 delete ctx._path;  
1998                                 delete ctx.tmpl;
1999                                 tagProperties.ctx = ctx;  
2000                                 tagProperties._ctor = tag + (hash ? "=" + hash.slice( 0, -1 ) : "");
2001
2002                                 tagProperties = extend( extend( {}, tagFn ), tagProperties );
2003                                 tagFn = viewsNs.tags.each; // Use each to render the layout template against the data
2004                         } 
2005
2006                         tagProperties._encode = encode;
2007                         tagProperties._view = view;
2008                         ret = tagFn.apply( tagProperties, args.length > 5 ? slice.call( args, 5 ) : [view.data] );
2009                         return ret || (ret === undefined ? "" : ret.toString()); // (If ret is the value 0 or false or null, will render to string) 
2010                 }
2011         },
2012
2013 //===============
2014 // render
2015 //===============
2016
2017         render: render = function( data, tmpl, context, parentView, path, tagName ) {
2018                 // Render template against data as a tree of subviews (nested template), or as a string (top-level template).
2019                 // tagName parameter for internal use only. Used for rendering templates registered as tags (which may have associated presenter objects)
2020                 var i, l, dataItem, arrayView, content, result = "";
2021
2022                 if ( arguments.length === 2 && data.jsViews ) {
2023                         parentView = data;
2024                         context = parentView.ctx;
2025                         data = parentView.data;
2026                 }
2027                 tmpl = $.template( tmpl );
2028                 if ( !tmpl ) {
2029                         return ""; // Could throw...
2030                 }
2031
2032                 if ( $.isArray( data )) {
2033                         // Create a view item for the array, whose child views correspond to each data item.
2034                         arrayView = new View( context, path, parentView, data);
2035                         l = data.length;
2036                         for ( i = 0, l = data.length; i < l; i++ ) {
2037                                 dataItem = data[ i ];
2038                                 content = dataItem ? tmpl( dataItem, new View( context, path, arrayView, dataItem, tmpl, this )) : "";
2039                                 result += viewsNs.activeViews ? "<!--item-->" + content + "<!--/item-->" : content;
2040                         }
2041                 } else {
2042                         result += tmpl( data, new View( context, path, parentView, data, tmpl ));
2043                 }
2044
2045                 return viewsNs.activeViews
2046                         // If in activeView mode, include annotations
2047                         ? "<!--tmpl(" + (path || "") + ") " + (tagName ? "tag=" + tagName : tmpl._name) + "-->" + result + "<!--/tmpl-->"
2048                         // else return just the string result
2049                         : result;
2050         },
2051
2052 //===============
2053 // template
2054 //===============
2055
2056         template: function( name, tmpl ) {
2057                 // Set:
2058                 // Use $.template( name, tmpl ) to cache a named template,
2059                 // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc.
2060                 // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration.
2061
2062                 // Get:
2063                 // Use $.template( name ) to access a cached template.
2064                 // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString )
2065                 // will return the compiled template, without adding a name reference.
2066                 // If templateString is not a selector, $.template( templateString ) is equivalent
2067                 // to $.template( null, templateString ). To ensure a string is treated as a template,
2068                 // include an HTML element, an HTML comment, or a template comment tag.
2069
2070                 if (tmpl) {
2071                         // Compile template and associate with name
2072                         if ( "" + tmpl === tmpl ) { // type string
2073                                 // This is an HTML string being passed directly in.
2074                                 tmpl = compile( tmpl );
2075                         } else if ( jQuery && tmpl instanceof $ ) {
2076                                 tmpl = tmpl[0];
2077                         }
2078                         if ( tmpl ) {
2079                                 if ( jQuery && tmpl.nodeType ) {
2080                                         // If this is a template block, use cached copy, or generate tmpl function and cache.
2081                                         tmpl = $.data( tmpl, "tmpl" ) || $.data( tmpl, "tmpl", compile( tmpl.innerHTML ));
2082                                 }
2083                                 viewsNs.templates[ tmpl._name = tmpl._name || name || "_" + autoName++ ] = tmpl;
2084                         }
2085                         return tmpl;
2086                 }
2087                 // Return named compiled template
2088                 return name
2089                         ? "" + name !== name // not type string
2090                                 ? (name._name
2091                                         ? name // already compiled
2092                                         : $.template( null, name ))
2093                                 : viewsNs.templates[ name ] ||
2094                                         // If not in map, treat as a selector. (If integrated with core, use quickExpr.exec)
2095                                         $.template( null, htmlExpr.test( name ) ? name : try$( name ))
2096                         : null;
2097         }
2098 });
2099
2100 viewsNs.setDelimiters( "{{", "}}" );
2101
2102 //=================
2103 // compile template
2104 //=================
2105
2106 // Generate a reusable function that will serve to render a template against data
2107 // (Compile AST then build template function)
2108
2109 function parsePath( all, comp, object, viewDataCtx, viewProperty, path, string, quot ) {
2110         return object
2111                 ? ((viewDataCtx
2112                         ? viewProperty
2113                                 ? ("$view." + viewProperty)
2114                                 : object
2115                         :("$data." + object)
2116                 )  + ( path || "" ))
2117                 : string || (comp || "");
2118 }
2119
2120 function compile( markup ) {
2121         var newNode,
2122                 loc = 0,
2123                 stack = [],
2124                 topNode = [],
2125                 content = topNode,
2126                 current = [,,topNode];
2127
2128         function pushPreceedingContent( shift ) {
2129                 shift -= loc;
2130                 if ( shift ) {
2131                         content.push( markup.substr( loc, shift ).replace( rNewLine,"\\n"));
2132                 }
2133         }
2134
2135         function parseTag( all, isBlock, tagName, equals, code, params, useEncode, encode, closeBlock, index ) {
2136                 // rTag    :    #    tagName          equals code        params         encode      closeBlock
2137                 // /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g;
2138
2139                 // Build abstract syntax tree: [ tagName, params, content, encode ]
2140                 var named,
2141                         hash = "",
2142                         parenDepth = 0,
2143                         quoted = FALSE, // boolean for string content in double qoutes
2144                         aposed = FALSE; // or in single qoutes
2145
2146                 function parseParams( all, path, paren, comp, eq, comma, apos, quot, rightParen, space, index ) {
2147                         //      path          paren eq      comma   apos   quot  rtPrn  space
2148                         // /(\$?[\w\.\[\]]+)(?:(\()|(===)|(\=))?|(\,\s*)|\\?(\')|\\?(\")|(\))|(\s+)/g
2149
2150                         return aposed
2151                                 // within single-quoted string
2152                                 ? ( aposed = !apos, (aposed ? all : '"'))
2153                                 : quoted
2154                                         // within double-quoted string
2155                                         ? ( quoted = !quot, (quoted ? all : '"'))
2156                                         : comp
2157                                                 // comparison
2158                                                 ? ( path.replace( rPath, parsePath ) + comp)
2159                                                 : eq
2160                                                         // named param
2161                                                         ? parenDepth ? "" :( named = TRUE, '\b' + path + ':')
2162                                                         : paren
2163                                                                 // function
2164                                                                 ? (parenDepth++, path.replace( rPath, parsePath ) + '(')
2165                                                                 : rightParen
2166                                                                         // function
2167                                                                         ? (parenDepth--, ")")
2168                                                                         : path
2169                                                                                 // path
2170                                                                                 ? path.replace( rPath, parsePath )
2171                                                                                 : comma
2172                                                                                         ? ","
2173                                                                                         : space
2174                                                                                                 ? (parenDepth
2175                                                                                                         ? ""
2176                                                                                                         : named
2177                                                                                                                 ? ( named = FALSE, "\b")
2178                                                                                                                 : ","
2179                                                                                                 )
2180                                                                                                 : (aposed = apos, quoted = quot, '"');
2181                 }
2182
2183                 tagName = tagName || equals;
2184                 pushPreceedingContent( index );
2185                 if ( code ) {
2186                         if ( viewsNs.allowCode ) {
2187                                 content.push([ "*", params.replace( rUnescapeQuotes, "$1" )]);
2188                         }
2189                 } else if ( tagName ) {
2190                         if ( tagName === "else" ) {
2191                                 current = stack.pop();
2192                                 content = current[ 2 ];
2193                                 isBlock = TRUE;
2194                         }
2195                         params = (params
2196                                 ? (params + " ")
2197                                         .replace( rParams, parseParams )
2198                                         .replace( rBuildHash, function( all, keyValue, index ) {
2199                                                 hash += keyValue + ",";
2200                                                 return "";
2201                                         })
2202                                 : "");
2203                         params = params.slice( 0, -1 );
2204                         newNode = [
2205                                 tagName,
2206                                 useEncode ? encode || "none" : "",
2207                                 isBlock && [],
2208                                 "{" + hash + "_hash:'" +  hash + "',_path:'" + params + "'}",
2209                                 params
2210                         ];
2211
2212                         if ( isBlock ) {
2213                                 stack.push( current );
2214                                 current = newNode;
2215                         }
2216                         content.push( newNode );
2217                 } else if ( closeBlock ) {
2218                         current = stack.pop();
2219                 }
2220                 loc = index + all.length; // location marker - parsed up to here
2221                 if ( !current ) {
2222                         throw "Expected block tag";
2223                 }
2224                 content = current[ 2 ];
2225         }
2226         markup = markup.replace( rEscapeQuotes, "\\$1" );
2227         markup.replace( rTag, parseTag );
2228         pushPreceedingContent( markup.length );
2229         return buildTmplFunction( topNode );
2230 }
2231
2232 // Build javascript compiled template function, from AST
2233 function buildTmplFunction( nodes ) {
2234         var ret, node, i,
2235                 nested = [],
2236                 l = nodes.length,
2237                 code = "try{var views="
2238                         + (jQuery ? "jQuery" : "JsViews")
2239                         + '.views,tag=views.renderTag,enc=views.encode,html=views.encoders.html,$ctx=$view && $view.ctx,result=""+\n\n';
2240
2241         for ( i = 0; i < l; i++ ) {
2242                 node = nodes[ i ];
2243                 if ( node[ 0 ] === "*" ) {
2244                         code = code.slice( 0, i ? -1 : -3 ) + ";" + node[ 1 ] + ( i + 1 < l ? "result+=" : "" );
2245                 } else if ( "" + node === node ) { // type string
2246                         code += '"' + node + '"+';
2247                 } else {
2248                         var tag = node[ 0 ],
2249                                 encode = node[ 1 ],
2250                                 content = node[ 2 ],
2251                                 obj = node[ 3 ],
2252                                 params = node[ 4 ],
2253                                 paramsOrEmptyString = params + '||"")+';
2254
2255                         if( content ) {
2256                                 nested.push( buildTmplFunction( content ));
2257                         }
2258                         code += tag === "="
2259                                 ? (!encode || encode === "html"
2260                                         ? "html(" + paramsOrEmptyString
2261                                         : encode === "none"
2262                                                 ? ("(" + paramsOrEmptyString)
2263                                                 : ('enc("' + encode + '",' + paramsOrEmptyString)
2264                                 )
2265                                 : 'tag("' + tag + '",$view,"' + ( encode || "" ) + '",'
2266                                         + (content ? nested.length : '""') // For block tags, pass in the key (nested.length) to the nested content template
2267                                         + "," + obj + (params ? "," : "") + params + ")+";
2268                 }
2269         }
2270         ret = new Function( "$data, $view", code.slice( 0, -1) + ";return result;\n\n}catch(e){return views.err(e);}" );
2271         ret.nested = nested;
2272         return ret;
2273 }
2274
2275 //========================== Private helper functions, used by code above ==========================
2276
2277 function replacerForHtml( ch ) {
2278         // Original code from Mike Samuel <msamuel@google.com>
2279         return escapeMapForHtml[ ch ]
2280                 // Intentional assignment that caches the result of encoding ch.
2281                 || ( escapeMapForHtml[ ch ] = "&#" + ch.charCodeAt( 0 ) + ";" );
2282 }
2283
2284 function try$( selector ) {
2285         // If selector is valid, return jQuery object, otherwise return (invalid) selector string
2286         try {
2287                 return $( selector );
2288         } catch( e) {}
2289         return selector;
2290 }
2291 })( window );
2292 (function ($, window, undefined) {
2293   var pos_oo = Number.POSITIVE_INFINITY,
2294       neg_oo = Number.NEGATIVE_INFINITY;
2295
2296   $.geo = {
2297     //
2298     // utility functions
2299     //
2300
2301     _allCoordinates: function (geom) {
2302       // return array of all positions in all geometries of geom
2303       // not in JTS
2304       var geometries = this._flatten(geom),
2305           curGeom = 0,
2306           result = [];
2307
2308       for (; curGeom < geometries.length; curGeom++) {
2309         var coordinates = geometries[curGeom].coordinates,
2310             isArray = coordinates && $.isArray(coordinates[0]),
2311             isDblArray = isArray && $.isArray(coordinates[0][0]),
2312             isTriArray = isDblArray && $.isArray(coordinates[0][0][0]),
2313             i, j, k;
2314
2315         if (!isTriArray) {
2316           if (!isDblArray) {
2317             if (!isArray) {
2318               coordinates = [coordinates];
2319             }
2320             coordinates = [coordinates];
2321           }
2322           coordinates = [coordinates];
2323         }
2324
2325         for (i = 0; i < coordinates.length; i++) {
2326           for (j = 0; j < coordinates[i].length; j++) {
2327             for (k = 0; k < coordinates[i][j].length; k++) {
2328               result.push(coordinates[i][j][k]);
2329             }
2330           }
2331         }
2332       }
2333       return result;
2334     },
2335
2336     _isGeodetic: function( coords ) {
2337       // returns true if the first coordinate it can find is geodetic
2338
2339       while ( $.isArray( coords ) ) {
2340         if ( coords.length > 1 && ! $.isArray( coords[ 0 ] ) ) {
2341           return ( coords[ 0 ] >= -180 && coords[ 0 ] <= 180 && coords[ 1 ] >= -85 && coords[ 1 ] <= 85 );
2342         } else {
2343           coords = coords[ 0 ];
2344         }
2345       }
2346
2347       return false;
2348     },
2349
2350     //
2351     // bbox functions
2352     //
2353
2354     center: function (bbox, _ignoreGeo /* Internal Use Only */) {
2355       // Envelope.centre in JTS
2356       // bbox only, use centroid for geom
2357       var wasGeodetic = false;
2358       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2359         wasGeodetic = true;
2360         bbox = $.geo.proj.fromGeodetic(bbox);
2361       }
2362
2363       var center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
2364       return wasGeodetic ? $.geo.proj.toGeodetic(center) : center;
2365     },
2366
2367     expandBy: function (bbox, dx, dy, _ignoreGeo /* Internal Use Only */) {
2368       var wasGeodetic = false;
2369       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2370         wasGeodetic = true;
2371         bbox = $.geo.proj.fromGeodetic(bbox);
2372       }
2373
2374       bbox = [bbox[0] - dx, bbox[1] - dy, bbox[2] + dx, bbox[3] + dy];
2375       return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2376     },
2377
2378     height: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
2379       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2380         bbox = $.geo.proj.fromGeodetic(bbox);
2381       }
2382
2383       return bbox[3] - bbox[1];
2384     },
2385
2386     _in: function(bbox1, bbox2) {
2387       return bbox1[0] <= bbox2[0] &&
2388              bbox1[1] <= bbox2[1] &&
2389              bbox1[2] >= bbox2[2] &&
2390              bbox1[3] >= bbox2[3];
2391     },
2392
2393     _bboxDisjoint: function( bbox1, bbox2 ) {
2394       return bbox2[ 0 ] > bbox1[ 2 ] || 
2395              bbox2[ 2 ] < bbox1[ 0 ] || 
2396              bbox2[ 1 ] > bbox1[ 3 ] ||
2397              bbox2[ 3 ] < bbox1[ 1 ];
2398     },
2399
2400     reaspect: function (bbox, ratio, _ignoreGeo /* Internal Use Only */ ) {
2401       // not in JTS
2402       var wasGeodetic = false;
2403       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2404         wasGeodetic = true;
2405         bbox = $.geo.proj.fromGeodetic(bbox);
2406       }
2407
2408       var width = this.width(bbox, true),
2409           height = this.height(bbox, true),
2410           center = this.center(bbox, true),
2411           dx, dy;
2412
2413       if (width != 0 && height != 0 && ratio > 0) {
2414         if (width / height > ratio) {
2415           dx = width / 2;
2416           dy = dx / ratio;
2417         } else {
2418           dy = height / 2;
2419           dx = dy * ratio;
2420         }
2421
2422         bbox = [center[0] - dx, center[1] - dy, center[0] + dx, center[1] + dy];
2423       }
2424
2425       return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2426     },
2427
2428     recenter: function( bbox, center, _ignoreGeo /* Internal Use Only */ ) {
2429       // not in JTS
2430       var wasGeodetic = false;
2431       if ( !_ignoreGeo && $.geo.proj ) {
2432         if ( this._isGeodetic( bbox ) ) {
2433           wasGeodetic = true;
2434           bbox = $.geo.proj.fromGeodetic(bbox);
2435         }
2436
2437         if ( this._isGeodetic( center ) ) {
2438           center = $.geo.proj.fromGeodetic(center);
2439         }
2440       }
2441
2442       var halfWidth = ( bbox[ 2 ] - bbox[ 0 ] ) / 2,
2443           halfHeight = ( bbox[ 3 ] - bbox[ 1 ] ) / 2;
2444
2445       bbox = [
2446         center[ 0 ] - halfWidth,
2447         center[ 1 ] - halfHeight,
2448         center[ 0 ] + halfWidth,
2449         center[ 1 ] + halfHeight
2450       ];
2451
2452       return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2453     },
2454
2455     scaleBy: function ( bbox, scale, _ignoreGeo /* Internal Use Only */ ) {
2456       // not in JTS
2457       var wasGeodetic = false;
2458       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2459         wasGeodetic = true;
2460         bbox = $.geo.proj.fromGeodetic(bbox);
2461       }
2462
2463       var c = this.center(bbox, true),
2464           dx = (bbox[2] - bbox[0]) * scale / 2,
2465           dy = (bbox[3] - bbox[1]) * scale / 2;
2466
2467       bbox = [c[0] - dx, c[1] - dy, c[0] + dx, c[1] + dy];
2468
2469       return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2470     },
2471
2472     width: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
2473       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2474         bbox = $.geo.proj.fromGeodetic(bbox);
2475       }
2476
2477       return bbox[2] - bbox[0];
2478     },
2479
2480     //
2481     // geometry functions
2482     //
2483
2484     // bbox (Geometry.getEnvelope in JTS)
2485
2486     bbox: function ( geom, _ignoreGeo /* Internal Use Only */ ) {
2487       if ( !geom ) {
2488         return undefined;
2489       } else if ( geom.bbox ) {
2490         result = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom.bbox ) ) ? $.geo.proj.fromGeodetic( geom.bbox ) : geom.bbox;
2491       } else {
2492         result = [ pos_oo, pos_oo, neg_oo, neg_oo ];
2493
2494         var coordinates = this._allCoordinates( geom ),
2495             curCoord = 0;
2496
2497         if ( coordinates.length == 0 ) {
2498           return undefined;
2499         }
2500
2501         var wasGeodetic = false;
2502         if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( coordinates ) ) {
2503           wasGeodetic = true;
2504           coordinates = $.geo.proj.fromGeodetic( coordinates );
2505         }
2506
2507         for ( ; curCoord < coordinates.length; curCoord++ ) {
2508           result[0] = Math.min(coordinates[curCoord][0], result[0]);
2509           result[1] = Math.min(coordinates[curCoord][1], result[1]);
2510           result[2] = Math.max(coordinates[curCoord][0], result[2]);
2511           result[3] = Math.max(coordinates[curCoord][1], result[3]);
2512         }
2513       }
2514
2515       return wasGeodetic ? $.geo.proj.toGeodetic(result) : result;
2516     },
2517
2518     // centroid
2519     
2520     centroid: function( geom, _ignoreGeo /* Internal Use Only */ ) {
2521       switch (geom.type) {
2522         case "Point":
2523           return $.extend({}, geom);
2524
2525         case "LineString":
2526         case "Polygon":
2527           var a = 0,
2528               c = [0, 0],
2529               coords = $.merge( [ ], geom.type == "Polygon" ? geom.coordinates[0] : geom.coordinates ),
2530               i = 1, j, n;
2531
2532           var wasGeodetic = false;
2533           if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( coords ) ) {
2534             wasGeodetic = true;
2535             coords = $.geo.proj.fromGeodetic(coords);
2536           }
2537
2538           //if (coords[0][0] != coords[coords.length - 1][0] || coords[0][1] != coords[coords.length - 1][1]) {
2539           //  coords.push(coords[0]);
2540           //}
2541
2542           for (; i <= coords.length; i++) {
2543             j = i % coords.length;
2544             n = (coords[i - 1][0] * coords[j][1]) - (coords[j][0] * coords[i - 1][1]);
2545             a += n;
2546             c[0] += (coords[i - 1][0] + coords[j][0]) * n;
2547             c[1] += (coords[i - 1][1] + coords[j][1]) * n;
2548           }
2549
2550           if (a == 0) {
2551             if (coords.length > 0) {
2552               c[0] = coords[0][0];
2553               c[1] = coords[0][1];
2554               return { type: "Point", coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c };
2555             } else {
2556               return undefined;
2557             }
2558           }
2559
2560           a *= 3;
2561           c[0] /= a;
2562           c[1] /= a;
2563
2564           return { type: "Point", coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c };
2565       }
2566       return undefined;
2567     },
2568
2569     // contains
2570
2571     contains: function (geom1, geom2) {
2572       if (geom1.type != "Polygon") {
2573         return false;
2574       }
2575
2576       switch (geom2.type) {
2577         case "Point":
2578           return this._containsPolygonPoint(geom1.coordinates, geom2.coordinates);
2579
2580         case "LineString":
2581           return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates);
2582
2583         case "Polygon":
2584           return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates[0]);
2585
2586         default:
2587           return false;
2588       }
2589     },
2590
2591     _containsPolygonPoint: function (polygonCoordinates, pointCoordinate) {
2592       if (polygonCoordinates.length == 0 || polygonCoordinates[0].length < 4) {
2593         return false;
2594       }
2595
2596       var rayCross = 0,
2597           a = polygonCoordinates[0][0],
2598           i = 1,
2599           b,
2600           x;
2601
2602       for (; i < polygonCoordinates[0].length; i++) {
2603         b = polygonCoordinates[0][i];
2604
2605         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])) {
2606           x = a[0] + (b[0] - a[0]) * (pointCoordinate[1] - a[1]) / (b[1] - a[1]);
2607
2608           if (x > pointCoordinate[0]) {
2609             rayCross++;
2610           }
2611         }
2612
2613         a = b;
2614       }
2615
2616       return rayCross % 2 == 1;
2617     },
2618
2619     _containsPolygonLineString: function (polygonCoordinates, lineStringCoordinates) {
2620       for (var i = 0; i < lineStringCoordinates.length; i++) {
2621         if (!this._containsPolygonPoint(polygonCoordinates, lineStringCoordinates[i])) {
2622           return false;
2623         }
2624       }
2625       return true;
2626     },
2627
2628     // distance
2629
2630     distance: function ( geom1, geom2, _ignoreGeo /* Internal Use Only */ ) {
2631       var geom1CoordinatesProjected = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom1.coordinates ) ) ? $.geo.proj.fromGeodetic(geom1.coordinates) : geom1.coordinates,
2632           geom2CoordinatesProjected = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom2.coordinates ) ) ? $.geo.proj.fromGeodetic(geom2.coordinates) : geom2.coordinates;
2633
2634       switch (geom1.type) {
2635         case "Point":
2636           switch (geom2.type) {
2637             case "Point":
2638               return this._distancePointPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
2639             case "LineString":
2640               return this._distanceLineStringPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
2641             case "Polygon":
2642               return this._containsPolygonPoint(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
2643             default:
2644               return undefined;
2645           }
2646           break;
2647
2648         case "LineString":
2649           switch (geom2.type) {
2650             case "Point":
2651               return this._distanceLineStringPoint(geom1CoordinatesProjected, geom2CoordinatesProjected);
2652             case "LineString":
2653               return this._distanceLineStringLineString(geom1CoordinatesProjected, geom2CoordinatesProjected);
2654             case "Polygon":
2655               return this._containsPolygonLineString(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
2656             default:
2657               return undefined;
2658           }
2659           break;
2660
2661         case "Polygon":
2662           switch (geom2.type) {
2663             case "Point":
2664               return this._containsPolygonPoint(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
2665             case "LineString":
2666               return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
2667             case "Polygon":
2668               return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected[0]) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected[0]);
2669             default:
2670               return undefined;
2671           }
2672           break;
2673       }
2674     },
2675
2676     _distancePointPoint: function (coordinate1, coordinate2) {
2677       var dx = coordinate2[0] - coordinate1[0],
2678           dy = coordinate2[1] - coordinate1[1];
2679       return Math.sqrt((dx * dx) + (dy * dy));
2680     },
2681
2682     _distanceLineStringPoint: function (lineStringCoordinates, pointCoordinate) {
2683       var minDist = pos_oo;
2684
2685       if (lineStringCoordinates.length > 0) {
2686         var a = lineStringCoordinates[0],
2687
2688             apx = pointCoordinate[0] - a[0],
2689             apy = pointCoordinate[1] - a[1];
2690
2691         if (lineStringCoordinates.length == 1) {
2692           return Math.sqrt(apx * apx + apy * apy);
2693         } else {
2694           for (var i = 1; i < lineStringCoordinates.length; i++) {
2695             var b = lineStringCoordinates[i],
2696
2697                 abx = b[0] - a[0],
2698                 aby = b[1] - a[1],
2699                 bpx = pointCoordinate[0] - b[0],
2700                 bpy = pointCoordinate[1] - b[1],
2701
2702                 d = this._distanceSegmentPoint(abx, aby, apx, apy, bpx, bpy);
2703
2704             if (d == 0) {
2705               return 0;
2706             }
2707
2708             if (d < minDist) {
2709               minDist = d;
2710             }
2711
2712             a = b;
2713             apx = bpx;
2714             apy = bpy;
2715           }
2716         }
2717       }
2718
2719       return Math.sqrt(minDist);
2720     },
2721
2722     _distanceSegmentPoint: function (abx, aby, apx, apy, bpx, bpy) {
2723       var dot1 = abx * apx + aby * apy;
2724
2725       if (dot1 <= 0) {
2726         return apx * apx + apy * apy;
2727       }
2728
2729       var dot2 = abx * abx + aby * aby;
2730
2731       if (dot1 >= dot2) {
2732         return bpx * bpx + bpy * bpy;
2733       }
2734
2735       return apx * apx + apy * apy - dot1 * dot1 / dot2;
2736     },
2737
2738     _distanceLineStringLineString: function (lineStringCoordinates1, lineStringCoordinates2) {
2739       var minDist = pos_oo;
2740       for (var i = 0; i < lineStringCoordinates2.length; i++) {
2741         minDist = Math.min(minDist, this._distanceLineStringPoint(lineStringCoordinates1, lineStringCoordinates2[i]));
2742       }
2743       return minDist;
2744     },
2745
2746     // buffer
2747
2748     _buffer: function( geom, distance, _ignoreGeo /* Internal Use Only */ ) {
2749       var wasGeodetic = false,
2750           coords = geom.coordinates;
2751
2752       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom.coordinates ) ) {
2753         wasGeodetic = true;
2754         coords = $.geo.proj.fromGeodetic( geom.coordinates );
2755       }
2756
2757       switch ( geom.type ) {
2758         case "Point":
2759           var resultCoords = [],
2760               slices = 180,
2761               i = 0,
2762               a;
2763
2764           for ( ; i <= slices; i++ ) {
2765             a = ( i * 360 / slices ) * ( Math.PI / 180 );
2766             resultCoords.push( [
2767               coords[ 0 ] + Math.cos( a ) * distance,
2768               coords[ 1 ] + Math.sin( a ) * distance
2769             ] );
2770           }
2771
2772           return {
2773             type: "Polygon",
2774             coordinates: [ ( wasGeodetic ? $.geo.proj.toGeodetic( resultCoords ) : resultCoords ) ]
2775           };
2776
2777           break;
2778
2779         default:
2780           return undefined;
2781       }
2782     },
2783
2784     
2785     //
2786     // feature
2787     //
2788
2789     _flatten: function (geom) {
2790       // return an array of all basic geometries
2791       // not in JTS
2792       var geometries = [],
2793           curGeom = 0;
2794       switch (geom.type) {
2795         case "Feature":
2796           $.merge(geometries, this._flatten(geom.geometry));
2797           break;
2798
2799         case "FeatureCollection":
2800           for (; curGeom < geom.features.length; curGeom++) {
2801             $.merge(geometries, this._flatten(geom.features[curGeom].geometry));
2802           }
2803           break;
2804
2805         case "GeometryCollection":
2806           for (; curGeom < geom.geometries.length; curGeom++) {
2807             $.merge(geometries, this._flatten(geom.geometries[curGeom]));
2808           }
2809           break;
2810
2811         default:
2812           geometries[0] = geom;
2813           break;
2814       }
2815       return geometries;
2816     },
2817
2818     length: function( geom, _ignoreGeo /* Internal Use Only */ ) {
2819       var sum = 0,
2820           lineStringCoordinates,
2821           i = 1, dx, dy;
2822
2823       switch ( geom.type ) {
2824         case "Point":
2825           return 0;
2826
2827         case "LineString":
2828           lineStringCoordinates = geom.coordinates;
2829           break;
2830
2831         case "Polygon":
2832           lineStringCoordinates = geom.coordinates[ 0 ];
2833           break;
2834       }
2835
2836       if ( lineStringCoordinates ) {
2837         if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( lineStringCoordinates ) ) {
2838           lineStringCoordinates = $.geo.proj.fromGeodetic( lineStringCoordinates );
2839         }
2840
2841         for ( ; i < lineStringCoordinates.length; i++ ) {
2842           dx = lineStringCoordinates[ i ][0] - lineStringCoordinates[ i - 1 ][0];
2843           dy = lineStringCoordinates[ i ][1] - lineStringCoordinates[ i - 1 ][1];
2844           sum += Math.sqrt((dx * dx) + (dy * dy));
2845         }
2846
2847         return sum;
2848       }
2849
2850       // return undefined;
2851     },
2852
2853     area: function( geom, _ignoreGeo /* Internal Use Only */ ) {
2854       var sum = 0,
2855           polygonCoordinates,
2856           i = 1, j;
2857
2858       switch ( geom.type ) {
2859         case "Point":
2860         case "LineString":
2861           return 0;
2862
2863         case "Polygon":
2864           polygonCoordinates = geom.coordinates[ 0 ];
2865           break;
2866       }
2867
2868       if ( polygonCoordinates ) {
2869         if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( polygonCoordinates ) ) {
2870           polygonCoordinates = $.geo.proj.fromGeodetic( polygonCoordinates );
2871         }
2872
2873         for ( ; i <= polygonCoordinates.length; i++) {
2874           j = i %  polygonCoordinates.length;
2875           sum += ( polygonCoordinates[ i - 1 ][ 0 ] - polygonCoordinates[ j ][ 0 ] ) * ( polygonCoordinates[ i - 1 ][ 1 ] + polygonCoordinates[ j ][ 1 ] ) / 2;
2876         }
2877
2878         return Math.abs( sum );
2879       }
2880     },
2881
2882     pointAlong: function( geom, percentage, _ignoreGeo /* Internal Use Only */ ) {
2883       var totalLength = 0,
2884           previousPercentageSum = 0,
2885           percentageSum = 0,
2886           remainderPercentageSum,
2887           len,
2888           lineStringCoordinates,
2889           segmentLengths = [],
2890           i = 1, dx, dy,
2891           c, c0, c1,
2892           wasGeodetic = false;
2893
2894       switch ( geom.type ) {
2895         case "Point":
2896           return $.extend( { }, geom );
2897
2898         case "LineString":
2899           lineStringCoordinates = geom.coordinates;
2900           break;
2901
2902         case "Polygon":
2903           lineStringCoordinates = geom.coordinates[ 0 ];
2904           break;
2905       }
2906
2907       if ( lineStringCoordinates ) {
2908         if ( percentage === 0 ) {
2909           return {
2910             type: "Point",
2911             coordinates: [ lineStringCoordinates[ 0 ][ 0 ], lineStringCoordinates[ 0 ][ 1 ] ]
2912           };
2913         } else if ( percentage === 1 ) {
2914           i = lineStringCoordinates.length - 1;
2915           return {
2916             type: "Point",
2917             coordinates: [ lineStringCoordinates[ i ][ 0 ], lineStringCoordinates[ i ][ 1 ] ]
2918           };
2919         } else {
2920           if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( lineStringCoordinates ) ) {
2921             wasGeodetic = true;
2922             lineStringCoordinates = $.geo.proj.fromGeodetic( lineStringCoordinates );
2923           }
2924
2925           for ( ; i < lineStringCoordinates.length; i++ ) {
2926             dx = lineStringCoordinates[ i ][ 0 ] - lineStringCoordinates[ i - 1 ][ 0 ];
2927             dy = lineStringCoordinates[ i ][ 1 ] - lineStringCoordinates[ i - 1 ][ 1 ];
2928             len = Math.sqrt((dx * dx) + (dy * dy));
2929             segmentLengths.push( len );
2930             totalLength += len;
2931           }
2932
2933           for ( i = 0; i < segmentLengths.length && percentageSum < percentage; i++ ) {
2934             previousPercentageSum = percentageSum;
2935             percentageSum += ( segmentLengths[ i ] / totalLength );
2936           }
2937
2938           remainderPercentageSum = percentage - previousPercentageSum;
2939
2940           c0 = lineStringCoordinates[ i - 1 ];
2941           c1 = lineStringCoordinates[ i ];
2942
2943           c = [
2944             c0[ 0 ] + ( remainderPercentageSum * ( c1[ 0 ] - c0[ 0 ] ) ),
2945             c0[ 1 ] + ( remainderPercentageSum * ( c1[ 1 ] - c0[ 1 ] ) )
2946           ];
2947
2948           return {
2949             type: "Point",
2950             coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c
2951           };
2952         }
2953       }
2954     },
2955
2956     //
2957     // WKT functions
2958     //
2959
2960     _WKT: (function () {
2961       function pointToString(value) {
2962         return "POINT " + pointToUntaggedString(value.coordinates);
2963       }
2964
2965       function pointToUntaggedString(coordinates) {
2966         if (!(coordinates && coordinates.length)) {
2967           return "EMPTY";
2968         } else {
2969           return "(" + coordinates.join(" ") + ")";
2970         }
2971       }
2972
2973       function lineStringToString(value) {
2974         return "LINESTRING " + lineStringToUntaggedString(value.coordinates);
2975       }
2976
2977       function lineStringToUntaggedString(coordinates) {
2978         if (!(coordinates && coordinates.length)) {
2979           return "EMPTY";
2980         } else {
2981           var points = []
2982
2983           for (var i = 0; i < coordinates.length; i++) {
2984             points.push(coordinates[i].join(" "));
2985           }
2986
2987           return "(" + points + ")";
2988         }
2989       }
2990
2991       function polygonToString(value) {
2992         return "POLYGON " + polygonToUntaggedString(value.coordinates);
2993       }
2994
2995       function polygonToUntaggedString(coordinates) {
2996         if (!(coordinates && coordinates.length)) {
2997           return "EMTPY";
2998         } else {
2999           var lineStrings = [];
3000
3001           for (var i = 0; i < coordinates.length; i++) {
3002             lineStrings.push(lineStringToUntaggedString(coordinates[i]));
3003           }
3004
3005           return "(" + lineStrings + ")";
3006         }
3007       }
3008
3009       function multiPointToString(value) {
3010         return "MULTIPOINT " + lineStringToUntaggedString(value.coordinates);
3011       }
3012
3013       function multiLineStringToString(value) {
3014         return "MULTILINSTRING " + polygonToUntaggedString(value.coordinates);
3015       }
3016
3017       function multiPolygonToString(value) {
3018         return "MULTIPOLYGON " + multiPolygonToUntaggedString(value.coordinates);
3019       }
3020
3021       function multiPolygonToUntaggedString(coordinates) {
3022         if (!(coordinates && coordinates.length)) {
3023           return "EMPTY";
3024         } else {
3025           var polygons = [];
3026           for (var i = 0; i < coordinates.length; i++) {
3027             polygons.push(polygonToUntaggedString(coordinates[i]));
3028           }
3029           return "(" + polygons + ")";
3030         }
3031       }
3032
3033       function geometryCollectionToString(value) {
3034         return "GEOMETRYCOLLECTION " + geometryCollectionToUntaggedString(value.geometries);
3035       }
3036
3037       function geometryCollectionToUntaggedString(geometries) {
3038         if (!(geometries && geometries.length)) {
3039           return "EMPTY";
3040         } else {
3041           var geometryText = [];
3042           for (var i = 0; i < geometries.length; i++) {
3043             geometryText.push(stringify(geometries[i]));
3044           }
3045           return "(" + geometries + ")";
3046         }
3047       }
3048
3049       function stringify(value) {
3050         if (!(value && value.type)) {
3051           return "";
3052         } else {
3053           switch (value.type) {
3054             case "Point":
3055               return pointToString(value);
3056
3057             case "LineString":
3058               return lineStringToString(value);
3059
3060             case "Polygon":
3061               return polygonToString(value);
3062
3063             case "MultiPoint":
3064               return multiPointToString(value);
3065
3066             case "MultiLineString":
3067               return multiLineStringToString(value);
3068
3069             case "MultiPolygon":
3070               return multiPolygonToString(value);
3071
3072             case "GeometryCollection":
3073               return geometryCollectionToString(value);
3074
3075             default:
3076               return "";
3077           }
3078         }
3079       }
3080
3081       function pointParseUntagged(wkt) {
3082         var pointString = wkt.match( /\(\s*([\d\.-]+)\s+([\d\.-]+)\s*\)/ );
3083         return pointString && pointString.length > 2 ? {
3084           type: "Point",
3085           coordinates: [
3086             parseFloat(pointString[1]),
3087             parseFloat(pointString[2])
3088           ]
3089         } : null;
3090       }
3091
3092       function lineStringParseUntagged(wkt) {
3093         var lineString = wkt.match( /\s*\((.*)\)/ ),
3094             coords = [],
3095             pointStrings,
3096             pointParts,
3097             i = 0;
3098
3099         if ( lineString.length > 1 ) {
3100           pointStrings = lineString[ 1 ].match( /[\d\.-]+\s+[\d\.-]+/g );
3101
3102           for ( ; i < pointStrings.length; i++ ) {
3103             pointParts = pointStrings[ i ].match( /\s*([\d\.-]+)\s+([\d\.-]+)\s*/ );
3104             coords[ i ] = [ parseFloat( pointParts[ 1 ] ), parseFloat( pointParts[ 2 ] ) ];
3105           }
3106
3107           return {
3108             type: "LineString",
3109             coordinates: coords
3110           };
3111         } else {
3112           return null
3113         }
3114       }
3115
3116       function polygonParseUntagged(wkt) {
3117         var polygon = wkt.match( /\s*\(\s*\((.*)\)\s*\)/ ),
3118             coords = [],
3119             pointStrings,
3120             pointParts,
3121             i = 0;
3122
3123         if ( polygon.length > 1 ) {
3124           pointStrings = polygon[ 1 ].match( /[\d\.-]+\s+[\d\.-]+/g );
3125
3126           for ( ; i < pointStrings.length; i++ ) {
3127             pointParts = pointStrings[ i ].match( /\s*([\d\.-]+)\s+([\d\.-]+)\s*/ );
3128             coords[ i ] = [ parseFloat( pointParts[ 1 ] ), parseFloat( pointParts[ 2 ] ) ];
3129           }
3130
3131           return {
3132             type: "Polygon",
3133             coordinates: [ coords ]
3134           };
3135         } else {
3136           return null;
3137         }
3138       }
3139
3140       function parse(wkt) {
3141         wkt = $.trim(wkt);
3142
3143         var typeIndex = wkt.indexOf( " " ),
3144             untagged = wkt.substr( typeIndex + 1 );
3145
3146         switch (wkt.substr(0, typeIndex).toUpperCase()) {
3147           case "POINT":
3148             return pointParseUntagged( untagged );
3149
3150           case "LINESTRING":
3151             return lineStringParseUntagged( untagged );
3152
3153           case "POLYGON":
3154             return polygonParseUntagged( untagged );
3155
3156           default:
3157             return null;
3158         }
3159       }
3160
3161       return {
3162         stringify: stringify,
3163
3164         parse: parse
3165       };
3166     })(),
3167
3168     //
3169     // projection functions
3170     //
3171
3172     proj: (function () {
3173       var halfPi = 1.5707963267948966192,
3174           quarterPi = 0.7853981633974483096,
3175           radiansPerDegree = 0.0174532925199432958,
3176           degreesPerRadian = 57.295779513082320877,
3177           semiMajorAxis = 6378137;
3178
3179       return {
3180         fromGeodeticPos: function (coordinate) {
3181           if (!coordinate) {
3182             debugger;
3183           }
3184           return [
3185             semiMajorAxis * coordinate[ 0 ] * radiansPerDegree,
3186             semiMajorAxis * Math.log(Math.tan(quarterPi + coordinate[ 1 ] * radiansPerDegree / 2))
3187           ];
3188         },
3189
3190         fromGeodetic: function ( coordinates ) {
3191           if ( ! $.geo._isGeodetic( coordinates ) ) {
3192             return coordinates;
3193           }
3194
3195           var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
3196               fromGeodeticPos = this.fromGeodeticPos;
3197
3198           if (!isMultiPointOrLineString && coordinates.length == 4) {
3199             // bbox
3200             var min = fromGeodeticPos([ coordinates[ 0 ], coordinates[ 1 ] ]),
3201                 max = fromGeodeticPos([ coordinates[ 2 ], coordinates[ 3 ] ]);
3202             return [ min[ 0 ], min[ 1 ], max[ 0 ], max[ 1 ] ];
3203           } else {
3204             // geometry
3205             var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
3206                 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
3207                 result = [ ],
3208                 i, j, k;
3209
3210             if (!isMultiPolygon) {
3211               if (!isMultiLineStringOrPolygon) {
3212                 if (!isMultiPointOrLineString) {
3213                   coordinates = [ coordinates ];
3214                 }
3215                 coordinates = [ coordinates ];
3216               }
3217               coordinates = [ coordinates ];
3218             }
3219
3220             for ( i = 0; i < coordinates.length; i++ ) {
3221               result[ i ] = [ ];
3222               for ( j = 0; j < coordinates[ i ].length; j++ ) {
3223                 result[ i ][ j ] = [ ];
3224                 for ( k = 0; k < coordinates[ i ][ j ].length; k++ ) {
3225                   result[ i ][ j ][ k ] = fromGeodeticPos(coordinates[ i ][ j ][ k ]);
3226                 }
3227               }
3228             }
3229
3230             return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
3231           }
3232         },
3233
3234         toGeodeticPos: function (coordinate) {
3235           return [
3236             (coordinate[ 0 ] / semiMajorAxis) * degreesPerRadian,
3237             (halfPi - 2 * Math.atan(1 / Math.exp(coordinate[ 1 ] / semiMajorAxis))) * degreesPerRadian
3238           ];
3239         },
3240
3241         toGeodetic: function (coordinates) {
3242           if ( $.geo._isGeodetic( coordinates ) ) {
3243             return coordinates;
3244           }
3245
3246           var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
3247               toGeodeticPos = this.toGeodeticPos;
3248
3249           if (!isMultiPointOrLineString && coordinates.length == 4) {
3250             // bbox
3251             var min = toGeodeticPos([ coordinates[ 0 ], coordinates[ 1 ] ]),
3252                 max = toGeodeticPos([ coordinates[ 2 ], coordinates[ 3 ] ]);
3253             return [ min[ 0 ], min[ 1 ], max[ 0 ], max[ 1 ] ];
3254           } else {
3255             // geometry
3256             var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
3257                 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
3258                 result = [ ];
3259
3260             if (!isMultiPolygon) {
3261               if (!isMultiLineStringOrPolygon) {
3262                 if (!isMultiPointOrLineString) {
3263                   coordinates = [ coordinates ];
3264                 }
3265                 coordinates = [ coordinates ];
3266               }
3267               coordinates = [ coordinates ];
3268             }
3269
3270             for ( i = 0; i < coordinates.length; i++ ) {
3271               result[ i ] = [ ];
3272               for ( j = 0; j < coordinates[ i ].length; j++ ) {
3273                 result[ i ][ j ] = [ ];
3274                 for ( k = 0; k < coordinates[ i ][ j ].length; k++ ) {
3275                   result[ i ][ j ][ k ] = toGeodeticPos(coordinates[ i ][ j ][ k ]);
3276                 }
3277               }
3278             }
3279
3280             return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
3281           }
3282         }
3283       }
3284     })(),
3285
3286     //
3287     // service types (defined in other files)
3288     //
3289
3290     _serviceTypes: {}
3291   }
3292 })(jQuery, this);
3293 (function ($, undefined) {
3294
3295   var _ieVersion = (function () {
3296     var v = 5, div = document.createElement("div"), a = div.all || [];
3297     while (div.innerHTML = "<!--[if gt IE " + (++v) + "]><br><![endif]-->", a[0]) { }
3298     return v > 6 ? v : !v;
3299   } ());
3300
3301   $.widget("geo.geographics", {
3302     _$elem: undefined,
3303     _options: {},
3304     _trueCanvas: true,
3305
3306     _width: 0,
3307     _height: 0,
3308
3309     _$canvas: undefined,
3310     _context: undefined,
3311     _$labelsContainer: undefined,
3312
3313     options: {
3314       style: {
3315         borderRadius: "8px",
3316         color: "#7f0000",
3317         //fill: undefined,
3318         fillOpacity: .2,
3319         height: "8px",
3320         opacity: 1,
3321         //stroke: undefined,
3322         strokeOpacity: 1,
3323         strokeWidth: "2px",
3324         visibility: "visible",
3325         width: "8px"
3326       }
3327     },
3328
3329     _create: function () {
3330       this._$elem = this.element;
3331       this._options = this.options;
3332
3333       this._$elem.css({ display: "inline-block", overflow: "hidden", textAlign: "left" });
3334
3335       if (this._$elem.css("position") == "static") {
3336         this._$elem.css("position", "relative");
3337       }
3338
3339       this._$elem.addClass( "geo-graphics" );
3340
3341       this._width = this._$elem.width();
3342       this._height = this._$elem.height();
3343
3344       if (!(this._width && this._height)) {
3345         this._width = parseInt(this._$elem.css("width"));
3346         this._height = parseInt(this._$elem.css("height"));
3347       }
3348
3349       var posCss = 'position:absolute;left:0;top:0;margin:0;padding:0;',
3350           sizeCss = 'width:' + this._width + 'px;height:' + this._height + 'px;',
3351           sizeAttr = 'width="' + this._width + '" height="' + this._height + '"';
3352
3353       if (document.createElement('canvas').getContext) {
3354         this._$elem.append('<canvas ' + sizeAttr + ' style="' + posCss + '"></canvas>');
3355         this._$canvas = this._$elem.children(':last');
3356         this._context = this._$canvas[0].getContext("2d");
3357       } else if (_ieVersion <= 8) {
3358         this._trueCanvas = false;
3359         this._$elem.append( '<div ' + sizeAttr + ' style="' + posCss + sizeCss + '"></div>');
3360         this._$canvas = this._$elem.children(':last');
3361
3362         G_vmlCanvasManager.initElement(this._$canvas[0]);
3363         this._context = this._$canvas[0].getContext("2d");
3364         this._$canvas.children().css({ backgroundColor: "transparent", width: this._width, height: this._height });
3365       }
3366
3367       this._$elem.append('<div class="geo-labels-container" style="' + posCss + sizeCss + '"></div>');
3368       this._$labelsContainer = this._$elem.children(':last');
3369     },
3370
3371     _setOption: function (key, value) {
3372       if (key == "style") {
3373         value = $.extend({}, this._options.style, value);
3374       }
3375       $.Widget.prototype._setOption.apply(this, arguments);
3376     },
3377
3378     destroy: function () {
3379       $.Widget.prototype.destroy.apply(this, arguments);
3380       this._$elem.html("");
3381       this._$elem.removeClass( "geo-graphics" );
3382     },
3383
3384     clear: function () {
3385       this._context.clearRect(0, 0, this._width, this._height);
3386       this._$labelsContainer.html("");
3387     },
3388
3389     drawArc: function (coordinates, startAngle, sweepAngle, style) {
3390       style = this._getGraphicStyle(style);
3391
3392       if (style.visibility != "hidden" && style.opacity > 0 && style.widthValue > 0 && style.heightValue > 0) {
3393         var r = Math.min(style.widthValue, style.heightValue) / 2;
3394
3395         startAngle = (startAngle * Math.PI / 180);
3396         sweepAngle = (sweepAngle * Math.PI / 180);
3397
3398         this._context.save();
3399         this._context.translate(coordinates[0], coordinates[1]);
3400         if (style.widthValue > style.heightValue) {
3401           this._context.scale(style.widthValue / style.heightValue, 1);
3402         } else {
3403           this._context.scale(1, style.heightValue / style.widthValue);
3404         }
3405
3406         this._context.beginPath();
3407         this._context.arc(0, 0, r, startAngle, sweepAngle, false);
3408
3409         if (this._trueCanvas) {
3410           this._context.restore();
3411         }
3412
3413         if (style.doFill) {
3414           this._context.fillStyle = style.fill;
3415           this._context.globalAlpha = style.opacity * style.fillOpacity;
3416           this._context.fill();
3417         }
3418
3419         if (style.doStroke) {
3420           this._context.lineJoin = "round";
3421           this._context.lineWidth = style.strokeWidthValue;
3422           this._context.strokeStyle = style.stroke;
3423
3424           this._context.globalAlpha = style.opacity * style.strokeOpacity;
3425           this._context.stroke();
3426         }
3427
3428         if (!this._trueCanvas) {
3429           this._context.restore();
3430         }
3431       }
3432     },
3433
3434     drawPoint: function (coordinates, style) {
3435       var style = this._getGraphicStyle(style);
3436       if (style.widthValue == style.heightValue && style.heightValue == style.borderRadiusValue) {
3437         this.drawArc(coordinates, 0, 360, style);
3438       } else if (style.visibility != "hidden" && style.opacity > 0) {
3439         style.borderRadiusValue = Math.min(Math.min(style.widthValue, style.heightValue) / 2, style.borderRadiusValue);
3440         coordinates[0] -= style.widthValue / 2;
3441         coordinates[1] -= style.heightValue / 2;
3442         this._context.beginPath();
3443         this._context.moveTo(coordinates[0] + style.borderRadiusValue, coordinates[1]);
3444         this._context.lineTo(coordinates[0] + style.widthValue - style.borderRadiusValue, coordinates[1]);
3445         this._context.quadraticCurveTo(coordinates[0] + style.widthValue, coordinates[1], coordinates[0] + style.widthValue, coordinates[1] + style.borderRadiusValue);
3446         this._context.lineTo(coordinates[0] + style.widthValue, coordinates[1] + style.heightValue - style.borderRadiusValue);
3447         this._context.quadraticCurveTo(coordinates[0] + style.widthValue, coordinates[1] + style.heightValue, coordinates[0] + style.widthValue - style.borderRadiusValue, coordinates[1] + style.heightValue);
3448         this._context.lineTo(coordinates[0] + style.borderRadiusValue, coordinates[1] + style.heightValue);
3449         this._context.quadraticCurveTo(coordinates[0], coordinates[1] + style.heightValue, coordinates[0], coordinates[1] + style.heightValue - style.borderRadiusValue);
3450         this._context.lineTo(coordinates[0], coordinates[1] + style.borderRadiusValue);
3451         this._context.quadraticCurveTo(coordinates[0], coordinates[1], coordinates[0] + style.borderRadiusValue, coordinates[1]);
3452         this._context.closePath();
3453
3454         if (style.doFill) {
3455           this._context.fillStyle = style.fill;
3456           this._context.globalAlpha = style.opacity * style.fillOpacity;
3457           this._context.fill();
3458         }
3459
3460         if (style.doStroke) {
3461           this._context.lineJoin = "round";
3462           this._context.lineWidth = style.strokeWidthValue;
3463           this._context.strokeStyle = style.stroke;
3464
3465           this._context.globalAlpha = style.opacity * style.strokeOpacity;
3466
3467           this._context.stroke();
3468         }
3469       }
3470     },
3471
3472     drawLineString: function (coordinates, style) {
3473       this._drawLines([coordinates], false, style);
3474     },
3475
3476     drawPolygon: function (coordinates, style) {
3477       this._drawLines(coordinates, true, style);
3478     },
3479
3480     drawBbox: function (bbox, style) {
3481       this._drawLines([[
3482         [bbox[0], bbox[1]],
3483         [bbox[0], bbox[3]],
3484         [bbox[2], bbox[3]],
3485         [bbox[2], bbox[1]],
3486         [bbox[0], bbox[1]]
3487       ]], true, style);
3488     },
3489
3490     drawLabel: function( coordinates, label ) {
3491       this._$labelsContainer.append( '<div class="geo-label" style="position:absolute; left:' + coordinates[ 0 ] + 'px; top:' + coordinates[ 1 ] + 'px;">' + label + '</div>');
3492     },
3493
3494     resize: function( ) {
3495       this._width = this._$elem.width();
3496       this._height = this._$elem.height();
3497
3498       if (!(this._width && this._height)) {
3499         this._width = parseInt(this._$elem.css("width"));
3500         this._height = parseInt(this._$elem.css("height"));
3501       }
3502
3503       if ( this._trueCanvas ) {
3504         this._$canvas[0].width = this._width;
3505         this._$canvas[0].height = this._height;
3506       } else {
3507       }
3508
3509       this._$labelsContainer.css( {
3510         width: this._width,
3511         height: this._height
3512       } );
3513     },
3514
3515     _getGraphicStyle: function (style) {
3516       function safeParse(value) {
3517         value = parseInt(value);
3518         return (+value + '') === value ? +value : value;
3519       }
3520
3521       style = $.extend({}, this._options.style, style);
3522       style.borderRadiusValue = safeParse(style.borderRadius);
3523       style.fill = style.fill || style.color;
3524       style.doFill = style.fill && style.fillOpacity > 0;
3525       style.stroke = style.stroke || style.color;
3526       style.strokeWidthValue = safeParse(style.strokeWidth);
3527       style.doStroke = style.stroke && style.strokeOpacity > 0 && style.strokeWidthValue > 0;
3528       style.widthValue = safeParse(style.width);
3529       style.heightValue = safeParse(style.height);
3530       return style;
3531     },
3532
3533     _drawLines: function (coordinates, close, style) {
3534       if (!coordinates || !coordinates.length || coordinates[0].length < 2) {
3535         return;
3536       }
3537
3538       var style = this._getGraphicStyle(style),
3539           i, j;
3540
3541       if (style.visibility != "hidden" && style.opacity > 0) {
3542         this._context.beginPath();
3543         this._context.moveTo(coordinates[0][0][0], coordinates[0][0][1]);
3544
3545         for (i = 0; i < coordinates.length; i++) {
3546           for (j = 0; j < coordinates[i].length; j++) {
3547             this._context.lineTo(coordinates[i][j][0], coordinates[i][j][1]);
3548           }
3549         }
3550
3551         if (close) {
3552           this._context.closePath();
3553         }
3554
3555         if (close && style.doFill) {
3556           this._context.fillStyle = style.fill;
3557           this._context.globalAlpha = style.opacity * style.fillOpacity;
3558           this._context.fill();
3559         }
3560
3561         if (style.doStroke) {
3562           this._context.lineCap = this._context.lineJoin = "round";
3563           this._context.lineWidth = style.strokeWidthValue;
3564           this._context.strokeStyle = style.stroke;
3565
3566           this._context.globalAlpha = style.opacity * style.strokeOpacity;
3567           this._context.stroke();
3568         }
3569       }
3570     }
3571   });
3572
3573
3574 })(jQuery);
3575
3576 (function ($, undefined) {
3577   var _ieVersion = (function () {
3578     var v = 5, div = document.createElement("div"), a = div.all || [];
3579     while (div.innerHTML = "<!--[if gt IE " + (++v) + "]><br><![endif]-->", a[0]) { }
3580     return v > 6 ? v : !v;
3581   } ()),
3582
3583       _defaultOptions = {
3584         bbox: [-180, -85, 180, 85],
3585         bboxMax: [-180, -85, 180, 85],
3586         center: [0, 0],
3587         cursors: {
3588           "static": "default",
3589           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",
3590           zoom: "crosshair",
3591           drawPoint: "crosshair",
3592           drawLineString: "crosshair",
3593           drawPolygon: "crosshair",
3594           measureLength: "crosshair",
3595           measureArea: "crosshair"
3596         },
3597         measureLabels: {
3598           length: "{{=length.toFixed( 2 )}} m",
3599           area: "{{=area.toFixed( 2 )}} sq m"
3600         },
3601         drawStyle: {},
3602         shapeStyle: {},
3603         mode: "pan",
3604         pannable: true,
3605         scroll: "default",
3606         services: [
3607             {
3608               "class": "osm",
3609               type: "tiled",
3610               src: function (view) {
3611                 return "http://tile.openstreetmap.org/" + view.zoom + "/" + view.tile.column + "/" + view.tile.row + ".png";
3612               },
3613               attr: "&copy; OpenStreetMap &amp; contributors, CC-BY-SA"
3614             }
3615           ],
3616         tilingScheme: {
3617           tileWidth: 256,
3618           tileHeight: 256,
3619           levels: 18,
3620           basePixelSize: 156543.03392799936,
3621           origin: [-20037508.342787, 20037508.342787]
3622         },
3623         axisLayout: "map",
3624         zoom: 0,
3625         pixelSize: 0
3626       };
3627
3628   $.widget("geo.geomap", {
3629     // private widget members
3630     _$elem: undefined, //< map div for maps, service div for services
3631     _map: undefined, //< only defined in services
3632     _created: false,
3633
3634     _contentBounds: {},
3635
3636     _$resizeContainer: undefined, //< all elements that should match _contentBounds' size
3637
3638     _$eventTarget: undefined,
3639     _$contentFrame: undefined,
3640     _$existingChildren: undefined,
3641     _$attrList: undefined,
3642     _$servicesContainer: undefined,
3643
3644     _$panContainer: undefined, //< all non-service elements that move while panning
3645     _$shapesContainer: undefined,
3646     _$drawContainer: undefined,
3647     _$measureContainer: undefined,
3648     _$measureLabel: undefined,
3649
3650     _dpi: 96,
3651
3652     _currentServices: [], //< internal copy
3653
3654     _center: undefined,
3655     _pixelSize: undefined,
3656     _centerMax: undefined,
3657     _pixelSizeMax: undefined,
3658
3659     _userGeodetic: true,
3660
3661     _wheelTimeout: null,
3662     _wheelLevel: 0,
3663
3664     _zoomFactor: 2, //< determines what a zoom level means
3665
3666     _fullZoomFactor: 2, //< interactiveScale factor needed to zoom a whole level
3667     _partialZoomFactor: 1.18920711500273, //< interactiveScale factor needed to zoom a fraction of a level (the fourth root of 2)
3668
3669     _mouseDown: undefined,
3670     _inOp: undefined,
3671     _toolPan: undefined,
3672     _shiftZoom: undefined,
3673     _anchor: undefined,
3674     _current: undefined,
3675     _downDate: undefined,
3676     _moveDate: undefined,
3677     _clickDate: undefined,
3678     _lastMove: undefined,
3679     _lastDrag: undefined,
3680
3681     _windowHandler: null,
3682     _resizeTimeout: null,
3683
3684     _panning: undefined,
3685     _velocity: undefined,
3686     _friction: undefined,
3687
3688     _supportTouch: undefined,
3689     _softDblClick: undefined,
3690     _isTap: undefined,
3691     _isDbltap: undefined,
3692
3693     _isMultiTouch: undefined,
3694     _multiTouchAnchor: undefined, //< TouchList
3695     _multiTouchAnchorBbox: undefined, //< bbox
3696     _multiTouchCurrentBbox: undefined, //< bbox
3697
3698     _drawTimeout: null, //< used in drawPoint mode so we don't send two shape events on dbltap
3699     _drawPixels: [], //< an array of coordinate arrays for drawing lines & polygons, in pixel coordinates
3700     _drawCoords: [],
3701
3702     _graphicShapes: [], //< an array of objects containing style object refs & GeoJSON object refs
3703
3704     _initOptions: {},
3705
3706     _options: {},
3707
3708     options: $.extend({}, _defaultOptions),
3709
3710     _createWidget: function (options, element) {
3711       this._$elem = $(element);
3712
3713       if (this._$elem.is(".geo-service")) {
3714         var $contentFrame = this._$elem.closest( ".geo-content-frame" );
3715         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>');
3716         this._$shapesContainer = this._$elem.children(':last');
3717         this._graphicShapes = [];
3718         $.Widget.prototype._createWidget.apply(this, arguments);
3719         return;
3720       }
3721
3722       this._$elem.addClass("geo-map");
3723
3724       this._initOptions = options || {};
3725
3726       this._forcePosition(this._$elem);
3727
3728       this._$elem.css("text-align", "left");
3729
3730       var size = this._findMapSize();
3731       this._contentBounds = {
3732         x: parseInt(this._$elem.css("padding-left")),
3733         y: parseInt(this._$elem.css("padding-top")),
3734         width: size["width"],
3735         height: size["height"]
3736       };
3737
3738       this._createChildren();
3739
3740       this._center = this._centerMax = [0, 0];
3741
3742       this.options["pixelSize"] = this._pixelSize = this._pixelSizeMax = 156543.03392799936;
3743
3744       this._mouseDown =
3745           this._inOp =
3746           this._toolPan =
3747           this._shiftZoom =
3748           this._panning =
3749           this._isTap =
3750           this._isDbltap = false;
3751
3752       this._anchor = [ 0, 0 ];
3753       this._current = [ 0, 0 ];
3754       this._lastMove = [ 0, 0 ];
3755       this._lastDrag = [ 0, 0 ];
3756       this._velocity = [ 0, 0 ];
3757
3758       this._friction = [.8, .8];
3759
3760       this._downDate =
3761           this._moveDate =
3762           this._clickDate = 0;
3763
3764       this._drawPixels = [];
3765       this._drawCoords =  [];
3766       this._graphicShapes = [];
3767
3768
3769       $.Widget.prototype._createWidget.apply(this, arguments);
3770     },
3771
3772     _create: function () {
3773       this._options = this.options;
3774
3775       if (this._$elem.is(".geo-service")) {
3776         this._map = this._$elem.data( "geoMap" );
3777         this._$shapesContainer.geographics( );
3778         this._options["shapeStyle"] = this._$shapesContainer.geographics("option", "style");
3779         return;
3780       }
3781
3782       this._map = this;
3783
3784       this._supportTouch = "ontouchend" in document;
3785       this._softDblClick = this._supportTouch || _ieVersion == 7;
3786
3787       var geomap = this,
3788           touchStartEvent = this._supportTouch ? "touchstart" : "mousedown",
3789           touchStopEvent = this._supportTouch ? "touchend touchcancel" : "mouseup",
3790           touchMoveEvent = this._supportTouch ? "touchmove" : "mousemove";
3791
3792       $(document).keydown($.proxy(this._document_keydown, this));
3793
3794       this._$eventTarget.dblclick($.proxy(this._eventTarget_dblclick, this));
3795
3796       this._$eventTarget.bind(touchStartEvent, $.proxy(this._eventTarget_touchstart, this));
3797
3798       var dragTarget = (this._$eventTarget[0].setCapture) ? this._$eventTarget : $(document);
3799       dragTarget.bind(touchMoveEvent, $.proxy(this._dragTarget_touchmove, this));
3800       dragTarget.bind(touchStopEvent, $.proxy(this._dragTarget_touchstop, this));
3801
3802       this._$eventTarget.mousewheel($.proxy(this._eventTarget_mousewheel, this));
3803
3804       this._windowHandler = function () {
3805         if (geomap._resizeTimeout) {
3806           clearTimeout(geomap._resizeTimeout);
3807         }
3808         geomap._resizeTimeout = setTimeout(function () {
3809           if (geomap._created) {
3810             geomap._$elem.geomap("resize");
3811           }
3812         }, 500);
3813       };
3814
3815       $(window).resize(this._windowHandler);
3816
3817       this._$drawContainer.geographics({ style: this._initOptions.drawStyle || {} });
3818       this._options["drawStyle"] = this._$drawContainer.geographics("option", "style");
3819
3820       this._$shapesContainer.geographics( { style: this._initOptions.shapeStyle || { } } );
3821       this._options["shapeStyle"] = this._$shapesContainer.geographics("option", "style");
3822
3823       if (this._initOptions) {
3824         if (this._initOptions.tilingScheme) {
3825           this._setOption("tilingScheme", this._initOptions.tilingScheme, false);
3826         }
3827         if ( this._initOptions.services ) {
3828           // jQuery UI Widget Factory merges user services with our default, we want to clobber the default
3829           this._options[ "services" ] = $.merge( [ ], this._initOptions.services );
3830         }
3831         if (this._initOptions.bbox) {
3832           this._setOption("bbox", this._initOptions.bbox, false);
3833         }
3834         if (this._initOptions.center) {
3835           this._setOption("center", this._initOptions.center, false);
3836         }
3837         if (this._initOptions.zoom !== undefined) {
3838           this._setZoom(this._initOptions.zoom, false, false);
3839         }
3840       }
3841
3842       $.template( "geoMeasureLength", this._options[ "measureLabels" ].length );
3843       $.template( "geoMeasureArea", this._options[ "measureLabels" ].area );
3844
3845       this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
3846
3847       this._createServices();
3848       this._refresh();
3849
3850       this._created = true;
3851     },
3852
3853     _setOption: function (key, value, refresh) {
3854       if ( key == "pixelSize" ) {
3855         return;
3856       }
3857
3858       refresh = (refresh === undefined || refresh);
3859
3860       if ( this._$elem.is( ".geo-map" ) ) {
3861         this._panFinalize();
3862       }
3863
3864       switch (key) {
3865         case "bbox":
3866           this._userGeodetic = $.geo.proj && $.geo._isGeodetic( value );
3867           if ( this._userGeodetic ) {
3868             value = $.geo.proj.fromGeodetic( value );
3869           }
3870
3871           this._setBbox(value, false, refresh);
3872           value = this._getBbox();
3873           break;
3874
3875         case "center":
3876           this._userGeodetic = $.geo.proj && $.geo._isGeodetic( value );
3877           if ( this._userGeodetic ) {
3878             value = $.geo.proj.fromGeodetic( value );
3879           }
3880
3881           this._setCenterAndSize( value, this._pixelSize, false, refresh );
3882           break;
3883
3884         case "measureLabels":
3885           value = $.extend( this._options[ "measureLabels" ], value );
3886           $.template( "geoMeasureLength", value.length );
3887           $.template( "geoMeasureArea", value.area );
3888           break;
3889
3890         case "drawStyle":
3891           if (this._$drawContainer) {
3892             this._$drawContainer.geographics("option", "style", value);
3893             value = this._$drawContainer.geographics("option", "style");
3894           }
3895           break;
3896
3897         case "shapeStyle":
3898           if (this._$shapesContainer) {
3899             this._$shapesContainer.geographics("option", "style", value);
3900             value = this._$shapesContainer.geographics("option", "style");
3901           }
3902           break;
3903
3904         case "mode":
3905           this._resetDrawing( );
3906           this._$eventTarget.css("cursor", this._options["cursors"][value]);
3907           break;
3908
3909         case "zoom":
3910           this._setZoom(value, false, refresh);
3911           break;
3912       }
3913
3914       $.Widget.prototype._setOption.apply(this, arguments);
3915
3916       switch ( key ) {
3917         case "bbox":
3918         case "center":
3919           if ( this._userGeodetic ) {
3920             this._options[ "bbox" ] = $.geo.proj.toGeodetic( this._options[ "bbox" ] );
3921             this._options[ "center" ] = $.geo.proj.toGeodetic( this._center );
3922           }
3923           break;
3924
3925         case "tilingScheme":
3926           if ( value != null ) {
3927             this._pixelSizeMax = this._getPixelSize( 0 );
3928             this._centerMax = [
3929               value.origin[ 0 ] + this._pixelSizeMax * value.tileWidth / 2,
3930               value.origin[ 1 ] + this._pixelSizeMax * value.tileHeight / 2
3931             ];
3932           }
3933           break;
3934
3935         case "bboxMax":
3936           this._pixelSizeMax = this._getPixelSize( 0 );
3937
3938           if ( $.geo.proj && $.geo._isGeodetic( value ) ) {
3939             this._centerMax = $.geo.center( $.geo.proj.fromGeodetic( value ) );
3940           } else {
3941             this._centerMax = $.geo.center( value );
3942           }
3943           break;
3944
3945         case "services":
3946           this._createServices();
3947           if (refresh) {
3948             this._refresh();
3949           }
3950           break;
3951
3952         case "shapeStyle":
3953           if ( refresh ) {
3954             this._$shapesContainer.geographics("clear");
3955             this._refreshShapes( this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes );
3956           }
3957           break;
3958       }
3959     },
3960
3961     destroy: function () {
3962       if ( this._$elem.is(".geo-service") ) {
3963         this._$shapesContainer.geographics("destroy");
3964         this._$shapesContainer = undefined;
3965       } else {
3966         this._created = false;
3967
3968         $(window).unbind("resize", this._windowHandler);
3969
3970         for ( var i = 0; i < this._currentServices.length; i++ ) {
3971           this._currentServices[ i ].serviceContainer.geomap("destroy");
3972           $.geo["_serviceTypes"][this._currentServices[i].type].destroy(this, this._$servicesContainer, this._currentServices[i]);
3973         }
3974
3975         this._$shapesContainer.geographics("destroy");
3976         this._$shapesContainer = undefined;
3977         this._$drawContainer.geographics("destroy");
3978         this._$drawContainer = undefined;
3979
3980         this._$existingChildren.detach();
3981         this._$elem.html("");
3982         this._$elem.append(this._$existingChildren);
3983         this._$elem.removeClass("geo-map");
3984       }
3985
3986       $.Widget.prototype.destroy.apply(this, arguments);
3987     },
3988
3989     toMap: function (p) {
3990       p = this._toMap(p);
3991       return this._userGeodetic ? $.geo.proj.toGeodetic(p) : p;
3992     },
3993
3994     toPixel: function ( p, _center /* Internal Use Only */, _pixelSize /* Internal Use Only */ ) {
3995       return this._toPixel( $.geo.proj ? $.geo.proj.fromGeodetic( p ) : p, _center, _pixelSize );
3996     },
3997
3998     opacity: function ( value, _serviceContainer ) {
3999       if ( this._$elem.is( ".geo-service" ) ) {
4000         this._$elem.closest( ".geo-map" ).geomap( "opacity", value, this._$elem );
4001       } else {
4002         if ( value >= 0 || value <= 1 ) {
4003           for ( var i = 0; i < this._currentServices.length; i++ ) {
4004             var service = this._currentServices[ i ];
4005             if ( !_serviceContainer || service.serviceContainer[ 0 ] == _serviceContainer[ 0 ] ) {
4006               service.style.opacity = value;
4007               $.geo[ "_serviceTypes" ][ service.type ].opacity( this, service );
4008             }
4009           }
4010         }
4011       }
4012     },
4013
4014     toggle: function ( value, _serviceContainer ) {
4015       if ( this._$elem.is( ".geo-service" ) ) {
4016         this._$elem.closest( ".geo-map" ).geomap( "toggle", value, this._$elem );
4017       } else {
4018
4019         for ( var i = 0; i < this._currentServices.length; i++ ) {
4020           var service = this._currentServices[ i ];
4021
4022           if ( !_serviceContainer || service.serviceContainer[ 0 ] == _serviceContainer[ 0 ] ) {
4023             if ( value === undefined ) {
4024               // toggle visibility
4025               value = ( service.style.visibility !== "visible" );
4026             }
4027
4028             service.style.visibility = ( value ? "visible" : "hidden" );
4029
4030             service.serviceContainer.toggle( value );
4031
4032             if ( value ) {
4033               $.geo[ "_serviceTypes" ][ service.type ].refresh( this, service );
4034             }
4035           }
4036         }
4037       }
4038     },
4039
4040     zoom: function (numberOfLevels) {
4041       if (numberOfLevels != null) {
4042         this._setZoom(this._options["zoom"] + numberOfLevels, false, true);
4043       }
4044     },
4045
4046     refresh: function () {
4047       this._refresh();
4048     },
4049
4050     resize: function () {
4051       var size = this._findMapSize(),
4052           dx = size["width"]/2 - this._contentBounds.width/2,
4053           dy = size["height"]/2 - this._contentBounds.height/2,
4054           i;
4055
4056       this._contentBounds = {
4057         x: parseInt(this._$elem.css("padding-left")),
4058         y: parseInt(this._$elem.css("padding-top")),
4059         width: size["width"],
4060         height: size["height"]
4061       };
4062
4063       this._$resizeContainer.css( {
4064         width: size["width"],
4065         height: size["height"]
4066       } );
4067
4068       for (i = 0; i < this._currentServices.length; i++) {
4069         $.geo["_serviceTypes"][this._currentServices[i].type].resize(this, this._currentServices[i]);
4070       }
4071
4072       this._$elem.find( ".geo-graphics" ).css( {
4073         width: size["width"],
4074         height: size["height"]
4075       }).geographics( "resize" );
4076
4077       for (i = 0; i < this._drawPixels.length; i++) {
4078         this._drawPixels[i][0] += dx;
4079         this._drawPixels[i][1] += dy;
4080       }
4081
4082       this._setCenterAndSize(this._center, this._pixelSize, false, true);
4083     },
4084
4085     append: function ( shape, style, label, refresh ) {
4086       if ( shape && $.isPlainObject( shape ) ) {
4087         var shapes, arg, i, realStyle, realLabel, realRefresh;
4088
4089         if ( shape.type == "FeatureCollection" ) {
4090           shapes = shape.features;
4091         } else {
4092           shapes = $.isArray( shape ) ? shape : [ shape ];
4093         }
4094
4095         for ( i = 1; i < arguments.length; i++ ) {
4096           arg = arguments[ i ];
4097
4098           if ( typeof arg === "object" ) {
4099             realStyle = arg;
4100           } else if ( typeof arg === "number" || typeof arg === "string" ) {
4101             realLabel = arg;
4102           } else if ( typeof arg === "boolean" ) {
4103             realRefresh = arg;
4104           }
4105         }
4106
4107         for ( i = 0; i < shapes.length; i++ ) {
4108           if ( shapes[ i ].type != "Point" ) {
4109             var bbox = $.geo.bbox( shapes[ i ] );
4110             if ( $.geo.proj && $.geo._isGeodetic( bbox ) ) {
4111               bbox = $.geo.proj.fromGeodetic( bbox );
4112             }
4113             $.data( shapes[ i ], "geoBbox", bbox );
4114           }
4115
4116           this._graphicShapes.push( {
4117             shape: shapes[ i ],
4118             style: realStyle,
4119             label: realLabel
4120           } );
4121         }
4122
4123         if ( realRefresh === undefined || realRefresh ) {
4124           this._refresh( );
4125         }
4126       }
4127     },
4128
4129     empty: function ( refresh ) {
4130       for ( var i = 0; i < this._graphicShapes.length; i++ ) {
4131         $.removeData( this._graphicShapes[ i ].shape, "geoBbox" );
4132       }
4133
4134       this._graphicShapes = [];
4135
4136       if ( refresh === undefined || refresh ) {
4137         this._refresh();
4138       }
4139     },
4140
4141     find: function ( selector, pixelTolerance ) {
4142       var isPoint = $.isPlainObject( selector ),
4143           searchPixel = isPoint ? this._map.toPixel( selector.coordinates ) : undefined,
4144           mapTol = this._map._pixelSize * pixelTolerance,
4145           result = [],
4146           graphicShape,
4147           geometries,
4148           curGeom,
4149           i = 0;
4150
4151       for ( ; i < this._graphicShapes.length; i++ ) {
4152         graphicShape = this._graphicShapes[ i ];
4153
4154         if ( isPoint ) {
4155           if ( graphicShape.shape.type == "Point" ) {
4156             if ( $.geo.distance( graphicShape.shape, selector ) <= mapTol ) {
4157               result.push( graphicShape.shape );
4158             }
4159           } else {
4160             var bbox = $.data( graphicShape.shape, "geoBbox" ),
4161                 bboxPolygon = {
4162                   type: "Polygon",
4163                   coordinates: [ [
4164                     [bbox[0], bbox[1]],
4165                     [bbox[0], bbox[3]],
4166                     [bbox[2], bbox[3]],
4167                     [bbox[2], bbox[1]],
4168                     [bbox[0], bbox[1]]
4169                   ] ]
4170                 },
4171                 projectedPoint = {
4172                   type: "Point",
4173                   coordinates: $.geo.proj && $.geo._isGeodetic( selector.coordinates ) ? $.geo.proj.fromGeodetic( selector.coordinates ) : selector.coordinates
4174                 };
4175
4176             if ( $.geo.distance( bboxPolygon, projectedPoint, true ) <= mapTol ) {
4177               geometries = $.geo._flatten( graphicShape.shape );
4178               for ( curGeom = 0; curGeom < geometries.length; curGeom++ ) {
4179                 if ( $.geo.distance( geometries[ curGeom ], selector ) <= mapTol ) {
4180                   result.push( graphicShape.shape );
4181                   break;
4182                 }
4183               }
4184             }
4185           }
4186         } else {
4187           result.push( graphicShape.shape );
4188         }
4189       }
4190
4191       if ( this._$elem.is( ".geo-map" ) ) {
4192         this._$elem.find( ".geo-service" ).each( function( ) {
4193           result = $.merge( result, $( this ).geomap( "find", selector, pixelTolerance ) );
4194         } );
4195       }
4196
4197       return result;
4198     },
4199
4200     remove: function ( shape, refresh ) {
4201       for ( var i = 0; i < this._graphicShapes.length; i++ ) {
4202         if ( this._graphicShapes[ i ].shape == shape ) {
4203           $.removeData( shape, "geoBbox" );
4204           var rest = this._graphicShapes.slice( i + 1 );
4205           this._graphicShapes.length = i;
4206           this._graphicShapes.push.apply( this._graphicShapes, rest );
4207           break;
4208         }
4209       }
4210
4211       if ( refresh === undefined || refresh ) {
4212         this._refresh();
4213       }
4214     },
4215
4216     _getBbox: function (center, pixelSize) {
4217       center = center || this._center;
4218       pixelSize = pixelSize || this._pixelSize;
4219
4220       // calculate the internal bbox
4221       var halfWidth = this._contentBounds[ "width" ] / 2 * pixelSize,
4222           halfHeight = this._contentBounds[ "height" ] / 2 * pixelSize;
4223       return [ center[ 0 ] - halfWidth, center[ 1 ] - halfHeight, center[ 0 ] + halfWidth, center[ 1 ] + halfHeight ];
4224     },
4225
4226     _setBbox: function (value, trigger, refresh) {
4227       var center = [value[0] + (value[2] - value[0]) / 2, value[1] + (value[3] - value[1]) / 2],
4228           pixelSize = Math.max($.geo.width(value, true) / this._contentBounds.width, $.geo.height(value, true) / this._contentBounds.height);
4229
4230       if (this._options["tilingScheme"]) {
4231         var zoom = this._getZoom( center, pixelSize );
4232         pixelSize = this._getPixelSize( zoom );
4233       } else {
4234         if ( this._getZoom( center, pixelSize ) < 0 ) {
4235           pixelSize = this._pixelSizeMax;
4236         }
4237       }
4238
4239       this._setCenterAndSize(center, pixelSize, trigger, refresh);
4240     },
4241
4242     _getBboxMax: function () {
4243       // calculate the internal bboxMax
4244       var halfWidth = this._contentBounds["width"] / 2 * this._pixelSizeMax,
4245         halfHeight = this._contentBounds["height"] / 2 * this._pixelSizeMax;
4246       return [this._centerMax[0] - halfWidth, this._centerMax[1] - halfHeight, this._centerMax[0] + halfWidth, this._centerMax[1] + halfHeight];
4247     },
4248
4249     _getCenter: function () {
4250       return this._center;
4251     },
4252
4253     _getContentBounds: function () {
4254       return this._contentBounds;
4255     },
4256
4257     _getServicesContainer: function () {
4258       return this._$servicesContainer;
4259     },
4260
4261     _getZoom: function ( center, pixelSize ) {
4262       center = center || this._center;
4263       pixelSize = pixelSize || this._pixelSize;
4264
4265       // calculate the internal zoom level, vs. public zoom property
4266       var tilingScheme = this._options["tilingScheme"];
4267       if ( tilingScheme ) {
4268         if ( tilingScheme.pixelSizes != null ) {
4269           var roundedPixelSize = Math.floor(pixelSize * 1000),
4270               levels = tilingScheme.pixelSizes.length,
4271               i = levels - 1;
4272
4273           for ( ; i >= 0; i-- ) {
4274             if ( Math.floor( tilingScheme.pixelSizes[ i ] * 1000 ) >= roundedPixelSize ) {
4275               return i;
4276             }
4277           }
4278
4279           return 0;
4280         } else {
4281           return Math.max( Math.round( Math.log( tilingScheme.basePixelSize / pixelSize) / Math.log( 2 ) ), 0 );
4282         }
4283       } else {
4284         var ratio = this._contentBounds["width"] / this._contentBounds["height"],
4285             bbox = $.geo.reaspect( this._getBbox( center, pixelSize ), ratio, true ),
4286             bboxMax = $.geo.reaspect(this._getBboxMax(), ratio, true);
4287
4288         return Math.max( Math.round( Math.log($.geo.width(bboxMax, true) / $.geo.width(bbox, true)) / Math.log(this._zoomFactor) ), 0 );
4289       }
4290     },
4291
4292     _setZoom: function ( value, trigger, refresh ) {
4293       value = Math.max( value, 0 );
4294
4295       this._setCenterAndSize( this._center, this._getPixelSize( value ), trigger, refresh );
4296     },
4297
4298     _createChildren: function () {
4299       this._$existingChildren = this._$elem.children().detach();
4300
4301       this._forcePosition(this._$existingChildren);
4302
4303       this._$existingChildren.css("-moz-user-select", "none");
4304
4305       var contentSizeCss = "width:" + this._contentBounds["width"] + "px; height:" + this._contentBounds["height"] + "px; margin:0; padding:0;",
4306           contentPosCss = "position:absolute; left:0; top:0;";
4307
4308       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>');
4309       this._$eventTarget = this._$contentFrame = this._$elem.children(':first');
4310
4311       this._$contentFrame.append('<div class="geo-services-container" style="' + contentPosCss + contentSizeCss + '"></div>');
4312       this._$servicesContainer = this._$contentFrame.children(':last');
4313
4314       this._$contentFrame.append('<div class="geo-shapes-container" style="' + contentPosCss + contentSizeCss + '"></div>');
4315       this._$shapesContainer = this._$contentFrame.children(':last');
4316
4317       this._$contentFrame.append( '<ul style="position: absolute; bottom: 8px; left: 8px; list-style-type: none; max-width: 50%; padding: 0; margin: 0;"></ul>' );
4318       this._$attrList = this._$contentFrame.children( ":last" );
4319
4320       this._$contentFrame.append('<div class="geo-draw-container" style="' + contentPosCss + contentSizeCss + '"></div>');
4321       this._$drawContainer = this._$contentFrame.children(':last');
4322
4323       this._$contentFrame.append('<div class="geo-measure-container" style="' + contentPosCss + contentSizeCss + '"><div class="geo-measure-label" style="' + contentPosCss + '; display: none;"></div></div>');
4324       this._$measureContainer = this._$contentFrame.children(':last');
4325       this._$measureLabel = this._$measureContainer.children();
4326
4327       this._$panContainer = $( [ this._$shapesContainer[ 0 ], this._$drawContainer[ 0 ], this._$measureContainer[ 0 ] ] );
4328
4329       this._$resizeContainer = $( [ this._$contentFrame[ 0 ], this._$servicesContainer[ 0 ], this._$eventTarget[ 0 ], this._$measureContainer[ 0 ] ] ); 
4330
4331       this._$contentFrame.append(this._$existingChildren);
4332
4333       if ( ! $("#geo-measure-style").length ) {
4334         $("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>' );
4335       }
4336     },
4337
4338     _createServices: function () {
4339       var service, i;
4340
4341       for ( i = 0; i < this._currentServices.length; i++ ) {
4342         this._currentServices[ i ].serviceContainer.geomap( "destroy" );
4343         $.geo[ "_serviceTypes" ][ this._currentServices[ i ].type ].destroy( this, this._$servicesContainer, this._currentServices[ i ] );
4344       }
4345
4346       this._currentServices = [ ];
4347       this._$servicesContainer.html( "" );
4348       this._$attrList.html( "" );
4349
4350       for ( i = 0; i < this._options[ "services" ].length; i++ ) {
4351         service = this._currentServices[ i ] = $.extend( { }, this._options[ "services" ][ i ] );
4352
4353         // default the service style property on our copy
4354         service.style = $.extend( {
4355                           visibility: "visible",
4356                           opacity: 1
4357                         }, service.style );
4358
4359         var idString = service.id ? ' id="' + service.id + '"' : "",
4360             classString = 'class="geo-service ' + ( service["class"] ? service["class"] : '' ) + '"',
4361             scHtml = '<div ' + idString + classString + ' style="position:absolute; left:0; top:0; width:32px; height:32px; margin:0; padding:0; display:' + ( service.style.visibility === "visible" ? "block" : "none" ) + ';"></div>',
4362             servicesContainer;
4363
4364         this._$servicesContainer.append( scHtml );
4365         serviceContainer = this._$servicesContainer.children( ":last" );
4366         this._currentServices[ i ].serviceContainer = serviceContainer;
4367         
4368         $.geo[ "_serviceTypes" ][ service.type ].create( this, serviceContainer, service, i );
4369
4370         serviceContainer.data( "geoMap", this ).geomap();
4371
4372         if ( service.attr ) {
4373           this._$attrList.append( '<li>' + service.attr + '</li>' );
4374         }
4375       }
4376
4377       this._$attrList.find( "a" ).css( {
4378         position: "relative",
4379         zIndex: 100
4380       } );
4381     },
4382
4383     _refreshDrawing: function ( ) {
4384       this._$drawContainer.geographics("clear");
4385
4386       if ( this._drawPixels.length > 0 ) {
4387         var mode = this._options[ "mode" ],
4388             pixels = this._drawPixels,
4389             coords = this._drawCoords,
4390             label,
4391             labelShape,
4392             labelPixel,
4393             widthOver,
4394             heightOver;
4395
4396         switch ( mode ) {
4397           case "measureLength":
4398             mode = "drawLineString";
4399             labelShape = {
4400               type: "LineString",
4401               coordinates: coords
4402             };
4403             label = $.render( { length: $.geo.length( labelShape, true ) }, "geoMeasureLength" );
4404             labelPixel = $.merge( [], pixels[ pixels.length - 1 ] );
4405             break;
4406
4407           case "measureArea":
4408             mode = "drawPolygon";
4409
4410             labelShape = {
4411               type: "Polygon",
4412               coordinates: [ $.merge( [ ], coords ) ]
4413             };
4414             labelShape.coordinates[ 0 ].push( coords[ 0 ] );
4415
4416             label = $.render( { area: $.geo.area( labelShape, true ) }, "geoMeasureArea" );
4417             labelPixel = $.merge( [], pixels[ pixels.length - 1 ] );
4418             pixels = [ pixels ];
4419             break;
4420
4421           case "drawPolygon":
4422             pixels = [ pixels ];
4423             break;
4424         }
4425
4426         this._$drawContainer.geographics( mode, pixels );
4427         
4428         if ( label ) {
4429           this._$measureLabel.html( label );
4430
4431           widthOver = this._contentBounds.width - ( this._$measureLabel.outerWidth( true ) + labelPixel[ 0 ] );
4432           heightOver = this._contentBounds.height - ( this._$measureLabel.outerHeight( true ) + labelPixel[ 1 ] );
4433
4434           if ( widthOver < 0 ) {
4435             labelPixel[ 0 ] += widthOver;
4436           }
4437
4438           if ( heightOver < 0 ) {
4439             labelPixel[ 1 ] += heightOver;
4440           }
4441
4442           this._$measureLabel.css( {
4443             left: labelPixel[ 0 ],
4444             top: labelPixel[ 1 ]
4445           } ).show();
4446         }
4447       }
4448     },
4449
4450     _resetDrawing: function () {
4451       this._drawPixels = [];
4452       this._drawCoords = [];
4453       this._$drawContainer.geographics("clear");
4454       this._$measureLabel.hide();
4455     },
4456
4457     _refreshShapes: function (geographics, shapes, styles, labels, center, pixelSize) {
4458       var i, mgi,
4459           shape,
4460           shapeBbox,
4461           style,
4462           label,
4463           hasLabel,
4464           labelPixel,
4465           bbox = this._map._getBbox(center, pixelSize);
4466
4467       for (i = 0; i < shapes.length; i++) {
4468         shape = shapes[i].shape || shapes[i];
4469         shape = shape.geometry || shape;
4470         shapeBbox = $.data(shape, "geoBbox");
4471
4472         if ( shapeBbox && $.geo._bboxDisjoint( bbox, shapeBbox ) ) {
4473           continue;
4474         }
4475
4476         style = $.isArray(styles) ? styles[i].style : styles;
4477         label = $.isArray(labels) ? labels[i].label : labels;
4478         hasLabel = ( label !== undefined );
4479         labelPixel = undefined;
4480
4481         switch (shape.type) {
4482           case "Point":
4483             labelPixel = this._map.toPixel( shape.coordinates, center, pixelSize );
4484             this._$shapesContainer.geographics("drawPoint", labelPixel, style);
4485             break;
4486           case "LineString":
4487             this._$shapesContainer.geographics("drawLineString", this._map.toPixel(shape.coordinates, center, pixelSize), style);
4488             if ( hasLabel ) {
4489               labelPixel = this._map.toPixel( $.geo.pointAlong( shape, .5 ).coordinates, center, pixelSize );
4490             }
4491             break;
4492           case "Polygon":
4493             this._$shapesContainer.geographics("drawPolygon", this._map.toPixel(shape.coordinates, center, pixelSize), style);
4494             if ( hasLabel ) {
4495               labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
4496             }
4497             break;
4498           case "MultiPoint":
4499             for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
4500               this._$shapesContainer.geographics("drawPoint", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
4501             }
4502             if ( hasLabel ) {
4503               labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
4504             }
4505             break;
4506           case "MultiLineString":
4507             for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
4508               this._$shapesContainer.geographics("drawLineString", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
4509             }
4510             if ( hasLabel ) {
4511               labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
4512             }
4513             break;
4514           case "MultiPolygon":
4515             for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
4516               this._$shapesContainer.geographics("drawPolygon", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
4517             }
4518             if ( hasLabel ) {
4519               labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
4520             }
4521             break;
4522
4523           case "GeometryCollection":
4524             this._refreshShapes(geographics, shape.geometries, style, label, center, pixelSize);
4525             break;
4526         }
4527
4528         if ( hasLabel && labelPixel ) {
4529           this._$shapesContainer.geographics( "drawLabel", labelPixel, label );
4530         }
4531       }
4532     },
4533
4534     _findMapSize: function () {
4535       // really, really attempt to find a size for this thing
4536       // even if it's hidden (look at parents)
4537       var size = { width: 0, height: 0 },
4538         sizeContainer = this._$elem;
4539
4540       while (sizeContainer.size() && !(size["width"] > 0 && size["height"] > 0)) {
4541         size = { width: sizeContainer.width(), height: sizeContainer.height() };
4542         if (size["width"] <= 0 || size["height"] <= 0) {
4543           size = { width: parseInt(sizeContainer.css("width")), height: parseInt(sizeContainer.css("height")) };
4544         }
4545         sizeContainer = sizeContainer.parent();
4546       }
4547       return size;
4548     },
4549
4550     _forcePosition: function (elem) {
4551       var cssPosition = elem.css("position");
4552       if (cssPosition != "relative" && cssPosition != "absolute" && cssPosition != "fixed") {
4553         elem.css("position", "relative");
4554       }
4555     },
4556
4557     _getPixelSize: function ( zoom ) {
4558       var tilingScheme = this._options["tilingScheme"];
4559       if (tilingScheme != null) {
4560         if (zoom === 0) {
4561           return tilingScheme.pixelSizes != null ? tilingScheme.pixelSizes[0] : tilingScheme.basePixelSize;
4562         }
4563
4564         zoom = Math.round(zoom);
4565         zoom = Math.max(zoom, 0);
4566         var levels = tilingScheme.pixelSizes != null ? tilingScheme.pixelSizes.length : tilingScheme.levels;
4567         zoom = Math.min(zoom, levels - 1);
4568
4569         if (tilingScheme.pixelSizes != null) {
4570           return tilingScheme.pixelSizes[zoom];
4571         } else {
4572           return tilingScheme.basePixelSize / Math.pow(2, zoom);
4573         }
4574       } else {
4575         var bbox = $.geo.scaleBy( this._getBboxMax(), 1 / Math.pow( this._zoomFactor, zoom ), true );
4576         return Math.max( $.geo.width( bbox, true ) / this._contentBounds.width, $.geo.height( bbox, true ) / this._contentBounds.height );
4577       }
4578     },
4579
4580     _getZoomCenterAndSize: function ( anchor, zoomDelta, full ) {
4581       var zoomFactor = ( full ? this._fullZoomFactor : this._partialZoomFactor ),
4582           scale = Math.pow( zoomFactor, -zoomDelta ),
4583           pixelSize,
4584           zoomLevel;
4585
4586       if ( this._options[ "tilingScheme" ] ) {
4587         zoomLevel = this._getZoom(this._center, this._pixelSize * scale);
4588         pixelSize = this._getPixelSize(zoomLevel);
4589       } else {
4590         pixelSize = this._pixelSize * scale;
4591
4592         if ( this._getZoom( this._center, pixelSize ) < 0 ) {
4593           pixelSize = this._pixelSizeMax;
4594         }
4595       }
4596
4597       var ratio = pixelSize / this._pixelSize,
4598           anchorMapCoord = this._toMap(anchor),
4599           centerDelta = [(this._center[0] - anchorMapCoord[0]) * ratio, (this._center[1] - anchorMapCoord[1]) * ratio],
4600           scaleCenter = [anchorMapCoord[0] + centerDelta[0], anchorMapCoord[1] + centerDelta[1]];
4601
4602       return { pixelSize: pixelSize, center: scaleCenter };
4603     },
4604
4605     _mouseWheelFinish: function () {
4606       this._wheelTimeout = null;
4607
4608       if (this._wheelLevel != 0) {
4609         var wheelCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, this._options[ "tilingScheme" ] != null );
4610
4611         this._setCenterAndSize(wheelCenterAndSize.center, wheelCenterAndSize.pixelSize, true, true);
4612
4613         this._wheelLevel = 0;
4614       } else {
4615         this._refresh();
4616       }
4617     },
4618
4619     _panEnd: function () {
4620       this._velocity = [
4621         (this._velocity[0] > 0 ? Math.floor(this._velocity[0] * this._friction[0]) : Math.ceil(this._velocity[0] * this._friction[0])),
4622         (this._velocity[1] > 0 ? Math.floor(this._velocity[1] * this._friction[1]) : Math.ceil(this._velocity[1] * this._friction[1]))
4623       ];
4624
4625       if (Math.abs(this._velocity[0]) < 4 && Math.abs(this._velocity[1]) < 4) {
4626         this._panFinalize();
4627       } else {
4628         this._current = [
4629           this._current[0] + this._velocity[0],
4630           this._current[1] + this._velocity[1]
4631         ];
4632
4633         this._panMove();
4634         setTimeout($.proxy(this._panEnd, this), 30);
4635       }
4636     },
4637
4638     _panFinalize: function () {
4639       if (this._panning) {
4640         this._velocity = [0, 0];
4641
4642         var dx = this._current[0] - this._anchor[0],
4643             dy = this._current[1] - this._anchor[1],
4644             image = this._options[ "axisLayout" ] === "image",
4645             dxMap = -dx * this._pixelSize,
4646             dyMap = ( image ? -1 : 1 ) * dy * this._pixelSize;
4647
4648         this._$panContainer.css({ left: 0, top: 0 });
4649
4650         this._$servicesContainer.find( ".geo-shapes-container" ).css( { left: 0, top: 0 } );
4651
4652         this._setCenterAndSize([this._center[0] + dxMap, this._center[1] + dyMap], this._pixelSize, true, true);
4653
4654         this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
4655
4656         this._inOp = false;
4657         this._anchor = this._current;
4658         this._mouseDown = this._toolPan = this._panning = false;
4659       }
4660     },
4661
4662     _panMove: function () {
4663       if ( ! this._options[ "pannable" ] ) {
4664         return;
4665       }
4666
4667       var dx = this._current[0] - this._lastDrag[0],
4668           dy = this._current[1] - this._lastDrag[1],
4669           i = 0,
4670           service,
4671           translateObj;
4672
4673       if (this._toolPan || dx > 3 || dx < -3 || dy > 3 || dy < -3) {
4674         if (!this._toolPan) {
4675           this._toolPan = true;
4676           this._$eventTarget.css("cursor", this._options["cursors"]["pan"]);
4677         }
4678
4679         if (this._mouseDown) {
4680           this._velocity = [dx, dy];
4681         }
4682
4683         if (dx != 0 || dy != 0) {
4684           this._panning = true;
4685           this._lastDrag = this._current;
4686
4687           translateObj = {
4688             left: function (index, value) {
4689               return parseInt(value) + dx;
4690             },
4691             top: function (index, value) {
4692               return parseInt(value) + dy;
4693             }
4694           };
4695
4696           for ( i = 0; i < this._currentServices.length; i++ ) {
4697             service = this._currentServices[ i ];
4698             $.geo[ "_serviceTypes" ][ service.type ].interactivePan( this, service, dx, dy );
4699             
4700             service.serviceContainer.find( ".geo-shapes-container" ).css( translateObj );
4701           }
4702
4703           this._$panContainer.css( translateObj );
4704
4705           //this._refreshDrawing();
4706         }
4707       }
4708     },
4709
4710     _refresh: function () {
4711       var service,
4712           i = 0;
4713
4714       if ( this._$elem.is( ".geo-map" ) ) {
4715         for ( ; i < this._currentServices.length; i++ ) {
4716           service = this._currentServices[ i ];
4717
4718           if ( !this._mouseDown && $.geo[ "_serviceTypes" ][ service.type ] !== null ) {
4719             $.geo[ "_serviceTypes" ][ service.type ].refresh( this, service );
4720             service.serviceContainer.geomap( "refresh" );
4721           }
4722         }
4723       }
4724
4725       if ( this._$shapesContainer ) {
4726         this._$shapesContainer.geographics( "clear" );
4727         if ( this._graphicShapes.length > 0 ) {
4728           this._refreshShapes( this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes );
4729         }
4730       }
4731     },
4732
4733     _setCenterAndSize: function (center, pixelSize, trigger, refresh) {
4734       if ( ! $.isArray( center ) || center.length != 2 || typeof center[ 0 ] !== "number" || typeof center[ 1 ] !== "number" ) {
4735         return;
4736       }
4737
4738       // the final call during any extent change
4739       if (this._pixelSize != pixelSize) {
4740         this._$elem.find( ".geo-shapes-container" ).geographics("clear");
4741         for (var i = 0; i < this._currentServices.length; i++) {
4742           var service = this._currentServices[i];
4743           $.geo["_serviceTypes"][service.type].interactiveScale(this, service, center, pixelSize);
4744         }
4745       }
4746
4747       this._center = $.merge( [ ], center );
4748       this._options["pixelSize"] = this._pixelSize = pixelSize;
4749
4750       if ( this._userGeodetic ) {
4751         this._options["bbox"] = $.geo.proj.toGeodetic( this._getBbox() );
4752         this._options["center"] = $.geo.proj.toGeodetic( this._center );
4753       } else {
4754         this._options["bbox"] = this._getBbox();
4755         this._options["center"] = $.merge( [ ], center );
4756       }
4757
4758       this._options["zoom"] = this._getZoom();
4759
4760       if (this._drawCoords.length > 0) {
4761         this._drawPixels = this._toPixel(this._drawCoords);
4762       }
4763
4764       if (trigger) {
4765         this._trigger("bboxchange", window.event, { bbox: $.merge( [ ], this._options["bbox"] ) });
4766       }
4767
4768       if (refresh) {
4769         this._refresh();
4770         this._refreshDrawing();
4771       }
4772     },
4773
4774     _toMap: function (p, center, pixelSize) {
4775       // ignores $.geo.proj
4776
4777       center = center || this._center;
4778       pixelSize = pixelSize || this._pixelSize;
4779
4780       var isMultiPointOrLineString = $.isArray( p[ 0 ] ),
4781           isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray( p[ 0 ][ 0 ] ),
4782           isMultiPolygon = isMultiLineStringOrPolygon && $.isArray( p[ 0 ][ 0 ][ 0 ] ),
4783           width = this._contentBounds["width"],
4784           height = this._contentBounds["height"],
4785           halfWidth = width / 2 * pixelSize,
4786           halfHeight = height / 2 * pixelSize,
4787           bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight],
4788           xRatio = $.geo.width(bbox, true) / width,
4789           yRatio = $.geo.height(bbox, true) / height,
4790           yOffset,
4791           image = this._options[ "axisLayout" ] === "image",
4792           result = [],
4793           i, j, k;
4794
4795       if ( !isMultiPolygon ) {
4796         if ( !isMultiLineStringOrPolygon ) {
4797           if ( !isMultiPointOrLineString ) {
4798             p = [ p ];
4799           }
4800           p = [ p ];
4801         }
4802         p = [ p ];
4803       }
4804
4805       for ( i = 0; i < p.length; i++ ) {
4806         result[ i ] = [ ];
4807         for ( j = 0; j < p[ i ].length; j++ ) {
4808           result[ i ][ j ] = [ ];
4809           for ( k = 0; k < p[ i ][ j ].length; k++ ) {
4810             yOffset = (p[ i ][ j ][ k ][1] * yRatio);
4811             result[ i ][ j ][ k ] = [
4812               bbox[ 0 ] + ( p[ i ][ j ][ k ][ 0 ] * xRatio ),
4813               image ? bbox[ 1 ] + yOffset : bbox[ 3 ] - yOffset
4814             ];
4815           }
4816         }
4817       }
4818
4819       return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
4820     },
4821
4822     _toPixel: function (p, center, pixelSize) {
4823       // ignores $.geo.proj
4824
4825       center = center || this._center;
4826       pixelSize = pixelSize || this._pixelSize;
4827
4828       var isMultiPointOrLineString = $.isArray( p[ 0 ] ),
4829           isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray( p[ 0 ][ 0 ] ),
4830           isMultiPolygon = isMultiLineStringOrPolygon && $.isArray( p[ 0 ][ 0 ][ 0 ] ),
4831           width = this._contentBounds["width"],
4832           height = this._contentBounds["height"],
4833           halfWidth = width / 2 * pixelSize,
4834           halfHeight = height / 2 * pixelSize,
4835           bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight],
4836           bboxWidth = $.geo.width(bbox, true),
4837           bboxHeight = $.geo.height(bbox, true),
4838           image = this._options[ "axisLayout" ] === "image",
4839           xRatio = width / bboxWidth,
4840           yRatio = height / bboxHeight,
4841           result = [ ],
4842           i, j, k;
4843
4844       if ( !isMultiPolygon ) {
4845         if ( !isMultiLineStringOrPolygon ) {
4846           if ( !isMultiPointOrLineString ) {
4847             p = [ p ];
4848           }
4849           p = [ p ];
4850         }
4851         p = [ p ];
4852       }
4853
4854       for ( i = 0; i < p.length; i++ ) {
4855         result[ i ] = [ ];
4856         for ( j = 0; j < p[ i ].length; j++ ) {
4857           result[ i ][ j ] = [ ];
4858           for ( k = 0; k < p[ i ][ j ].length; k++ ) {
4859             result[ i ][ j ][ k ] = [
4860               Math.round( ( p[ i ][ j ][ k ][ 0 ] - bbox[ 0 ] ) * xRatio ),
4861               Math.round( ( image ? p[ i ][ j ][ k ][ 1 ] - bbox[ 1 ] : bbox[ 3 ] - p[ i ][ j ][ k ][ 1 ] ) * yRatio )
4862             ];
4863           }
4864         }
4865       }
4866
4867       return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
4868     },
4869
4870     _zoomTo: function (coord, zoom, trigger, refresh) {
4871       zoom = zoom < 0 ? 0 : zoom;
4872
4873       var pixelSize = this._getPixelSize( zoom );
4874
4875       this._setCenterAndSize( coord, pixelSize, trigger, refresh );
4876     },
4877
4878     _document_keydown: function (e) {
4879       var len = this._drawCoords.length;
4880       if (len > 0 && e.which == 27) {
4881         if (len <= 2) {
4882           this._resetDrawing();
4883           this._inOp = false;
4884         } else {
4885           this._drawCoords[len - 2] = $.merge( [], this._drawCoords[ len - 1 ] );
4886           this._drawPixels[len - 2] = $.merge( [], this._drawPixels[ len - 1 ] );
4887
4888           this._drawCoords.length--;
4889           this._drawPixels.length--;
4890
4891           this._refreshDrawing();
4892         }
4893       }
4894     },
4895
4896     _eventTarget_dblclick_zoom: function(e) {
4897       this._trigger("dblclick", e, { type: "Point", coordinates: this.toMap(this._current) });
4898       if (!e.isDefaultPrevented()) {
4899         var centerAndSize = this._getZoomCenterAndSize(this._current, 1, true );
4900         this._setCenterAndSize(centerAndSize.center, centerAndSize.pixelSize, true, true);
4901       }
4902     },
4903
4904     _eventTarget_dblclick: function (e) {
4905       if ( this._options[ "mode" ] === "static" ) {
4906         return;
4907       }
4908
4909       this._panFinalize();
4910
4911       if (this._drawTimeout) {
4912         window.clearTimeout(this._drawTimeout);
4913         this._drawTimeout = null;
4914       }
4915
4916       var offset = $(e.currentTarget).offset();
4917
4918       switch (this._options["mode"]) {
4919         case "drawLineString":
4920           if ( this._drawCoords.length > 1 && ! ( this._drawCoords[0][0] == this._drawCoords[1][0] &&
4921                                                   this._drawCoords[0][1] == this._drawCoords[1][1] ) ) {
4922               this._drawCoords.length--;
4923               this._trigger( "shape", e, {
4924                 type: "LineString",
4925                 coordinates: this._userGeodetic ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords
4926               } );
4927           } else {
4928             this._eventTarget_dblclick_zoom(e);
4929           }
4930           this._resetDrawing();
4931           break;
4932
4933         case "drawPolygon":
4934           if ( this._drawCoords.length > 1 && ! ( this._drawCoords[0][0] == this._drawCoords[1][0] &&
4935                                                   this._drawCoords[0][1] == this._drawCoords[1][1] ) ) {
4936             var endIndex = this._drawCoords.length - 1;
4937             if (endIndex > 2) {
4938               this._drawCoords[endIndex] = $.merge( [], this._drawCoords[0] );
4939               this._trigger( "shape", e, {
4940                 type: "Polygon",
4941                 coordinates: [ this._userGeodetic ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords ]
4942               } );
4943             }
4944           } else {
4945             this._eventTarget_dblclick_zoom(e);
4946           }
4947           this._resetDrawing();
4948           break;
4949
4950         case "measureLength":
4951         case "measureArea":
4952           this._resetDrawing();
4953           break;
4954
4955         default:
4956           this._eventTarget_dblclick_zoom(e);
4957           break;
4958       }
4959
4960       this._inOp = false;
4961     },
4962
4963     _eventTarget_touchstart: function (e) {
4964       if ( this._options[ "mode" ] === "static" ) {
4965         return;
4966       }
4967
4968       if ( !this._supportTouch && e.which != 1 ) {
4969         return;
4970       }
4971
4972       this._panFinalize();
4973       this._mouseWheelFinish();
4974
4975       var offset = $(e.currentTarget).offset(),
4976           touches = e.originalEvent.changedTouches;
4977
4978       if ( this._supportTouch ) {
4979         this._multiTouchAnchor = $.merge( [ ], touches );
4980
4981         this._isMultiTouch = this._multiTouchAnchor.length > 1;
4982
4983         if ( this._isMultiTouch ) {
4984           this._multiTouchCurrentBbox = [
4985             touches[0].pageX - offset.left,
4986             touches[0].pageY - offset.top,
4987             touches[1].pageX - offset.left,
4988             touches[1].pageY - offset.top
4989           ];
4990
4991           this._multiTouchAnchorBbox = $.merge( [ ], this._multiTouchCurrentBbox );
4992
4993           this._current = $.geo.center( this._multiTouchCurrentBbox, true );
4994         } else {
4995           this._multiTouchCurrentBbox = [
4996             touches[0].pageX - offset.left,
4997             touches[0].pageY - offset.top,
4998             NaN,
4999             NaN
5000           ];
5001
5002           this._current = [ touches[0].pageX - offset.left, touches[0].pageY - offset.top ];
5003         }
5004       } else {
5005         this._current = [e.pageX - offset.left, e.pageY - offset.top];
5006       }
5007
5008       if (this._softDblClick) {
5009         var downDate = $.now();
5010         if (downDate - this._downDate < 750) {
5011           if (this._isTap) {
5012             var dx = this._current[0] - this._anchor[0],
5013                 dy = this._current[1] - this._anchor[1],
5014                 distance = Math.sqrt((dx * dx) + (dy * dy));
5015             if (distance > 8) {
5016               this._isTap = false;
5017             } else {
5018               this._current = $.merge( [ ], this._anchor );
5019             }
5020           }
5021
5022           if (this._isDbltap) {
5023             this._isDbltap = false;
5024           } else {
5025             this._isDbltap = this._isTap;
5026           }
5027         } else {
5028           this._isDbltap = false;
5029         }
5030         this._isTap = true;
5031         this._downDate = downDate;
5032       }
5033
5034       this._mouseDown = true;
5035       this._anchor = $.merge( [ ], this._current );
5036
5037       if (!this._inOp && e.shiftKey) {
5038         this._shiftZoom = true;
5039         this._$eventTarget.css("cursor", this._options["cursors"]["zoom"]);
5040       } else if ( !this._isMultiTouch && this._options[ "pannable" ] ) {
5041         this._inOp = true;
5042
5043         switch (this._options["mode"]) {
5044           case "zoom":
5045             break;
5046
5047           default:
5048             this._lastDrag = this._current;
5049
5050             if (e.currentTarget.setCapture) {
5051               e.currentTarget.setCapture();
5052             }
5053
5054             break;
5055         }
5056       }
5057
5058       e.preventDefault();
5059       return false;
5060     },
5061
5062     _dragTarget_touchmove: function (e) {
5063       if ( this._options[ "mode" ] === "static" ) {
5064         return;
5065       }
5066
5067       var offset = this._$eventTarget.offset(),
5068           drawCoordsLen = this._drawCoords.length,
5069           touches = e.originalEvent.changedTouches,
5070           current,
5071           service,
5072           i = 0;
5073
5074       if ( this._supportTouch ) {
5075         if ( !this._isMultiTouch && touches[ 0 ].identifier !== this._multiTouchAnchor[ 0 ].identifier ) {
5076           // switch to multitouch
5077           this._mouseDown = false;
5078           this._dragTarget_touchstop( e );
5079
5080           this._isMultiTouch = true;
5081
5082           this._multiTouchAnchor.push( touches[ 0 ] );
5083
5084           this._multiTouchCurrentBbox = [
5085             this._multiTouchCurrentBbox[ 0 ],
5086             this._multiTouchCurrentBbox[ 1 ],
5087             this._multiTouchAnchor[1].pageX - offset.left,
5088             this._multiTouchAnchor[1].pageY - offset.top
5089           ];
5090
5091           this._multiTouchAnchorBbox = $.merge( [ ], this._multiTouchCurrentBbox );
5092
5093           this._mouseDown = true;
5094           this._anchor = this._current = $.geo.center( this._multiTouchCurrentBbox, true );
5095
5096           return false;
5097         }
5098
5099         if ( this._isMultiTouch ) {
5100           for ( ; i < touches.length; i++ ) {
5101             if ( touches[ i ].identifier === this._multiTouchAnchor[ 0 ].identifier ) {
5102               this._multiTouchCurrentBbox[ 0 ] = touches[ i ].pageX - offset.left;
5103               this._multiTouchCurrentBbox[ 1 ] = touches[ i ].pageY - offset.top;
5104             } else if ( touches[ i ].identifier === this._multiTouchAnchor[ 1 ].identifier ) {
5105               this._multiTouchCurrentBbox[ 2 ] = touches[ i ].pageX - offset.left;
5106               this._multiTouchCurrentBbox[ 3 ] = touches[ i ].pageY - offset.top;
5107             }
5108           }
5109
5110           current = $.geo.center( this._multiTouchCurrentBbox, true );
5111
5112           var currentWidth = this._multiTouchCurrentBbox[ 2 ] - this._multiTouchCurrentBbox[ 0 ],
5113               anchorWidth = this._multiTouchAnchorBbox[ 2 ] - this._multiTouchAnchorBbox[ 0 ],
5114               ratioWidth = currentWidth / anchorWidth;
5115
5116           this._wheelLevel = Math.abs( Math.floor( ( 1 - ratioWidth ) * 10 ) );
5117           if ( Math.abs( currentWidth ) < Math.abs( anchorWidth ) ) {
5118             this._wheelLevel = - this._wheelLevel;
5119           }
5120
5121           var pinchCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, false );
5122           this._$elem.find( ".geo-shapes-container" ).geographics("clear");
5123
5124           for ( i = 0; i < this._currentServices.length; i++ ) {
5125             service = this._currentServices[ i ];
5126             $.geo[ "_serviceTypes" ][ service.type ].interactiveScale( this, service, pinchCenterAndSize.center, pinchCenterAndSize.pixelSize );
5127           }
5128
5129           if (this._graphicShapes.length > 0 && this._graphicShapes.length < 256) {
5130             this._refreshShapes(this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes, pinchCenterAndSize.center, pinchCenterAndSize.pixelSize);
5131           }
5132
5133
5134           if (this._drawCoords.length > 0) {
5135             this._drawPixels = this._toPixel(this._drawCoords, pinchCenterAndSize.center, pinchCenterAndSize.pixelSize);
5136             this._refreshDrawing();
5137           }
5138
5139           current = $.geo.center( this._multiTouchCurrentBbox, true );
5140         } else {
5141           current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
5142         }
5143       } else {
5144         current = [e.pageX - offset.left, e.pageY - offset.top];
5145       }
5146
5147       if (current[0] === this._lastMove[0] && current[1] === this._lastMove[1]) {
5148         if ( this._inOp ) {
5149           e.preventDefault();
5150           return false;
5151         }
5152       }
5153
5154       if ( _ieVersion == 7 ) {
5155         this._isDbltap = this._isTap = false;
5156       }
5157
5158       if (this._mouseDown) {
5159         this._current = current;
5160         this._moveDate = $.now();
5161       }
5162
5163       if ( this._isMultiTouch ) {
5164         e.preventDefault( );
5165         this._isDbltap = this._isTap = false;
5166         return false;
5167       }
5168
5169       var mode = this._shiftZoom ? "zoom" : this._options["mode"];
5170
5171       switch (mode) {
5172         case "zoom":
5173           if ( this._mouseDown ) {
5174             this._$drawContainer.geographics( "clear" );
5175             this._$drawContainer.geographics( "drawBbox", [
5176               this._anchor[ 0 ],
5177               this._anchor[ 1 ],
5178               current[ 0 ],
5179               current[ 1 ]
5180             ] );
5181           } else {
5182             this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
5183           }
5184           break;
5185
5186         case "drawLineString":
5187         case "drawPolygon":
5188         case "measureLength":
5189         case "measureArea":
5190           if (this._mouseDown || this._toolPan) {
5191             this._panMove();
5192           } else {
5193             if (drawCoordsLen > 0) {
5194               this._drawCoords[drawCoordsLen - 1] = this._toMap(current);
5195               this._drawPixels[drawCoordsLen - 1] = current;
5196
5197               this._refreshDrawing();
5198             }
5199
5200             this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
5201           }
5202           break;
5203
5204         default:
5205           if (this._mouseDown || this._toolPan) {
5206             this._panMove();
5207           } else {
5208             this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
5209           }
5210           break;
5211       }
5212
5213       this._lastMove = current;
5214
5215       if ( this._inOp ) {
5216         e.preventDefault();
5217         return false;
5218       }
5219     },
5220
5221     _dragTarget_touchstop: function (e) {
5222       if ( this._options[ "mode" ] === "static" ) {
5223         return;
5224       }
5225
5226       if (!this._mouseDown && _ieVersion == 7) {
5227         // ie7 doesn't appear to trigger dblclick on this._$eventTarget,
5228         // we fake regular click here to cause soft dblclick
5229         this._eventTarget_touchstart(e);
5230       }
5231
5232       var mouseWasDown = this._mouseDown,
5233           wasToolPan = this._toolPan,
5234           offset = this._$eventTarget.offset(),
5235           mode = this._shiftZoom ? "zoom" : this._options["mode"],
5236           current, i, clickDate,
5237           dx, dy;
5238
5239       if (this._supportTouch) {
5240         current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
5241       } else {
5242         current = [e.pageX - offset.left, e.pageY - offset.top];
5243       }
5244
5245       if (this._softDblClick) {
5246         if (this._isTap) {
5247           var dx = current[0] - this._anchor[0],
5248               dy = current[1] - this._anchor[1],
5249               distance = Math.sqrt((dx * dx) + (dy * dy));
5250           if (distance <= 8) {
5251             current = $.merge( [ ], this._anchor );
5252           }
5253         }
5254       }
5255
5256       dx = current[0] - this._anchor[0];
5257       dy = current[1] - this._anchor[1];
5258
5259       this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
5260
5261       this._shiftZoom = this._mouseDown = this._toolPan = false;
5262
5263       if ( this._isMultiTouch ) {
5264         e.preventDefault( );
5265         this._isMultiTouch = false;
5266
5267         var pinchCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, false );
5268
5269         this._setCenterAndSize(pinchCenterAndSize.center, pinchCenterAndSize.pixelSize, true, true);
5270
5271         this._wheelLevel = 0;
5272
5273         return false;
5274       }
5275
5276       if (document.releaseCapture) {
5277         document.releaseCapture();
5278       }
5279
5280       if (mouseWasDown) {
5281         clickDate = $.now();
5282         this._current = current;
5283
5284         switch (mode) {
5285           case "zoom":
5286             if ( dx > 0 || dy > 0 ) {
5287               var minSize = this._pixelSize * 6,
5288                   bboxCoords = this._toMap( [ [
5289                       Math.min( this._anchor[ 0 ], current[ 0 ] ),
5290                       Math.max( this._anchor[ 1 ], current[ 1 ] )
5291                     ], [
5292                       Math.max( this._anchor[ 0 ], current[ 0 ] ),
5293                       Math.min( this._anchor[ 1 ], current[ 1 ] )
5294                     ]
5295                   ] ),
5296                   bbox = [
5297                     bboxCoords[0][0],
5298                     bboxCoords[0][1],
5299                     bboxCoords[1][0],
5300                     bboxCoords[1][1]
5301                   ];
5302
5303               if ( ( bbox[2] - bbox[0] ) < minSize && ( bbox[3] - bbox[1] ) < minSize ) {
5304                 bbox = $.geo.scaleBy( this._getBbox( $.geo.center( bbox, true ) ), .5, true );
5305               }
5306
5307               this._setBbox(bbox, true, true);
5308             }
5309
5310             this._resetDrawing();
5311             break;
5312
5313           case "drawPoint":
5314             if (this._drawTimeout) {
5315               window.clearTimeout(this._drawTimeout);
5316               this._drawTimeout = null;
5317             }
5318
5319             if (wasToolPan) {
5320               this._panFinalize();
5321             } else {
5322               if (clickDate - this._clickDate > 100) {
5323                 var geomap = this;
5324                 this._drawTimeout = setTimeout(function () {
5325                   if (geomap._drawTimeout) {
5326                     geomap._trigger("shape", e, { type: "Point", coordinates: geomap.toMap(current) });
5327                     geomap._inOp = false;
5328                     geomap._drawTimeout = null;
5329                   }
5330                 }, 250);
5331               }
5332             }
5333             break;
5334
5335           case "drawLineString":
5336           case "drawPolygon":
5337           case "measureLength":
5338           case "measureArea":
5339             if (wasToolPan) {
5340               this._panFinalize();
5341             } else {
5342               i = (this._drawCoords.length == 0 ? 0 : this._drawCoords.length - 1);
5343
5344               this._drawCoords[i] = this._toMap(current);
5345               this._drawPixels[i] = current;
5346
5347               if (i < 2 || !(this._drawCoords[i][0] == this._drawCoords[i-1][0] &&
5348                              this._drawCoords[i][1] == this._drawCoords[i-1][1])) {
5349                 this._drawCoords[i + 1] = this._toMap(current);
5350                 this._drawPixels[i + 1] = current;
5351               }
5352
5353               this._refreshDrawing();
5354             }
5355             break;
5356
5357           default:
5358             if (wasToolPan) {
5359               this._panEnd();
5360             } else {
5361               if (clickDate - this._clickDate > 100) {
5362                 this._trigger("click", e, { type: "Point", coordinates: this.toMap(current) });
5363                 this._inOp = false;
5364               }
5365             }
5366             break;
5367         }
5368
5369         this._clickDate = clickDate;
5370
5371         if (this._softDblClick && this._isDbltap) {
5372           this._isDbltap = this._isTap = false;
5373           this._$eventTarget.trigger("dblclick", e);
5374         }
5375       }
5376
5377       if ( this._inOp ) {
5378         e.preventDefault();
5379         return false;
5380       }
5381     },
5382
5383     _eventTarget_mousewheel: function (e, delta) {
5384       if ( this._options[ "mode" ] === "static" || this._options[ "scroll" ] === "off" ) {
5385         return;
5386       }
5387
5388       e.preventDefault();
5389
5390       this._panFinalize();
5391
5392       if ( this._mouseDown ) {
5393         return false;
5394       }
5395
5396       if (delta != 0) {
5397         if (this._wheelTimeout) {
5398           window.clearTimeout(this._wheelTimeout);
5399           this._wheelTimeout = null;
5400         } else {
5401           var offset = $(e.currentTarget).offset();
5402           this._anchor = [e.pageX - offset.left, e.pageY - offset.top];
5403         }
5404
5405         this._wheelLevel += delta;
5406
5407         var wheelCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, this._options[ "tilingScheme" ] != null ),
5408             service,
5409             i = 0;
5410
5411         this._$elem.find( ".geo-shapes-container" ).geographics("clear");
5412
5413         for ( ; i < this._currentServices.length; i++ ) {
5414           service = this._currentServices[ i ];
5415           $.geo["_serviceTypes"][service.type].interactiveScale(this, service, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
5416         }
5417
5418         if (this._graphicShapes.length > 0 && this._graphicShapes.length < 256) {
5419           this._refreshShapes(this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
5420         }
5421
5422         if (this._drawCoords.length > 0) {
5423           this._drawPixels = this._toPixel(this._drawCoords, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
5424           this._refreshDrawing();
5425         }
5426
5427         var geomap = this;
5428         this._wheelTimeout = window.setTimeout(function () {
5429           geomap._mouseWheelFinish();
5430         }, 1000);
5431       }
5432
5433       return false;
5434     }
5435   }
5436   );
5437 })(jQuery);
5438
5439 (function ($, undefined) {
5440   $.geo._serviceTypes.tiled = (function () {
5441     return {
5442       create: function (map, serviceContainer, service, index) {
5443         var serviceState = $.data(service, "geoServiceState");
5444
5445         if ( !serviceState ) {
5446           serviceState = {
5447             loadCount: 0,
5448             reloadTiles: false
5449           };
5450
5451           var scHtml = '<div data-geo-service="tiled" style="position:absolute; left:0; top:0; width:8px; height:8px; margin:0; padding:0;"></div>';
5452
5453           serviceContainer.append(scHtml);
5454
5455           serviceState.serviceContainer = serviceContainer.children( ":last" );
5456
5457           $.data(service, "geoServiceState", serviceState);
5458         }
5459
5460         return serviceState.serviceContainer;
5461       },
5462
5463       destroy: function (map, serviceContainer, service) {
5464         var serviceState = $.data(service, "geoServiceState");
5465
5466         serviceState.serviceContainer.remove();
5467
5468         $.removeData(service, "geoServiceState");
5469       },
5470
5471       interactivePan: function ( map, service, dx, dy ) {
5472         var serviceState = $.data( service, "geoServiceState" );
5473
5474         if ( serviceState ) {
5475           this._cancelUnloaded( map, service );
5476
5477           serviceState.serviceContainer.children( ).css( "-moz-transition", "").css( {
5478             webkitTransition: "",
5479             transition: "",
5480             left: function ( index, value ) {
5481               return parseInt( value ) + dx;
5482             },
5483             top: function ( index, value ) {
5484               return parseInt( value ) + dy;
5485             }
5486           });
5487
5488           if ( service && service.style.visibility === "visible" ) {
5489             var pixelSize = map._pixelSize,
5490
5491                 serviceObj = this,
5492                 serviceContainer = serviceState.serviceContainer,
5493                 scaleContainer = serviceContainer.children("[data-pixelSize='" + pixelSize + "']"),
5494
5495                 /* same as refresh 1 */
5496                 contentBounds = map._getContentBounds(),
5497                 mapWidth = contentBounds["width"],
5498                 mapHeight = contentBounds["height"],
5499
5500                 image = map.options[ "axisLayout" ] === "image",
5501                 ySign = image ? +1 : -1,
5502
5503                 tilingScheme = map.options["tilingScheme"],
5504                 tileWidth = tilingScheme.tileWidth,
5505                 tileHeight = tilingScheme.tileHeight,
5506                 /* end same as refresh 1 */
5507
5508                 halfWidth = mapWidth / 2 * pixelSize,
5509                 halfHeight = mapHeight / 2 * pixelSize,
5510
5511                 currentPosition = scaleContainer.position(),
5512                 scaleOriginParts = scaleContainer.data("scaleOrigin").split(","),
5513                 totalDx = parseInt(scaleOriginParts[0]) - currentPosition.left,
5514                 totalDy = parseInt(scaleOriginParts[1]) - currentPosition.top,
5515
5516                 mapCenterOriginal = map._getCenter(),
5517                 mapCenter = [
5518                   mapCenterOriginal[0] + totalDx * pixelSize,
5519                   mapCenterOriginal[1] + ySign * totalDy * pixelSize
5520                 ],
5521
5522                 /* same as refresh 2 */
5523                 tileX = Math.floor(((mapCenter[0] - halfWidth) - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
5524                 tileY = Math.max( Math.floor(( image ? (mapCenter[1] - halfHeight) - tilingScheme.origin[1] : tilingScheme.origin[1] - (mapCenter[1] + halfHeight)) / (pixelSize * tileHeight)), 0 ),
5525                 tileX2 = Math.ceil(((mapCenter[0] + halfWidth) - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
5526                 tileY2 = Math.ceil(( image ? (mapCenter[1] + halfHeight) - tilingScheme.origin[1] : tilingScheme.origin[1] - (mapCenter[1] - halfHeight)) / (pixelSize * tileHeight)),
5527
5528                 bboxMax = map._getBboxMax(),
5529                 pixelSizeAtZero = map._getPixelSize(0),
5530                 ratio = pixelSizeAtZero / pixelSize,
5531                 fullXAtScale = Math.floor((bboxMax[0] - tilingScheme.origin[0]) / (pixelSizeAtZero * tileWidth)) * ratio,
5532                 fullYAtScale = Math.floor((tilingScheme.origin[1] + ySign * bboxMax[3]) / (pixelSizeAtZero * tileHeight)) * ratio,
5533
5534                 fullXMinX = tilingScheme.origin[0] + (fullXAtScale * tileWidth) * pixelSize,
5535                 fullYMinOrMaxY = tilingScheme.origin[1] + ySign * (fullYAtScale * tileHeight) * pixelSize,
5536                 /* end same as refresh 2 */
5537
5538                 serviceLeft = Math.round((fullXMinX - (mapCenterOriginal[0] - halfWidth)) / pixelSize),
5539                 serviceTop = Math.round(( image ? fullYMinOrMaxY - (mapCenterOriginal[1] - halfHeight) : (mapCenterOriginal[1] + halfHeight) - fullYMinOrMaxY  ) / pixelSize),
5540
5541                 opacity = service.style.opacity,
5542
5543                 x, y;
5544
5545             for ( x = tileX; x < tileX2; x++ ) {
5546               for ( y = tileY; y < tileY2; y++ ) {
5547                 var tileStr = "" + x + "," + y,
5548                     $img = scaleContainer.children("[data-tile='" + tileStr + "']").removeAttr("data-dirty");
5549
5550                 if ( $img.size( ) === 0 ) {
5551                   /* same as refresh 3 */
5552                   var bottomLeft = [
5553                         tilingScheme.origin[0] + (x * tileWidth) * pixelSize,
5554                         tilingScheme.origin[1] + ySign * (y * tileHeight) * pixelSize
5555                       ],
5556
5557                       topRight = [
5558                         tilingScheme.origin[0] + ((x + 1) * tileWidth - 1) * pixelSize,
5559                         tilingScheme.origin[1] + ySign * ((y + 1) * tileHeight - 1) * pixelSize
5560                       ],
5561
5562                       tileBbox = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]],
5563
5564                       urlProp = ( service.hasOwnProperty("src") ? "src" : "getUrl" ),
5565                       urlArgs = {
5566                         bbox: tileBbox,
5567                         width: tileWidth,
5568                         height: tileHeight,
5569                         zoom: map._getZoom(),
5570                         tile: {
5571                           row: y,
5572                           column: x
5573                         },
5574                         index: Math.abs(y + x)
5575                       },
5576                       isFunc = $.isFunction( service[ urlProp ] ),
5577                       imageUrl;
5578
5579                   if ( isFunc ) {
5580                     imageUrl = service[ urlProp ]( urlArgs );
5581                   } else {
5582                     $.template( "geoSrc", service[ urlProp ] );
5583                     imageUrl = $.render( urlArgs, "geoSrc" );
5584                   }
5585                   /* end same as refresh 3 */
5586
5587                   serviceState.loadCount++;
5588                   //this._map._requestQueued();
5589
5590                   if ( serviceState.reloadTiles && $img.size() > 0 ) {
5591                     $img.attr( "src", imageUrl );
5592                   } else {
5593                     /* same as refresh 4 */
5594                     var imgMarkup = "<img style='position:absolute; " +
5595                           "left:" + (((x - fullXAtScale) * 100) + (serviceLeft - (serviceLeft % tileWidth)) / tileWidth * 100) + "%; " +
5596                           "top:" + (((y - fullYAtScale) * 100) + (serviceTop - (serviceTop % tileHeight)) / tileHeight * 100) + "%; ";
5597
5598                     if ($("body")[0].filters === undefined) {
5599                       imgMarkup += "width: 100%; height: 100%;";
5600                     }
5601
5602                     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 + "' />";
5603
5604                     scaleContainer.append( imgMarkup );
5605                     $img = scaleContainer.children(":last");
5606                   }
5607
5608                   if ( typeof imageUrl === "string" ) {
5609                     serviceObj._loadImage( $img, imageUrl, pixelSize, serviceState, serviceContainer, opacity );
5610                   } else {
5611                     // assume Deferred
5612                     imageUrl.done( function( url ) {
5613                       serviceObj._loadImage( $img, url, pixelSize, serviceState, serviceContainer, opacity );
5614                     } ).fail( function( ) {
5615                       $img.remove( );
5616                       serviceState.loadCount--;
5617                     } );
5618                   }
5619
5620                   /* end same as refresh 4 */
5621                 }
5622               }
5623             }
5624           }
5625         }
5626       },
5627
5628       interactiveScale: function (map, service, center, pixelSize) {
5629         var serviceState = $.data( service, "geoServiceState" );
5630
5631         if ( serviceState && service && service.style.visibility === "visible" ) {
5632           this._cancelUnloaded(map, service);
5633
5634           var serviceContainer = serviceState.serviceContainer,
5635
5636               tilingScheme = map.options["tilingScheme"],
5637               tileWidth = tilingScheme.tileWidth,
5638               tileHeight = tilingScheme.tileHeight;
5639
5640
5641           serviceContainer.children( ).each( function ( i ) {
5642             var $scaleContainer = $(this),
5643                 scaleRatio = $scaleContainer.attr("data-pixelSize") / pixelSize,
5644                 transitionCss = ""; //"width .25s ease-in, height .25s ease-in, left .25s ease-in, top .25s ease-in";
5645
5646             scaleRatio = Math.round(scaleRatio * 1000) / 1000;
5647
5648
5649             var scaleOriginParts = $scaleContainer.data("scaleOrigin").split(","),
5650                 oldMapCoord = map._toMap([scaleOriginParts[0], scaleOriginParts[1]]),
5651                 newPixelPoint = map._toPixel(oldMapCoord, center, pixelSize);
5652
5653             $scaleContainer.css( "-moz-transition", transitionCss ).css( {
5654               webkitTransition: transitionCss,
5655               transition: transitionCss,
5656               left: Math.round(newPixelPoint[0]) + "px",
5657               top: Math.round(newPixelPoint[1]) + "px",
5658               width: tileWidth * scaleRatio,
5659               height: tileHeight * scaleRatio
5660             } );
5661
5662             if ( $("body")[0].filters !== undefined ) {
5663               $scaleContainer.children().each( function ( i ) {
5664                 $( this ).css( "filter", "progid:DXImageTransform.Microsoft.Matrix(FilterType=bilinear,M11=" + scaleRatio + ",M22=" + scaleRatio + ",sizingmethod='auto expand')" );
5665               } );
5666             }
5667           });
5668         }
5669       },
5670
5671       refresh: function (map, service) {
5672         var serviceState = $.data( service, "geoServiceState" );
5673
5674         this._cancelUnloaded(map, service);
5675
5676         if ( serviceState && service && service.style.visibility === "visible" && !( serviceState.serviceContainer.is( ":hidden" ) ) ) {
5677
5678           var bbox = map._getBbox(),
5679               pixelSize = map._pixelSize,
5680
5681               serviceObj = this,
5682               $serviceContainer = serviceState.serviceContainer,
5683
5684               contentBounds = map._getContentBounds(),
5685               mapWidth = contentBounds["width"],
5686               mapHeight = contentBounds["height"],
5687
5688               image = map.options[ "axisLayout" ] === "image",
5689               ySign = image ? +1 : -1,
5690
5691               tilingScheme = map.options["tilingScheme"],
5692               tileWidth = tilingScheme.tileWidth,
5693               tileHeight = tilingScheme.tileHeight,
5694
5695               tileX = Math.floor((bbox[0] - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
5696               tileY = Math.max( Math.floor( ( image ? bbox[1] - tilingScheme.origin[1] : tilingScheme.origin[1] - bbox[ 3 ] ) / (pixelSize * tileHeight) ), 0 ),
5697               tileX2 = Math.ceil((bbox[2] - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
5698               tileY2 = Math.ceil( ( image ? bbox[3] - tilingScheme.origin[1] : tilingScheme.origin[1] - bbox[ 1 ] ) / (pixelSize * tileHeight) ),
5699
5700               bboxMax = map._getBboxMax(),
5701               pixelSizeAtZero = map._getPixelSize(0),
5702               ratio = pixelSizeAtZero / pixelSize,
5703               fullXAtScale = Math.floor((bboxMax[0] - tilingScheme.origin[0]) / (pixelSizeAtZero * tileWidth)) * ratio,
5704               fullYAtScale = Math.floor((tilingScheme.origin[1] + ySign * bboxMax[3]) / (pixelSizeAtZero * tileHeight)) * ratio,
5705
5706               fullXMinX = tilingScheme.origin[0] + (fullXAtScale * tileWidth) * pixelSize,
5707               fullYMinOrMaxY = tilingScheme.origin[1] + ySign * (fullYAtScale * tileHeight) * pixelSize,
5708
5709               serviceLeft = Math.round((fullXMinX - bbox[0]) / pixelSize),
5710               serviceTop = Math.round( ( image ? fullYMinOrMaxY - bbox[1] : bbox[3] - fullYMinOrMaxY ) / pixelSize),
5711
5712               scaleContainers = $serviceContainer.children().show(),
5713               scaleContainer = scaleContainers.filter("[data-pixelSize='" + pixelSize + "']").appendTo($serviceContainer),
5714
5715               opacity = service.style.opacity,
5716
5717               x, y;
5718
5719           if (serviceState.reloadTiles) {
5720             scaleContainers.find("img").attr("data-dirty", "true");
5721           }
5722
5723           if (!scaleContainer.size()) {
5724             $serviceContainer.append("<div style='position:absolute; left:" + serviceLeft % tileWidth + "px; top:" + serviceTop % tileHeight + "px; width:" + tileWidth + "px; height:" + tileHeight + "px; margin:0; padding:0;' data-pixelSize='" + pixelSize + "'></div>");
5725             scaleContainer = $serviceContainer.children(":last").data("scaleOrigin", (serviceLeft % tileWidth) + "," + (serviceTop % tileHeight));
5726           } else {
5727             scaleContainer.css({
5728               left: (serviceLeft % tileWidth) + "px",
5729               top: (serviceTop % tileHeight) + "px"
5730             }).data("scaleOrigin", (serviceLeft % tileWidth) + "," + (serviceTop % tileHeight));
5731
5732             scaleContainer.children().each(function (i) {
5733               var 
5734               $img = $(this),
5735               tile = $img.attr("data-tile").split(",");
5736
5737               $img.css({
5738                 left: Math.round(((parseInt(tile[0]) - fullXAtScale) * 100) + (serviceLeft - (serviceLeft % tileWidth)) / tileWidth * 100) + "%",
5739                 top: Math.round(((parseInt(tile[1]) - fullYAtScale) * 100) + (serviceTop - (serviceTop % tileHeight)) / tileHeight * 100) + "%"
5740               });
5741
5742               if (opacity < 1) {
5743                 $img.fadeTo(0, opacity);
5744               }
5745             });
5746           }
5747
5748           for (x = tileX; x < tileX2; x++) {
5749             for (y = tileY; y < tileY2; y++) {
5750               var tileStr = "" + x + "," + y,
5751                   $img = scaleContainer.children("[data-tile='" + tileStr + "']").removeAttr("data-dirty");
5752
5753               if ($img.size() === 0 || serviceState.reloadTiles) {
5754                 var bottomLeft = [
5755                       tilingScheme.origin[0] + (x * tileWidth) * pixelSize,
5756                       tilingScheme.origin[1] + ySign * (y * tileHeight) * pixelSize
5757                     ],
5758
5759                     topRight = [
5760                       tilingScheme.origin[0] + ((x + 1) * tileWidth - 1) * pixelSize,
5761                       tilingScheme.origin[1] + ySign * ((y + 1) * tileHeight - 1) * pixelSize
5762                     ],
5763
5764                     tileBbox = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]],
5765
5766                     urlProp = ( service.hasOwnProperty( "src" ) ? "src" : "getUrl" ),
5767                     urlArgs = {
5768                       bbox: tileBbox,
5769                       width: tileWidth,
5770                       height: tileHeight,
5771                       zoom: map._getZoom(),
5772                       tile: {
5773                         row: y,
5774                         column: x
5775                       },
5776                       index: Math.abs(y + x)
5777                     },
5778                     isFunc = $.isFunction( service[ urlProp ] ),
5779                     imageUrl;
5780
5781                 if ( isFunc ) {
5782                   imageUrl = service[ urlProp ]( urlArgs );
5783                 } else {
5784                   $.template( "geoSrc", service[ urlProp ] );
5785                   imageUrl = $.render( urlArgs, "geoSrc" );
5786                 }
5787
5788                 serviceState.loadCount++;
5789                 //this._map._requestQueued();
5790
5791                 if (serviceState.reloadTiles && $img.size() > 0) {
5792                   $img.attr("src", imageUrl);
5793                 } else {
5794                   var imgMarkup = "<img style='position:absolute; " +
5795                     "left:" + (((x - fullXAtScale) * 100) + (serviceLeft - (serviceLeft % tileWidth)) / tileWidth * 100) + "%; " +
5796                     "top:" + (((y - fullYAtScale) * 100) + (serviceTop - (serviceTop % tileHeight)) / tileHeight * 100) + "%; ";
5797
5798                   if ($("body")[0].filters === undefined) {
5799                     imgMarkup += "width: 100%; height: 100%;";
5800                   }
5801
5802                   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 + "' />";
5803
5804                   scaleContainer.append(imgMarkup);
5805                   $img = scaleContainer.children(":last");
5806                 }
5807
5808                 if ( typeof imageUrl === "string" ) {
5809                   serviceObj._loadImage( $img, imageUrl, pixelSize, serviceState, $serviceContainer, opacity );
5810                 } else {
5811                   // assume Deferred
5812                   imageUrl.done( function( url ) {
5813                     serviceObj._loadImage( $img, url, pixelSize, serviceState, $serviceContainer, opacity );
5814                   } ).fail( function( ) {
5815                     $img.remove( );
5816                     serviceState.loadCount--;
5817                   } );
5818                 }
5819               }
5820             }
5821           }
5822
5823           scaleContainers.find("[data-dirty]").remove();
5824           serviceState.reloadTiles = false;
5825         }
5826       },
5827
5828       resize: function (map, service) {
5829       },
5830
5831       opacity: function ( map, service ) {
5832         var serviceState = $.data( service, "geoServiceState" );
5833         serviceState.serviceContainer.find( "img" ).stop( true ).fadeTo( "fast", service.style.opacity );
5834       },
5835
5836       toggle: function ( map, service ) {
5837         var serviceState = $.data( service, "geoServiceState" );
5838         serviceState.serviceContainer.css( "display", service.style.visibility === "visible" ? "block" : "none" );
5839       },
5840
5841       _cancelUnloaded: function (map, service) {
5842         var serviceState = $.data( service, "geoServiceState" );
5843
5844         if (serviceState && serviceState.loadCount > 0) {
5845           serviceState.serviceContainer.find("img:hidden").remove();
5846           while (serviceState.loadCount > 0) {
5847             serviceState.loadCount--;
5848           }
5849         }
5850       },
5851
5852       _loadImage: function ( $img, url, pixelSize, serviceState, serviceContainer, opacity ) {
5853         $img.load(function (e) {
5854           if (opacity < 1) {
5855             $(e.target).fadeTo(0, opacity);
5856           } else {
5857             $(e.target).show();
5858           }
5859
5860           serviceState.loadCount--;
5861
5862           if (serviceState.loadCount <= 0) {
5863             serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
5864             serviceState.loadCount = 0;
5865           }
5866         }).error(function (e) {
5867           $(e.target).remove();
5868           serviceState.loadCount--;
5869
5870           if (serviceState.loadCount <= 0) {
5871             serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
5872             serviceState.loadCount = 0;
5873           }
5874         }).attr("src", url);
5875       }
5876     };
5877   })();
5878 })(jQuery);
5879 (function ($, undefined) {
5880   $.geo._serviceTypes.shingled = (function () {
5881     return {
5882       create: function (map, serviceContainer, service, index) {
5883         var serviceState = $.data(service, "geoServiceState");
5884
5885         if ( !serviceState ) {
5886           serviceState = {
5887             loadCount: 0
5888           };
5889
5890           var scHtml = '<div data-geo-service="shingled" style="position:absolute; left:0; top:0; width:16px; height:16px; margin:0; padding:0;"></div>';
5891
5892           serviceContainer.append(scHtml);
5893
5894           serviceState.serviceContainer = serviceContainer.children(":last");
5895           $.data(service, "geoServiceState", serviceState);
5896         }
5897
5898         return serviceState.serviceContainer;
5899       },
5900
5901       destroy: function (map, serviceContainer, service) {
5902         var serviceState = $.data(service, "geoServiceState");
5903
5904         serviceState.serviceContainer.remove();
5905
5906         $.removeData(service, "geoServiceState");
5907       },
5908
5909       interactivePan: function (map, service, dx, dy) {
5910         var serviceState = $.data(service, "geoServiceState");
5911
5912         if ( serviceState ) {
5913           this._cancelUnloaded(map, service);
5914
5915           var serviceContainer = serviceState.serviceContainer,
5916               pixelSize = map._pixelSize,
5917               scaleContainer = serviceContainer.children("[data-pixelSize='" + pixelSize + "']"),
5918               panContainer = scaleContainer.children("div");
5919
5920           if ( !panContainer.length ) {
5921             scaleContainer.children("img").wrap('<div style="position:absolute; left:0; top:0; width:100%; height:100%;"></div>');
5922             panContainer = scaleContainer.children("div");
5923           }
5924
5925           panContainer.css( {
5926             left: function (index, value) {
5927               return parseInt(value) + dx;
5928             },
5929             top: function (index, value) {
5930               return parseInt(value) + dy;
5931             }
5932           } );
5933
5934           // until pan/zoom rewrite, remove all containers not in this scale
5935           serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
5936         }
5937       },
5938
5939       interactiveScale: function (map, service, center, pixelSize) {
5940         var serviceState = $.data(service, "geoServiceState");
5941
5942         if ( serviceState ) {
5943           this._cancelUnloaded(map, service);
5944
5945           var serviceContainer = serviceState.serviceContainer,
5946
5947               contentBounds = map._getContentBounds(),
5948               mapWidth = contentBounds["width"],
5949               mapHeight = contentBounds["height"],
5950
5951               halfWidth = mapWidth / 2,
5952               halfHeight = mapHeight / 2,
5953
5954               bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight];
5955
5956           serviceContainer.children().each(function (i) {
5957             var $scaleContainer = $(this),
5958                 scalePixelSize = $scaleContainer.attr("data-pixelSize"),
5959                 ratio = scalePixelSize / pixelSize;
5960                 
5961             $scaleContainer.css( {
5962               width: mapWidth * ratio,
5963               height: mapHeight * ratio } ).children("img").each(function (i) {
5964               var $img = $(this),
5965                   imgCenter = $img.data("center"),
5966                   x = (Math.round((imgCenter[0] - center[0]) / scalePixelSize) - halfWidth) * ratio,
5967                   y = (Math.round((center[1] - imgCenter[1]) / scalePixelSize) - halfHeight) * ratio;
5968
5969               $img.css({ left: x + "px", top: y + "px" });
5970             });
5971           });
5972         }
5973       },
5974
5975       refresh: function (map, service) {
5976         var serviceState = $.data(service, "geoServiceState");
5977
5978         this._cancelUnloaded(map, service);
5979
5980         if ( serviceState && service && service.style.visibility === "visible" && !( serviceState.serviceContainer.is( ":hidden" ) ) ) {
5981
5982           var bbox = map._getBbox(),
5983               pixelSize = map._pixelSize,
5984
5985               serviceObj = this,
5986               serviceContainer = serviceState.serviceContainer,
5987
5988               contentBounds = map._getContentBounds(),
5989               mapWidth = contentBounds["width"],
5990               mapHeight = contentBounds["height"],
5991
5992               halfWidth = mapWidth / 2,
5993               halfHeight = mapHeight / 2,
5994
5995               scaleContainer = serviceContainer.children('[data-pixelSize="' + pixelSize + '"]'),
5996
5997               opacity = service.style.opacity,
5998
5999               $img;
6000
6001           if ( !scaleContainer.size() ) {
6002             serviceContainer.append('<div style="position:absolute; left:' + halfWidth + 'px; top:' + halfHeight + 'px; width:' + mapWidth + 'px; height:' + mapHeight + 'px; margin:0; padding:0;" data-pixelSize="' + pixelSize + '"></div>');
6003             scaleContainer = serviceContainer.children(":last");
6004           }
6005
6006           scaleContainer.children("img").each(function (i) {
6007             var $thisimg = $(this),
6008                 imgCenter = $thisimg.data("center"),
6009                 center = map._getCenter(),
6010                 x = Math.round((imgCenter[0] - center[0]) / pixelSize) - halfWidth,
6011                 y = Math.round((center[1] - imgCenter[1]) / pixelSize) - halfHeight;
6012
6013             $thisimg.css({ left: x + "px", top: y + "px" });
6014           });
6015
6016           if (opacity < 1) {
6017             serviceContainer.find("img").attr("data-keepAlive", "0");
6018           }
6019
6020           var urlProp = ( service.hasOwnProperty("src") ? "src" : "getUrl" ),
6021               urlArgs = {
6022                 bbox: bbox,
6023                 width: mapWidth,
6024                 height: mapHeight,
6025                 zoom: map._getZoom(),
6026                 tile: null,
6027                 index: 0
6028               },
6029               isFunc = $.isFunction( service[ urlProp ] ),
6030               imageUrl;
6031
6032
6033           if ( isFunc ) {
6034             imageUrl = service[ urlProp ]( urlArgs );
6035           } else {
6036             $.template( "geoSrc", service[ urlProp ] );
6037             imageUrl = $.render( urlArgs, "geoSrc" );
6038           }
6039
6040           serviceState.loadCount++;
6041           //this._map._requestQueued();
6042
6043           scaleContainer.append('<img style="position:absolute; left:-' + halfWidth + 'px; top:-' + halfHeight + 'px; 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" />');
6044           $img = scaleContainer.children(":last").data("center", map._getCenter());
6045
6046           if ( typeof imageUrl === "string" ) {
6047             serviceObj._loadImage( $img, imageUrl, pixelSize, serviceState, serviceContainer, opacity );
6048           } else {
6049             // assume Deferred
6050             imageUrl.done( function( url ) {
6051               serviceObj._loadImage( $img, url, pixelSize, serviceState, serviceContainer, opacity );
6052             } ).fail( function( ) {
6053               $img.remove( );
6054               serviceState.loadCount--;
6055             } );
6056           }
6057
6058         }
6059       },
6060
6061       resize: function (map, service) {
6062         var serviceState = $.data(service, "geoServiceState");
6063
6064         if ( serviceState && service && service.style.visibility === "visible" ) {
6065           this._cancelUnloaded(map, service);
6066
6067           var serviceContainer = serviceState.serviceContainer,
6068
6069               contentBounds = map._getContentBounds(),
6070               mapWidth = contentBounds["width"],
6071               mapHeight = contentBounds["height"],
6072
6073               halfWidth = mapWidth / 2,
6074               halfHeight = mapHeight / 2,
6075
6076               scaleContainer = serviceContainer.children();
6077
6078           scaleContainer.attr("data-pixelSize", "0");
6079           scaleContainer.css({
6080             left: halfWidth + 'px',
6081             top: halfHeight + 'px'
6082           });
6083         }
6084       },
6085
6086       opacity: function ( map, service ) {
6087         var serviceState = $.data( service, "geoServiceState" );
6088         serviceState.serviceContainer.find( "img" ).stop( true ).fadeTo( "fast", service.style.opacity );
6089       },
6090
6091       toggle: function (map, service) {
6092         var serviceState = $.data(service, "geoServiceState");
6093         serviceState.serviceContainer.css("display", service.style.visibility === "visible" ? "block" : "none");
6094       },
6095
6096       _cancelUnloaded: function (map, service) {
6097         var serviceState = $.data(service, "geoServiceState");
6098
6099         if (serviceState && serviceState.loadCount > 0) {
6100           serviceState.serviceContainer.find("img:hidden").remove();
6101           while (serviceState.loadCount > 0) {
6102             serviceState.loadCount--;
6103           }
6104         }
6105       },
6106
6107       _loadImage: function ( $img, url, pixelSize, serviceState, serviceContainer, opacity ) {
6108         $img.load(function (e) {
6109           if (opacity < 1) {
6110             $(e.target).fadeTo(0, opacity);
6111           } else {
6112             $(e.target).show();
6113           }
6114
6115           serviceState.loadCount--;
6116
6117           if (serviceState.loadCount <= 0) {
6118             serviceContainer.children(':not([data-pixelSize="' + pixelSize + '"])').remove();
6119
6120             var panContainer = serviceContainer.find('[data-pixelSize="' + pixelSize + '"]>div');
6121             if (panContainer.size() > 0) {
6122               var panContainerPos = panContainer.position();
6123
6124               panContainer.children("img").each(function (i) {
6125                 var $thisimg = $(this),
6126                     x = panContainerPos.left + parseInt($thisimg.css("left")),
6127                     y = panContainerPos.top + parseInt($thisimg.css("top"));
6128
6129                 $thisimg.css({ left: x + "px", top: y + "px" });
6130               }).unwrap();
6131
6132               panContainer.remove();
6133             }
6134
6135             serviceState.loadCount = 0;
6136           }
6137         }).error(function (e) {
6138           $(e.target).remove();
6139           serviceState.loadCount--;
6140
6141           if (serviceState.loadCount <= 0) {
6142             serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
6143             serviceState.loadCount = 0;
6144           }
6145         }).attr("src", url);
6146       }
6147     }
6148   })();
6149 })(jQuery);
6150 /*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
6151  * Licensed under the MIT License (LICENSE.txt).
6152  *
6153  * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
6154  * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
6155  * Thanks to: Seamus Leahy for adding deltaX and deltaY
6156  *
6157  * Version: 3.0.6
6158  * 
6159  * Requires: 1.2.2+
6160  */
6161
6162 (function($) {
6163
6164 var types = ['DOMMouseScroll', 'mousewheel'];
6165
6166 if ($.event.fixHooks) {
6167     for ( var i=types.length; i; ) {
6168         $.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
6169     }
6170 }
6171
6172 $.event.special.mousewheel = {
6173     setup: function() {
6174         if ( this.addEventListener ) {
6175             for ( var i=types.length; i; ) {
6176                 this.addEventListener( types[--i], handler, false );
6177             }
6178         } else {
6179             this.onmousewheel = handler;
6180         }
6181     },
6182     
6183     teardown: function() {
6184         if ( this.removeEventListener ) {
6185             for ( var i=types.length; i; ) {
6186                 this.removeEventListener( types[--i], handler, false );
6187             }
6188         } else {
6189             this.onmousewheel = null;
6190         }
6191     }
6192 };
6193
6194 $.fn.extend({
6195     mousewheel: function(fn) {
6196         return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
6197     },
6198     
6199     unmousewheel: function(fn) {
6200         return this.unbind("mousewheel", fn);
6201     }
6202 });
6203
6204
6205 function handler(event) {
6206     var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
6207     event = $.event.fix(orgEvent);
6208     event.type = "mousewheel";
6209     
6210     // Old school scrollwheel delta
6211     if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; }
6212     if ( orgEvent.detail     ) { delta = -orgEvent.detail/3; }
6213     
6214     // New school multidimensional scroll (touchpads) deltas
6215     deltaY = delta;
6216     
6217     // Gecko
6218     if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
6219         deltaY = 0;
6220         deltaX = -1*delta;
6221     }
6222     
6223     // Webkit
6224     if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
6225     if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
6226     
6227     // Add event and delta to the front of the arguments
6228     args.unshift(event, delta, deltaX, deltaY);
6229     
6230     return ($.event.dispatch || $.event.handle).apply(this, args);
6231 }
6232
6233 })(jQuery);