2 // Copyright 2006 Google Inc.
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
8 // http://www.apache.org/licenses/LICENSE-2.0
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.
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
24 // Copyright 2006 Google Inc.
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
30 // http://www.apache.org/licenses/LICENSE-2.0
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.
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
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.
57 // Only add this code if we do not already have a canvas implementation
58 if (!document.createElement('canvas').getContext) {
62 // alias some functions to make (compiled) code shorter
70 // this is used for sub pixel precision
74 var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
77 * This funtion is assigned to the <canvas> elements as element.getContext().
79 * @return {CanvasRenderingContext2D_}
81 function getContext() {
82 return this.context_ ||
83 (this.context_ = new CanvasRenderingContext2D_(this));
86 var slice = Array.prototype.slice;
89 * Binds a function to an object. The returned function will always use the
90 * passed in {@code obj} as {@code this}.
94 * g = bind(f, obj, a, b)
95 * g(c, d) // will do f.call(obj, a, b, c, d)
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
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
104 function bind(f, obj, var_args) {
105 var a = slice.call(arguments, 2);
107 return f.apply(obj, a.concat(slice.call(arguments)));
111 function encodeHtmlAttribute(s) {
112 return String(s).replace(/&/g, '&').replace(/"/g, '"');
115 function addNamespace(doc, prefix, urn) {
116 if (!doc.namespaces[prefix]) {
117 doc.namespaces.add(prefix, urn, '#default#VML');
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');
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}';
135 // Add namespaces and stylesheet at startup.
136 addNamespacesAndStylesheet(document);
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
143 doc.createElement('canvas');
144 doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
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]);
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.
163 initElement: function (el) {
164 if (!el.getContext) {
165 el.getContext = getContext;
167 // Add namespaces and stylesheet to document of the element.
168 addNamespacesAndStylesheet(el.ownerDocument);
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.
175 // do not use inline function because that will leak memory
176 el.attachEvent('onpropertychange', onPropertyChange);
177 el.attachEvent('onresize', onResize);
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';
185 el.width = el.clientWidth;
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';
192 el.height = el.clientHeight;
194 //el.getContext().setCoordsize_()
200 function onPropertyChange(e) {
201 var el = e.srcElement;
203 switch (e.propertyName) {
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';
211 el.getContext().clearRect();
212 el.style.height = el.attributes.height.nodeValue + 'px';
213 el.firstChild.style.height = el.clientHeight + 'px';
218 function onResize(e) {
219 var el = e.srcElement;
221 el.firstChild.style.width = el.clientWidth + 'px';
222 el.firstChild.style.height = el.clientHeight + 'px';
226 G_vmlCanvasManager_.init();
228 // precompute "00" to "FF"
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);
236 function createMatrixIdentity() {
244 function matrixMultiply(m1, m2) {
245 var result = createMatrixIdentity();
247 for (var x = 0; x < 3; x++) {
248 for (var y = 0; y < 3; y++) {
251 for (var z = 0; z < 3; z++) {
252 sum += m1[x][z] * m2[z][y];
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;
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_;
282 // aliceblue: '#F0F8FF',
283 // antiquewhite: '#FAEBD7',
284 // aquamarine: '#7FFFD4',
287 // bisque: '#FFE4C4',
289 // blanchedalmond: '#FFEBCD',
290 // blueviolet: '#8A2BE2',
292 // burlywood: '#DEB887',
293 // cadetblue: '#5F9EA0',
294 // chartreuse: '#7FFF00',
295 // chocolate: '#D2691E',
297 // cornflowerblue: '#6495ED',
298 // cornsilk: '#FFF8DC',
299 // crimson: '#DC143C',
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',
331 // goldenrod: '#DAA520',
333 // greenyellow: '#ADFF2F',
334 // honeydew: '#F0FFF0',
335 // hotpink: '#FF69B4',
336 // indianred: '#CD5C5C',
337 // indigo: '#4B0082',
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',
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',
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',
403 // springgreen: '#00FF7F',
404 // steelblue: '#4682B4',
406 // thistle: '#D8BFD8',
407 // tomato: '#FF6347',
408 // turquoise: '#40E0D0',
409 // violet: '#EE82EE',
411 // whitesmoke: '#F5F5F5',
412 // yellowgreen: '#9ACD32'
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') {
427 function percent(s) {
428 return parseFloat(s) / 100;
431 function clamp(v, min, max) {
432 return Math.min(max, Math.max(min, v));
435 function hslToRgb(parts) {
436 var r, g, b, h, s, l;
437 h = parseFloat(parts[0]) / 360 % 360;
440 s = clamp(percent(parts[1]), 0, 1);
441 l = clamp(percent(parts[2]), 0, 1);
443 r = g = b = l; // achromatic
445 var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
447 r = hueToRgb(p, q, h + 1 / 3);
448 g = hueToRgb(p, q, h);
449 b = hueToRgb(p, q, h - 1 / 3);
452 return '#' + decToHex[Math.floor(r * 255)] +
453 decToHex[Math.floor(g * 255)] +
454 decToHex[Math.floor(b * 255)];
457 function hueToRgb(m1, m2, h) {
464 return m1 + (m2 - m1) * 6 * h;
468 return m1 + (m2 - m1) * (2 / 3 - h) * 6;
473 var processStyleCache = {};
475 function processStyle(styleString) {
476 if (styleString in processStyleCache) {
477 return processStyleCache[styleString];
482 styleString = String(styleString);
483 if (styleString.charAt(0) == '#') {
485 } else if (/^rgb/.test(styleString)) {
486 var parts = getRgbHslContent(styleString);
488 for (var i = 0; i < 3; i++) {
489 if (parts[i].indexOf('%') != -1) {
490 n = Math.floor(percent(parts[i]) * 255);
494 str += decToHex[clamp(n, 0, 255)];
497 } else if (/^hsl/.test(styleString)) {
498 var parts = getRgbHslContent(styleString);
499 str = hslToRgb(parts);
502 str = /*colorData[styleString] ||*/styleString;
504 return processStyleCache[styleString] = { color: str, alpha: alpha };
507 var DEFAULT_STYLE = {
515 // Internal text style cache
516 // var fontStyleCache = {};
518 // function processFontStyle(styleString) {
519 // if (fontStyleCache[styleString]) {
520 // return fontStyleCache[styleString];
523 // var el = document.createElement('div');
524 // var style = el.style;
526 // style.font = styleString;
528 // // Ignore failures to set to invalid font.
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
540 // function getComputedStyle(style, element) {
541 // var computedStyle = {};
543 // for (var p in style) {
544 // computedStyle[p] = style[p];
547 // // Compute the size
548 // var canvasFontSize = parseFloat(element.currentStyle.fontSize),
549 // fontSize = parseFloat(style.size);
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;
562 // computedStyle.size = canvasFontSize;
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;
569 // return computedStyle;
572 // function buildStyle(style) {
573 // return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
574 // style.size + 'px ' + style.family;
582 function processLineCap(lineCap) {
583 return lineCapMap[lineCap] || 'square';
587 * This class implements CanvasRenderingContext2D interface as described by
589 * @param {HTMLElement} canvasElement The element that the 2D context should
592 function CanvasRenderingContext2D_(canvasElement) {
593 this.m_ = createMatrixIdentity();
597 this.currentPath_ = [];
599 // Canvas context properties
600 this.strokeStyle = '#000';
601 this.fillStyle = '#000';
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;
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);
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);
631 var contextPrototype = CanvasRenderingContext2D_.prototype;
632 contextPrototype.clearRect = function () {
633 if (this.textMeasureEl_) {
634 this.textMeasureEl_.removeNode(true);
635 this.textMeasureEl_ = null;
637 this.element_.innerHTML = '';
640 contextPrototype.beginPath = function () {
641 // TODO: Branch current matrix so that save/restore has no effect
642 // as per safari docs.
643 this.currentPath_ = [];
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;
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 });
657 this.currentX_ = p.x;
658 this.currentY_ = p.y;
661 contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
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);
670 // Helper function that takes the already fixed cordinates.
671 function bezierCurveTo(self, cp1, cp2, p) {
672 self.currentPath_.push({
673 type: 'bezierCurveTo',
681 self.currentX_ = p.x;
682 self.currentY_ = p.y;
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
689 var cp = getCoords(this, aCPx, aCPy);
690 var p = getCoords(this, aX, aY);
693 x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
694 y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
697 x: cp1.x + (p.x - this.currentX_) / 3.0,
698 y: cp1.y + (p.y - this.currentY_) / 3.0
701 bezierCurveTo(this, cp1, cp2, p);
704 contextPrototype.arc = function (aX, aY, aRadius,
705 aStartAngle, aEndAngle, aClockwise) {
707 var arcType = aClockwise ? 'at' : 'wa';
709 var xStart = aX + mc(aStartAngle) * aRadius - Z2;
710 var yStart = aY + ms(aStartAngle) * aRadius - Z2;
712 var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
713 var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
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
721 var p = getCoords(this, aX, aY);
722 var pStart = getCoords(this, xStart, yStart);
723 var pEnd = getCoords(this, xEnd, yEnd);
725 this.currentPath_.push({ type: arcType,
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);
745 // contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
746 // var oldPath = this.currentPath_;
749 // this.moveTo(aX, aY);
750 // this.lineTo(aX + aWidth, aY);
751 // this.lineTo(aX + aWidth, aY + aHeight);
752 // this.lineTo(aX, aY + aHeight);
756 // this.currentPath_ = oldPath;
759 // contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
760 // var oldPath = this.currentPath_;
763 // this.moveTo(aX, aY);
764 // this.lineTo(aX + aWidth, aY);
765 // this.lineTo(aX + aWidth, aY + aHeight);
766 // this.lineTo(aX, aY + aHeight);
770 // this.currentPath_ = oldPath;
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;
782 // contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
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;
794 // contextPrototype.drawImage = function(image, var_args) {
795 // var dx, dy, dw, dh, sx, sy, sw, sh;
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';
803 // // get the original size
804 // var w = image.width;
805 // var h = image.height;
807 // // and remove overides
808 // image.runtimeStyle.width = oldRuntimeWidth;
809 // image.runtimeStyle.height = oldRuntimeHeight;
811 // if (arguments.length == 3) {
812 // dx = arguments[1];
813 // dy = arguments[2];
817 // } else if (arguments.length == 5) {
818 // dx = arguments[1];
819 // dy = arguments[2];
820 // dw = arguments[3];
821 // dh = arguments[4];
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];
835 // throw Error('Invalid number of arguments');
838 // var d = getCoords(this, dx, dy);
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;');
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.
859 // if (this.m_[0][0] != 1 || this.m_[0][1] ||
860 // this.m_[1][1] != 1 || this.m_[1][0]) {
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), '');
871 // // Bounding box calculation (need to minimize displayed area so that
872 // // filters don't waste time on unused pixels.
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);
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);
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');");
886 // vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
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, '"',
898 // '</g_vml_:group>');
900 // this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
903 contextPrototype.stroke = function (aFill) {
905 var lineOpen = false;
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, '"',
919 var min = { x: null, y: null };
920 var max = { x: null, y: null };
922 for (var i = 0; i < this.currentPath_.length; i++) {
923 var p = this.currentPath_[i];
929 lineStr.push(' m ', mr(p.x), ',', mr(p.y));
932 lineStr.push(' l ', mr(p.x), ',', mr(p.y));
938 case 'bezierCurveTo':
940 mr(p.cp1x), ',', mr(p.cp1y), ',',
941 mr(p.cp2x), ',', mr(p.cp2y), ',',
942 mr(p.x), ',', mr(p.y));
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));
957 // TODO: Following is broken for curves due to
958 // move to proper paths.
960 // Figure out dimensions so we can do gradient fills
963 if (min.x == null || p.x < min.x) {
966 if (max.x == null || p.x > max.x) {
969 if (min.y == null || p.y < min.y) {
972 if (max.y == null || p.y > max.y) {
980 appendStroke(this, lineStr);
982 appendFill(this, lineStr, min, max);
985 lineStr.push('</g_vml_:shape>');
987 this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
990 function appendStroke(ctx, lineStr) {
991 var a = processStyle(ctx.strokeStyle);
993 var opacity = a.alpha * ctx.globalAlpha;
994 var lineWidth = ctx.lineScale_ * ctx.lineWidth;
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.
999 opacity *= lineWidth;
1004 ' opacity="', opacity, '"',
1005 ' joinstyle="', ctx.lineJoin, '"',
1006 ' miterlimit="', ctx.miterLimit, '"',
1007 ' endcap="', processLineCap(ctx.lineCap), '"',
1008 ' weight="', lineWidth, 'px"',
1009 ' color="', color, '" />'
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.
1022 // var focus = {x: 0, y: 0};
1024 // // additional offset
1026 // // scale factor for offset
1027 // var expansion = 1;
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;
1040 // // The angle should be a non-negative number.
1045 // // Very small angles produce an unexpected result because they are
1046 // // converted to a scientific notation string.
1047 // if (angle < 1e-6) {
1051 // var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
1053 // x: (p0.x - min.x) / width,
1054 // y: (p0.y - min.y) / height
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;
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;
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;
1078 // for (var i = 0; i < length; i++) {
1079 // var stop = stops[i];
1080 // colors.push(stop.offset * expansion + shift + ' ' + stop.color);
1083 // // When colors attribute is used, the meanings of opacity and o:opacity2
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',
1100 // deltaLeft / width * arcScaleX * arcScaleX, ',',
1101 // deltaTop / height * arcScaleY * arcScaleY, '"',
1103 // // TODO: Figure out the correct size to fit the scale.
1104 // //' size="', w, 'px ', h, 'px"',
1105 // ' src="', fillStyle.src_, '" />');
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,
1116 contextPrototype.fill = function () {
1120 contextPrototype.closePath = function () {
1121 this.currentPath_.push({ type: 'close' });
1124 function getCoords(ctx, aX, aY) {
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
1132 contextPrototype.save = function () {
1135 this.aStack_.push(o);
1136 this.mStack_.push(this.m_);
1137 this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
1140 contextPrototype.restore = function () {
1141 if (this.aStack_.length) {
1142 copyState(this.aStack_.pop(), this);
1143 this.m_ = this.mStack_.pop();
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]);
1153 function setM(ctx, m, updateLineScale) {
1154 if (!matrixIsFinite(m)) {
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
1164 var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
1165 ctx.lineScale_ = sqrt(abs(det));
1169 contextPrototype.translate = function (aX, aY) {
1176 setM(this, matrixMultiply(m1, this.m_), false);
1179 // contextPrototype.rotate = function(aRot) {
1180 // var c = mc(aRot);
1181 // var s = ms(aRot);
1189 // setM(this, matrixMultiply(m1, this.m_), false);
1192 contextPrototype.scale = function (aX, aY) {
1193 this.arcScaleX_ *= aX;
1194 this.arcScaleY_ *= aY;
1201 setM(this, matrixMultiply(m1, this.m_), true);
1204 // contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
1211 // setM(this, matrixMultiply(m1, this.m_), true);
1214 // contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
1221 // setM(this, m, true);
1225 * The text drawing function.
1226 * The maxWidth argument isn't taken in account, since no browser supports
1229 // contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
1234 // offset = {x: 0, y: 0},
1237 // var fontStyle = getComputedStyle(processFontStyle(this.font),
1240 // var fontStyleString = buildStyle(fontStyle);
1242 // var elementStyle = this.element_.currentStyle;
1243 // var textAlign = this.textAlign.toLowerCase();
1244 // switch (textAlign) {
1250 // textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
1253 // textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
1256 // textAlign = 'left';
1259 // // 1.75 is an arbitrary number, as there is no info about the text baseline
1260 // switch (this.textBaseline) {
1263 // offset.y = fontStyle.size / 1.75;
1269 // case 'alphabetic':
1270 // case 'ideographic':
1272 // offset.y = -fontStyle.size / 2.25;
1276 // switch(textAlign) {
1282 // left = right = delta / 2;
1286 // var d = getCoords(this, x + offset.x, y + offset.y);
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;">');
1294 // appendStroke(this, lineStr);
1296 // // TODO: Fix the min and max params.
1297 // appendFill(this, lineStr, {x: -left, y: 0},
1298 // {x: right, y: fontStyle.size});
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';
1304 // var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
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>');
1315 // this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
1318 // contextPrototype.fillText = function(text, x, y, maxWidth) {
1319 // this.drawText_(text, x, y, maxWidth, false);
1322 // contextPrototype.strokeText = function(text, x, y, maxWidth) {
1323 // this.drawText_(text, x, y, maxWidth, true);
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;
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};
1342 /******** STUBS ********/
1343 // contextPrototype.clip = function() {
1344 // // TODO: Implement
1347 // contextPrototype.arcTo = function() {
1348 // // TODO: Implement
1351 // contextPrototype.createPattern = function(image, repetition) {
1352 // return new CanvasPattern_(image, repetition);
1355 // // Gradient / Pattern Stubs
1356 // function CanvasGradient_(aType) {
1357 // this.type_ = aType;
1364 // this.colors_ = [];
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});
1374 // function CanvasPattern_(image, repetition) {
1375 // assertImageIsValid(image);
1376 // switch (repetition) {
1380 // this.repetition_ = 'repeat';
1384 // case 'no-repeat':
1385 // this.repetition_ = repetition;
1388 // throwException('SYNTAX_ERR');
1391 // this.src_ = image.src;
1392 // this.width_ = image.width;
1393 // this.height_ = image.height;
1396 function throwException(s) {
1397 throw new DOMException_(s);
1400 // function assertImageIsValid(img) {
1401 // if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
1402 // throwException('TYPE_MISMATCH_ERR');
1404 // if (img.readyState != 'complete') {
1405 // throwException('INVALID_STATE_ERR');
1409 function DOMException_(s) {
1410 this.code = this[s];
1411 this.message = s + ': DOM Exception ' + this.code;
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;
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;
1433 G_vmlCanvasManager = G_vmlCanvasManager_;
1434 CanvasRenderingContext2D = CanvasRenderingContext2D_;
1435 //CanvasGradient = CanvasGradient_;
1436 //CanvasPattern = CanvasPattern_;
1437 DOMException = DOMException_;
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.
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);
1454 * jQuery UI Widget @VERSION
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
1460 * http://docs.jquery.com/UI/Widget
1463 (function( $, undefined ) {
1465 var slice = Array.prototype.slice;
1467 var _cleanData = $.cleanData;
1468 $.cleanData = function( elems ) {
1469 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
1470 $( elem ).triggerHandler( "remove" );
1472 _cleanData( elems );
1475 $.widget = function( name, base, prototype ) {
1476 var namespace = name.split( "." )[ 0 ],
1478 name = name.split( "." )[ 1 ];
1479 fullName = namespace + "-" + name;
1486 // create selector for plugin
1487 $.expr[ ":" ][ fullName ] = function( elem ) {
1488 return !!$.data( elem, name );
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 );
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 );
1505 }, $[ namespace ][ name ] );
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
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 ) );
1518 var _superApply = function( method, args ) {
1519 return base.prototype[ method ].apply( this, args );
1522 var __super = this._super,
1523 __superApply = this._superApply,
1526 this._super = _super;
1527 this._superApply = _superApply;
1529 returnValue = value.apply( this, arguments );
1531 this._super = __super;
1532 this._superApply = __superApply;
1539 $[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
1540 namespace: namespace,
1542 widgetEventPrefix: name,
1543 widgetBaseClass: fullName
1546 $.widget.bridge( name, $[ namespace ][ name ] );
1549 $.widget.bridge = function( name, object ) {
1550 $.fn[ name ] = function( options ) {
1551 var isMethodCall = typeof options === "string",
1552 args = slice.call( arguments, 1 ),
1555 // allow multiple hashes to be passed on init
1556 options = !isMethodCall && args.length ?
1557 $.extend.apply( null, [ true, options ].concat(args) ) :
1560 if ( isMethodCall ) {
1561 this.each(function() {
1562 var instance = $.data( this, name );
1564 return $.error( "cannot call methods on " + name + " prior to initialization; " +
1565 "attempted to call method '" + options + "'" );
1567 if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
1568 return $.error( "no such method '" + options + "' for " + name + " widget instance" );
1570 var methodValue = instance[ options ].apply( instance, args );
1571 if ( methodValue !== instance && methodValue !== undefined ) {
1572 returnValue = methodValue;
1577 this.each(function() {
1578 var instance = $.data( this, name );
1580 instance.option( options || {} )._init();
1582 object( options, this );
1591 $.Widget = function( options, element ) {
1592 // allow instantiation without "new" keyword
1593 if ( !this._createWidget ) {
1594 return new $[ namespace ][ name ]( options, element );
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 );
1604 $.Widget.prototype = {
1605 widgetName: "widget",
1606 widgetEventPrefix: "",
1607 defaultElement: "<div>",
1611 _createWidget: function( options, element ) {
1612 element = $( element || this.defaultElement || this )[ 0 ];
1613 this.element = $( element );
1614 this.options = $.extend( true, {},
1616 this._getCreateOptions(),
1619 this.bindings = $();
1620 this.hoverable = $();
1621 this.focusable = $();
1623 if ( element !== this ) {
1624 $.data( element, this.widgetName, this );
1625 this._bind({ remove: "destroy" });
1629 this._trigger( "create" );
1632 _getCreateOptions: function() {
1633 return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
1638 destroy: function() {
1640 // we can probably remove the unbind calls in 2.0
1641 // all event bindings should go through this._bind()
1643 .unbind( "." + this.widgetName )
1644 .removeData( this.widgetName );
1646 .unbind( "." + this.widgetName )
1647 .removeAttr( "aria-disabled" )
1649 this.widgetBaseClass + "-disabled " +
1650 "ui-state-disabled" );
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" );
1659 widget: function() {
1660 return this.element;
1663 option: function( key, value ) {
1666 if ( arguments.length === 0 ) {
1667 // don't return a reference to the internal hash
1668 return $.extend( {}, this.options );
1671 if (typeof key === "string" ) {
1672 if ( value === undefined ) {
1673 return this.options[ key ];
1676 options[ key ] = value;
1679 this._setOptions( options );
1683 _setOptions: function( options ) {
1685 $.each( options, function( key, value ) {
1686 self._setOption( key, value );
1691 _setOption: function( key, value ) {
1692 this.options[ key ] = value;
1694 if ( key === "disabled" ) {
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" );
1705 enable: function() {
1706 return this._setOption( "disabled", false );
1708 disable: function() {
1709 return this._setOption( "disabled", true );
1712 _bind: function( element, handlers ) {
1713 // no element argument, shuffle and use this.element
1716 element = this.element;
1718 // accept selectors, DOM elements
1719 element = $( element );
1720 this.bindings = this.bindings.add( element );
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" ) ) {
1732 return ( typeof handler === "string" ? instance[ handler ] : handler )
1733 .apply( instance, arguments );
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" );
1744 mouseleave: function( event ) {
1745 $( event.currentTarget ).removeClass( "ui-state-hover" );
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" );
1756 focusout: function( event ) {
1757 $( event.currentTarget ).removeClass( "ui-state-focus" );
1762 _trigger: function( type, event, data ) {
1763 var callback = this.options[ type ],
1766 event = $.Event( event );
1767 event.type = ( type === this.widgetEventPrefix ?
1769 this.widgetEventPrefix + type ).toLowerCase();
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 ];
1782 this.element.trigger( event, data );
1784 args = $.isArray( data ) ?
1785 [ event ].concat( data ) :
1788 return !( $.isFunction( callback ) &&
1789 callback.apply( this.element[0], args ) === false ||
1790 event.isDefaultPrevented() );
1795 }(function ($, window, undefined) {
1796 var pos_oo = Number.POSITIVE_INFINITY,
1797 neg_oo = Number.NEGATIVE_INFINITY;
1801 // utility functions
1804 _allCoordinates: function (geom) {
1805 // return array of all positions in all geometries of geom
1807 var geometries = this._flatten(geom),
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]),
1821 coordinates = [coordinates];
1823 coordinates = [coordinates];
1825 coordinates = [coordinates];
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]);
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);
1849 var center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
1850 return !_ignoreGeo && $.geo.proj ? $.geo.proj.toGeodetic(center) : center;
1853 expandBy: function (bbox, dx, dy) {
1855 bbox = $.geo.proj.fromGeodetic(bbox);
1857 bbox = [bbox[0] - dx, bbox[1] - dy, bbox[2] + dx, bbox[3] + dy];
1858 return $.geo.proj ? $.geo.proj.toGeodetic(bbox) : bbox;
1861 height: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
1862 if (!_ignoreGeo && $.geo.proj) {
1863 bbox = $.geo.proj.fromGeodetic(bbox);
1865 return bbox[3] - bbox[1];
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];
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 ];
1882 reaspect: function (bbox, ratio, _ignoreGeo /* Internal Use Only */ ) {
1884 if (!_ignoreGeo && $.geo.proj) {
1885 bbox = $.geo.proj.fromGeodetic(bbox);
1887 var width = this.width(bbox, true),
1888 height = this.height(bbox, true),
1889 center = this.center(bbox, true),
1892 if (width != 0 && height != 0 && ratio > 0) {
1893 if (width / height > ratio) {
1901 bbox = [center[0] - dx, center[1] - dy, center[0] + dx, center[1] + dy];
1903 return $.geo.proj ? $.geo.proj.toGeodetic(bbox) : bbox;
1906 scaleBy: function ( bbox, scale, _ignoreGeo /* Internal Use Only */ ) {
1908 if (!_ignoreGeo && $.geo.proj) {
1909 bbox = $.geo.proj.fromGeodetic(bbox);
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;
1918 width: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
1919 if (!_ignoreGeo && $.geo.proj) {
1920 bbox = $.geo.proj.fromGeodetic(bbox);
1922 return bbox[2] - bbox[0];
1926 // geometry functions
1929 // bbox (Geometry.getEnvelope in JTS)
1931 bbox: function ( geom, _ignoreGeo /* Internal Use Only */ ) {
1934 } else if ( geom.bbox ) {
1935 result = !_ignoreGeo && $.geo.proj ? $.geo.proj.fromGeodetic( geom.bbox ) : geom.bbox;
1937 result = [ pos_oo, pos_oo, neg_oo, neg_oo ];
1939 var coordinates = this._allCoordinates( geom ),
1942 if ( coordinates.length == 0 ) {
1947 coordinates = $.geo.proj.fromGeodetic( coordinates );
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]);
1958 return $.geo.proj ? $.geo.proj.toGeodetic(result) : result;
1963 centroid: function( geom, _ignoreGeo /* Internal Use Only */ ) {
1964 switch (geom.type) {
1966 return $.extend({}, geom);
1972 coords = $.merge( [ ], geom.type == "Polygon" ? geom.coordinates[0] : geom.coordinates ),
1975 if ( !_ignoreGeo && $.geo.proj ) {
1976 coords = $.geo.proj.fromGeodetic(coords);
1979 //if (coords[0][0] != coords[coords.length - 1][0] || coords[0][1] != coords[coords.length - 1][1]) {
1980 // coords.push(coords[0]);
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]);
1987 c[0] += (coords[i - 1][0] + coords[j][0]) * n;
1988 c[1] += (coords[i - 1][1] + coords[j][1]) * n;
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 };
2005 return { type: "Point", coordinates: !_ignoreGeo && $.geo.proj ? $.geo.proj.toGeodetic(c) : c };
2012 contains: function (geom1, geom2) {
2013 if (geom1.type != "Polygon") {
2017 switch (geom2.type) {
2019 return this._containsPolygonPoint(geom1.coordinates, geom2.coordinates);
2022 return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates);
2025 return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates[0]);
2032 _containsPolygonPoint: function (polygonCoordinates, pointCoordinate) {
2033 if (polygonCoordinates.length == 0 || polygonCoordinates[0].length < 4) {
2038 a = polygonCoordinates[0][0],
2043 for (; i < polygonCoordinates[0].length; i++) {
2044 b = polygonCoordinates[0][i];
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]);
2049 if (x > pointCoordinate[0]) {
2057 return rayCross % 2 == 1;
2060 _containsPolygonLineString: function (polygonCoordinates, lineStringCoordinates) {
2061 for (var i = 0; i < lineStringCoordinates.length; i++) {
2062 if (!this._containsPolygonPoint(polygonCoordinates, lineStringCoordinates[i])) {
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;
2075 switch (geom1.type) {
2077 switch (geom2.type) {
2079 return this._distancePointPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
2081 return this._distanceLineStringPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
2083 return this._containsPolygonPoint(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
2090 switch (geom2.type) {
2092 return this._distanceLineStringPoint(geom1CoordinatesProjected, geom2CoordinatesProjected);
2094 return this._distanceLineStringLineString(geom1CoordinatesProjected, geom2CoordinatesProjected);
2096 return this._containsPolygonLineString(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
2103 switch (geom2.type) {
2105 return this._containsPolygonPoint(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
2107 return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
2109 return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected[0]) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected[0]);
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));
2123 _distanceLineStringPoint: function (lineStringCoordinates, pointCoordinate) {
2124 var minDist = pos_oo;
2126 if (lineStringCoordinates.length > 0) {
2127 var a = lineStringCoordinates[0],
2129 apx = pointCoordinate[0] - a[0],
2130 apy = pointCoordinate[1] - a[1];
2132 if (lineStringCoordinates.length == 1) {
2133 return Math.sqrt(apx * apx + apy * apy);
2135 for (var i = 1; i < lineStringCoordinates.length; i++) {
2136 var b = lineStringCoordinates[i],
2140 bpx = pointCoordinate[0] - b[0],
2141 bpy = pointCoordinate[1] - b[1],
2143 d = this._distanceSegmentPoint(abx, aby, apx, apy, bpx, bpy);
2160 return Math.sqrt(minDist);
2163 _distanceSegmentPoint: function (abx, aby, apx, apy, bpx, bpy) {
2164 var dot1 = abx * apx + aby * apy;
2167 return apx * apx + apy * apy;
2170 var dot2 = abx * abx + aby * aby;
2173 return bpx * bpx + bpy * bpy;
2176 return apx * apx + apy * apy - dot1 * dot1 / dot2;
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]));
2191 _flatten: function (geom) {
2192 // return an array of all basic geometries
2194 var geometries = [],
2196 switch (geom.type) {
2198 $.merge(geometries, this._flatten(geom.geometry));
2201 case "FeatureCollection":
2202 for (; curGeom < geom.features.length; curGeom++) {
2203 $.merge(geometries, this._flatten(geom.features[curGeom].geometry));
2207 case "GeometryCollection":
2208 for (; curGeom < geom.geometries.length; curGeom++) {
2209 $.merge(geometries, this._flatten(geom.geometries[curGeom]));
2214 geometries[0] = geom;
2224 _WKT: (function () {
2225 function pointToString(value) {
2226 return "POINT " + pointToUntaggedString(value.coordinates);
2229 function pointToUntaggedString(coordinates) {
2230 if (!(coordinates && coordinates.length)) {
2233 return "(" + coordinates.join(" ") + ")";
2237 function lineStringToString(value) {
2238 return "LINESTRING " + lineStringToUntaggedString(value.coordinates);
2241 function lineStringToUntaggedString(coordinates) {
2242 if (!(coordinates && coordinates.length)) {
2247 for (var i = 0; i < coordinates.length; i++) {
2248 points.push(coordinates[i].join(" "));
2251 return "(" + points + ")";
2255 function polygonToString(value) {
2256 return "POLYGON " + polygonToUntaggedString(value.coordinates);
2259 function polygonToUntaggedString(coordinates) {
2260 if (!(coordinates && coordinates.length)) {
2263 var lineStrings = [];
2265 for (var i = 0; i < coordinates.length; i++) {
2266 lineStrings.push(lineStringToUntaggedString(coordinates[i]));
2269 return "(" + lineStrings + ")";
2273 function multiPointToString(value) {
2274 return "MULTIPOINT " + lineStringToUntaggedString(value.coordinates);
2277 function multiLineStringToString(value) {
2278 return "MULTILINSTRING " + polygonToUntaggedString(value.coordinates);
2281 function multiPolygonToString(value) {
2282 return "MULTIPOLYGON " + multiPolygonToUntaggedString(value.coordinates);
2285 function multiPolygonToUntaggedString(coordinates) {
2286 if (!(coordinates && coordinates.length)) {
2290 for (var i = 0; i < coordinates.length; i++) {
2291 polygons.push(polygonToUntaggedString(coordinates[i]));
2293 return "(" + polygons + ")";
2297 function geometryCollectionToString(value) {
2298 return "GEOMETRYCOLLECTION " + geometryCollectionToUntaggedString(value.geometries);
2301 function geometryCollectionToUntaggedString(geometries) {
2302 if (!(geometries && geometries.length)) {
2305 var geometryText = [];
2306 for (var i = 0; i < geometries.length; i++) {
2307 geometryText.push(stringify(geometries[i]));
2309 return "(" + geometries + ")";
2313 function stringify(value) {
2314 if (!(value && value.type)) {
2317 switch (value.type) {
2319 return pointToString(value);
2322 return lineStringToString(value);
2325 return polygonToString(value);
2328 return multiPointToString(value);
2330 case "MultiLineString":
2331 return multiLineStringToString(value);
2333 case "MultiPolygon":
2334 return multiPolygonToString(value);
2336 case "GeometryCollection":
2337 return geometryCollectionToString(value);
2345 function pointParseUntagged(wkt) {
2346 var pointString = wkt.match(/\(\s*([\d\.-]+)\s+([\d\.-]+)\s*\)/);
2347 return pointString && pointString.length >= 2 ? {
2350 parseFloat(pointString[1]),
2351 parseFloat(pointString[2])
2356 function parse(wkt) {
2359 var typeIndex = wkt.indexOf(" ");
2361 switch (wkt.substr(0, typeIndex).toUpperCase()) {
2363 return pointParseUntagged(wkt.substr(typeIndex + 1));
2368 stringify: stringify,
2375 // projection functions
2378 proj: (function () {
2379 var halfPi = 1.5707963267948966192,
2380 quarterPi = 0.7853981633974483096,
2381 radiansPerDegree = 0.0174532925199432958,
2382 degreesPerRadian = 57.295779513082320877,
2383 semiMajorAxis = 6378137;
2386 fromGeodeticPos: function (coordinate) {
2391 semiMajorAxis * coordinate[ 0 ] * radiansPerDegree,
2392 semiMajorAxis * Math.log(Math.tan(quarterPi + coordinate[ 1 ] * radiansPerDegree / 2))
2396 fromGeodetic: function (coordinates) {
2397 var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
2398 fromGeodeticPos = this.fromGeodeticPos;
2400 if (!isMultiPointOrLineString && coordinates.length == 4) {
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 ] ];
2407 var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
2408 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
2412 if (!isMultiPolygon) {
2413 if (!isMultiLineStringOrPolygon) {
2414 if (!isMultiPointOrLineString) {
2415 coordinates = [ coordinates ];
2417 coordinates = [ coordinates ];
2419 coordinates = [ coordinates ];
2422 for ( i = 0; i < coordinates.length; 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 ]);
2432 return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
2436 toGeodeticPos: function (coordinate) {
2438 (coordinate[ 0 ] / semiMajorAxis) * degreesPerRadian,
2439 (halfPi - 2 * Math.atan(1 / Math.exp(coordinate[ 1 ] / semiMajorAxis))) * degreesPerRadian
2443 toGeodetic: function (coordinates) {
2444 var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
2445 toGeodeticPos = this.toGeodeticPos;
2447 if (!isMultiPointOrLineString && coordinates.length == 4) {
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 ] ];
2454 var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
2455 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
2458 if (!isMultiPolygon) {
2459 if (!isMultiLineStringOrPolygon) {
2460 if (!isMultiPointOrLineString) {
2461 coordinates = [ coordinates ];
2463 coordinates = [ coordinates ];
2465 coordinates = [ coordinates ];
2468 for ( i = 0; i < coordinates.length; 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 ]);
2478 return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
2485 // service types (defined in other files)
2491 (function ($, undefined) {
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;
2499 $.widget("geo.geographics", {
2507 _$canvas: undefined,
2508 _context: undefined,
2512 borderRadius: "8px",
2518 //stroke: undefined,
2521 visibility: "visible",
2526 _create: function () {
2527 this._$elem = this.element;
2528 this._options = this.options;
2530 this._$elem.css({ display: "inline-block", overflow: "hidden", textAlign: "left" });
2532 if (this._$elem.css("position") == "static") {
2533 this._$elem.css("position", "relative");
2536 this._width = this._$elem.width();
2537 this._height = this._$elem.height();
2539 if (!(this._width && this._height)) {
2540 this._width = parseInt(this._$elem.css("width"));
2541 this._height = parseInt(this._$elem.css("height"));
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');
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 });
2559 _setOption: function (key, value) {
2560 if (key == "style") {
2561 value = $.extend({}, this._options.style, value);
2563 $.Widget.prototype._setOption.apply(this, arguments);
2566 destroy: function () {
2567 $.Widget.prototype.destroy.apply(this, arguments);
2568 this._$elem.html("");
2571 clear: function () {
2572 this._context.clearRect(0, 0, this._width, this._height);
2575 drawArc: function (coordinates, startAngle, sweepAngle, style) {
2576 style = this._getGraphicStyle(style);
2578 if (style.visibility != "hidden" && style.opacity > 0 && style.widthValue > 0 && style.heightValue > 0) {
2579 var r = Math.min(style.widthValue, style.heightValue) / 2;
2581 startAngle = (startAngle * Math.PI / 180);
2582 sweepAngle = (sweepAngle * Math.PI / 180);
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);
2589 this._context.scale(1, style.heightValue / style.widthValue);
2592 this._context.beginPath();
2593 this._context.arc(0, 0, r, startAngle, sweepAngle, false);
2595 if (this._trueCanvas) {
2596 this._context.restore();
2600 this._context.fillStyle = style.fill;
2601 this._context.globalAlpha = style.opacity * style.fillOpacity;
2602 this._context.fill();
2605 if (style.doStroke) {
2606 this._context.lineJoin = "round";
2607 this._context.lineWidth = style.strokeWidthValue;
2608 this._context.strokeStyle = style.stroke;
2610 this._context.globalAlpha = style.opacity * style.strokeOpacity;
2611 this._context.stroke();
2614 if (!this._trueCanvas) {
2615 this._context.restore();
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();
2641 this._context.fillStyle = style.fill;
2642 this._context.globalAlpha = style.opacity * style.fillOpacity;
2643 this._context.fill();
2646 if (style.doStroke) {
2647 this._context.lineJoin = "round";
2648 this._context.lineWidth = style.strokeWidthValue;
2649 this._context.strokeStyle = style.stroke;
2651 this._context.globalAlpha = style.opacity * style.strokeOpacity;
2653 this._context.stroke();
2658 drawLineString: function (coordinates, style) {
2659 this._drawLines([coordinates], false, style);
2662 drawPolygon: function (coordinates, style) {
2663 this._drawLines(coordinates, true, style);
2666 drawBbox: function (bbox, style) {
2676 _getGraphicStyle: function (style) {
2677 function safeParse(value) {
2678 value = parseInt(value);
2679 return (+value + '') === value ? +value : value;
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);
2694 _drawLines: function (coordinates, close, style) {
2695 if (!coordinates || !coordinates.length || coordinates[0].length < 2) {
2699 var style = this._getGraphicStyle(style),
2702 if (style.visibility != "hidden" && style.opacity > 0) {
2703 this._context.beginPath();
2704 this._context.moveTo(coordinates[0][0][0], coordinates[0][0][1]);
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]);
2713 this._context.closePath();
2716 if (close && style.doFill) {
2717 this._context.fillStyle = style.fill;
2718 this._context.globalAlpha = style.opacity * style.fillOpacity;
2719 this._context.fill();
2722 if (style.doStroke) {
2723 this._context.lineCap = this._context.lineJoin = "round";
2724 this._context.lineWidth = style.strokeWidthValue;
2725 this._context.strokeStyle = style.stroke;
2727 this._context.globalAlpha = style.opacity * style.strokeOpacity;
2728 this._context.stroke();
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;
2745 bbox: [-180, -85, 180, 85],
2746 bboxMax: [-180, -85, 180, 85],
2751 drawPoint: "crosshair",
2752 drawLineString: "crosshair",
2753 drawPolygon: "crosshair"
2762 getUrl: function (view) {
2763 return "http://tile.openstreetmap.org/" + view.zoom + "/" + view.tile.column + "/" + view.tile.row + ".png";
2765 attr: "© OpenStreetMap & contributors, CC-BY-SA"
2772 basePixelSize: 156543.03392799936,
2773 origin: [-20037508.342787, 20037508.342787]
2779 $.widget("geo.geomap", {
2780 // private widget members
2786 _$contentFrame: undefined,
2787 _$existingChildren: undefined,
2788 _$servicesContainer: undefined,
2789 _$drawContainer: undefined,
2790 _$shapesContainer: undefined,
2791 _$textContainer: undefined,
2792 _$textContent: undefined,
2793 _$eventTarget: undefined,
2797 _currentServices: [], //< internal copy
2800 _pixelSize: undefined,
2801 _centerMax: undefined,
2802 _pixelSizeMax: undefined,
2804 _wheelZoomFactor: 1.18920711500273,
2805 _wheelTimeout: null,
2810 _mouseDown: undefined,
2812 _toolPan: undefined,
2813 _shiftZoom: undefined,
2815 _current: undefined,
2816 _downDate: undefined,
2817 _moveDate: undefined,
2818 _clickDate: undefined,
2819 _lastMove: undefined,
2820 _lastDrag: undefined,
2822 _windowHandler: null,
2823 _resizeTimeout: null,
2825 _panning: undefined,
2826 _velocity: undefined,
2827 _friction: undefined,
2829 _supportTouch: undefined,
2830 _softDblClick: undefined,
2832 _isDbltap: undefined,
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
2838 _graphicShapes: [], //< an array of objects containing style object refs & GeoJSON object refs
2844 options: $.extend({}, _defaultOptions),
2846 _createWidget: function (options, element) {
2847 this._$elem = $(element);
2849 if (this._$elem.is("[data-geo-service]")) {
2850 $.Widget.prototype._createWidget.apply(this, arguments);
2854 this._$elem.attr("data-geo-map", "data-geo-map");
2856 this._graphicShapes = [];
2858 this._initOptions = options || {};
2860 this._forcePosition(this._$elem);
2862 this._$elem.css("text-align", "left");
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"]
2872 this._createChildren();
2874 this._center = this._centerMax = [0, 0];
2876 this.options["pixelSize"] = this._pixelSize = this._pixelSizeMax = 156543.03392799936;
2884 this._isDbltap = false;
2890 this._velocity = [0, 0];
2892 this._friction = [.8, .8];
2896 this._clickDate = 0;
2898 $.Widget.prototype._createWidget.apply(this, arguments);
2901 _create: function () {
2902 if (this._$elem.is("[data-geo-service]")) {
2906 this._options = this.options;
2908 this._supportTouch = "ontouchend" in document;
2909 this._softDblClick = this._supportTouch || _ieVersion == 7;
2911 var touchStartEvent = this._supportTouch ? "touchstart" : "mousedown",
2912 touchStopEvent = this._supportTouch ? "touchend touchcancel" : "mouseup",
2913 touchMoveEvent = this._supportTouch ? "touchmove" : "mousemove";
2915 $(document).keydown($.proxy(this._document_keydown, this));
2917 this._$eventTarget.dblclick($.proxy(this._eventTarget_dblclick, this));
2919 this._$eventTarget.bind(touchStartEvent, $.proxy(this._eventTarget_touchstart, this));
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));
2925 this._$eventTarget.mousewheel($.proxy(this._eventTarget_mousewheel, this));
2928 this._windowHandler = function () {
2929 if (geomap._resizeTimeout) {
2930 clearTimeout(geomap._resizeTimeout);
2932 this._resizeTimeout = setTimeout(function () {
2933 if (geomap._created) {
2934 geomap._$elem.geomap("resize");
2939 $(window).resize(this._windowHandler);
2941 this._$drawContainer.geographics({ style: this._initOptions.drawStyle || {} });
2942 this._options["drawStyle"] = this._$drawContainer.geographics("option", "style");
2944 this._$shapesContainer.geographics( { style: this._initOptions.shapeStyle || { } } );
2945 this._options["shapeStyle"] = this._$shapesContainer.geographics("option", "style");
2947 if (this._initOptions) {
2948 if (this._initOptions.bbox) {
2949 this._setOption("bbox", this._initOptions.bbox, false);
2951 if (this._initOptions.center) {
2952 this._setOption("center", this._initOptions.center, false);
2954 if (this._initOptions.zoom) {
2955 this._setZoom(this._initOptions.zoom, false, false);
2959 this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
2961 this._createServices();
2965 this._created = true;
2968 _setOption: function (key, value, refresh) {
2969 if ( this._$elem.is( "[data-geo-service]" ) || key == "pixelSize" ) {
2973 refresh = (refresh === undefined || refresh);
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]];
2982 this._setBbox(value, false, refresh);
2983 value = this._getBbox();
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]];
2992 this._setCenterAndSize($.geo.proj ? $.geo.proj.fromGeodetic([[value[0], value[1]]])[0] : value, this._pixelSize, false, refresh);
2996 if (this._$drawContainer) {
2997 this._$drawContainer.geographics("option", "style", value);
2998 value = this._$drawContainer.geographics("option", "style");
3003 if (this._$shapesContainer) {
3004 this._$shapesContainer.geographics("option", "style", value);
3005 value = this._$shapesContainer.geographics("option", "style");
3010 this._$drawContainer.geographics("clear");
3011 this._$eventTarget.css("cursor", this._options["cursors"][value]);
3015 this._setZoom(value, false, refresh);
3019 $.Widget.prototype._setOption.apply(this, arguments);
3023 this._createServices();
3031 this._$shapesContainer.geographics("clear");
3032 this._refreshShapes( this._$shapesContainer, this._graphicShapes, this._graphicShapes );
3038 destroy: function () {
3039 if (this._$elem.is("[data-geo-map]")) {
3040 this._created = false;
3042 $(window).unbind("resize", this._windowHandler);
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]);
3049 this._$shapesContainer.geographics("destroy");
3050 this._$drawContainer.geographics("destroy");
3052 this._$existingChildren.detach();
3053 this._$elem.html("");
3054 this._$elem.append(this._$existingChildren);
3055 this._$elem.removeAttr("data-geo-map");
3057 $.Widget.prototype.destroy.apply(this, arguments);
3060 toMap: function (p) {
3062 return $.geo.proj ? $.geo.proj.toGeodetic(p) : p;
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);
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);
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);
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);
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);
3097 this._options["services"][i].visibility = service.visibility = ( value ? "visible" : "hidden" );
3098 $.geo["_serviceTypes"][service.type].toggle(this, service);
3101 $.geo["_serviceTypes"][service.type].refresh(this, service);
3108 zoom: function (numberOfLevels) {
3109 if (numberOfLevels != null) {
3110 this._setZoom(this._options["zoom"] + numberOfLevels, false, true);
3114 refresh: function () {
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,
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"]
3131 this._$contentFrame.css({
3132 width: size["width"],
3133 height: size["height"]
3136 this._$servicesContainer.css({
3137 width: size["width"],
3138 height: size["height"]
3141 this._$eventTarget.css({
3142 width: size["width"],
3143 height: size["height"]
3146 var shapeStyle = this._$shapesContainer.geographics("option", "style");
3148 this._$shapesContainer.geographics("destroy");
3149 this._$drawContainer.geographics("destroy");
3151 for (i = 0; i < this._currentServices.length; i++) {
3152 $.geo["_serviceTypes"][this._currentServices[i].type].resize(this, this._currentServices[i]);
3156 this._$drawContainer.css({
3160 this._$drawContainer.geographics();
3162 this._$shapesContainer.css({
3166 this._$shapesContainer.geographics( { style: shapeStyle } );
3168 for (i = 0; i < this._drawPixels.length; i++) {
3169 this._drawPixels[i][0] += dx;
3170 this._drawPixels[i][1] += dy;
3173 this._setCenterAndSize(this._center, this._pixelSize, false, true);
3176 append: function ( shape, style, refresh ) {
3179 if ( shape.type == "FeatureCollection" ) {
3180 shapes = shape.features;
3182 shapes = $.isArray( shape ) ? shape : [ shape ];
3185 if ( typeof style === "boolean" ) {
3190 for ( ; i < shapes.length; i++ ) {
3191 if ( shapes[ i ].type != "Point" ) {
3192 var bbox = $.geo.bbox( shapes[ i ] );
3194 bbox = $.geo.proj.fromGeodetic( bbox );
3196 $.data( shapes[ i ], "geoBbox", bbox );
3199 this._graphicShapes.push( {
3205 if ( refresh === undefined || refresh ) {
3211 empty: function ( refresh ) {
3212 $.each( this._graphicShapes, function( ) {
3213 $.removeData( this, "geoBbox" );
3215 this._graphicShapes = [];
3216 if ( refresh === undefined || refresh ) {
3221 find: function (point, pixelTolerance) {
3222 var searchPixel = this.toPixel( point.coordinates ),
3223 mapTol = this._pixelSize * pixelTolerance,
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 );
3233 var bbox = $.data( this.shape, "geoBbox" ),
3246 coordinates: $.geo.proj ? $.geo.proj.fromGeodetic( point.coordinates ) : point.coordinates
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 );
3264 remove: function ( shape, refresh ) {
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);
3276 if ( refresh === undefined || refresh ) {
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];
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);
3294 if (this._options["tilingScheme"]) {
3295 var zoom = this._getTiledZoom(pixelSize);
3296 pixelSize = this._getTiledPixelSize(zoom);
3298 this._setCenterAndSize(center, pixelSize, trigger, refresh);
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];
3308 _getCenter: function () {
3309 return this._center;
3312 _getContentBounds: function () {
3313 return this._contentBounds;
3316 _getServicesContainer: function () {
3317 return this._$servicesContainer;
3320 _getZoom: function () {
3321 // calculate the internal zoom level, vs. public zoom property
3322 if (this._options["tilingScheme"]) {
3323 return this._getTiledZoom(this._pixelSize);
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);
3329 return Math.log($.geo.width(bboxMax, true) / $.geo.width(bbox, true)) / Math.log(this._zoomFactor);
3333 _setZoom: function (value, trigger, refresh) {
3334 value = Math.max(value, 0);
3336 if (this._options["tilingScheme"]) {
3337 this._setCenterAndSize(this._center, this._getTiledPixelSize(value), trigger, refresh);
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);
3345 _createChildren: function () {
3346 this._$existingChildren = this._$elem.children().detach();
3348 this._forcePosition(this._$existingChildren);
3350 this._$existingChildren.css("-moz-user-select", "none");
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');
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');
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');
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');
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();
3368 this._$contentFrame.append(this._$existingChildren);
3371 _createServices: function () {
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]);
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();
3386 _refreshDrawing: function () {
3387 this._$drawContainer.geographics("clear");
3389 if ( this._drawPixels.length > 0 ) {
3390 var mode = this._options[ "mode" ],
3391 coords = this._drawPixels;
3393 if ( mode == "drawPolygon" ) {
3394 coords = [ coords ];
3397 this._$drawContainer.geographics( mode, coords );
3401 _resetDrawing: function () {
3402 //this._$textContainer.hide();
3403 this._drawPixels = [];
3404 this._drawCoords = [];
3405 this._$drawContainer.geographics("clear");
3408 _refreshShapes: function (geographics, shapes, styles, center, pixelSize) {
3415 bbox = this._getBbox(center, pixelSize),
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");
3423 if ( shapeBbox && $.geo._bboxDisjoint( bbox, shapeBbox ) ) {
3427 style = $.isArray(styles) ? styles[i].style : styles;
3429 switch (shape.type) {
3431 this._$shapesContainer.geographics("drawPoint", this.toPixel(shape.coordinates, center, pixelSize), style);
3434 this._$shapesContainer.geographics("drawLineString", this.toPixel(shape.coordinates, center, pixelSize), style);
3437 pixelPositions = [];
3438 $.each(shape.coordinates, function (i) {
3439 pixelPositions[i] = geomap.toPixel(this, center, pixelSize);
3441 this._$shapesContainer.geographics("drawPolygon", pixelPositions, style);
3444 for (mgi = 0; mgi < shape.coordinates; mgi++) {
3445 this._$shapesContainer.geographics("drawPoint", this.toPixel(shape.coordinates[mgi], center, pixelSize), style);
3448 case "MultiLineString":
3449 for (mgi = 0; mgi < shape.coordinates; mgi++) {
3450 this._$shapesContainer.geographics("drawLineString", this.toPixel(shape.coordinates[mgi], center, pixelSize), style);
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);
3459 this._$shapesContainer.geographics("drawPolygon", pixelPositions, style);
3463 case "GeometryCollection":
3464 geomap._refreshShapes(geographics, shape.geometries, style, center, pixelSize);
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;
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")) };
3481 sizeContainer = sizeContainer.parent();
3486 _forcePosition: function (elem) {
3487 var cssPosition = elem.css("position");
3488 if (cssPosition != "relative" && cssPosition != "absolute" && cssPosition != "fixed") {
3489 elem.css("position", "relative");
3493 _getTiledPixelSize: function (zoom) {
3494 var tilingScheme = this._options["tilingScheme"];
3495 if (tilingScheme != null) {
3497 return tilingScheme.pixelSizes != null ? tilingScheme.pixelSizes[0] : tilingScheme.basePixelSize;
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);
3505 if (tilingScheme.pixelSizes != null) {
3506 return tilingScheme.pixelSizes[zoom];
3508 return tilingScheme.basePixelSize / Math.pow(2, zoom);
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) {
3527 return Math.max(Math.round(Math.log(tilingScheme.basePixelSize / pixelSize) / Math.log(2)), 0);
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);
3537 scale = Math.pow(zoomFactor, -zoomDelta);
3538 pixelSize = this._pixelSize * scale;
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]];
3547 return { pixelSize: pixelSize, center: scaleCenter };
3550 _mouseWheelFinish: function () {
3551 this._wheelTimeout = null;
3553 if (this._wheelLevel != 0) {
3554 var wheelCenterAndSize = this._getZoomCenterAndSize(this._anchor, this._wheelLevel, this._wheelZoomFactor);
3556 this._setCenterAndSize(wheelCenterAndSize.center, wheelCenterAndSize.pixelSize, true, true);
3558 this._wheelLevel = 0;
3564 _panEnd: function () {
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]))
3570 if (Math.abs(this._velocity[0]) < 4 && Math.abs(this._velocity[1]) < 4) {
3571 this._panFinalize();
3574 this._current[0] + this._velocity[0],
3575 this._current[1] + this._velocity[1]
3579 setTimeout($.proxy(this._panEnd, this), 30);
3583 _panFinalize: function () {
3584 if (this._panning) {
3585 this._velocity = [0, 0];
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;
3592 this._$shapesContainer.css({ left: 0, top: 0 });
3594 this._setCenterAndSize([this._center[0] + dxMap, this._center[1] + dyMap], this._pixelSize, true, true);
3597 this._anchor = this._current;
3598 this._toolPan = this._panning = false;
3600 this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
3604 _panMove: function () {
3605 var dx = this._current[0] - this._lastDrag[0],
3606 dy = this._current[1] - this._lastDrag[1],
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"]);
3616 if (this._mouseDown) {
3617 this._velocity = [dx, dy];
3620 if (dx != 0 || dy != 0) {
3621 this._panning = true;
3622 this._lastDrag = this._current;
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);
3629 this._$shapesContainer.css({
3630 left: function (index, value) {
3631 return parseInt(value) + dx;
3633 top: function (index, value) {
3634 return parseInt(value) + dy;
3638 for (i = 0; i < this._drawPixels.length; i++) {
3639 this._drawPixels[i][0] += dx;
3640 this._drawPixels[i][1] += dy;
3643 this._refreshDrawing();
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);
3656 if (this._$shapesContainer) {
3657 this._$shapesContainer.geographics("clear");
3658 if (this._graphicShapes.length > 0) {
3659 this._refreshShapes(this._$shapesContainer, this._graphicShapes, this._graphicShapes);
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);
3674 this._center = center;
3675 this.options["pixelSize"] = this._pixelSize = pixelSize;
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;
3683 this._options["center"] = $.geo.proj.toGeodetic([[this._center[0], this._center[1]]])[0];
3685 this._options["bbox"] = this._getBbox();
3687 this._options["center"] = this._center;
3690 this._options["zoom"] = this._getZoom();
3692 if (this._drawCoords.length > 0) {
3693 this._drawPixels = this._toPixel(this._drawCoords);
3697 this._trigger("bboxchange", window.event, { bbox: this._options["bbox"] });
3702 this._refreshDrawing();
3706 _toMap: function (p, center, pixelSize) {
3707 // ignores $.geo.proj
3708 var isArray = $.isArray(p[0]);
3713 center = center || this._center;
3714 pixelSize = pixelSize || this._pixelSize;
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,
3725 $.each(p, function (i) {
3726 var yOffset = (this[1] * yRatio);
3727 result[i] = [bbox[0] + (this[0] * xRatio), bbox[3] - yOffset];
3730 return isArray ? result : result[0];
3733 _toPixel: function (p, center, pixelSize) {
3734 // ignores $.geo.proj
3735 var isArray = $.isArray(p[0]);
3740 center = center || this._center;
3741 pixelSize = pixelSize || this._pixelSize;
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),
3753 $.each(p, function (i) {
3755 Math.round((this[0] - bbox[0]) * width / bboxWidth),
3756 Math.round((bbox[3] - this[1]) * height / bboxHeight)
3760 return isArray ? result : result[0];
3763 _zoomTo: function (coord, zoom, trigger, refresh) {
3764 zoom = zoom < 0 ? 0 : zoom;
3766 var tiledPixelSize = this._getTiledPixelSize(zoom);
3768 if (!isNaN(tiledPixelSize)) {
3769 this._setCenterAndSize(coord, tiledPixelSize, trigger, refresh);
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"]);
3774 this._setCenterAndSize(coord, pixelSize, trigger, refresh);
3778 _document_keydown: function (e) {
3779 var len = this._drawCoords.length;
3780 if (len > 0 && e.which == 27) {
3782 this._resetDrawing();
3785 this._drawCoords[len - 2] = $.merge( [], this._drawCoords[ len - 1 ] );
3786 this._drawPixels[len - 2] = $.merge( [], this._drawPixels[ len - 1 ] );
3788 this._drawCoords.length--;
3789 this._drawPixels.length--;
3791 this._refreshDrawing();
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);
3804 _eventTarget_dblclick: function (e) {
3805 this._panFinalize();
3807 if (this._drawTimeout) {
3808 window.clearTimeout(this._drawTimeout);
3809 this._drawTimeout = null;
3812 var offset = $(e.currentTarget).offset();
3814 switch (this._options["mode"]) {
3817 this._eventTarget_dblclick_zoom(e);
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, {
3826 coordinates: $.geo.proj ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords
3829 this._eventTarget_dblclick_zoom(e);
3831 this._resetDrawing();
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;
3839 this._drawCoords[endIndex] = $.merge( [], this._drawCoords[0] );
3840 this._trigger( "shape", e, {
3842 coordinates: [ $.geo.proj ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords ]
3846 this._eventTarget_dblclick_zoom(e);
3848 this._resetDrawing();
3855 _eventTarget_touchstart: function (e) {
3856 if (!this._supportTouch && e.which != 1) {
3860 this._panFinalize();
3861 this._mouseWheelFinish();
3863 var offset = $(e.currentTarget).offset();
3865 if (this._supportTouch) {
3866 this._current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
3868 this._current = [e.pageX - offset.left, e.pageY - offset.top];
3871 if (this._softDblClick) {
3872 var downDate = $.now();
3873 if (downDate - this._downDate < 750) {
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;
3881 this._current = this._anchor;
3885 if (this._isDbltap) {
3886 this._isDbltap = false;
3888 this._isDbltap = this._isTap;
3891 this._isDbltap = false;
3894 this._downDate = downDate;
3898 this._mouseDown = true;
3899 this._anchor = this._current;
3901 if (!this._inOp && e.shiftKey) {
3902 this._shiftZoom = true;
3903 this._$eventTarget.css("cursor", this._options["cursors"]["zoom"]);
3907 switch (this._options["mode"]) {
3910 case "drawLineString":
3912 this._lastDrag = this._current;
3914 if (e.currentTarget.setCapture) {
3915 e.currentTarget.setCapture();
3928 _dragTarget_touchmove: function (e) {
3929 var offset = this._$eventTarget.offset(),
3930 drawCoordsLen = this._drawCoords.length,
3933 if (this._supportTouch) {
3934 current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
3936 current = [e.pageX - offset.left, e.pageY - offset.top];
3939 if (current[0] === this._lastMove[0] && current[1] === this._lastMove[1]) {
3946 if (this._softDblClick) {
3947 this._isDbltap = this._isTap = false;
3950 if (this._mouseDown) {
3951 this._current = current;
3952 this._moveDate = $.now();
3955 var mode = this._shiftZoom ? "zoom" : this._options["mode"];
3959 if ( this._mouseDown ) {
3960 this._$drawContainer.geographics( "clear" );
3961 this._$drawContainer.geographics( "drawBbox", [
3968 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
3974 if (this._mouseDown || this._toolPan) {
3977 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
3981 case "drawLineString":
3983 if (this._mouseDown || this._toolPan) {
3986 if (drawCoordsLen > 0) {
3987 this._drawCoords[drawCoordsLen - 1] = this._toMap(current);
3988 this._drawPixels[drawCoordsLen - 1] = current;
3990 this._refreshDrawing();
3993 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
3998 this._lastMove = current;
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);
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,
4020 if (this._supportTouch) {
4021 current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
4023 current = [e.pageX - offset.left, e.pageY - offset.top];
4026 dx = current[0] - this._anchor[0];
4027 dy = current[1] - this._anchor[1];
4029 this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
4031 this._shiftZoom = this._mouseDown = this._toolPan = false;
4033 if (document.releaseCapture) {
4034 document.releaseCapture();
4038 clickDate = $.now();
4039 this._current = current;
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 ] )
4049 Math.max( this._anchor[ 0 ], current[ 0 ] ),
4050 Math.min( this._anchor[ 1 ], current[ 1 ] )
4060 if ( ( bbox[2] - bbox[0] ) < minSize && ( bbox[3] - bbox[1] ) < minSize ) {
4061 bbox = $.geo.scaleBy( this._getBbox( $.geo.center( bbox, true ) ), .5, true );
4064 this._setBbox(bbox, true, true);
4067 this._resetDrawing();
4074 if (clickDate - this._clickDate > 100) {
4075 this._trigger("click", e, { type: "Point", coordinates: this.toMap(current) });
4082 if (this._drawTimeout) {
4083 window.clearTimeout(this._drawTimeout);
4084 this._drawTimeout = null;
4088 this._panFinalize();
4090 if (clickDate - this._clickDate > 100) {
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;
4103 case "drawLineString":
4106 this._panFinalize();
4108 i = (this._drawCoords.length == 0 ? 0 : this._drawCoords.length - 1);
4110 this._drawCoords[i] = this._toMap(current);
4111 this._drawPixels[i] = current;
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;
4119 this._refreshDrawing();
4124 this._clickDate = clickDate;
4126 if (this._softDblClick && this._isDbltap) {
4127 this._isDbltap = this._isTap = false;
4128 this._$eventTarget.trigger("dblclick", e);
4138 _eventTarget_mousewheel: function (e, delta) {
4141 this._panFinalize();
4143 if (this._mouseDown) {
4148 if (this._wheelTimeout) {
4149 window.clearTimeout(this._wheelTimeout);
4150 this._wheelTimeout = null;
4152 var offset = $(e.currentTarget).offset();
4153 this._anchor = [e.pageX - offset.left, e.pageY - offset.top];
4156 this._wheelLevel += delta;
4158 var wheelCenterAndSize = this._getZoomCenterAndSize(this._anchor, this._wheelLevel, this._wheelZoomFactor);
4160 this._$shapesContainer.geographics("clear");
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);
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);
4172 if (this._drawCoords.length > 0) {
4173 this._drawPixels = this._toPixel(this._drawCoords, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
4174 this._refreshDrawing();
4178 this._wheelTimeout = window.setTimeout(function () {
4179 geomap._mouseWheelFinish();
4189 (function ($, undefined) {
4190 $.geo._serviceTypes.tiled = (function () {
4192 create: function (map, servicesContainer, service, index) {
4193 var serviceState = $.data(service, "geoServiceState");
4195 if ( !serviceState ) {
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>';
4205 servicesContainer.append(scHtml);
4207 serviceState.serviceContainer = servicesContainer.children(":last");
4208 $.data(service, "geoServiceState", serviceState);
4211 return serviceState.serviceContainer;
4214 destroy: function (map, servicesContainer, service) {
4215 var serviceState = $.data(service, "geoServiceState");
4217 serviceState.serviceContainer.remove();
4219 $.removeData(service, "geoServiceState");
4222 interactivePan: function ( map, service, dx, dy ) {
4223 var serviceState = $.data( service, "geoServiceState" );
4225 if ( serviceState ) {
4226 this._cancelUnloaded( map, service );
4228 serviceState.serviceContainer.children( ).css( {
4229 left: function ( index, value ) {
4230 return parseInt( value ) + dx;
4232 top: function ( index, value ) {
4233 return parseInt( value ) + dy;
4237 if ( service && ( service.visibility === undefined || service.visibility === "visible" ) ) {
4238 var pixelSize = map._pixelSize,
4240 serviceContainer = serviceState.serviceContainer,
4241 scaleContainer = serviceContainer.children("[data-pixelSize='" + pixelSize + "']"),
4243 /* same as refresh 1 */
4244 contentBounds = map._getContentBounds(),
4245 mapWidth = contentBounds["width"],
4246 mapHeight = contentBounds["height"],
4248 tilingScheme = map.options["tilingScheme"],
4249 tileWidth = tilingScheme.tileWidth,
4250 tileHeight = tilingScheme.tileHeight,
4251 /* end same as refresh 1 */
4253 halfWidth = mapWidth / 2 * pixelSize,
4254 halfHeight = mapHeight / 2 * pixelSize,
4256 currentPosition = scaleContainer.position(),
4257 scaleOriginParts = scaleContainer.data("scaleOrigin").split(","),
4258 totalDx = parseInt(scaleOriginParts[0]) - currentPosition.left,
4259 totalDy = parseInt(scaleOriginParts[1]) - currentPosition.top,
4261 mapCenterOriginal = map._getCenter(),
4262 mapCenter = [mapCenterOriginal[0] + totalDx * pixelSize, mapCenterOriginal[1] - totalDy * pixelSize],
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)),
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,
4276 fullXMinX = tilingScheme.origin[0] + (fullXAtScale * tileWidth) * pixelSize,
4277 fullYMaxY = tilingScheme.origin[1] - (fullYAtScale * tileHeight) * pixelSize,
4278 /* end same as refresh 2 */
4280 serviceLeft = Math.round((fullXMinX - (mapCenterOriginal[0] - halfWidth)) / pixelSize),
4281 serviceTop = Math.round(((mapCenterOriginal[1] + halfHeight) - fullYMaxY) / pixelSize),
4283 opacity = (service.opacity === undefined ? 1 : service.opacity),
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");
4292 if ( $img.size( ) === 0 ) {
4293 /* same as refresh 3 */
4295 tilingScheme.origin[0] + (x * tileWidth) * pixelSize,
4296 tilingScheme.origin[1] - (y * tileHeight) * pixelSize
4300 tilingScheme.origin[0] + ((x + 1) * tileWidth - 1) * pixelSize,
4301 tilingScheme.origin[1] - ((y + 1) * tileHeight - 1) * pixelSize
4304 tileBbox = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]],
4306 imageUrl = service.getUrl( {
4310 zoom: map._getZoom(),
4315 index: Math.abs(y + x)
4317 /* end same as refresh 3 */
4319 serviceState.loadCount++;
4320 //this._map._requestQueued();
4322 if ( serviceState.reloadTiles && $img.size() > 0 ) {
4323 $img.attr( "src", imageUrl );
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) + "%; ";
4330 if ($("body")[0].filters === undefined) {
4331 imgMarkup += "width: 100%; height: 100%;";
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 + "' />";
4336 scaleContainer.append( imgMarkup );
4337 $img = scaleContainer.children(":last");
4338 $img.load(function (e) {
4340 $(e.target).fadeTo(0, opacity);
4345 serviceState.loadCount--;
4347 if (serviceState.loadCount <= 0) {
4348 serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
4349 serviceState.loadCount = 0;
4351 }).error(function (e) {
4352 $(e.target).remove();
4353 serviceState.loadCount--;
4355 if (serviceState.loadCount <= 0) {
4356 serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
4357 serviceState.loadCount = 0;
4359 }).attr("src", imageUrl);
4360 /* end same as refresh 4 */
4369 interactiveScale: function (map, service, center, pixelSize) {
4370 var serviceState = $.data( service, "geoServiceState" );
4372 if ( serviceState && service && ( service.visibility === undefined || service.visibility === "visible" ) ) {
4373 this._cancelUnloaded(map, service);
4375 var serviceContainer = serviceState.serviceContainer,
4377 tilingScheme = map.options["tilingScheme"],
4378 tileWidth = tilingScheme.tileWidth,
4379 tileHeight = tilingScheme.tileHeight;
4382 serviceContainer.children( ).each( function ( i ) {
4383 var $scaleContainer = $(this),
4384 scaleRatio = $scaleContainer.attr("data-pixelSize") / pixelSize;
4386 scaleRatio = Math.round(scaleRatio * 1000) / 1000;
4388 var scaleOriginParts = $scaleContainer.data("scaleOrigin").split(","),
4389 oldMapCoord = map._toMap([scaleOriginParts[0], scaleOriginParts[1]]),
4390 newPixelPoint = map._toPixel(oldMapCoord, center, pixelSize);
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
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')" );
4408 refresh: function (map, service) {
4409 var serviceState = $.data( service, "geoServiceState" );
4411 if ( serviceState && service && ( service.visibility === undefined || service.visibility === "visible" ) ) {
4412 this._cancelUnloaded(map, service);
4414 var bbox = map._getBbox(),
4415 pixelSize = map._pixelSize,
4417 $serviceContainer = serviceState.serviceContainer,
4419 contentBounds = map._getContentBounds(),
4420 mapWidth = contentBounds["width"],
4421 mapHeight = contentBounds["height"],
4423 tilingScheme = map.options["tilingScheme"],
4424 tileWidth = tilingScheme.tileWidth,
4425 tileHeight = tilingScheme.tileHeight,
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)),
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,
4438 fullXMinX = tilingScheme.origin[0] + (fullXAtScale * tileWidth) * pixelSize,
4439 fullYMaxY = tilingScheme.origin[1] - (fullYAtScale * tileHeight) * pixelSize,
4441 serviceLeft = Math.round((fullXMinX - bbox[0]) / pixelSize),
4442 serviceTop = Math.round((bbox[3] - fullYMaxY) / pixelSize),
4444 scaleContainers = $serviceContainer.children().show(),
4445 scaleContainer = scaleContainers.filter("[data-pixelSize='" + pixelSize + "']").appendTo($serviceContainer),
4447 opacity = (service.opacity === undefined ? 1 : service.opacity),
4451 if (serviceState.reloadTiles) {
4452 scaleContainers.find("img").attr("data-dirty", "true");
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));
4459 scaleContainer.css({
4460 left: (serviceLeft % tileWidth) + "px",
4461 top: (serviceTop % tileHeight) + "px"
4462 }).data("scaleOrigin", (serviceLeft % tileWidth) + "," + (serviceTop % tileHeight));
4464 scaleContainer.children().each(function (i) {
4467 tile = $img.attr("data-tile").split(",");
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) + "%"
4475 $img.fadeTo(0, opacity);
4480 for (x = tileX; x < tileX2; x++) {
4481 for (y = tileY; y < tileY2; y++) {
4483 tileStr = "" + x + "," + y,
4484 $img = scaleContainer.children("[data-tile='" + tileStr + "']").removeAttr("data-dirty");
4486 if ($img.size() === 0 || serviceState.reloadTiles) {
4488 tilingScheme.origin[0] + (x * tileWidth) * pixelSize,
4489 tilingScheme.origin[1] - (y * tileHeight) * pixelSize
4493 tilingScheme.origin[0] + ((x + 1) * tileWidth - 1) * pixelSize,
4494 tilingScheme.origin[1] - ((y + 1) * tileHeight - 1) * pixelSize
4497 tileBbox = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]],
4499 imageUrl = service.getUrl({
4503 zoom: map._getZoom(),
4508 index: Math.abs(y + x)
4511 serviceState.loadCount++;
4512 //this._map._requestQueued();
4514 if (serviceState.reloadTiles && $img.size() > 0) {
4515 $img.attr("src", imageUrl);
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) + "%; ";
4521 if ($("body")[0].filters === undefined) {
4522 imgMarkup += "width: 100%; height: 100%;";
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 + "' />";
4527 scaleContainer.append(imgMarkup);
4528 $img = scaleContainer.children(":last");
4529 $img.load(function (e) {
4531 $(e.target).fadeTo(0, opacity);
4536 serviceState.loadCount--;
4538 if (serviceState.loadCount <= 0) {
4539 $serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
4540 serviceState.loadCount = 0;
4542 }).error(function (e) {
4543 $(e.target).remove();
4544 serviceState.loadCount--;
4546 if (serviceState.loadCount <= 0) {
4547 $serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
4548 serviceState.loadCount = 0;
4550 }).attr("src", imageUrl);
4556 scaleContainers.find("[data-dirty]").remove();
4557 serviceState.reloadTiles = false;
4561 resize: function (map, service) {
4564 opacity: function (map, service) {
4565 var serviceState = $.data( service, "geoServiceState" );
4566 serviceState.serviceContainer.find("img").stop(true).fadeTo("fast", service.opacity);
4569 toggle: function (map, service) {
4570 var serviceState = $.data( service, "geoServiceState" );
4571 serviceState.serviceContainer.css("display", service.visibility === "visible" ? "block" : "none");
4574 _cancelUnloaded: function (map, service) {
4575 var serviceState = $.data( service, "geoServiceState" );
4577 if (serviceState && serviceState.loadCount > 0) {
4578 serviceState.serviceContainer.find("img:hidden").remove();
4579 while (serviceState.loadCount > 0) {
4580 serviceState.loadCount--;
4587 (function ($, undefined) {
4588 $.geo._serviceTypes.shingled = (function () {
4590 create: function (map, servicesContainer, service, index) {
4591 var serviceState = $.data(service, "geoServiceState");
4593 if ( !serviceState ) {
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>';
4602 servicesContainer.append(scHtml);
4604 serviceState.serviceContainer = servicesContainer.children(":last");
4605 $.data(service, "geoServiceState", serviceState);
4608 return serviceState.serviceContainer;
4611 destroy: function (map, servicesContainer, service) {
4612 var serviceState = $.data(service, "geoServiceState");
4614 serviceState.serviceContainer.remove();
4616 $.removeData(service, "geoServiceState");
4619 interactivePan: function (map, service, dx, dy) {
4620 var serviceState = $.data(service, "geoServiceState");
4622 if ( serviceState ) {
4623 this._cancelUnloaded(map, service);
4625 var serviceContainer = serviceState.serviceContainer,
4626 pixelSize = map._pixelSize,
4627 scaleContainer = serviceContainer.children("[data-pixelSize='" + pixelSize + "']"),
4628 panContainer = scaleContainer.children("div");
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");
4636 left: function (index, value) {
4637 return parseInt(value) + dx;
4639 top: function (index, value) {
4640 return parseInt(value) + dy;
4646 interactiveScale: function (map, service, center, pixelSize) {
4647 var serviceState = $.data(service, "geoServiceState");
4649 if ( serviceState ) {
4650 this._cancelUnloaded(map, service);
4652 var serviceContainer = serviceState.serviceContainer,
4654 contentBounds = map._getContentBounds(),
4655 mapWidth = contentBounds["width"],
4656 mapHeight = contentBounds["height"],
4658 halfWidth = mapWidth / 2,
4659 halfHeight = mapHeight / 2,
4661 bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight];
4663 serviceContainer.children().each(function (i) {
4664 var $scaleContainer = $(this),
4665 scalePixelSize = $scaleContainer.attr("data-pixelSize"),
4666 ratio = scalePixelSize / pixelSize;
4668 $scaleContainer.css({ width: mapWidth * ratio, height: mapHeight * ratio }).children("img").each(function (i) {
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;
4674 $img.css({ left: x + "px", top: y + "px" });
4680 refresh: function (map, service) {
4681 var serviceState = $.data(service, "geoServiceState");
4683 if (serviceState && service && (service.visibility === undefined || service.visibility === "visible")) {
4684 this._cancelUnloaded(map, service);
4686 var bbox = map._getBbox(),
4687 pixelSize = map._pixelSize,
4689 serviceContainer = serviceState.serviceContainer,
4691 contentBounds = map._getContentBounds(),
4692 mapWidth = contentBounds["width"],
4693 mapHeight = contentBounds["height"],
4695 halfWidth = mapWidth / 2,
4696 halfHeight = mapHeight / 2,
4698 scaleContainer = serviceContainer.children('[data-pixelSize="' + pixelSize + '"]'),
4700 opacity = (service.opacity === undefined ? 1 : service.opacity),
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");
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;
4716 $thisimg.css({ left: x + "px", top: y + "px" });
4720 serviceContainer.find("img").attr("data-keepAlive", "0");
4723 var imageUrl = service.getUrl({
4727 zoom: map._getZoom(),
4732 serviceState.loadCount++;
4733 //this._map._requestQueued();
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) {
4739 $(e.target).fadeTo(0, opacity);
4744 serviceState.loadCount--;
4746 if (serviceState.loadCount <= 0) {
4747 serviceContainer.children(':not([data-pixelSize="' + pixelSize + '"])').remove();
4749 var panContainer = serviceContainer.find('[data-pixelSize="' + pixelSize + '"]>div');
4750 if (panContainer.size() > 0) {
4751 var panContainerPos = panContainer.position();
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"));
4758 $thisimg.css({ left: x + "px", top: y + "px" });
4761 panContainer.remove();
4764 serviceState.loadCount = 0;
4766 }).error(function (e) {
4767 $(e.target).remove();
4768 serviceState.loadCount--;
4770 if (serviceState.loadCount <= 0) {
4771 serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
4772 serviceState.loadCount = 0;
4774 }).attr("src", imageUrl);
4778 resize: function (map, service) {
4779 var serviceState = $.data(service, "geoServiceState");
4781 if ( serviceState && service && (service.visibility === undefined || service.visibility === "visible")) {
4782 this._cancelUnloaded(map, service);
4784 var serviceState = shingledServicesState[service.id],
4785 serviceContainer = serviceState.serviceContainer,
4787 contentBounds = map._getContentBounds(),
4788 mapWidth = contentBounds["width"],
4789 mapHeight = contentBounds["height"],
4791 halfWidth = mapWidth / 2,
4792 halfHeight = mapHeight / 2,
4794 scaleContainer = serviceContainer.children();
4796 scaleContainer.attr("data-pixelSize", "0");
4797 scaleContainer.css({
4798 left: halfWidth + 'px',
4799 top: halfHeight + 'px'
4804 opacity: function (map, service) {
4805 var serviceState = $.data(service, "geoServiceState");
4806 serviceState.serviceContainer.find("img").stop(true).fadeTo("fast", service.opacity);
4809 toggle: function (map, service) {
4810 var serviceState = $.data(service, "geoServiceState");
4811 serviceState.serviceContainer.css("display", service.visibility === "visible" ? "block" : "none");
4814 _cancelUnloaded: function (map, service) {
4815 var serviceState = $.data(service, "geoServiceState");
4817 if (serviceState && serviceState.loadCount > 0) {
4818 serviceState.serviceContainer.find("img:hidden").remove();
4819 while (serviceState.loadCount > 0) {
4820 serviceState.loadCount--;