2.0_beta sync to rsa
[framework/web/web-ui-fw.git] / libs / js / jquery-geo-1.0a4 / docs / jquery.geo-1.0a3.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 /* Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net)
1442  * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
1443  * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
1444  * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
1445  * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
1446  *
1447  * Version: 3.0.2
1448  * 
1449  * Requires: 1.2.2+
1450  */
1451 (function(c){var a=["DOMMouseScroll","mousewheel"];c.event.special.mousewheel={setup:function(){if(this.addEventListener){for(var d=a.length;d;){this.addEventListener(a[--d],b,false)}}else{this.onmousewheel=b}},teardown:function(){if(this.removeEventListener){for(var d=a.length;d;){this.removeEventListener(a[--d],b,false)}}else{this.onmousewheel=null}}};c.fn.extend({mousewheel:function(d){return d?this.bind("mousewheel",d):this.trigger("mousewheel")},unmousewheel:function(d){return this.unbind("mousewheel",d)}});function b(f){var d=[].slice.call(arguments,1),g=0,e=true;f=c.event.fix(f||window.event);f.type="mousewheel";if(f.wheelDelta){g=f.wheelDelta/120}if(f.detail){g=-f.detail/3}d.unshift(f,g);return c.event.handle.apply(this,d)}})(jQuery);
1452
1453 /*!
1454  * jQuery UI Widget @VERSION
1455  *
1456  * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
1457  * Dual licensed under the MIT or GPL Version 2 licenses.
1458  * http://jquery.org/license
1459  *
1460  * http://docs.jquery.com/UI/Widget
1461  */
1462 if (!$.widget) {
1463 (function( $, undefined ) {
1464
1465 var slice = Array.prototype.slice;
1466
1467 var _cleanData = $.cleanData;
1468 $.cleanData = function( elems ) {
1469         for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
1470                 $( elem ).triggerHandler( "remove" );
1471         }
1472         _cleanData( elems );
1473 };
1474
1475 $.widget = function( name, base, prototype ) {
1476         var namespace = name.split( "." )[ 0 ],
1477                 fullName;
1478         name = name.split( "." )[ 1 ];
1479         fullName = namespace + "-" + name;
1480
1481         if ( !prototype ) {
1482                 prototype = base;
1483                 base = $.Widget;
1484         }
1485
1486         // create selector for plugin
1487         $.expr[ ":" ][ fullName ] = function( elem ) {
1488                 return !!$.data( elem, name );
1489         };
1490
1491         $[ namespace ] = $[ namespace ] || {};
1492         // create the constructor using $.extend() so we can carry over any
1493         // static properties stored on the existing constructor (if there is one)
1494         $[ namespace ][ name ] = $.extend( function( options, element ) {
1495                 // allow instantiation without "new" keyword
1496                 if ( !this._createWidget ) {
1497                         return new $[ namespace ][ name ]( options, element );
1498                 }
1499
1500                 // allow instantiation without initializing for simple inheritance
1501                 // must use "new" keyword (the code above always passes args)
1502                 if ( arguments.length ) {
1503                         this._createWidget( options, element );
1504                 }
1505         }, $[ namespace ][ name ] );
1506
1507         var basePrototype = new base();
1508         // we need to make the options hash a property directly on the new instance
1509         // otherwise we'll modify the options hash on the prototype that we're
1510         // inheriting from
1511         basePrototype.options = $.extend( true, {}, basePrototype.options );
1512         $.each( prototype, function( prop, value ) {
1513                 if ( $.isFunction( value ) ) {
1514                         prototype[ prop ] = (function() {
1515                                 var _super = function( method ) {
1516                                         return base.prototype[ method ].apply( this, slice.call( arguments, 1 ) );
1517                                 };
1518                                 var _superApply = function( method, args ) {
1519                                         return base.prototype[ method ].apply( this, args );
1520                                 };
1521                                 return function() {
1522                                         var __super = this._super,
1523                                                 __superApply = this._superApply,
1524                                                 returnValue;
1525
1526                                         this._super = _super;
1527                                         this._superApply = _superApply;
1528
1529                                         returnValue = value.apply( this, arguments );
1530
1531                                         this._super = __super;
1532                                         this._superApply = __superApply;
1533
1534                                         return returnValue;
1535                                 };
1536                         }());
1537                 }
1538         });
1539         $[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
1540                 namespace: namespace,
1541                 widgetName: name,
1542                 widgetEventPrefix: name,
1543                 widgetBaseClass: fullName
1544         }, prototype );
1545
1546         $.widget.bridge( name, $[ namespace ][ name ] );
1547 };
1548
1549 $.widget.bridge = function( name, object ) {
1550         $.fn[ name ] = function( options ) {
1551                 var isMethodCall = typeof options === "string",
1552                         args = slice.call( arguments, 1 ),
1553                         returnValue = this;
1554
1555                 // allow multiple hashes to be passed on init
1556                 options = !isMethodCall && args.length ?
1557                         $.extend.apply( null, [ true, options ].concat(args) ) :
1558                         options;
1559
1560                 if ( isMethodCall ) {
1561                         this.each(function() {
1562                                 var instance = $.data( this, name );
1563                                 if ( !instance ) {
1564                                         return $.error( "cannot call methods on " + name + " prior to initialization; " +
1565                                                 "attempted to call method '" + options + "'" );
1566                                 }
1567                                 if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
1568                                         return $.error( "no such method '" + options + "' for " + name + " widget instance" );
1569                                 }
1570                                 var methodValue = instance[ options ].apply( instance, args );
1571                                 if ( methodValue !== instance && methodValue !== undefined ) {
1572                                         returnValue = methodValue;
1573                                         return false;
1574                                 }
1575                         });
1576                 } else {
1577                         this.each(function() {
1578                                 var instance = $.data( this, name );
1579                                 if ( instance ) {
1580                                         instance.option( options || {} )._init();
1581                                 } else {
1582                                         object( options, this );
1583                                 }
1584                         });
1585                 }
1586
1587                 return returnValue;
1588         };
1589 };
1590
1591 $.Widget = function( options, element ) {
1592         // allow instantiation without "new" keyword
1593         if ( !this._createWidget ) {
1594                 return new $[ namespace ][ name ]( options, element );
1595         }
1596
1597         // allow instantiation without initializing for simple inheritance
1598         // must use "new" keyword (the code above always passes args)
1599         if ( arguments.length ) {
1600                 this._createWidget( options, element );
1601         }
1602 };
1603
1604 $.Widget.prototype = {
1605         widgetName: "widget",
1606         widgetEventPrefix: "",
1607         defaultElement: "<div>",
1608         options: {
1609                 disabled: false
1610         },
1611         _createWidget: function( options, element ) {
1612                 element = $( element || this.defaultElement || this )[ 0 ];
1613                 this.element = $( element );
1614                 this.options = $.extend( true, {},
1615                         this.options,
1616                         this._getCreateOptions(),
1617                         options );
1618
1619                 this.bindings = $();
1620                 this.hoverable = $();
1621                 this.focusable = $();
1622
1623                 if ( element !== this ) {
1624                         $.data( element, this.widgetName, this );
1625                         this._bind({ remove: "destroy" });
1626                 }
1627
1628                 this._create();
1629                 this._trigger( "create" );
1630                 this._init();
1631         },
1632         _getCreateOptions: function() {
1633                 return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
1634         },
1635         _create: $.noop,
1636         _init: $.noop,
1637
1638         destroy: function() {
1639                 this._destroy();
1640                 // we can probably remove the unbind calls in 2.0
1641                 // all event bindings should go through this._bind()
1642                 this.element
1643                         .unbind( "." + this.widgetName )
1644                         .removeData( this.widgetName );
1645                 this.widget()
1646                         .unbind( "." + this.widgetName )
1647                         .removeAttr( "aria-disabled" )
1648                         .removeClass(
1649                                 this.widgetBaseClass + "-disabled " +
1650                                 "ui-state-disabled" );
1651
1652                 // clean up events and states
1653                 this.bindings.unbind( "." + this.widgetName );
1654                 this.hoverable.removeClass( "ui-state-hover" );
1655                 this.focusable.removeClass( "ui-state-focus" );
1656         },
1657         _destroy: $.noop,
1658
1659         widget: function() {
1660                 return this.element;
1661         },
1662
1663         option: function( key, value ) {
1664                 var options = key;
1665
1666                 if ( arguments.length === 0 ) {
1667                         // don't return a reference to the internal hash
1668                         return $.extend( {}, this.options );
1669                 }
1670
1671                 if  (typeof key === "string" ) {
1672                         if ( value === undefined ) {
1673                                 return this.options[ key ];
1674                         }
1675                         options = {};
1676                         options[ key ] = value;
1677                 }
1678
1679                 this._setOptions( options );
1680
1681                 return this;
1682         },
1683         _setOptions: function( options ) {
1684                 var self = this;
1685                 $.each( options, function( key, value ) {
1686                         self._setOption( key, value );
1687                 });
1688
1689                 return this;
1690         },
1691         _setOption: function( key, value ) {
1692                 this.options[ key ] = value;
1693
1694                 if ( key === "disabled" ) {
1695                         this.widget()
1696                                 .toggleClass( this.widgetBaseClass + "-disabled ui-state-disabled", !!value )
1697                                 .attr( "aria-disabled", value );
1698                         this.hoverable.removeClass( "ui-state-hover" );
1699                         this.focusable.removeClass( "ui-state-focus" );
1700                 }
1701
1702                 return this;
1703         },
1704
1705         enable: function() {
1706                 return this._setOption( "disabled", false );
1707         },
1708         disable: function() {
1709                 return this._setOption( "disabled", true );
1710         },
1711
1712         _bind: function( element, handlers ) {
1713                 // no element argument, shuffle and use this.element
1714                 if ( !handlers ) {
1715                         handlers = element;
1716                         element = this.element;
1717                 } else {
1718                         // accept selectors, DOM elements
1719                         element = $( element );
1720                         this.bindings = this.bindings.add( element );
1721                 }
1722                 var instance = this;
1723                 $.each( handlers, function( event, handler ) {
1724                         element.bind( event + "." + instance.widgetName, function() {
1725                                 // allow widgets to customize the disabled handling
1726                                 // - disabled as an array instead of boolean
1727                                 // - disabled class as method for disabling individual parts
1728                                 if ( instance.options.disabled === true ||
1729                                                 $( this ).hasClass( "ui-state-disabled" ) ) {
1730                                         return;
1731                                 }
1732                                 return ( typeof handler === "string" ? instance[ handler ] : handler )
1733                                         .apply( instance, arguments );
1734                         });
1735                 });
1736         },
1737
1738         _hoverable: function( element ) {
1739                 this.hoverable = this.hoverable.add( element );
1740                 this._bind( element, {
1741                         mouseenter: function( event ) {
1742                                 $( event.currentTarget ).addClass( "ui-state-hover" );
1743                         },
1744                         mouseleave: function( event ) {
1745                                 $( event.currentTarget ).removeClass( "ui-state-hover" );
1746                         }
1747                 });
1748         },
1749
1750         _focusable: function( element ) {
1751                 this.focusable = this.focusable.add( element );
1752                 this._bind( element, {
1753                         focusin: function( event ) {
1754                                 $( event.currentTarget ).addClass( "ui-state-focus" );
1755                         },
1756                         focusout: function( event ) {
1757                                 $( event.currentTarget ).removeClass( "ui-state-focus" );
1758                         }
1759                 });
1760         },
1761
1762         _trigger: function( type, event, data ) {
1763                 var callback = this.options[ type ],
1764                         args;
1765
1766                 event = $.Event( event );
1767                 event.type = ( type === this.widgetEventPrefix ?
1768                         type :
1769                         this.widgetEventPrefix + type ).toLowerCase();
1770                 data = data || {};
1771
1772                 // copy original event properties over to the new event
1773                 // this would happen if we could call $.event.fix instead of $.Event
1774                 // but we don't have a way to force an event to be fixed multiple times
1775                 if ( event.originalEvent ) {
1776                         for ( var i = $.event.props.length, prop; i; ) {
1777                                 prop = $.event.props[ --i ];
1778                                 event[ prop ] = event.originalEvent[ prop ];
1779                         }
1780                 }
1781
1782                 this.element.trigger( event, data );
1783
1784                 args = $.isArray( data ) ?
1785                         [ event ].concat( data ) :
1786                         [ event, data ];
1787
1788                 return !( $.isFunction( callback ) &&
1789                         callback.apply( this.element[0], args ) === false ||
1790                         event.isDefaultPrevented() );
1791         }
1792 };
1793
1794 })( jQuery );
1795 }(function ($, window, undefined) {
1796   var pos_oo = Number.POSITIVE_INFINITY,
1797       neg_oo = Number.NEGATIVE_INFINITY;
1798
1799   $.geo = {
1800     //
1801     // utility functions
1802     //
1803
1804     _allCoordinates: function (geom) {
1805       // return array of all positions in all geometries of geom
1806       // not in JTS
1807       var geometries = this._flatten(geom),
1808           curGeom = 0,
1809           result = [];
1810
1811       for (; curGeom < geometries.length; curGeom++) {
1812         var coordinates = geometries[curGeom].coordinates,
1813             isArray = coordinates && $.isArray(coordinates[0]),
1814             isDblArray = isArray && $.isArray(coordinates[0][0]),
1815             isTriArray = isDblArray && $.isArray(coordinates[0][0][0]),
1816             i, j, k;
1817
1818         if (!isTriArray) {
1819           if (!isDblArray) {
1820             if (!isArray) {
1821               coordinates = [coordinates];
1822             }
1823             coordinates = [coordinates];
1824           }
1825           coordinates = [coordinates];
1826         }
1827
1828         for (i = 0; i < coordinates.length; i++) {
1829           for (j = 0; j < coordinates[i].length; j++) {
1830             for (k = 0; k < coordinates[i][j].length; k++) {
1831               result.push(coordinates[i][j][k]);
1832             }
1833           }
1834         }
1835       }
1836       return result;
1837     },
1838
1839     //
1840     // bbox functions
1841     //
1842
1843     center: function (bbox, _ignoreGeo /* Internal Use Only */) {
1844       // Envelope.centre in JTS
1845       // bbox only, use centroid for geom
1846       if (!_ignoreGeo && $.geo.proj) {
1847         bbox = $.geo.proj.fromGeodetic(bbox);
1848       }
1849       var center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
1850       return !_ignoreGeo && $.geo.proj ? $.geo.proj.toGeodetic(center) : center;
1851     },
1852
1853     expandBy: function (bbox, dx, dy) {
1854       if ($.geo.proj) {
1855         bbox = $.geo.proj.fromGeodetic(bbox);
1856       }
1857       bbox = [bbox[0] - dx, bbox[1] - dy, bbox[2] + dx, bbox[3] + dy];
1858       return $.geo.proj ? $.geo.proj.toGeodetic(bbox) : bbox;
1859     },
1860
1861     height: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
1862       if (!_ignoreGeo && $.geo.proj) {
1863         bbox = $.geo.proj.fromGeodetic(bbox);
1864       }
1865       return bbox[3] - bbox[1];
1866     },
1867
1868     _in: function(bbox1, bbox2) {
1869       return bbox1[0] <= bbox2[0] &&
1870              bbox1[1] <= bbox2[1] &&
1871              bbox1[2] >= bbox2[2] &&
1872              bbox1[3] >= bbox2[3];
1873     },
1874
1875     _bboxDisjoint: function( bbox1, bbox2 ) {
1876       return bbox2[ 0 ] > bbox1[ 2 ] || 
1877              bbox2[ 2 ] < bbox1[ 0 ] || 
1878              bbox2[ 1 ] > bbox1[ 3 ] ||
1879              bbox2[ 3 ] < bbox1[ 1 ];
1880     },
1881
1882     reaspect: function (bbox, ratio, _ignoreGeo /* Internal Use Only */ ) {
1883       // not in JTS
1884       if (!_ignoreGeo && $.geo.proj) {
1885         bbox = $.geo.proj.fromGeodetic(bbox);
1886       }
1887       var width = this.width(bbox, true),
1888           height = this.height(bbox, true),
1889           center = this.center(bbox, true),
1890           dx, dy;
1891
1892       if (width != 0 && height != 0 && ratio > 0) {
1893         if (width / height > ratio) {
1894           dx = width / 2;
1895           dy = dx / ratio;
1896         } else {
1897           dy = height / 2;
1898           dx = dy * ratio;
1899         }
1900
1901         bbox = [center[0] - dx, center[1] - dy, center[0] + dx, center[1] + dy];
1902       }
1903       return $.geo.proj ? $.geo.proj.toGeodetic(bbox) : bbox;
1904     },
1905
1906     scaleBy: function ( bbox, scale, _ignoreGeo /* Internal Use Only */ ) {
1907       // not in JTS
1908       if (!_ignoreGeo && $.geo.proj) {
1909         bbox = $.geo.proj.fromGeodetic(bbox);
1910       }
1911       var c = this.center(bbox, true),
1912           dx = (bbox[2] - bbox[0]) * scale / 2,
1913           dy = (bbox[3] - bbox[1]) * scale / 2;
1914       bbox = [c[0] - dx, c[1] - dy, c[0] + dx, c[1] + dy];
1915       return !_ignoreGeo && $.geo.proj ? $.geo.proj.toGeodetic(bbox) : bbox;
1916     },
1917
1918     width: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
1919       if (!_ignoreGeo && $.geo.proj) {
1920         bbox = $.geo.proj.fromGeodetic(bbox);
1921       }
1922       return bbox[2] - bbox[0];
1923     },
1924
1925     //
1926     // geometry functions
1927     //
1928
1929     // bbox (Geometry.getEnvelope in JTS)
1930
1931     bbox: function ( geom, _ignoreGeo /* Internal Use Only */ ) {
1932       if ( !geom ) {
1933         return undefined;
1934       } else if ( geom.bbox ) {
1935         result = !_ignoreGeo && $.geo.proj ? $.geo.proj.fromGeodetic( geom.bbox ) : geom.bbox;
1936       } else {
1937         result = [ pos_oo, pos_oo, neg_oo, neg_oo ];
1938
1939         var coordinates = this._allCoordinates( geom ),
1940             curCoord = 0;
1941
1942         if ( coordinates.length == 0 ) {
1943           return undefined;
1944         }
1945
1946         if ( $.geo.proj ) {
1947           coordinates = $.geo.proj.fromGeodetic( coordinates );
1948         }
1949
1950         for ( ; curCoord < coordinates.length; curCoord++ ) {
1951           result[0] = Math.min(coordinates[curCoord][0], result[0]);
1952           result[1] = Math.min(coordinates[curCoord][1], result[1]);
1953           result[2] = Math.max(coordinates[curCoord][0], result[2]);
1954           result[3] = Math.max(coordinates[curCoord][1], result[3]);
1955         }
1956       }
1957
1958       return $.geo.proj ? $.geo.proj.toGeodetic(result) : result;
1959     },
1960
1961     // centroid
1962     
1963     centroid: function( geom, _ignoreGeo /* Internal Use Only */ ) {
1964       switch (geom.type) {
1965         case "Point":
1966           return $.extend({}, geom);
1967
1968         case "LineString":
1969         case "Polygon":
1970           var a = 0,
1971               c = [0, 0],
1972               coords = $.merge( [ ], geom.type == "Polygon" ? geom.coordinates[0] : geom.coordinates ),
1973               i = 1, j, n;
1974
1975           if ( !_ignoreGeo && $.geo.proj ) {
1976             coords = $.geo.proj.fromGeodetic(coords);
1977           }
1978
1979           //if (coords[0][0] != coords[coords.length - 1][0] || coords[0][1] != coords[coords.length - 1][1]) {
1980           //  coords.push(coords[0]);
1981           //}
1982
1983           for (; i <= coords.length; i++) {
1984             j = i % coords.length;
1985             n = (coords[i - 1][0] * coords[j][1]) - (coords[j][0] * coords[i - 1][1]);
1986             a += n;
1987             c[0] += (coords[i - 1][0] + coords[j][0]) * n;
1988             c[1] += (coords[i - 1][1] + coords[j][1]) * n;
1989           }
1990
1991           if (a == 0) {
1992             if (coords.length > 0) {
1993               c[0] = coords[0][0];
1994               c[1] = coords[0][1];
1995               return { type: "Point", coordinates: !_ignoreGeo && $.geo.proj ? $.geo.proj.toGeodetic(c) : c };
1996             } else {
1997               return undefined;
1998             }
1999           }
2000
2001           a *= 3;
2002           c[0] /= a;
2003           c[1] /= a;
2004
2005           return { type: "Point", coordinates: !_ignoreGeo && $.geo.proj ? $.geo.proj.toGeodetic(c) : c };
2006       }
2007       return undefined;
2008     },
2009
2010     // contains
2011
2012     contains: function (geom1, geom2) {
2013       if (geom1.type != "Polygon") {
2014         return false;
2015       }
2016
2017       switch (geom2.type) {
2018         case "Point":
2019           return this._containsPolygonPoint(geom1.coordinates, geom2.coordinates);
2020
2021         case "LineString":
2022           return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates);
2023
2024         case "Polygon":
2025           return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates[0]);
2026
2027         default:
2028           return false;
2029       }
2030     },
2031
2032     _containsPolygonPoint: function (polygonCoordinates, pointCoordinate) {
2033       if (polygonCoordinates.length == 0 || polygonCoordinates[0].length < 4) {
2034         return false;
2035       }
2036
2037       var rayCross = 0,
2038           a = polygonCoordinates[0][0],
2039           i = 1,
2040           b,
2041           x;
2042
2043       for (; i < polygonCoordinates[0].length; i++) {
2044         b = polygonCoordinates[0][i];
2045
2046         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])) {
2047           x = a[0] + (b[0] - a[0]) * (pointCoordinate[1] - a[1]) / (b[1] - a[1]);
2048
2049           if (x > pointCoordinate[0]) {
2050             rayCross++;
2051           }
2052         }
2053
2054         a = b;
2055       }
2056
2057       return rayCross % 2 == 1;
2058     },
2059
2060     _containsPolygonLineString: function (polygonCoordinates, lineStringCoordinates) {
2061       for (var i = 0; i < lineStringCoordinates.length; i++) {
2062         if (!this._containsPolygonPoint(polygonCoordinates, lineStringCoordinates[i])) {
2063           return false;
2064         }
2065       }
2066       return true;
2067     },
2068
2069     // distance
2070
2071     distance: function ( geom1, geom2, _ignoreGeo /* Internal Use Only */ ) {
2072       var geom1CoordinatesProjected = !_ignoreGeo && $.geo.proj ? $.geo.proj.fromGeodetic(geom1.coordinates) : geom1.coordinates,
2073           geom2CoordinatesProjected = !_ignoreGeo && $.geo.proj ? $.geo.proj.fromGeodetic(geom2.coordinates) : geom2.coordinates;
2074
2075       switch (geom1.type) {
2076         case "Point":
2077           switch (geom2.type) {
2078             case "Point":
2079               return this._distancePointPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
2080             case "LineString":
2081               return this._distanceLineStringPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
2082             case "Polygon":
2083               return this._containsPolygonPoint(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
2084             default:
2085               return undefined;
2086           }
2087           break;
2088
2089         case "LineString":
2090           switch (geom2.type) {
2091             case "Point":
2092               return this._distanceLineStringPoint(geom1CoordinatesProjected, geom2CoordinatesProjected);
2093             case "LineString":
2094               return this._distanceLineStringLineString(geom1CoordinatesProjected, geom2CoordinatesProjected);
2095             case "Polygon":
2096               return this._containsPolygonLineString(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
2097             default:
2098               return undefined;
2099           }
2100           break;
2101
2102         case "Polygon":
2103           switch (geom2.type) {
2104             case "Point":
2105               return this._containsPolygonPoint(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
2106             case "LineString":
2107               return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
2108             case "Polygon":
2109               return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected[0]) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected[0]);
2110             default:
2111               return undefined;
2112           }
2113           break;
2114       }
2115     },
2116
2117     _distancePointPoint: function (coordinate1, coordinate2) {
2118       var dx = coordinate2[0] - coordinate1[0],
2119           dy = coordinate2[1] - coordinate1[1];
2120       return Math.sqrt((dx * dx) + (dy * dy));
2121     },
2122
2123     _distanceLineStringPoint: function (lineStringCoordinates, pointCoordinate) {
2124       var minDist = pos_oo;
2125
2126       if (lineStringCoordinates.length > 0) {
2127         var a = lineStringCoordinates[0],
2128
2129             apx = pointCoordinate[0] - a[0],
2130             apy = pointCoordinate[1] - a[1];
2131
2132         if (lineStringCoordinates.length == 1) {
2133           return Math.sqrt(apx * apx + apy * apy);
2134         } else {
2135           for (var i = 1; i < lineStringCoordinates.length; i++) {
2136             var b = lineStringCoordinates[i],
2137
2138                 abx = b[0] - a[0],
2139                 aby = b[1] - a[1],
2140                 bpx = pointCoordinate[0] - b[0],
2141                 bpy = pointCoordinate[1] - b[1],
2142
2143                 d = this._distanceSegmentPoint(abx, aby, apx, apy, bpx, bpy);
2144
2145             if (d == 0) {
2146               return 0;
2147             }
2148
2149             if (d < minDist) {
2150               minDist = d;
2151             }
2152
2153             a = b;
2154             apx = bpx;
2155             apy = bpy;
2156           }
2157         }
2158       }
2159
2160       return Math.sqrt(minDist);
2161     },
2162
2163     _distanceSegmentPoint: function (abx, aby, apx, apy, bpx, bpy) {
2164       var dot1 = abx * apx + aby * apy;
2165
2166       if (dot1 <= 0) {
2167         return apx * apx + apy * apy;
2168       }
2169
2170       var dot2 = abx * abx + aby * aby;
2171
2172       if (dot1 >= dot2) {
2173         return bpx * bpx + bpy * bpy;
2174       }
2175
2176       return apx * apx + apy * apy - dot1 * dot1 / dot2;
2177     },
2178
2179     _distanceLineStringLineString: function (lineStringCoordinates1, lineStringCoordinates2) {
2180       var minDist = pos_oo;
2181       for (var i = 0; i < lineStringCoordinates2.length; i++) {
2182         minDist = Math.min(minDist, this._distanceLineStringPoint(lineStringCoordinates1, lineStringCoordinates2[i]));
2183       }
2184       return minDist;
2185     },
2186
2187     //
2188     // feature
2189     //
2190
2191     _flatten: function (geom) {
2192       // return an array of all basic geometries
2193       // not in JTS
2194       var geometries = [],
2195           curGeom = 0;
2196       switch (geom.type) {
2197         case "Feature":
2198           $.merge(geometries, this._flatten(geom.geometry));
2199           break;
2200
2201         case "FeatureCollection":
2202           for (; curGeom < geom.features.length; curGeom++) {
2203             $.merge(geometries, this._flatten(geom.features[curGeom].geometry));
2204           }
2205           break;
2206
2207         case "GeometryCollection":
2208           for (; curGeom < geom.geometries.length; curGeom++) {
2209             $.merge(geometries, this._flatten(geom.geometries[curGeom]));
2210           }
2211           break;
2212
2213         default:
2214           geometries[0] = geom;
2215           break;
2216       }
2217       return geometries;
2218     },
2219
2220     //
2221     // WKT functions
2222     //
2223
2224     _WKT: (function () {
2225       function pointToString(value) {
2226         return "POINT " + pointToUntaggedString(value.coordinates);
2227       }
2228
2229       function pointToUntaggedString(coordinates) {
2230         if (!(coordinates && coordinates.length)) {
2231           return "EMPTY";
2232         } else {
2233           return "(" + coordinates.join(" ") + ")";
2234         }
2235       }
2236
2237       function lineStringToString(value) {
2238         return "LINESTRING " + lineStringToUntaggedString(value.coordinates);
2239       }
2240
2241       function lineStringToUntaggedString(coordinates) {
2242         if (!(coordinates && coordinates.length)) {
2243           return "EMPTY";
2244         } else {
2245           var points = []
2246
2247           for (var i = 0; i < coordinates.length; i++) {
2248             points.push(coordinates[i].join(" "));
2249           }
2250
2251           return "(" + points + ")";
2252         }
2253       }
2254
2255       function polygonToString(value) {
2256         return "POLYGON " + polygonToUntaggedString(value.coordinates);
2257       }
2258
2259       function polygonToUntaggedString(coordinates) {
2260         if (!(coordinates && coordinates.length)) {
2261           return "EMTPY";
2262         } else {
2263           var lineStrings = [];
2264
2265           for (var i = 0; i < coordinates.length; i++) {
2266             lineStrings.push(lineStringToUntaggedString(coordinates[i]));
2267           }
2268
2269           return "(" + lineStrings + ")";
2270         }
2271       }
2272
2273       function multiPointToString(value) {
2274         return "MULTIPOINT " + lineStringToUntaggedString(value.coordinates);
2275       }
2276
2277       function multiLineStringToString(value) {
2278         return "MULTILINSTRING " + polygonToUntaggedString(value.coordinates);
2279       }
2280
2281       function multiPolygonToString(value) {
2282         return "MULTIPOLYGON " + multiPolygonToUntaggedString(value.coordinates);
2283       }
2284
2285       function multiPolygonToUntaggedString(coordinates) {
2286         if (!(coordinates && coordinates.length)) {
2287           return "EMPTY";
2288         } else {
2289           var polygons = [];
2290           for (var i = 0; i < coordinates.length; i++) {
2291             polygons.push(polygonToUntaggedString(coordinates[i]));
2292           }
2293           return "(" + polygons + ")";
2294         }
2295       }
2296
2297       function geometryCollectionToString(value) {
2298         return "GEOMETRYCOLLECTION " + geometryCollectionToUntaggedString(value.geometries);
2299       }
2300
2301       function geometryCollectionToUntaggedString(geometries) {
2302         if (!(geometries && geometries.length)) {
2303           return "EMPTY";
2304         } else {
2305           var geometryText = [];
2306           for (var i = 0; i < geometries.length; i++) {
2307             geometryText.push(stringify(geometries[i]));
2308           }
2309           return "(" + geometries + ")";
2310         }
2311       }
2312
2313       function stringify(value) {
2314         if (!(value && value.type)) {
2315           return "";
2316         } else {
2317           switch (value.type) {
2318             case "Point":
2319               return pointToString(value);
2320
2321             case "LineString":
2322               return lineStringToString(value);
2323
2324             case "Polygon":
2325               return polygonToString(value);
2326
2327             case "MultiPoint":
2328               return multiPointToString(value);
2329
2330             case "MultiLineString":
2331               return multiLineStringToString(value);
2332
2333             case "MultiPolygon":
2334               return multiPolygonToString(value);
2335
2336             case "GeometryCollection":
2337               return geometryCollectionToString(value);
2338
2339             default:
2340               return "";
2341           }
2342         }
2343       }
2344
2345       function pointParseUntagged(wkt) {
2346         var pointString = wkt.match(/\(\s*([\d\.-]+)\s+([\d\.-]+)\s*\)/);
2347         return pointString && pointString.length >= 2 ? {
2348           type: "Point",
2349           coordinates: [
2350             parseFloat(pointString[1]),
2351             parseFloat(pointString[2])
2352           ]
2353         } : null;
2354       }
2355
2356       function parse(wkt) {
2357         wkt = $.trim(wkt);
2358
2359         var typeIndex = wkt.indexOf(" ");
2360
2361         switch (wkt.substr(0, typeIndex).toUpperCase()) {
2362           case "POINT":
2363             return pointParseUntagged(wkt.substr(typeIndex + 1));
2364         }
2365       }
2366
2367       return {
2368         stringify: stringify,
2369
2370         parse: parse
2371       };
2372     })(),
2373
2374     //
2375     // projection functions
2376     //
2377
2378     proj: (function () {
2379       var halfPi = 1.5707963267948966192,
2380           quarterPi = 0.7853981633974483096,
2381           radiansPerDegree = 0.0174532925199432958,
2382           degreesPerRadian = 57.295779513082320877,
2383           semiMajorAxis = 6378137;
2384
2385       return {
2386         fromGeodeticPos: function (coordinate) {
2387           if (!coordinate) {
2388             debugger;
2389           }
2390           return [
2391             semiMajorAxis * coordinate[ 0 ] * radiansPerDegree,
2392             semiMajorAxis * Math.log(Math.tan(quarterPi + coordinate[ 1 ] * radiansPerDegree / 2))
2393           ];
2394         },
2395
2396         fromGeodetic: function (coordinates) {
2397           var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
2398               fromGeodeticPos = this.fromGeodeticPos;
2399
2400           if (!isMultiPointOrLineString && coordinates.length == 4) {
2401             // bbox
2402             var min = fromGeodeticPos([ coordinates[ 0 ], coordinates[ 1 ] ]),
2403                 max = fromGeodeticPos([ coordinates[ 2 ], coordinates[ 3 ] ]);
2404             return [ min[ 0 ], min[ 1 ], max[ 0 ], max[ 1 ] ];
2405           } else {
2406             // geometry
2407             var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
2408                 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
2409                 result = [ ],
2410                 i, j, k;
2411
2412             if (!isMultiPolygon) {
2413               if (!isMultiLineStringOrPolygon) {
2414                 if (!isMultiPointOrLineString) {
2415                   coordinates = [ coordinates ];
2416                 }
2417                 coordinates = [ coordinates ];
2418               }
2419               coordinates = [ coordinates ];
2420             }
2421
2422             for ( i = 0; i < coordinates.length; i++ ) {
2423               result[ i ] = [ ];
2424               for ( j = 0; j < coordinates[ i ].length; j++ ) {
2425                 result[ i ][ j ] = [ ];
2426                 for ( k = 0; k < coordinates[ i ][ j ].length; k++ ) {
2427                   result[ i ][ j ][ k ] = fromGeodeticPos(coordinates[ i ][ j ][ k ]);
2428                 }
2429               }
2430             }
2431
2432             return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
2433           }
2434         },
2435
2436         toGeodeticPos: function (coordinate) {
2437           return [
2438             (coordinate[ 0 ] / semiMajorAxis) * degreesPerRadian,
2439             (halfPi - 2 * Math.atan(1 / Math.exp(coordinate[ 1 ] / semiMajorAxis))) * degreesPerRadian
2440           ];
2441         },
2442
2443         toGeodetic: function (coordinates) {
2444           var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
2445               toGeodeticPos = this.toGeodeticPos;
2446
2447           if (!isMultiPointOrLineString && coordinates.length == 4) {
2448             // bbox
2449             var min = toGeodeticPos([ coordinates[ 0 ], coordinates[ 1 ] ]),
2450                 max = toGeodeticPos([ coordinates[ 2 ], coordinates[ 3 ] ]);
2451             return [ min[ 0 ], min[ 1 ], max[ 0 ], max[ 1 ] ];
2452           } else {
2453             // geometry
2454             var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
2455                 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
2456                 result = [ ];
2457
2458             if (!isMultiPolygon) {
2459               if (!isMultiLineStringOrPolygon) {
2460                 if (!isMultiPointOrLineString) {
2461                   coordinates = [ coordinates ];
2462                 }
2463                 coordinates = [ coordinates ];
2464               }
2465               coordinates = [ coordinates ];
2466             }
2467
2468             for ( i = 0; i < coordinates.length; i++ ) {
2469               result[ i ] = [ ];
2470               for ( j = 0; j < coordinates[ i ].length; j++ ) {
2471                 result[ i ][ j ] = [ ];
2472                 for ( k = 0; k < coordinates[ i ][ j ].length; k++ ) {
2473                   result[ i ][ j ][ k ] = toGeodeticPos(coordinates[ i ][ j ][ k ]);
2474                 }
2475               }
2476             }
2477
2478             return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
2479           }
2480         }
2481       }
2482     })(),
2483
2484     //
2485     // service types (defined in other files)
2486     //
2487
2488     _serviceTypes: {}
2489   }
2490 })(jQuery, this);
2491 (function ($, undefined) {
2492
2493   var _ieVersion = (function () {
2494     var v = 5, div = document.createElement("div"), a = div.all || [];
2495     while (div.innerHTML = "<!--[if gt IE " + (++v) + "]><br><![endif]-->", a[0]) { }
2496     return v > 6 ? v : !v;
2497   } ());
2498
2499   $.widget("geo.geographics", {
2500     _$elem: undefined,
2501     _options: {},
2502     _trueCanvas: true,
2503
2504     _width: 0,
2505     _height: 0,
2506
2507     _$canvas: undefined,
2508     _context: undefined,
2509
2510     options: {
2511       style: {
2512         borderRadius: "8px",
2513         color: "#7f0000",
2514         //fill: undefined,
2515         fillOpacity: .2,
2516         height: "8px",
2517         opacity: 1,
2518         //stroke: undefined,
2519         strokeOpacity: 1,
2520         strokeWidth: "2px",
2521         visibility: "visible",
2522         width: "8px"
2523       }
2524     },
2525
2526     _create: function () {
2527       this._$elem = this.element;
2528       this._options = this.options;
2529
2530       this._$elem.css({ display: "inline-block", overflow: "hidden", textAlign: "left" });
2531
2532       if (this._$elem.css("position") == "static") {
2533         this._$elem.css("position", "relative");
2534       }
2535
2536       this._width = this._$elem.width();
2537       this._height = this._$elem.height();
2538
2539       if (!(this._width && this._height)) {
2540         this._width = parseInt(this._$elem.css("width"));
2541         this._height = parseInt(this._$elem.css("height"));
2542       }
2543
2544       if (document.createElement('canvas').getContext) {
2545         this._$elem.append('<canvas width="' + this._width + '" height="' + this._height + '" style="position:absolute; left:0; top:0; width:' + this._width + 'px; height:' + this._height + 'px;"></canvas>');
2546         this._$canvas = this._$elem.children(':last');
2547         this._context = this._$canvas[0].getContext("2d");
2548       } else if (_ieVersion <= 8) {
2549         this._trueCanvas = false;
2550         this._$elem.append('<div width="' + this._width + '" height="' + this._height + '" style="position:absolute; left:0; top:0; width:' + this._width + 'px; height:' + this._height + 'px; margin:0; padding:0;"></div>');
2551         this._$canvas = this._$elem.children(':last');
2552
2553         G_vmlCanvasManager.initElement(this._$canvas[0]);
2554         this._context = this._$canvas[0].getContext("2d");
2555         this._$canvas.children().css({ backgroundColor: "transparent", width: this._width, height: this._height });
2556       }
2557     },
2558
2559     _setOption: function (key, value) {
2560       if (key == "style") {
2561         value = $.extend({}, this._options.style, value);
2562       }
2563       $.Widget.prototype._setOption.apply(this, arguments);
2564     },
2565
2566     destroy: function () {
2567       $.Widget.prototype.destroy.apply(this, arguments);
2568       this._$elem.html("");
2569     },
2570
2571     clear: function () {
2572       this._context.clearRect(0, 0, this._width, this._height);
2573     },
2574
2575     drawArc: function (coordinates, startAngle, sweepAngle, style) {
2576       style = this._getGraphicStyle(style);
2577
2578       if (style.visibility != "hidden" && style.opacity > 0 && style.widthValue > 0 && style.heightValue > 0) {
2579         var r = Math.min(style.widthValue, style.heightValue) / 2;
2580
2581         startAngle = (startAngle * Math.PI / 180);
2582         sweepAngle = (sweepAngle * Math.PI / 180);
2583
2584         this._context.save();
2585         this._context.translate(coordinates[0], coordinates[1]);
2586         if (style.widthValue > style.heightValue) {
2587           this._context.scale(style.widthValue / style.heightValue, 1);
2588         } else {
2589           this._context.scale(1, style.heightValue / style.widthValue);
2590         }
2591
2592         this._context.beginPath();
2593         this._context.arc(0, 0, r, startAngle, sweepAngle, false);
2594
2595         if (this._trueCanvas) {
2596           this._context.restore();
2597         }
2598
2599         if (style.doFill) {
2600           this._context.fillStyle = style.fill;
2601           this._context.globalAlpha = style.opacity * style.fillOpacity;
2602           this._context.fill();
2603         }
2604
2605         if (style.doStroke) {
2606           this._context.lineJoin = "round";
2607           this._context.lineWidth = style.strokeWidthValue;
2608           this._context.strokeStyle = style.stroke;
2609
2610           this._context.globalAlpha = style.opacity * style.strokeOpacity;
2611           this._context.stroke();
2612         }
2613
2614         if (!this._trueCanvas) {
2615           this._context.restore();
2616         }
2617       }
2618     },
2619
2620     drawPoint: function (coordinates, style) {
2621       var style = this._getGraphicStyle(style);
2622       if (style.widthValue == style.heightValue && style.heightValue == style.borderRadiusValue) {
2623         this.drawArc(coordinates, 0, 360, style);
2624       } else if (style.visibility != "hidden" && style.opacity > 0) {
2625         style.borderRadiusValue = Math.min(Math.min(style.widthValue, style.heightValue) / 2, style.borderRadiusValue);
2626         coordinates[0] -= style.widthValue / 2;
2627         coordinates[1] -= style.heightValue / 2;
2628         this._context.beginPath();
2629         this._context.moveTo(coordinates[0] + style.borderRadiusValue, coordinates[1]);
2630         this._context.lineTo(coordinates[0] + style.widthValue - style.borderRadiusValue, coordinates[1]);
2631         this._context.quadraticCurveTo(coordinates[0] + style.widthValue, coordinates[1], coordinates[0] + style.widthValue, coordinates[1] + style.borderRadiusValue);
2632         this._context.lineTo(coordinates[0] + style.widthValue, coordinates[1] + style.heightValue - style.borderRadiusValue);
2633         this._context.quadraticCurveTo(coordinates[0] + style.widthValue, coordinates[1] + style.heightValue, coordinates[0] + style.widthValue - style.borderRadiusValue, coordinates[1] + style.heightValue);
2634         this._context.lineTo(coordinates[0] + style.borderRadiusValue, coordinates[1] + style.heightValue);
2635         this._context.quadraticCurveTo(coordinates[0], coordinates[1] + style.heightValue, coordinates[0], coordinates[1] + style.heightValue - style.borderRadiusValue);
2636         this._context.lineTo(coordinates[0], coordinates[1] + style.borderRadiusValue);
2637         this._context.quadraticCurveTo(coordinates[0], coordinates[1], coordinates[0] + style.borderRadiusValue, coordinates[1]);
2638         this._context.closePath();
2639
2640         if (style.doFill) {
2641           this._context.fillStyle = style.fill;
2642           this._context.globalAlpha = style.opacity * style.fillOpacity;
2643           this._context.fill();
2644         }
2645
2646         if (style.doStroke) {
2647           this._context.lineJoin = "round";
2648           this._context.lineWidth = style.strokeWidthValue;
2649           this._context.strokeStyle = style.stroke;
2650
2651           this._context.globalAlpha = style.opacity * style.strokeOpacity;
2652
2653           this._context.stroke();
2654         }
2655       }
2656     },
2657
2658     drawLineString: function (coordinates, style) {
2659       this._drawLines([coordinates], false, style);
2660     },
2661
2662     drawPolygon: function (coordinates, style) {
2663       this._drawLines(coordinates, true, style);
2664     },
2665
2666     drawBbox: function (bbox, style) {
2667       this._drawLines([[
2668         [bbox[0], bbox[1]],
2669         [bbox[0], bbox[3]],
2670         [bbox[2], bbox[3]],
2671         [bbox[2], bbox[1]],
2672         [bbox[0], bbox[1]]
2673       ]], true, style);
2674     },
2675
2676     _getGraphicStyle: function (style) {
2677       function safeParse(value) {
2678         value = parseInt(value);
2679         return (+value + '') === value ? +value : value;
2680       }
2681
2682       style = $.extend({}, this._options.style, style);
2683       style.borderRadiusValue = safeParse(style.borderRadius);
2684       style.fill = style.fill || style.color;
2685       style.doFill = style.fill && style.fillOpacity > 0;
2686       style.stroke = style.stroke || style.color;
2687       style.strokeWidthValue = safeParse(style.strokeWidth);
2688       style.doStroke = style.stroke && style.strokeOpacity > 0 && style.strokeWidthValue > 0;
2689       style.widthValue = safeParse(style.width);
2690       style.heightValue = safeParse(style.height);
2691       return style;
2692     },
2693
2694     _drawLines: function (coordinates, close, style) {
2695       if (!coordinates || !coordinates.length || coordinates[0].length < 2) {
2696         return;
2697       }
2698
2699       var style = this._getGraphicStyle(style),
2700           i, j;
2701
2702       if (style.visibility != "hidden" && style.opacity > 0) {
2703         this._context.beginPath();
2704         this._context.moveTo(coordinates[0][0][0], coordinates[0][0][1]);
2705
2706         for (i = 0; i < coordinates.length; i++) {
2707           for (j = 0; j < coordinates[i].length; j++) {
2708             this._context.lineTo(coordinates[i][j][0], coordinates[i][j][1]);
2709           }
2710         }
2711
2712         if (close) {
2713           this._context.closePath();
2714         }
2715
2716         if (close && style.doFill) {
2717           this._context.fillStyle = style.fill;
2718           this._context.globalAlpha = style.opacity * style.fillOpacity;
2719           this._context.fill();
2720         }
2721
2722         if (style.doStroke) {
2723           this._context.lineCap = this._context.lineJoin = "round";
2724           this._context.lineWidth = style.strokeWidthValue;
2725           this._context.strokeStyle = style.stroke;
2726
2727           this._context.globalAlpha = style.opacity * style.strokeOpacity;
2728           this._context.stroke();
2729         }
2730       }
2731     }
2732   });
2733
2734
2735 })(jQuery);
2736
2737 (function ($, undefined) {
2738   var _ieVersion = (function () {
2739     var v = 5, div = document.createElement("div"), a = div.all || [];
2740     while (div.innerHTML = "<!--[if gt IE " + (++v) + "]><br><![endif]-->", a[0]) { }
2741     return v > 6 ? v : !v;
2742   } ()),
2743
2744       _defaultOptions = {
2745         bbox: [-180, -85, 180, 85],
2746         bboxMax: [-180, -85, 180, 85],
2747         center: [0, 0],
2748         cursors: {
2749           pan: "move",
2750           zoom: "crosshair",
2751           drawPoint: "crosshair",
2752           drawLineString: "crosshair",
2753           drawPolygon: "crosshair"
2754         },
2755         drawStyle: {},
2756         shapeStyle: {},
2757         mode: "pan",
2758         services: [
2759             {
2760               "class": "osm",
2761               type: "tiled",
2762               getUrl: function (view) {
2763                 return "http://tile.openstreetmap.org/" + view.zoom + "/" + view.tile.column + "/" + view.tile.row + ".png";
2764               },
2765               attr: "&copy; OpenStreetMap &amp; contributors, CC-BY-SA"
2766             }
2767           ],
2768         tilingScheme: {
2769           tileWidth: 256,
2770           tileHeight: 256,
2771           levels: 18,
2772           basePixelSize: 156543.03392799936,
2773           origin: [-20037508.342787, 20037508.342787]
2774         },
2775         zoom: 0,
2776         pixelSize: 0
2777       };
2778
2779   $.widget("geo.geomap", {
2780     // private widget members
2781     _$elem: undefined,
2782     _created: false,
2783
2784     _contentBounds: {},
2785
2786     _$contentFrame: undefined,
2787     _$existingChildren: undefined,
2788     _$servicesContainer: undefined,
2789     _$drawContainer: undefined,
2790     _$shapesContainer: undefined,
2791     _$textContainer: undefined,
2792     _$textContent: undefined,
2793     _$eventTarget: undefined,
2794
2795     _dpi: 96,
2796
2797     _currentServices: [], //< internal copy
2798
2799     _center: undefined,
2800     _pixelSize: undefined,
2801     _centerMax: undefined,
2802     _pixelSizeMax: undefined,
2803
2804     _wheelZoomFactor: 1.18920711500273,
2805     _wheelTimeout: null,
2806     _wheelLevel: 0,
2807
2808     _zoomFactor: 2,
2809
2810     _mouseDown: undefined,
2811     _inOp: undefined,
2812     _toolPan: undefined,
2813     _shiftZoom: undefined,
2814     _anchor: undefined,
2815     _current: undefined,
2816     _downDate: undefined,
2817     _moveDate: undefined,
2818     _clickDate: undefined,
2819     _lastMove: undefined,
2820     _lastDrag: undefined,
2821
2822     _windowHandler: null,
2823     _resizeTimeout: null,
2824
2825     _panning: undefined,
2826     _velocity: undefined,
2827     _friction: undefined,
2828
2829     _supportTouch: undefined,
2830     _softDblClick: undefined,
2831     _isTap: undefined,
2832     _isDbltap: undefined,
2833
2834     _drawTimeout: null, //< used in drawPoint mode so we don't send two shape events on dbltap
2835     _drawPixels: [], //< an array of coordinate arrays for drawing lines & polygons, in pixel coordinates
2836     _drawCoords: [],
2837
2838     _graphicShapes: [], //< an array of objects containing style object refs & GeoJSON object refs
2839
2840     _initOptions: {},
2841
2842     _options: {},
2843
2844     options: $.extend({}, _defaultOptions),
2845
2846     _createWidget: function (options, element) {
2847       this._$elem = $(element);
2848
2849       if (this._$elem.is("[data-geo-service]")) {
2850         $.Widget.prototype._createWidget.apply(this, arguments);
2851         return;
2852       }
2853
2854       this._$elem.attr("data-geo-map", "data-geo-map");
2855
2856       this._graphicShapes = [];
2857
2858       this._initOptions = options || {};
2859
2860       this._forcePosition(this._$elem);
2861
2862       this._$elem.css("text-align", "left");
2863
2864       var size = this._findMapSize();
2865       this._contentBounds = {
2866         x: parseInt(this._$elem.css("padding-left")),
2867         y: parseInt(this._$elem.css("padding-top")),
2868         width: size["width"],
2869         height: size["height"]
2870       };
2871
2872       this._createChildren();
2873
2874       this._center = this._centerMax = [0, 0];
2875
2876       this.options["pixelSize"] = this._pixelSize = this._pixelSizeMax = 156543.03392799936;
2877
2878       this._mouseDown =
2879           this._inOp =
2880           this._toolPan =
2881           this._shiftZoom =
2882           this._panning =
2883           this._isTap =
2884           this._isDbltap = false;
2885
2886       this._anchor =
2887           this._current =
2888           this._lastMove =
2889           this._lastDrag =
2890           this._velocity = [0, 0];
2891
2892       this._friction = [.8, .8];
2893
2894       this._downDate =
2895           this._moveDate =
2896           this._clickDate = 0;
2897
2898       $.Widget.prototype._createWidget.apply(this, arguments);
2899     },
2900
2901     _create: function () {
2902       if (this._$elem.is("[data-geo-service]")) {
2903         return;
2904       }
2905
2906       this._options = this.options;
2907
2908       this._supportTouch = "ontouchend" in document;
2909       this._softDblClick = this._supportTouch || _ieVersion == 7;
2910
2911       var touchStartEvent = this._supportTouch ? "touchstart" : "mousedown",
2912             touchStopEvent = this._supportTouch ? "touchend touchcancel" : "mouseup",
2913             touchMoveEvent = this._supportTouch ? "touchmove" : "mousemove";
2914
2915       $(document).keydown($.proxy(this._document_keydown, this));
2916
2917       this._$eventTarget.dblclick($.proxy(this._eventTarget_dblclick, this));
2918
2919       this._$eventTarget.bind(touchStartEvent, $.proxy(this._eventTarget_touchstart, this));
2920
2921       var dragTarget = (this._$eventTarget[0].setCapture) ? this._$eventTarget : $(document);
2922       dragTarget.bind(touchMoveEvent, $.proxy(this._dragTarget_touchmove, this));
2923       dragTarget.bind(touchStopEvent, $.proxy(this._dragTarget_touchstop, this));
2924
2925       this._$eventTarget.mousewheel($.proxy(this._eventTarget_mousewheel, this));
2926
2927       var geomap = this;
2928       this._windowHandler = function () {
2929         if (geomap._resizeTimeout) {
2930           clearTimeout(geomap._resizeTimeout);
2931         }
2932         this._resizeTimeout = setTimeout(function () {
2933           if (geomap._created) {
2934             geomap._$elem.geomap("resize");
2935           }
2936         }, 500);
2937       };
2938
2939       $(window).resize(this._windowHandler);
2940
2941       this._$drawContainer.geographics({ style: this._initOptions.drawStyle || {} });
2942       this._options["drawStyle"] = this._$drawContainer.geographics("option", "style");
2943
2944       this._$shapesContainer.geographics( { style: this._initOptions.shapeStyle || { } } );
2945       this._options["shapeStyle"] = this._$shapesContainer.geographics("option", "style");
2946
2947       if (this._initOptions) {
2948         if (this._initOptions.bbox) {
2949           this._setOption("bbox", this._initOptions.bbox, false);
2950         }
2951         if (this._initOptions.center) {
2952           this._setOption("center", this._initOptions.center, false);
2953         }
2954         if (this._initOptions.zoom) {
2955           this._setZoom(this._initOptions.zoom, false, false);
2956         }
2957       }
2958
2959       this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
2960
2961       this._createServices();
2962
2963       this._refresh();
2964
2965       this._created = true;
2966     },
2967
2968     _setOption: function (key, value, refresh) {
2969       if ( this._$elem.is( "[data-geo-service]" ) || key == "pixelSize" ) {
2970         return;
2971       }
2972
2973       refresh = (refresh === undefined || refresh);
2974
2975       switch (key) {
2976         case "bbox":
2977           if ($.geo.proj) {
2978             value = $.geo.proj.fromGeodetic([[value[0], value[1]], [value[2], value[3]]]);
2979             value = [value[0][0], value[0][1], value[1][0], value[1][1]];
2980           }
2981
2982           this._setBbox(value, false, refresh);
2983           value = this._getBbox();
2984
2985           if ($.geo.proj) {
2986             value = $.geo.proj.toGeodetic([[value[0], value[1]], [value[2], value[3]]]);
2987             value = [value[0][0], value[0][1], value[1][0], value[1][1]];
2988           }
2989           break;
2990
2991         case "center":
2992           this._setCenterAndSize($.geo.proj ? $.geo.proj.fromGeodetic([[value[0], value[1]]])[0] : value, this._pixelSize, false, refresh);
2993           break;
2994
2995         case "drawStyle":
2996           if (this._$drawContainer) {
2997             this._$drawContainer.geographics("option", "style", value);
2998             value = this._$drawContainer.geographics("option", "style");
2999           }
3000           break;
3001
3002         case "shapeStyle":
3003           if (this._$shapesContainer) {
3004             this._$shapesContainer.geographics("option", "style", value);
3005             value = this._$shapesContainer.geographics("option", "style");
3006           }
3007           break;
3008
3009         case "mode":
3010           this._$drawContainer.geographics("clear");
3011           this._$eventTarget.css("cursor", this._options["cursors"][value]);
3012           break;
3013
3014         case "zoom":
3015           this._setZoom(value, false, refresh);
3016           break;
3017       }
3018
3019       $.Widget.prototype._setOption.apply(this, arguments);
3020
3021       switch (key) {
3022         case "services":
3023           this._createServices();
3024           if (refresh) {
3025             this._refresh();
3026           }
3027           break;
3028
3029         case "shapeStyle":
3030           if ( refresh ) {
3031             this._$shapesContainer.geographics("clear");
3032             this._refreshShapes( this._$shapesContainer, this._graphicShapes, this._graphicShapes );
3033           }
3034           break;
3035       }
3036     },
3037
3038     destroy: function () {
3039       if (this._$elem.is("[data-geo-map]")) {
3040         this._created = false;
3041
3042         $(window).unbind("resize", this._windowHandler);
3043
3044         for ( var i = 0; i < this._currentServices.length; i++ ) {
3045           this._currentServices[i].serviceContainer.geomap("destroy");
3046           $.geo["_serviceTypes"][this._currentServices[i].type].destroy(this, this._$servicesContainer, this._currentServices[i]);
3047         }
3048
3049         this._$shapesContainer.geographics("destroy");
3050         this._$drawContainer.geographics("destroy");
3051
3052         this._$existingChildren.detach();
3053         this._$elem.html("");
3054         this._$elem.append(this._$existingChildren);
3055         this._$elem.removeAttr("data-geo-map");
3056       }
3057       $.Widget.prototype.destroy.apply(this, arguments);
3058     },
3059
3060     toMap: function (p) {
3061       p = this._toMap(p);
3062       return $.geo.proj ? $.geo.proj.toGeodetic(p) : p;
3063     },
3064
3065     toPixel: function ( p, _center /* Internal Use Only */, _pixelSize /* Internal Use Only */ ) {
3066       p = $.geo.proj ? $.geo.proj.fromGeodetic(p) : p;
3067       return this._toPixel(p, _center, _pixelSize);
3068     },
3069
3070     opacity: function (value, _serviceContainer) {
3071       if (this._$elem.is("[data-geo-service]")) {
3072         this._$elem.closest("[data-geo-map]").geomap("opacity", value, this._$elem);
3073       } else {
3074         if (value >= 0 || value <= 1) {
3075           for ( var i = 0; i < this._currentServices.length; i++ ) {
3076             var service = this._currentServices[i];
3077             if ( !_serviceContainer || service.serviceContainer[0] == _serviceContainer[0] ) {
3078               this._options["services"][i].opacity = service.opacity = value;
3079               $.geo["_serviceTypes"][service.type].opacity(this, service);
3080             }
3081           }
3082         }
3083       }
3084     },
3085
3086     toggle: function (value, _serviceContainer) {
3087       if (this._$elem.is("[data-geo-service]")) {
3088         this._$elem.closest("[data-geo-map]").geomap("toggle", value, this._$elem);
3089       } else {
3090         for (var i = 0; i < this._currentServices.length; i++) {
3091           var service = this._currentServices[i];
3092           if (!_serviceContainer || service.serviceContainer[0] == _serviceContainer[0]) {
3093             if (value === undefined) {
3094               value = (service.visibility === undefined || service.visibility === "visible" ? false : true);
3095             }
3096
3097             this._options["services"][i].visibility = service.visibility = ( value ? "visible" : "hidden" );
3098             $.geo["_serviceTypes"][service.type].toggle(this, service);
3099
3100             if (value) {
3101               $.geo["_serviceTypes"][service.type].refresh(this, service);
3102             }
3103           }
3104         }
3105       }
3106     },
3107
3108     zoom: function (numberOfLevels) {
3109       if (numberOfLevels != null) {
3110         this._setZoom(this._options["zoom"] + numberOfLevels, false, true);
3111       }
3112     },
3113
3114     refresh: function () {
3115       this._refresh();
3116     },
3117
3118     resize: function () {
3119       var size = this._findMapSize(),
3120           dx = size["width"]/2 - this._contentBounds.width/2,
3121           dy = size["height"]/2 - this._contentBounds.height/2,
3122           i;
3123
3124       this._contentBounds = {
3125         x: parseInt(this._$elem.css("padding-left")),
3126         y: parseInt(this._$elem.css("padding-top")),
3127         width: size["width"],
3128         height: size["height"]
3129       };
3130
3131       this._$contentFrame.css({
3132         width: size["width"],
3133         height: size["height"]
3134       });
3135
3136       this._$servicesContainer.css({
3137         width: size["width"],
3138         height: size["height"]
3139       });
3140
3141       this._$eventTarget.css({
3142         width: size["width"],
3143         height: size["height"]
3144       });
3145
3146       var shapeStyle = this._$shapesContainer.geographics("option", "style");
3147
3148       this._$shapesContainer.geographics("destroy");
3149       this._$drawContainer.geographics("destroy");
3150
3151       for (i = 0; i < this._currentServices.length; i++) {
3152         $.geo["_serviceTypes"][this._currentServices[i].type].resize(this, this._currentServices[i]);
3153       }
3154
3155
3156       this._$drawContainer.css({
3157         width: size.width,
3158         height: size.height
3159       });
3160       this._$drawContainer.geographics();
3161
3162       this._$shapesContainer.css({
3163         width: size.width,
3164         height: size.height
3165       });
3166       this._$shapesContainer.geographics( { style: shapeStyle } );
3167
3168       for (i = 0; i < this._drawPixels.length; i++) {
3169         this._drawPixels[i][0] += dx;
3170         this._drawPixels[i][1] += dy;
3171       }
3172
3173       this._setCenterAndSize(this._center, this._pixelSize, false, true);
3174     },
3175
3176     append: function ( shape, style, refresh ) {
3177       if ( shape ) {
3178         var shapes, i = 0;
3179         if ( shape.type == "FeatureCollection" ) {
3180           shapes = shape.features;
3181         } else {
3182           shapes = $.isArray( shape ) ? shape : [ shape ];
3183         }
3184
3185         if ( typeof style === "boolean" ) {
3186           refresh = style;
3187           style = null;
3188         }
3189
3190         for ( ; i < shapes.length; i++ ) {
3191           if ( shapes[ i ].type != "Point" ) {
3192             var bbox = $.geo.bbox( shapes[ i ] );
3193             if ( $.geo.proj ) {
3194               bbox = $.geo.proj.fromGeodetic( bbox );
3195             }
3196             $.data( shapes[ i ], "geoBbox", bbox );
3197           }
3198
3199           this._graphicShapes.push( {
3200             shape: shapes[ i ],
3201             style: style
3202           } );
3203         }
3204
3205         if ( refresh === undefined || refresh ) {
3206           this._refresh( );
3207         }
3208       }
3209     },
3210
3211     empty: function ( refresh ) {
3212       $.each( this._graphicShapes, function( ) {
3213         $.removeData( this, "geoBbox" );
3214       } );
3215       this._graphicShapes = [];
3216       if ( refresh === undefined || refresh ) {
3217         this._refresh();
3218       }
3219     },
3220
3221     find: function (point, pixelTolerance) {
3222       var searchPixel = this.toPixel( point.coordinates ),
3223           mapTol = this._pixelSize * pixelTolerance,
3224           result = [],
3225           curGeom;
3226
3227       $.each( this._graphicShapes, function ( i ) {
3228         if ( this.shape.type == "Point" ) {
3229           if ( $.geo.distance(this.shape, point) <= mapTol ) {
3230             result.push( this.shape );
3231           }
3232         } else {
3233           var bbox = $.data( this.shape, "geoBbox" ),
3234               bboxPolygon = {
3235                 type: "Polygon",
3236                 coordinates: [ [
3237                   [bbox[0], bbox[1]],
3238                   [bbox[0], bbox[3]],
3239                   [bbox[2], bbox[3]],
3240                   [bbox[2], bbox[1]],
3241                   [bbox[0], bbox[1]]
3242                 ] ]
3243               },
3244               projectedPoint = {
3245                 type: "Point",
3246                 coordinates: $.geo.proj ? $.geo.proj.fromGeodetic( point.coordinates ) : point.coordinates
3247               };
3248
3249           if ( $.geo.distance( bboxPolygon, projectedPoint, true ) <= mapTol ) {
3250             var geometries = $.geo._flatten( this.shape );
3251             for ( curGeom = 0; curGeom < geometries.length; curGeom++ ) {
3252               if ( $.geo.distance( geometries[curGeom], point ) <= mapTol ) {
3253                 result.push( this.shape );
3254                 break;
3255               }
3256             }
3257           }
3258         }
3259       });
3260
3261       return result;
3262     },
3263
3264     remove: function ( shape, refresh ) {
3265       var geomap = this;
3266       $.each( this._graphicShapes, function ( i ) {
3267         if ( this.shape == shape ) {
3268           $.removeData( shape, "geoBbox" );
3269           var rest = geomap._graphicShapes.slice( i + 1 );
3270           geomap._graphicShapes.length = i;
3271           geomap._graphicShapes.push.apply(geomap._graphicShapes, rest);
3272           return false;
3273         }
3274       });
3275
3276       if ( refresh === undefined || refresh ) {
3277         this._refresh();
3278       }
3279     },
3280
3281     _getBbox: function (center, pixelSize) {
3282       center = center || this._center;
3283       pixelSize = pixelSize || this._pixelSize;
3284       // calculate the internal bbox
3285       var halfWidth = this._contentBounds["width"] / 2 * pixelSize,
3286           halfHeight = this._contentBounds["height"] / 2 * pixelSize;
3287       return [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight];
3288     },
3289
3290     _setBbox: function (value, trigger, refresh) {
3291       var center = [value[0] + (value[2] - value[0]) / 2, value[1] + (value[3] - value[1]) / 2],
3292           pixelSize = Math.max($.geo.width(value, true) / this._contentBounds.width, $.geo.height(value, true) / this._contentBounds.height);
3293
3294       if (this._options["tilingScheme"]) {
3295         var zoom = this._getTiledZoom(pixelSize);
3296         pixelSize = this._getTiledPixelSize(zoom);
3297       }
3298       this._setCenterAndSize(center, pixelSize, trigger, refresh);
3299     },
3300
3301     _getBboxMax: function () {
3302       // calculate the internal bboxMax
3303       var halfWidth = this._contentBounds["width"] / 2 * this._pixelSizeMax,
3304         halfHeight = this._contentBounds["height"] / 2 * this._pixelSizeMax;
3305       return [this._centerMax[0] - halfWidth, this._centerMax[1] - halfHeight, this._centerMax[0] + halfWidth, this._centerMax[1] + halfHeight];
3306     },
3307
3308     _getCenter: function () {
3309       return this._center;
3310     },
3311
3312     _getContentBounds: function () {
3313       return this._contentBounds;
3314     },
3315
3316     _getServicesContainer: function () {
3317       return this._$servicesContainer;
3318     },
3319
3320     _getZoom: function () {
3321       // calculate the internal zoom level, vs. public zoom property
3322       if (this._options["tilingScheme"]) {
3323         return this._getTiledZoom(this._pixelSize);
3324       } else {
3325         var ratio = this._contentBounds["width"] / this._contentBounds["height"],
3326             bbox = $.geo.reaspect(this._getBbox(), ratio, true),
3327             bboxMax = $.geo.reaspect(this._getBboxMax(), ratio, true);
3328
3329         return Math.log($.geo.width(bboxMax, true) / $.geo.width(bbox, true)) / Math.log(this._zoomFactor);
3330       }
3331     },
3332
3333     _setZoom: function (value, trigger, refresh) {
3334       value = Math.max(value, 0);
3335
3336       if (this._options["tilingScheme"]) {
3337         this._setCenterAndSize(this._center, this._getTiledPixelSize(value), trigger, refresh);
3338       } else {
3339         var bbox = $.geo.scaleBy(this._getBboxMax(), 1 / Math.pow(this._zoomFactor, value), true),
3340             pixelSize = Math.max($.geo.width(bbox, true) / this._contentBounds.width, $.geo.height(bbox, true) / this._contentBounds.height);
3341         this._setCenterAndSize(this._center, pixelSize, trigger, refresh);
3342       }
3343     },
3344
3345     _createChildren: function () {
3346       this._$existingChildren = this._$elem.children().detach();
3347
3348       this._forcePosition(this._$existingChildren);
3349
3350       this._$existingChildren.css("-moz-user-select", "none");
3351
3352       this._$elem.prepend("<div style='position:absolute; left:" + this._contentBounds.x + "px; top:" + this._contentBounds.y + "px; width:" + this._contentBounds["width"] + "px; height:" + this._contentBounds["height"] + "px; margin:0; padding:0; overflow:hidden; -khtml-user-select:none; -moz-user-select:none; -webkit-user-select:none; user-select:none;' unselectable='on'></div>");
3353       this._$eventTarget = this._$contentFrame = this._$elem.children(':first');
3354
3355       this._$contentFrame.append('<div style="position:absolute; left:0; top:0; width:' + this._contentBounds["width"] + 'px; height:' + this._contentBounds["height"] + 'px; margin:0; padding:0;"></div>');
3356       this._$servicesContainer = this._$contentFrame.children(':last');
3357
3358       this._$contentFrame.append('<div style="position:absolute; left:0; top:0; width:' + this._contentBounds["width"] + 'px; height:' + this._contentBounds["height"] + 'px; margin:0; padding:0;"></div>');
3359       this._$shapesContainer = this._$contentFrame.children(':last');
3360
3361       this._$contentFrame.append('<div style="position:absolute; left:0; top:0; width:' + this._contentBounds["width"] + 'px; height:' + this._contentBounds["height"] + 'px; margin:0; padding:0;"></div>');
3362       this._$drawContainer = this._$contentFrame.children(':last');
3363
3364       this._$contentFrame.append('<div class="ui-widget ui-widget-content ui-corner-all" style="position:absolute; left:0; top:0px; max-width:128px; display:none;"><div style="margin:.2em;"></div></div>');
3365       this._$textContainer = this._$contentFrame.children(':last');
3366       this._$textContent = this._$textContainer.children();
3367
3368       this._$contentFrame.append(this._$existingChildren);
3369     },
3370
3371     _createServices: function () {
3372       var i;
3373
3374       for (i = 0; i < this._currentServices.length; i++) {
3375         this._currentServices[i].serviceContainer.geomap("destroy");
3376         $.geo["_serviceTypes"][this._currentServices[i].type].destroy(this, this._$servicesContainer, this._currentServices[i]);
3377       }
3378
3379       this._currentServices = [];
3380       for (i = 0; i < this._options["services"].length; i++) {
3381         this._currentServices[i] = this._options["services"][i];
3382         this._currentServices[i].serviceContainer = $.geo["_serviceTypes"][this._currentServices[i].type].create(this, this._$servicesContainer, this._currentServices[i], i).geomap();
3383       }
3384     },
3385
3386     _refreshDrawing: function () {
3387       this._$drawContainer.geographics("clear");
3388
3389       if ( this._drawPixels.length > 0 ) {
3390         var mode = this._options[ "mode" ],
3391             coords = this._drawPixels;
3392
3393         if ( mode == "drawPolygon" ) {
3394           coords = [ coords ];
3395         }
3396
3397         this._$drawContainer.geographics( mode, coords );
3398       }
3399     },
3400
3401     _resetDrawing: function () {
3402       //this._$textContainer.hide();
3403       this._drawPixels = [];
3404       this._drawCoords = [];
3405       this._$drawContainer.geographics("clear");
3406     },
3407
3408     _refreshShapes: function (geographics, shapes, styles, center, pixelSize) {
3409       var i,
3410           mgi,
3411           shape,
3412           shapeBbox,
3413           style,
3414           pixelPositions,
3415           bbox = this._getBbox(center, pixelSize),
3416           geomap = this;
3417
3418       for (i = 0; i < shapes.length; i++) {
3419         shape = shapes[i].shape || shapes[i];
3420         shape = shape.geometry || shape;
3421         shapeBbox = $.data(shape, "geoBbox");
3422
3423         if ( shapeBbox && $.geo._bboxDisjoint( bbox, shapeBbox ) ) {
3424           continue;
3425         }
3426
3427         style = $.isArray(styles) ? styles[i].style : styles;
3428
3429          switch (shape.type) {
3430           case "Point":
3431             this._$shapesContainer.geographics("drawPoint", this.toPixel(shape.coordinates, center, pixelSize), style);
3432             break;
3433           case "LineString":
3434             this._$shapesContainer.geographics("drawLineString", this.toPixel(shape.coordinates, center, pixelSize), style);
3435             break;
3436           case "Polygon":
3437             pixelPositions = [];
3438             $.each(shape.coordinates, function (i) {
3439               pixelPositions[i] = geomap.toPixel(this, center, pixelSize);
3440             });
3441             this._$shapesContainer.geographics("drawPolygon", pixelPositions, style);
3442             break;
3443           case "MultiPoint":
3444             for (mgi = 0; mgi < shape.coordinates; mgi++) {
3445               this._$shapesContainer.geographics("drawPoint", this.toPixel(shape.coordinates[mgi], center, pixelSize), style);
3446             }
3447             break;
3448           case "MultiLineString":
3449             for (mgi = 0; mgi < shape.coordinates; mgi++) {
3450               this._$shapesContainer.geographics("drawLineString", this.toPixel(shape.coordinates[mgi], center, pixelSize), style);
3451             }
3452             break;
3453           case "MultiPolygon":
3454             for (mgi = 0; mgi < shape.coordinates; mgi++) {
3455               pixelPositions = [];
3456               $.each(shape.coordinates[mgi], function (i) {
3457                 pixelPositions[i] = geomap.toPixel(this, center, pixelSize);
3458               });
3459               this._$shapesContainer.geographics("drawPolygon", pixelPositions, style);
3460             }
3461             break;
3462
3463           case "GeometryCollection":
3464             geomap._refreshShapes(geographics, shape.geometries, style, center, pixelSize);
3465             break;
3466         }
3467       }
3468     },
3469
3470     _findMapSize: function () {
3471       // really, really attempt to find a size for this thing
3472       // even if it's hidden (look at parents)
3473       var size = { width: 0, height: 0 },
3474         sizeContainer = this._$elem;
3475
3476       while (sizeContainer.size() && !(size["width"] > 0 && size["height"] > 0)) {
3477         size = { width: sizeContainer.width(), height: sizeContainer.height() };
3478         if (size["width"] <= 0 || size["height"] <= 0) {
3479           size = { width: parseInt(sizeContainer.css("width")), height: parseInt(sizeContainer.css("height")) };
3480         }
3481         sizeContainer = sizeContainer.parent();
3482       }
3483       return size;
3484     },
3485
3486     _forcePosition: function (elem) {
3487       var cssPosition = elem.css("position");
3488       if (cssPosition != "relative" && cssPosition != "absolute" && cssPosition != "fixed") {
3489         elem.css("position", "relative");
3490       }
3491     },
3492
3493     _getTiledPixelSize: function (zoom) {
3494       var tilingScheme = this._options["tilingScheme"];
3495       if (tilingScheme != null) {
3496         if (zoom === 0) {
3497           return tilingScheme.pixelSizes != null ? tilingScheme.pixelSizes[0] : tilingScheme.basePixelSize;
3498         }
3499
3500         zoom = Math.round(zoom);
3501         zoom = Math.max(zoom, 0);
3502         var levels = tilingScheme.pixelSizes != null ? tilingScheme.pixelSizes.length : tilingScheme.levels;
3503         zoom = Math.min(zoom, levels - 1);
3504
3505         if (tilingScheme.pixelSizes != null) {
3506           return tilingScheme.pixelSizes[zoom];
3507         } else {
3508           return tilingScheme.basePixelSize / Math.pow(2, zoom);
3509         }
3510       } else {
3511         return NaN;
3512       }
3513     },
3514
3515     _getTiledZoom: function (pixelSize) {
3516       var tilingScheme = this._options["tilingScheme"];
3517       if (tilingScheme.pixelSizes != null) {
3518         var roundedPixelSize = Math.floor(pixelSize * 1000),
3519           levels = tilingScheme.pixelSizes != null ? tilingScheme.pixelSizes.length : tilingScheme.levels;
3520         for (var i = levels - 1; i >= 0; i--) {
3521           if (Math.floor(tilingScheme.pixelSizes[i] * 1000) >= roundedPixelSize) {
3522             return i;
3523           }
3524         }
3525         return 0;
3526       } else {
3527         return Math.max(Math.round(Math.log(tilingScheme.basePixelSize / pixelSize) / Math.log(2)), 0);
3528       }
3529     },
3530
3531     _getZoomCenterAndSize: function (anchor, zoomDelta, zoomFactor) {
3532       var pixelSize, zoomLevel, scale;
3533       if (this._options["tilingScheme"]) {
3534         zoomLevel = this._getTiledZoom(this._pixelSize) + zoomDelta;
3535         pixelSize = this._getTiledPixelSize(zoomLevel);
3536       } else {
3537         scale = Math.pow(zoomFactor, -zoomDelta);
3538         pixelSize = this._pixelSize * scale;
3539       }
3540
3541       var 
3542         ratio = pixelSize / this._pixelSize,
3543         anchorMapCoord = this._toMap(anchor),
3544         centerDelta = [(this._center[0] - anchorMapCoord[0]) * ratio, (this._center[1] - anchorMapCoord[1]) * ratio],
3545         scaleCenter = [anchorMapCoord[0] + centerDelta[0], anchorMapCoord[1] + centerDelta[1]];
3546
3547       return { pixelSize: pixelSize, center: scaleCenter };
3548     },
3549
3550     _mouseWheelFinish: function () {
3551       this._wheelTimeout = null;
3552
3553       if (this._wheelLevel != 0) {
3554         var wheelCenterAndSize = this._getZoomCenterAndSize(this._anchor, this._wheelLevel, this._wheelZoomFactor);
3555
3556         this._setCenterAndSize(wheelCenterAndSize.center, wheelCenterAndSize.pixelSize, true, true);
3557
3558         this._wheelLevel = 0;
3559       } else {
3560         this._refresh();
3561       }
3562     },
3563
3564     _panEnd: function () {
3565       this._velocity = [
3566         (this._velocity[0] > 0 ? Math.floor(this._velocity[0] * this._friction[0]) : Math.ceil(this._velocity[0] * this._friction[0])),
3567         (this._velocity[1] > 0 ? Math.floor(this._velocity[1] * this._friction[1]) : Math.ceil(this._velocity[1] * this._friction[1]))
3568       ];
3569
3570       if (Math.abs(this._velocity[0]) < 4 && Math.abs(this._velocity[1]) < 4) {
3571         this._panFinalize();
3572       } else {
3573         this._current = [
3574           this._current[0] + this._velocity[0],
3575           this._current[1] + this._velocity[1]
3576         ];
3577
3578         this._panMove();
3579         setTimeout($.proxy(this._panEnd, this), 30);
3580       }
3581     },
3582
3583     _panFinalize: function () {
3584       if (this._panning) {
3585         this._velocity = [0, 0];
3586
3587         var dx = this._current[0] - this._anchor[0],
3588             dy = this._current[1] - this._anchor[1],
3589             dxMap = -dx * this._pixelSize,
3590             dyMap = dy * this._pixelSize;
3591
3592         this._$shapesContainer.css({ left: 0, top: 0 });
3593
3594         this._setCenterAndSize([this._center[0] + dxMap, this._center[1] + dyMap], this._pixelSize, true, true);
3595
3596         this._inOp = false;
3597         this._anchor = this._current;
3598         this._toolPan = this._panning = false;
3599
3600         this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
3601       }
3602     },
3603
3604     _panMove: function () {
3605       var dx = this._current[0] - this._lastDrag[0],
3606           dy = this._current[1] - this._lastDrag[1],
3607           i = 0,
3608           service;
3609
3610       if (this._toolPan || dx > 3 || dx < -3 || dy > 3 || dy < -3) {
3611         if (!this._toolPan) {
3612           this._toolPan = true;
3613           this._$eventTarget.css("cursor", this._options["cursors"]["pan"]);
3614         }
3615
3616         if (this._mouseDown) {
3617           this._velocity = [dx, dy];
3618         }
3619
3620         if (dx != 0 || dy != 0) {
3621           this._panning = true;
3622           this._lastDrag = this._current;
3623
3624           for (i = 0; i < this._options["services"].length; i++) {
3625             service = this._options["services"][i];
3626             $.geo["_serviceTypes"][service.type].interactivePan(this, service, dx, dy);
3627           }
3628
3629           this._$shapesContainer.css({
3630             left: function (index, value) {
3631               return parseInt(value) + dx;
3632             },
3633             top: function (index, value) {
3634               return parseInt(value) + dy;
3635             }
3636           });
3637
3638           for (i = 0; i < this._drawPixels.length; i++) {
3639             this._drawPixels[i][0] += dx;
3640             this._drawPixels[i][1] += dy;
3641           }
3642
3643           this._refreshDrawing();
3644         }
3645       }
3646     },
3647
3648     _refresh: function () {
3649       for (var i = 0; i < this._options["services"].length; i++) {
3650         var service = this._options["services"][i];
3651         if (!this._mouseDown && $.geo["_serviceTypes"][service.type] != null) {
3652           $.geo["_serviceTypes"][service.type].refresh(this, service);
3653         }
3654       }
3655
3656       if (this._$shapesContainer) {
3657         this._$shapesContainer.geographics("clear");
3658         if (this._graphicShapes.length > 0) {
3659           this._refreshShapes(this._$shapesContainer, this._graphicShapes, this._graphicShapes);
3660         }
3661       }
3662     },
3663
3664     _setCenterAndSize: function (center, pixelSize, trigger, refresh) {
3665       // the final call during any extent change
3666       if (this._pixelSize != pixelSize) {
3667         this._$shapesContainer.geographics("clear");
3668         for (var i = 0; i < this._options["services"].length; i++) {
3669           var service = this._options["services"][i];
3670           $.geo["_serviceTypes"][service.type].interactiveScale(this, service, center, pixelSize);
3671         }
3672       }
3673
3674       this._center = center;
3675       this.options["pixelSize"] = this._pixelSize = pixelSize;
3676
3677       if ($.geo.proj) {
3678         var bbox = this._getBbox();
3679         bbox = $.geo.proj.toGeodetic([[bbox[0], bbox[1]], [bbox[2], bbox[3]]]);
3680         bbox = [bbox[0][0], bbox[0][1], bbox[1][0], bbox[1][1]];
3681         this._options["bbox"] = bbox;
3682
3683         this._options["center"] = $.geo.proj.toGeodetic([[this._center[0], this._center[1]]])[0];
3684       } else {
3685         this._options["bbox"] = this._getBbox();
3686
3687         this._options["center"] = this._center;
3688       }
3689
3690       this._options["zoom"] = this._getZoom();
3691
3692       if (this._drawCoords.length > 0) {
3693         this._drawPixels = this._toPixel(this._drawCoords);
3694       }
3695
3696       if (trigger) {
3697         this._trigger("bboxchange", window.event, { bbox: this._options["bbox"] });
3698       }
3699
3700       if (refresh) {
3701         this._refresh();
3702         this._refreshDrawing();
3703       }
3704     },
3705
3706     _toMap: function (p, center, pixelSize) {
3707       // ignores $.geo.proj
3708       var isArray = $.isArray(p[0]);
3709       if (!isArray) {
3710         p = [p];
3711       }
3712
3713       center = center || this._center;
3714       pixelSize = pixelSize || this._pixelSize;
3715
3716       var width = this._contentBounds["width"],
3717           height = this._contentBounds["height"],
3718           halfWidth = width / 2 * pixelSize,
3719           halfHeight = height / 2 * pixelSize,
3720           bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight],
3721           xRatio = $.geo.width(bbox, true) / width,
3722           yRatio = $.geo.height(bbox, true) / height,
3723           result = [];
3724
3725       $.each(p, function (i) {
3726         var yOffset = (this[1] * yRatio);
3727         result[i] = [bbox[0] + (this[0] * xRatio), bbox[3] - yOffset];
3728       });
3729
3730       return isArray ? result : result[0];
3731     },
3732
3733     _toPixel: function (p, center, pixelSize) {
3734       // ignores $.geo.proj
3735       var isArray = $.isArray(p[0]);
3736       if (!isArray) {
3737         p = [p];
3738       }
3739
3740       center = center || this._center;
3741       pixelSize = pixelSize || this._pixelSize;
3742
3743       var 
3744         width = this._contentBounds["width"],
3745         height = this._contentBounds["height"],
3746         halfWidth = width / 2 * pixelSize,
3747         halfHeight = height / 2 * pixelSize,
3748         bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight],
3749         bboxWidth = $.geo.width(bbox, true),
3750         bboxHeight = $.geo.height(bbox, true),
3751         result = [];
3752
3753       $.each(p, function (i) {
3754         result[i] = [
3755             Math.round((this[0] - bbox[0]) * width / bboxWidth),
3756             Math.round((bbox[3] - this[1]) * height / bboxHeight)
3757           ];
3758       });
3759
3760       return isArray ? result : result[0];
3761     },
3762
3763     _zoomTo: function (coord, zoom, trigger, refresh) {
3764       zoom = zoom < 0 ? 0 : zoom;
3765
3766       var tiledPixelSize = this._getTiledPixelSize(zoom);
3767
3768       if (!isNaN(tiledPixelSize)) {
3769         this._setCenterAndSize(coord, tiledPixelSize, trigger, refresh);
3770       } else {
3771         var bboxMax = $.geo._scaleBy(this._getBboxMax(), 1 / Math.pow(this._zoomFactor, zoom), true),
3772             pixelSize = Math.max($.geo.width(bboxMax, true) / this._contentBounds["width"], $.geo.height(bboxMax, true) / this._contentBounds["height"]);
3773
3774         this._setCenterAndSize(coord, pixelSize, trigger, refresh);
3775       }
3776     },
3777
3778     _document_keydown: function (e) {
3779       var len = this._drawCoords.length;
3780       if (len > 0 && e.which == 27) {
3781         if (len <= 2) {
3782           this._resetDrawing();
3783           this._inOp = false;
3784         } else {
3785           this._drawCoords[len - 2] = $.merge( [], this._drawCoords[ len - 1 ] );
3786           this._drawPixels[len - 2] = $.merge( [], this._drawPixels[ len - 1 ] );
3787
3788           this._drawCoords.length--;
3789           this._drawPixels.length--;
3790
3791           this._refreshDrawing();
3792         }
3793       }
3794     },
3795
3796     _eventTarget_dblclick_zoom: function(e) {
3797       this._trigger("dblclick", e, { type: "Point", coordinates: this.toMap(this._current) });
3798       if (!e.isDefaultPrevented()) {
3799         var centerAndSize = this._getZoomCenterAndSize(this._current, 1, this._zoomFactor);
3800         this._setCenterAndSize(centerAndSize.center, centerAndSize.pixelSize, true, true);
3801       }
3802     },
3803
3804     _eventTarget_dblclick: function (e) {
3805       this._panFinalize();
3806
3807       if (this._drawTimeout) {
3808         window.clearTimeout(this._drawTimeout);
3809         this._drawTimeout = null;
3810       }
3811
3812       var offset = $(e.currentTarget).offset();
3813
3814       switch (this._options["mode"]) {
3815         case "pan":
3816         case "drawPoint":
3817           this._eventTarget_dblclick_zoom(e);
3818           break;
3819
3820         case "drawLineString":
3821           if ( this._drawCoords.length > 1 && ! ( this._drawCoords[0][0] == this._drawCoords[1][0] &&
3822                                                   this._drawCoords[0][1] == this._drawCoords[1][1] ) ) {
3823               this._drawCoords.length--;
3824               this._trigger( "shape", e, {
3825                 type: "LineString",
3826                 coordinates: $.geo.proj ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords
3827               } );
3828           } else {
3829             this._eventTarget_dblclick_zoom(e);
3830           }
3831           this._resetDrawing();
3832           break;
3833
3834         case "drawPolygon":
3835           if ( this._drawCoords.length > 1 && ! ( this._drawCoords[0][0] == this._drawCoords[1][0] &&
3836                                                   this._drawCoords[0][1] == this._drawCoords[1][1] ) ) {
3837             var endIndex = this._drawCoords.length - 1;
3838             if (endIndex > 2) {
3839               this._drawCoords[endIndex] = $.merge( [], this._drawCoords[0] );
3840               this._trigger( "shape", e, {
3841                 type: "Polygon",
3842                 coordinates: [ $.geo.proj ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords ]
3843               } );
3844             }
3845           } else {
3846             this._eventTarget_dblclick_zoom(e);
3847           }
3848           this._resetDrawing();
3849           break;
3850       }
3851
3852       this._inOp = false;
3853     },
3854
3855     _eventTarget_touchstart: function (e) {
3856       if (!this._supportTouch && e.which != 1) {
3857         return;
3858       }
3859
3860       this._panFinalize();
3861       this._mouseWheelFinish();
3862
3863       var offset = $(e.currentTarget).offset();
3864
3865       if (this._supportTouch) {
3866         this._current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
3867       } else {
3868         this._current = [e.pageX - offset.left, e.pageY - offset.top];
3869       }
3870
3871       if (this._softDblClick) {
3872         var downDate = $.now();
3873         if (downDate - this._downDate < 750) {
3874           if (this._isTap) {
3875             var dx = this._current[0] - this._anchor[0],
3876                 dy = this._current[1] - this._anchor[1],
3877                 distance = Math.sqrt((dx * dx) + (dy * dy));
3878             if (distance > 10) {
3879               this._isTap = false;
3880             } else {
3881               this._current = this._anchor;
3882             }
3883           }
3884
3885           if (this._isDbltap) {
3886             this._isDbltap = false;
3887           } else {
3888             this._isDbltap = this._isTap;
3889           }
3890         } else {
3891           this._isDbltap = false;
3892         }
3893         this._isTap = true;
3894         this._downDate = downDate;
3895       }
3896
3897
3898       this._mouseDown = true;
3899       this._anchor = this._current;
3900
3901       if (!this._inOp && e.shiftKey) {
3902         this._shiftZoom = true;
3903         this._$eventTarget.css("cursor", this._options["cursors"]["zoom"]);
3904       } else {
3905         this._inOp = true;
3906
3907         switch (this._options["mode"]) {
3908           case "pan":
3909           case "drawPoint":
3910           case "drawLineString":
3911           case "drawPolygon":
3912             this._lastDrag = this._current;
3913
3914             if (e.currentTarget.setCapture) {
3915               e.currentTarget.setCapture();
3916             }
3917
3918             break;
3919         }
3920       }
3921
3922       if ( this._inOp ) {
3923         e.preventDefault();
3924         return false;
3925       }
3926     },
3927
3928     _dragTarget_touchmove: function (e) {
3929       var offset = this._$eventTarget.offset(),
3930           drawCoordsLen = this._drawCoords.length,
3931           current;
3932
3933       if (this._supportTouch) {
3934         current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
3935       } else {
3936         current = [e.pageX - offset.left, e.pageY - offset.top];
3937       }
3938
3939       if (current[0] === this._lastMove[0] && current[1] === this._lastMove[1]) {
3940         if ( this._inOp ) {
3941           e.preventDefault();
3942           return false;
3943         }
3944       }
3945
3946       if (this._softDblClick) {
3947         this._isDbltap = this._isTap = false;
3948       }
3949
3950       if (this._mouseDown) {
3951         this._current = current;
3952         this._moveDate = $.now();
3953       }
3954
3955       var mode = this._shiftZoom ? "zoom" : this._options["mode"];
3956
3957       switch (mode) {
3958         case "zoom":
3959           if ( this._mouseDown ) {
3960             this._$drawContainer.geographics( "clear" );
3961             this._$drawContainer.geographics( "drawBbox", [
3962               this._anchor[ 0 ],
3963               this._anchor[ 1 ],
3964               current[ 0 ],
3965               current[ 1 ]
3966             ] );
3967           } else {
3968             this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
3969           }
3970           break;
3971
3972         case "pan":
3973         case "drawPoint":
3974           if (this._mouseDown || this._toolPan) {
3975             this._panMove();
3976           } else {
3977             this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
3978           }
3979           break;
3980
3981         case "drawLineString":
3982         case "drawPolygon":
3983           if (this._mouseDown || this._toolPan) {
3984             this._panMove();
3985           } else {
3986             if (drawCoordsLen > 0) {
3987               this._drawCoords[drawCoordsLen - 1] = this._toMap(current);
3988               this._drawPixels[drawCoordsLen - 1] = current;
3989
3990               this._refreshDrawing();
3991             }
3992
3993             this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
3994           }
3995           break;
3996       }
3997
3998       this._lastMove = current;
3999
4000       if ( this._inOp ) {
4001         e.preventDefault();
4002         return false;
4003       }
4004     },
4005
4006     _dragTarget_touchstop: function (e) {
4007       if (!this._mouseDown && _ieVersion == 7) {
4008         // ie7 doesn't appear to trigger dblclick on this._$eventTarget,
4009         // we fake regular click here to cause soft dblclick
4010         this._eventTarget_touchstart(e);
4011       }
4012
4013       var mouseWasDown = this._mouseDown,
4014           wasToolPan = this._toolPan,
4015           offset = this._$eventTarget.offset(),
4016           mode = this._shiftZoom ? "zoom" : this._options["mode"],
4017           current, i, clickDate,
4018           dx, dy;
4019
4020       if (this._supportTouch) {
4021         current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
4022       } else {
4023         current = [e.pageX - offset.left, e.pageY - offset.top];
4024       }
4025
4026       dx = current[0] - this._anchor[0];
4027       dy = current[1] - this._anchor[1];
4028
4029       this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
4030
4031       this._shiftZoom = this._mouseDown = this._toolPan = false;
4032
4033       if (document.releaseCapture) {
4034         document.releaseCapture();
4035       }
4036
4037       if (mouseWasDown) {
4038         clickDate = $.now();
4039         this._current = current;
4040
4041         switch (mode) {
4042           case "zoom":
4043             if ( dx > 0 || dy > 0 ) {
4044               var minSize = this._pixelSize * 6,
4045                   bboxCoords = this._toMap( [ [
4046                       Math.min( this._anchor[ 0 ], current[ 0 ] ),
4047                       Math.max( this._anchor[ 1 ], current[ 1 ] )
4048                     ], [
4049                       Math.max( this._anchor[ 0 ], current[ 0 ] ),
4050                       Math.min( this._anchor[ 1 ], current[ 1 ] )
4051                     ]
4052                   ] ),
4053                   bbox = [
4054                     bboxCoords[0][0],
4055                     bboxCoords[0][1],
4056                     bboxCoords[1][0],
4057                     bboxCoords[1][1]
4058                   ];
4059
4060               if ( ( bbox[2] - bbox[0] ) < minSize && ( bbox[3] - bbox[1] ) < minSize ) {
4061                 bbox = $.geo.scaleBy( this._getBbox( $.geo.center( bbox, true ) ), .5, true );
4062               }
4063
4064               this._setBbox(bbox, true, true);
4065             }
4066
4067             this._resetDrawing();
4068             break;
4069
4070           case "pan":
4071             if (wasToolPan) {
4072               this._panEnd();
4073             } else {
4074               if (clickDate - this._clickDate > 100) {
4075                 this._trigger("click", e, { type: "Point", coordinates: this.toMap(current) });
4076                 this._inOp = false;
4077               }
4078             }
4079             break;
4080
4081           case "drawPoint":
4082             if (this._drawTimeout) {
4083               window.clearTimeout(this._drawTimeout);
4084               this._drawTimeout = null;
4085             }
4086
4087             if (wasToolPan) {
4088               this._panFinalize();
4089             } else {
4090               if (clickDate - this._clickDate > 100) {
4091                 var geomap = this;
4092                 this._drawTimeout = setTimeout(function () {
4093                   if (geomap._drawTimeout) {
4094                     geomap._trigger("shape", e, { type: "Point", coordinates: geomap.toMap(current) });
4095                     geomap._inOp = false;
4096                     geomap._drawTimeout = false;
4097                   }
4098                 }, 250);
4099               }
4100             }
4101             break;
4102
4103           case "drawLineString":
4104           case "drawPolygon":
4105             if (wasToolPan) {
4106               this._panFinalize();
4107             } else {
4108               i = (this._drawCoords.length == 0 ? 0 : this._drawCoords.length - 1);
4109
4110               this._drawCoords[i] = this._toMap(current);
4111               this._drawPixels[i] = current;
4112
4113               if (i < 2 || !(this._drawCoords[i][0] == this._drawCoords[i-1][0] &&
4114                              this._drawCoords[i][1] == this._drawCoords[i-1][1])) {
4115                 this._drawCoords[i + 1] = this._toMap(current);
4116                 this._drawPixels[i + 1] = current;
4117               }
4118
4119               this._refreshDrawing();
4120             }
4121             break;
4122         }
4123
4124         this._clickDate = clickDate;
4125
4126         if (this._softDblClick && this._isDbltap) {
4127           this._isDbltap = this._isTap = false;
4128           this._$eventTarget.trigger("dblclick", e);
4129         }
4130       }
4131
4132       if ( this._inOp ) {
4133         e.preventDefault();
4134         return false;
4135       }
4136     },
4137
4138     _eventTarget_mousewheel: function (e, delta) {
4139       e.preventDefault();
4140
4141       this._panFinalize();
4142
4143       if (this._mouseDown) {
4144         return false;
4145       }
4146
4147       if (delta != 0) {
4148         if (this._wheelTimeout) {
4149           window.clearTimeout(this._wheelTimeout);
4150           this._wheelTimeout = null;
4151         } else {
4152           var offset = $(e.currentTarget).offset();
4153           this._anchor = [e.pageX - offset.left, e.pageY - offset.top];
4154         }
4155
4156         this._wheelLevel += delta;
4157
4158         var wheelCenterAndSize = this._getZoomCenterAndSize(this._anchor, this._wheelLevel, this._wheelZoomFactor);
4159
4160         this._$shapesContainer.geographics("clear");
4161
4162         for (i = 0; i < this._options["services"].length; i++) {
4163           var service = this._options["services"][i];
4164           $.geo["_serviceTypes"][service.type].interactiveScale(this, service, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
4165         }
4166
4167         this._$shapesContainer.geographics("clear");
4168         if (this._graphicShapes.length > 0 && this._graphicShapes.length < 256) {
4169           this._refreshShapes(this._$shapesContainer, this._graphicShapes, this._graphicShapes, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
4170         }
4171
4172         if (this._drawCoords.length > 0) {
4173           this._drawPixels = this._toPixel(this._drawCoords, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
4174           this._refreshDrawing();
4175         }
4176
4177         var geomap = this;
4178         this._wheelTimeout = window.setTimeout(function () {
4179           geomap._mouseWheelFinish();
4180         }, 1000);
4181       }
4182
4183       return false;
4184     }
4185   }
4186   );
4187 })(jQuery);
4188
4189 (function ($, undefined) {
4190   $.geo._serviceTypes.tiled = (function () {
4191     return {
4192       create: function (map, servicesContainer, service, index) {
4193         var serviceState = $.data(service, "geoServiceState");
4194
4195         if ( !serviceState ) {
4196           serviceState = {
4197             loadCount: 0,
4198             reloadTiles: false
4199           };
4200
4201           var idString = service.id ? ' id="' + service.id + '"' : "",
4202               classString = service["class"] ? ' class="' + service["class"] + '"' : "",
4203               scHtml = '<div data-geo-service="tiled"' + idString + classString + ' style="position:absolute; left:0; top:0; width:8px; height:8px; margin:0; padding:0; display:' + (service.visibility === undefined || service.visibility === "visible" ? "block" : "none") + ';"></div>';
4204
4205           servicesContainer.append(scHtml);
4206
4207           serviceState.serviceContainer = servicesContainer.children(":last");
4208           $.data(service, "geoServiceState", serviceState);
4209         }
4210
4211         return serviceState.serviceContainer;
4212       },
4213
4214       destroy: function (map, servicesContainer, service) {
4215         var serviceState = $.data(service, "geoServiceState");
4216
4217         serviceState.serviceContainer.remove();
4218
4219         $.removeData(service, "geoServiceState");
4220       },
4221
4222       interactivePan: function ( map, service, dx, dy ) {
4223         var serviceState = $.data( service, "geoServiceState" );
4224
4225         if ( serviceState ) {
4226           this._cancelUnloaded( map, service );
4227
4228           serviceState.serviceContainer.children( ).css( {
4229             left: function ( index, value ) {
4230               return parseInt( value ) + dx;
4231             },
4232             top: function ( index, value ) {
4233               return parseInt( value ) + dy;
4234             }
4235           });
4236
4237           if ( service && ( service.visibility === undefined || service.visibility === "visible" ) ) {
4238             var pixelSize = map._pixelSize,
4239
4240                 serviceContainer = serviceState.serviceContainer,
4241                 scaleContainer = serviceContainer.children("[data-pixelSize='" + pixelSize + "']"),
4242
4243                 /* same as refresh 1 */
4244                 contentBounds = map._getContentBounds(),
4245                 mapWidth = contentBounds["width"],
4246                 mapHeight = contentBounds["height"],
4247
4248                 tilingScheme = map.options["tilingScheme"],
4249                 tileWidth = tilingScheme.tileWidth,
4250                 tileHeight = tilingScheme.tileHeight,
4251                 /* end same as refresh 1 */
4252
4253                 halfWidth = mapWidth / 2 * pixelSize,
4254                 halfHeight = mapHeight / 2 * pixelSize,
4255
4256                 currentPosition = scaleContainer.position(),
4257                 scaleOriginParts = scaleContainer.data("scaleOrigin").split(","),
4258                 totalDx = parseInt(scaleOriginParts[0]) - currentPosition.left,
4259                 totalDy = parseInt(scaleOriginParts[1]) - currentPosition.top,
4260
4261                 mapCenterOriginal = map._getCenter(),
4262                 mapCenter = [mapCenterOriginal[0] + totalDx * pixelSize, mapCenterOriginal[1] - totalDy * pixelSize],
4263
4264                 /* same as refresh 2 */
4265                 tileX = Math.floor(((mapCenter[0] - halfWidth) - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
4266                 tileY = Math.floor((tilingScheme.origin[1] - (mapCenter[1] + halfHeight)) / (pixelSize * tileHeight)),
4267                 tileX2 = Math.ceil(((mapCenter[0] + halfWidth) - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
4268                 tileY2 = Math.ceil((tilingScheme.origin[1] - (mapCenter[1] - halfHeight)) / (pixelSize * tileHeight)),
4269
4270                 bboxMax = map._getBboxMax(),
4271                 pixelSizeAtZero = map._getTiledPixelSize(0),
4272                 ratio = pixelSizeAtZero / pixelSize,
4273                 fullXAtScale = Math.floor((bboxMax[0] - tilingScheme.origin[0]) / (pixelSizeAtZero * tileWidth)) * ratio,
4274                 fullYAtScale = Math.floor((tilingScheme.origin[1] - bboxMax[3]) / (pixelSizeAtZero * tileHeight)) * ratio,
4275
4276                 fullXMinX = tilingScheme.origin[0] + (fullXAtScale * tileWidth) * pixelSize,
4277                 fullYMaxY = tilingScheme.origin[1] - (fullYAtScale * tileHeight) * pixelSize,
4278                 /* end same as refresh 2 */
4279
4280                 serviceLeft = Math.round((fullXMinX - (mapCenterOriginal[0] - halfWidth)) / pixelSize),
4281                 serviceTop = Math.round(((mapCenterOriginal[1] + halfHeight) - fullYMaxY) / pixelSize),
4282
4283                 opacity = (service.opacity === undefined ? 1 : service.opacity),
4284
4285                 x, y;
4286
4287             for ( x = tileX; x < tileX2; x++ ) {
4288               for ( y = tileY; y < tileY2; y++ ) {
4289                 var tileStr = "" + x + "," + y,
4290                     $img = scaleContainer.children("[data-tile='" + tileStr + "']").removeAttr("data-dirty");
4291
4292                 if ( $img.size( ) === 0 ) {
4293                   /* same as refresh 3 */
4294                   var bottomLeft = [
4295                         tilingScheme.origin[0] + (x * tileWidth) * pixelSize,
4296                         tilingScheme.origin[1] - (y * tileHeight) * pixelSize
4297                       ],
4298
4299                       topRight = [
4300                         tilingScheme.origin[0] + ((x + 1) * tileWidth - 1) * pixelSize,
4301                         tilingScheme.origin[1] - ((y + 1) * tileHeight - 1) * pixelSize
4302                       ],
4303
4304                       tileBbox = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]],
4305
4306                       imageUrl = service.getUrl( {
4307                         bbox: tileBbox,
4308                         width: tileWidth,
4309                         height: tileHeight,
4310                         zoom: map._getZoom(),
4311                         tile: {
4312                           row: y,
4313                           column: x
4314                         },
4315                         index: Math.abs(y + x)
4316                       } );
4317                   /* end same as refresh 3 */
4318
4319                   serviceState.loadCount++;
4320                   //this._map._requestQueued();
4321
4322                   if ( serviceState.reloadTiles && $img.size() > 0 ) {
4323                     $img.attr( "src", imageUrl );
4324                   } else {
4325                     /* same as refresh 4 */
4326                     var imgMarkup = "<img style='position:absolute; " +
4327                           "left:" + (((x - fullXAtScale) * 100) + (serviceLeft - (serviceLeft % tileWidth)) / tileWidth * 100) + "%; " +
4328                           "top:" + (((y - fullYAtScale) * 100) + (serviceTop - (serviceTop % tileHeight)) / tileHeight * 100) + "%; ";
4329
4330                     if ($("body")[0].filters === undefined) {
4331                       imgMarkup += "width: 100%; height: 100%;";
4332                     }
4333
4334                     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 + "' />";
4335
4336                     scaleContainer.append( imgMarkup );
4337                     $img = scaleContainer.children(":last");
4338                     $img.load(function (e) {
4339                       if (opacity < 1) {
4340                         $(e.target).fadeTo(0, opacity);
4341                       } else {
4342                         $(e.target).show();
4343                       }
4344
4345                       serviceState.loadCount--;
4346
4347                       if (serviceState.loadCount <= 0) {
4348                         serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
4349                         serviceState.loadCount = 0;
4350                       }
4351                     }).error(function (e) {
4352                       $(e.target).remove();
4353                       serviceState.loadCount--;
4354
4355                       if (serviceState.loadCount <= 0) {
4356                         serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
4357                         serviceState.loadCount = 0;
4358                       }
4359                     }).attr("src", imageUrl);
4360                     /* end same as refresh 4 */
4361                   }
4362                 }
4363               }
4364             }
4365           }
4366         }
4367       },
4368
4369       interactiveScale: function (map, service, center, pixelSize) {
4370         var serviceState = $.data( service, "geoServiceState" );
4371
4372         if ( serviceState && service && ( service.visibility === undefined || service.visibility === "visible" ) ) {
4373           this._cancelUnloaded(map, service);
4374
4375           var serviceContainer = serviceState.serviceContainer,
4376
4377               tilingScheme = map.options["tilingScheme"],
4378               tileWidth = tilingScheme.tileWidth,
4379               tileHeight = tilingScheme.tileHeight;
4380
4381
4382           serviceContainer.children( ).each( function ( i ) {
4383             var $scaleContainer = $(this),
4384                 scaleRatio = $scaleContainer.attr("data-pixelSize") / pixelSize;
4385
4386             scaleRatio = Math.round(scaleRatio * 1000) / 1000;
4387
4388             var scaleOriginParts = $scaleContainer.data("scaleOrigin").split(","),
4389                 oldMapCoord = map._toMap([scaleOriginParts[0], scaleOriginParts[1]]),
4390                 newPixelPoint = map._toPixel(oldMapCoord, center, pixelSize);
4391
4392             $scaleContainer.css( {
4393               left: Math.round(newPixelPoint[0]) + "px",
4394               top: Math.round(newPixelPoint[1]) + "px",
4395               width: tileWidth * scaleRatio,
4396               height: tileHeight * scaleRatio
4397             } );
4398
4399             if ( $("body")[0].filters !== undefined ) {
4400               $scaleContainer.children().each( function ( i ) {
4401                 $( this ).css( "filter", "progid:DXImageTransform.Microsoft.Matrix(FilterType=bilinear,M11=" + scaleRatio + ",M22=" + scaleRatio + ",sizingmethod='auto expand')" );
4402               } );
4403             }
4404           });
4405         }
4406       },
4407
4408       refresh: function (map, service) {
4409         var serviceState = $.data( service, "geoServiceState" );
4410
4411         if ( serviceState && service && ( service.visibility === undefined || service.visibility === "visible" ) ) {
4412           this._cancelUnloaded(map, service);
4413
4414           var bbox = map._getBbox(),
4415               pixelSize = map._pixelSize,
4416
4417               $serviceContainer = serviceState.serviceContainer,
4418
4419               contentBounds = map._getContentBounds(),
4420               mapWidth = contentBounds["width"],
4421               mapHeight = contentBounds["height"],
4422
4423               tilingScheme = map.options["tilingScheme"],
4424               tileWidth = tilingScheme.tileWidth,
4425               tileHeight = tilingScheme.tileHeight,
4426
4427               tileX = Math.floor((bbox[0] - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
4428               tileY = Math.floor((tilingScheme.origin[1] - bbox[3]) / (pixelSize * tileHeight)),
4429               tileX2 = Math.ceil((bbox[2] - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
4430               tileY2 = Math.ceil((tilingScheme.origin[1] - bbox[1]) / (pixelSize * tileHeight)),
4431
4432               bboxMax = map._getBboxMax(),
4433               pixelSizeAtZero = map._getTiledPixelSize(0),
4434               ratio = pixelSizeAtZero / pixelSize,
4435               fullXAtScale = Math.floor((bboxMax[0] - tilingScheme.origin[0]) / (pixelSizeAtZero * tileWidth)) * ratio,
4436               fullYAtScale = Math.floor((tilingScheme.origin[1] - bboxMax[3]) / (pixelSizeAtZero * tileHeight)) * ratio,
4437
4438               fullXMinX = tilingScheme.origin[0] + (fullXAtScale * tileWidth) * pixelSize,
4439               fullYMaxY = tilingScheme.origin[1] - (fullYAtScale * tileHeight) * pixelSize,
4440
4441               serviceLeft = Math.round((fullXMinX - bbox[0]) / pixelSize),
4442               serviceTop = Math.round((bbox[3] - fullYMaxY) / pixelSize),
4443
4444               scaleContainers = $serviceContainer.children().show(),
4445               scaleContainer = scaleContainers.filter("[data-pixelSize='" + pixelSize + "']").appendTo($serviceContainer),
4446
4447               opacity = (service.opacity === undefined ? 1 : service.opacity),
4448
4449               x, y;
4450
4451           if (serviceState.reloadTiles) {
4452             scaleContainers.find("img").attr("data-dirty", "true");
4453           }
4454
4455           if (!scaleContainer.size()) {
4456             $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>");
4457             scaleContainer = $serviceContainer.children(":last").data("scaleOrigin", (serviceLeft % tileWidth) + "," + (serviceTop % tileHeight));
4458           } else {
4459             scaleContainer.css({
4460               left: (serviceLeft % tileWidth) + "px",
4461               top: (serviceTop % tileHeight) + "px"
4462             }).data("scaleOrigin", (serviceLeft % tileWidth) + "," + (serviceTop % tileHeight));
4463
4464             scaleContainer.children().each(function (i) {
4465               var 
4466               $img = $(this),
4467               tile = $img.attr("data-tile").split(",");
4468
4469               $img.css({
4470                 left: Math.round(((parseInt(tile[0]) - fullXAtScale) * 100) + (serviceLeft - (serviceLeft % tileWidth)) / tileWidth * 100) + "%",
4471                 top: Math.round(((parseInt(tile[1]) - fullYAtScale) * 100) + (serviceTop - (serviceTop % tileHeight)) / tileHeight * 100) + "%"
4472               });
4473
4474               if (opacity < 1) {
4475                 $img.fadeTo(0, opacity);
4476               }
4477             });
4478           }
4479
4480           for (x = tileX; x < tileX2; x++) {
4481             for (y = tileY; y < tileY2; y++) {
4482               var 
4483               tileStr = "" + x + "," + y,
4484               $img = scaleContainer.children("[data-tile='" + tileStr + "']").removeAttr("data-dirty");
4485
4486               if ($img.size() === 0 || serviceState.reloadTiles) {
4487                 var bottomLeft = [
4488                   tilingScheme.origin[0] + (x * tileWidth) * pixelSize,
4489                   tilingScheme.origin[1] - (y * tileHeight) * pixelSize
4490                 ],
4491
4492                 topRight = [
4493                   tilingScheme.origin[0] + ((x + 1) * tileWidth - 1) * pixelSize,
4494                   tilingScheme.origin[1] - ((y + 1) * tileHeight - 1) * pixelSize
4495                 ],
4496
4497                 tileBbox = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]],
4498
4499                 imageUrl = service.getUrl({
4500                   bbox: tileBbox,
4501                   width: tileWidth,
4502                   height: tileHeight,
4503                   zoom: map._getZoom(),
4504                   tile: {
4505                     row: y,
4506                     column: x
4507                   },
4508                   index: Math.abs(y + x)
4509                 });
4510
4511                 serviceState.loadCount++;
4512                 //this._map._requestQueued();
4513
4514                 if (serviceState.reloadTiles && $img.size() > 0) {
4515                   $img.attr("src", imageUrl);
4516                 } else {
4517                   var imgMarkup = "<img style='position:absolute; " +
4518                     "left:" + (((x - fullXAtScale) * 100) + (serviceLeft - (serviceLeft % tileWidth)) / tileWidth * 100) + "%; " +
4519                     "top:" + (((y - fullYAtScale) * 100) + (serviceTop - (serviceTop % tileHeight)) / tileHeight * 100) + "%; ";
4520
4521                   if ($("body")[0].filters === undefined) {
4522                     imgMarkup += "width: 100%; height: 100%;";
4523                   }
4524
4525                   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 + "' />";
4526
4527                   scaleContainer.append(imgMarkup);
4528                   $img = scaleContainer.children(":last");
4529                   $img.load(function (e) {
4530                     if (opacity < 1) {
4531                       $(e.target).fadeTo(0, opacity);
4532                     } else {
4533                       $(e.target).show();
4534                     }
4535
4536                     serviceState.loadCount--;
4537
4538                     if (serviceState.loadCount <= 0) {
4539                       $serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
4540                       serviceState.loadCount = 0;
4541                     }
4542                   }).error(function (e) {
4543                     $(e.target).remove();
4544                     serviceState.loadCount--;
4545
4546                     if (serviceState.loadCount <= 0) {
4547                       $serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
4548                       serviceState.loadCount = 0;
4549                     }
4550                   }).attr("src", imageUrl);
4551                 }
4552               }
4553             }
4554           }
4555
4556           scaleContainers.find("[data-dirty]").remove();
4557           serviceState.reloadTiles = false;
4558         }
4559       },
4560
4561       resize: function (map, service) {
4562       },
4563
4564       opacity: function (map, service) {
4565         var serviceState = $.data( service, "geoServiceState" );
4566         serviceState.serviceContainer.find("img").stop(true).fadeTo("fast", service.opacity);
4567       },
4568
4569       toggle: function (map, service) {
4570         var serviceState = $.data( service, "geoServiceState" );
4571         serviceState.serviceContainer.css("display", service.visibility === "visible" ? "block" : "none");
4572       },
4573
4574       _cancelUnloaded: function (map, service) {
4575         var serviceState = $.data( service, "geoServiceState" );
4576
4577         if (serviceState && serviceState.loadCount > 0) {
4578           serviceState.serviceContainer.find("img:hidden").remove();
4579           while (serviceState.loadCount > 0) {
4580             serviceState.loadCount--;
4581           }
4582         }
4583       }
4584     };
4585   })();
4586 })(jQuery);
4587 (function ($, undefined) {
4588   $.geo._serviceTypes.shingled = (function () {
4589     return {
4590       create: function (map, servicesContainer, service, index) {
4591         var serviceState = $.data(service, "geoServiceState");
4592
4593         if ( !serviceState ) {
4594           serviceState = {
4595             loadCount: 0
4596           };
4597
4598           var idString = service.id ? ' id="' + service.id + '"' : "",
4599               classString = service["class"] ? ' class="' + service["class"] + '"' : "",
4600               scHtml = '<div data-geo-service="shingled"' + idString + classString + ' style="position:absolute; left:0; top:0; width:16px; height:16px; margin:0; padding:0; display:' + (service.visibility === undefined || service.visibility === "visible" ? "block" : "none") + ';"></div>';
4601
4602           servicesContainer.append(scHtml);
4603
4604           serviceState.serviceContainer = servicesContainer.children(":last");
4605           $.data(service, "geoServiceState", serviceState);
4606         }
4607
4608         return serviceState.serviceContainer;
4609       },
4610
4611       destroy: function (map, servicesContainer, service) {
4612         var serviceState = $.data(service, "geoServiceState");
4613
4614         serviceState.serviceContainer.remove();
4615
4616         $.removeData(service, "geoServiceState");
4617       },
4618
4619       interactivePan: function (map, service, dx, dy) {
4620         var serviceState = $.data(service, "geoServiceState");
4621
4622         if ( serviceState ) {
4623           this._cancelUnloaded(map, service);
4624
4625           var serviceContainer = serviceState.serviceContainer,
4626               pixelSize = map._pixelSize,
4627               scaleContainer = serviceContainer.children("[data-pixelSize='" + pixelSize + "']"),
4628               panContainer = scaleContainer.children("div");
4629
4630           if ( !panContainer.length ) {
4631             scaleContainer.children("img").wrap('<div style="position:absolute; left:0; top:0; width:100%; height:100%;"></div>');
4632             panContainer = scaleContainer.children("div");
4633           }
4634
4635           panContainer.css( {
4636             left: function (index, value) {
4637               return parseInt(value) + dx;
4638             },
4639             top: function (index, value) {
4640               return parseInt(value) + dy;
4641             }
4642           } );
4643         }
4644       },
4645
4646       interactiveScale: function (map, service, center, pixelSize) {
4647         var serviceState = $.data(service, "geoServiceState");
4648
4649         if ( serviceState ) {
4650           this._cancelUnloaded(map, service);
4651
4652           var serviceContainer = serviceState.serviceContainer,
4653
4654               contentBounds = map._getContentBounds(),
4655               mapWidth = contentBounds["width"],
4656               mapHeight = contentBounds["height"],
4657
4658               halfWidth = mapWidth / 2,
4659               halfHeight = mapHeight / 2,
4660
4661               bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight];
4662
4663           serviceContainer.children().each(function (i) {
4664             var $scaleContainer = $(this),
4665                 scalePixelSize = $scaleContainer.attr("data-pixelSize"),
4666                 ratio = scalePixelSize / pixelSize;
4667
4668             $scaleContainer.css({ width: mapWidth * ratio, height: mapHeight * ratio }).children("img").each(function (i) {
4669               var $img = $(this),
4670                   imgCenter = $img.data("center"),
4671                   x = (Math.round((imgCenter[0] - center[0]) / scalePixelSize) - halfWidth) * ratio,
4672                   y = (Math.round((center[1] - imgCenter[1]) / scalePixelSize) - halfHeight) * ratio;
4673
4674               $img.css({ left: x + "px", top: y + "px" });
4675             });
4676           });
4677         }
4678       },
4679
4680       refresh: function (map, service) {
4681         var serviceState = $.data(service, "geoServiceState");
4682
4683         if (serviceState && service && (service.visibility === undefined || service.visibility === "visible")) {
4684           this._cancelUnloaded(map, service);
4685
4686           var bbox = map._getBbox(),
4687               pixelSize = map._pixelSize,
4688
4689               serviceContainer = serviceState.serviceContainer,
4690
4691               contentBounds = map._getContentBounds(),
4692               mapWidth = contentBounds["width"],
4693               mapHeight = contentBounds["height"],
4694
4695               halfWidth = mapWidth / 2,
4696               halfHeight = mapHeight / 2,
4697
4698               scaleContainer = serviceContainer.children('[data-pixelSize="' + pixelSize + '"]'),
4699
4700               opacity = (service.opacity === undefined ? 1 : service.opacity),
4701
4702               $img;
4703
4704           if ( !scaleContainer.size() ) {
4705             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>');
4706             scaleContainer = serviceContainer.children(":last");
4707           }
4708
4709           scaleContainer.children("img").each(function (i) {
4710             var $thisimg = $(this),
4711                 imgCenter = $thisimg.data("center"),
4712                 center = map._getCenter(),
4713                 x = Math.round((imgCenter[0] - center[0]) / pixelSize) - halfWidth,
4714                 y = Math.round((center[1] - imgCenter[1]) / pixelSize) - halfHeight;
4715
4716             $thisimg.css({ left: x + "px", top: y + "px" });
4717           });
4718
4719           if (opacity < 1) {
4720             serviceContainer.find("img").attr("data-keepAlive", "0");
4721           }
4722
4723           var imageUrl = service.getUrl({
4724                 bbox: bbox,
4725                 width: mapWidth,
4726                 height: mapHeight,
4727                 zoom: map._getZoom(),
4728                 tile: null,
4729                 index: 0
4730               });
4731
4732           serviceState.loadCount++;
4733           //this._map._requestQueued();
4734
4735           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" />');
4736           $img = scaleContainer.children(":last").data("center", map._getCenter());
4737           $img.load(function (e) {
4738             if (opacity < 1) {
4739               $(e.target).fadeTo(0, opacity);
4740             } else {
4741               $(e.target).show();
4742             }
4743
4744             serviceState.loadCount--;
4745
4746             if (serviceState.loadCount <= 0) {
4747               serviceContainer.children(':not([data-pixelSize="' + pixelSize + '"])').remove();
4748
4749               var panContainer = serviceContainer.find('[data-pixelSize="' + pixelSize + '"]>div');
4750               if (panContainer.size() > 0) {
4751                 var panContainerPos = panContainer.position();
4752
4753                 panContainer.children("img").each(function (i) {
4754                   var $thisimg = $(this),
4755                       x = panContainerPos.left + parseInt($thisimg.css("left")),
4756                       y = panContainerPos.top + parseInt($thisimg.css("top"));
4757
4758                   $thisimg.css({ left: x + "px", top: y + "px" });
4759                 }).unwrap();
4760
4761                 panContainer.remove();
4762               }
4763
4764               serviceState.loadCount = 0;
4765             }
4766           }).error(function (e) {
4767             $(e.target).remove();
4768             serviceState.loadCount--;
4769
4770             if (serviceState.loadCount <= 0) {
4771               serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
4772               serviceState.loadCount = 0;
4773             }
4774           }).attr("src", imageUrl);
4775         }
4776       },
4777
4778       resize: function (map, service) {
4779         var serviceState = $.data(service, "geoServiceState");
4780
4781         if ( serviceState && service && (service.visibility === undefined || service.visibility === "visible")) {
4782           this._cancelUnloaded(map, service);
4783
4784           var serviceState = shingledServicesState[service.id],
4785               serviceContainer = serviceState.serviceContainer,
4786
4787               contentBounds = map._getContentBounds(),
4788               mapWidth = contentBounds["width"],
4789               mapHeight = contentBounds["height"],
4790
4791               halfWidth = mapWidth / 2,
4792               halfHeight = mapHeight / 2,
4793
4794               scaleContainer = serviceContainer.children();
4795
4796           scaleContainer.attr("data-pixelSize", "0");
4797           scaleContainer.css({
4798             left: halfWidth + 'px',
4799             top: halfHeight + 'px'
4800           });
4801         }
4802       },
4803
4804       opacity: function (map, service) {
4805         var serviceState = $.data(service, "geoServiceState");
4806         serviceState.serviceContainer.find("img").stop(true).fadeTo("fast", service.opacity);
4807       },
4808
4809       toggle: function (map, service) {
4810         var serviceState = $.data(service, "geoServiceState");
4811         serviceState.serviceContainer.css("display", service.visibility === "visible" ? "block" : "none");
4812       },
4813
4814       _cancelUnloaded: function (map, service) {
4815         var serviceState = $.data(service, "geoServiceState");
4816
4817         if (serviceState && serviceState.loadCount > 0) {
4818           serviceState.serviceContainer.find("img:hidden").remove();
4819           while (serviceState.loadCount > 0) {
4820             serviceState.loadCount--;
4821           }
4822         }
4823       }
4824     }
4825   })();
4826 })(jQuery);