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_;
1442 * jQuery UI Widget @VERSION
1444 * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
1445 * Dual licensed under the MIT or GPL Version 2 licenses.
1446 * http://jquery.org/license
1448 * http://docs.jquery.com/UI/Widget
1453 (function( $, undefined ) {
1456 if ( $.cleanData ) {
1457 var _cleanData = $.cleanData;
1458 $.cleanData = function( elems ) {
1459 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
1461 $( elem ).triggerHandler( "remove" );
1462 // http://bugs.jquery.com/ticket/8235
1465 _cleanData( elems );
1468 var _remove = $.fn.remove;
1469 $.fn.remove = function( selector, keepData ) {
1470 return this.each(function() {
1472 if ( !selector || $.filter( selector, [ this ] ).length ) {
1473 $( "*", this ).add( [ this ] ).each(function() {
1475 $( this ).triggerHandler( "remove" );
1476 // http://bugs.jquery.com/ticket/8235
1481 return _remove.call( $(this), selector, keepData );
1486 $.widget = function( name, base, prototype ) {
1487 var namespace = name.split( "." )[ 0 ],
1489 name = name.split( "." )[ 1 ];
1490 fullName = namespace + "-" + name;
1497 // create selector for plugin
1498 $.expr[ ":" ][ fullName ] = function( elem ) {
1499 return !!$.data( elem, name );
1502 $[ namespace ] = $[ namespace ] || {};
1503 $[ namespace ][ name ] = function( options, element ) {
1504 // allow instantiation without initializing for simple inheritance
1505 if ( arguments.length ) {
1506 this._createWidget( options, element );
1510 var basePrototype = new base();
1511 // we need to make the options hash a property directly on the new instance
1512 // otherwise we'll modify the options hash on the prototype that we're
1514 // $.each( basePrototype, function( key, val ) {
1515 // if ( $.isPlainObject(val) ) {
1516 // basePrototype[ key ] = $.extend( {}, val );
1519 basePrototype.options = $.extend( true, {}, basePrototype.options );
1520 $[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
1521 namespace: namespace,
1523 widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
1524 widgetBaseClass: fullName
1527 $.widget.bridge( name, $[ namespace ][ name ] );
1530 $.widget.bridge = function( name, object ) {
1531 $.fn[ name ] = function( options ) {
1532 var isMethodCall = typeof options === "string",
1533 args = Array.prototype.slice.call( arguments, 1 ),
1536 // allow multiple hashes to be passed on init
1537 options = !isMethodCall && args.length ?
1538 $.extend.apply( null, [ true, options ].concat(args) ) :
1541 // prevent calls to internal methods
1542 if ( isMethodCall && options.charAt( 0 ) === "_" ) {
1546 if ( isMethodCall ) {
1547 this.each(function() {
1548 var instance = $.data( this, name ),
1549 methodValue = instance && $.isFunction( instance[options] ) ?
1550 instance[ options ].apply( instance, args ) :
1552 // TODO: add this back in 1.9 and use $.error() (see #5972)
1553 // if ( !instance ) {
1554 // throw "cannot call methods on " + name + " prior to initialization; " +
1555 // "attempted to call method '" + options + "'";
1557 // if ( !$.isFunction( instance[options] ) ) {
1558 // throw "no such method '" + options + "' for " + name + " widget instance";
1560 // var methodValue = instance[ options ].apply( instance, args );
1561 if ( methodValue !== instance && methodValue !== undefined ) {
1562 returnValue = methodValue;
1567 this.each(function() {
1568 var instance = $.data( this, name );
1570 instance.option( options || {} )._init();
1572 $.data( this, name, new object( options, this ) );
1581 $.Widget = function( options, element ) {
1582 // allow instantiation without initializing for simple inheritance
1583 if ( arguments.length ) {
1584 this._createWidget( options, element );
1588 $.Widget.prototype = {
1589 widgetName: "widget",
1590 widgetEventPrefix: "",
1594 _createWidget: function( options, element ) {
1595 // $.widget.bridge stores the plugin instance, but we do it anyway
1596 // so that it's stored even before the _create function runs
1597 $.data( element, this.widgetName, this );
1598 this.element = $( element );
1599 this.options = $.extend( true, {},
1601 this._getCreateOptions(),
1605 this.element.bind( "remove." + this.widgetName, function() {
1610 this._trigger( "create" );
1613 _getCreateOptions: function() {
1614 return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
1616 _create: function() {},
1617 _init: function() {},
1619 destroy: function() {
1621 .unbind( "." + this.widgetName )
1622 .removeData( this.widgetName );
1624 .unbind( "." + this.widgetName )
1625 .removeAttr( "aria-disabled" )
1627 this.widgetBaseClass + "-disabled " +
1628 "ui-state-disabled" );
1631 widget: function() {
1632 return this.element;
1635 option: function( key, value ) {
1638 if ( arguments.length === 0 ) {
1639 // don't return a reference to the internal hash
1640 return $.extend( {}, this.options );
1643 if (typeof key === "string" ) {
1644 if ( value === undefined ) {
1645 return this.options[ key ];
1648 options[ key ] = value;
1651 this._setOptions( options );
1655 _setOptions: function( options ) {
1657 $.each( options, function( key, value ) {
1658 self._setOption( key, value );
1663 _setOption: function( key, value ) {
1664 this.options[ key ] = value;
1666 if ( key === "disabled" ) {
1668 [ value ? "addClass" : "removeClass"](
1669 this.widgetBaseClass + "-disabled" + " " +
1670 "ui-state-disabled" )
1671 .attr( "aria-disabled", value );
1677 enable: function() {
1678 return this._setOption( "disabled", false );
1680 disable: function() {
1681 return this._setOption( "disabled", true );
1684 _trigger: function( type, event, data ) {
1686 callback = this.options[ type ];
1689 event = $.Event( event );
1690 event.type = ( type === this.widgetEventPrefix ?
1692 this.widgetEventPrefix + type ).toLowerCase();
1693 // the original event may come from any element
1694 // so we need to reset the target on the new event
1695 event.target = this.element[ 0 ];
1697 // copy original event properties over to the new event
1698 orig = event.originalEvent;
1700 for ( prop in orig ) {
1701 if ( !( prop in event ) ) {
1702 event[ prop ] = orig[ prop ];
1707 this.element.trigger( event, data );
1709 return !( $.isFunction(callback) &&
1710 callback.call( this.element[0], event, data ) === false ||
1711 event.isDefaultPrevented() );
1719 /*! JsRender v1.0pre - (jsrender.js version: does not require jQuery): http://github.com/BorisMoore/jsrender */
1721 * Optimized version of jQuery Templates, fosr rendering to string, using 'codeless' markup.
1723 * Copyright 2011, Boris Moore
1724 * Released under the MIT License.
1726 window.JsViews || window.jQuery && jQuery.views || (function( window, undefined ) {
1728 var $, _$, JsViews, viewsNs, tmplEncode, render, rTag, registerTags, registerHelpers, extend,
1729 FALSE = false, TRUE = true,
1730 jQuery = window.jQuery, document = window.document,
1731 htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,
1732 rPath = /^(true|false|null|[\d\.]+)|(\w+|\$(view|data|ctx|(\w+)))([\w\.]*)|((['"])(?:\\\1|.)*\7)$/g,
1733 rParams = /(\$?[\w\.\[\]]+)(?:(\()|\s*(===|!==|==|!=|<|>|<=|>=)\s*|\s*(\=)\s*)?|(\,\s*)|\\?(\')|\\?(\")|(\))|(\s+)/g,
1734 rNewLine = /\r?\n/g,
1735 rUnescapeQuotes = /\\(['"])/g,
1736 rEscapeQuotes = /\\?(['"])/g,
1737 rBuildHash = /\x08([^\x08]+)\x08/g,
1739 escapeMapForHtml = {
1744 htmlSpecialChar = /[\x00"&'<>]/g,
1745 slice = Array.prototype.slice;
1749 ////////////////////////////////////////////////////////////////////////////////////////////////
1750 // jQuery is loaded, so make $ the jQuery object
1754 // Use first wrapped element as template markup.
1755 // Return string obtained by rendering the template against data.
1756 render: function( data, context, parentView, path ) {
1757 return render( data, this[0], context, parentView, path );
1760 // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template.
1761 template: function( name, context ) {
1762 return $.template( name, this[0], context );
1768 ////////////////////////////////////////////////////////////////////////////////////////////////
1769 // jQuery is not loaded. Make $ the JsViews object
1771 // Map over the $ in case of overwrite
1774 window.JsViews = JsViews = window.$ = $ = {
1775 extend: function( target, source ) {
1777 for ( name in source ) {
1778 target[ name ] = source[ name ];
1782 isArray: Array.isArray || function( obj ) {
1783 return Object.prototype.toString.call( obj ) === "[object Array]";
1785 noConflict: function() {
1786 if ( window.$ === JsViews ) {
1800 function View( context, path, parentView, data, template ) {
1801 // Returns a view data structure for a new rendered instance of a template.
1802 // The content field is a hierarchical array of strings and nested views.
1804 parentView = parentView || { viewsCount:0, ctx: viewsNs.helpers };
1806 var parentContext = parentView && parentView.ctx;
1811 // inherit context from parentView, merged with new context.
1812 itemNumber: ++parentView.viewsCount || 1,
1815 data: data || parentView.data || {},
1816 // Set additional context on this view (which will modify the context inherited from the parent, and be inherited by child views)
1817 ctx : context && context === parentContext
1819 : (parentContext ? extend( extend( {}, parentContext ), context ) : context||{}),
1820 // If no jQuery, extend does not support chained copies - so limit to two parameters
1831 view.onElse = function( presenter, args ) {
1834 while ( l && !args[ i++ ]) {
1835 // Only render content if args.length === 0 (i.e. this is an else with no condition) or if a condition argument is truey
1840 view.onElse = undefined; // If condition satisfied, so won't run 'else'.
1841 return render( view.data, presenter.tmpl, view.ctx, view);
1843 return view.onElse( this, arguments );
1845 "else": function() {
1846 var view = this._view;
1847 return view.onElse ? view.onElse( this, arguments ) : "";
1855 content = self.tmpl,
1857 for ( i = 0; i < l; i++ ) {
1858 result += args[ i ] ? render( args[ i ], content, self.ctx || view.ctx, view, self._path, self._ctor ) : "";
1861 // If no data parameter, use the current $data from view, and render once
1862 : result + render( view.data, content, view.ctx, view, self._path, self.tag );
1864 "=": function( value ) {
1867 "*": function( value ) {
1872 not: function( value ) {
1878 err: function( e ) {
1879 return viewsNs.debugMode ? ("<br/><b>Error:</b> <em> " + (e.message || e) + ". </em>"): '""';
1886 setDelimiters: function( openTag, closeTag ) {
1887 // Set or modify the delimiter characters for tags: "{{" and "}}"
1888 var firstCloseChar = closeTag.charAt( 0 ),
1889 secondCloseChar = closeTag.charAt( 1 );
1890 openTag = "\\" + openTag.charAt( 0 ) + "\\" + openTag.charAt( 1 );
1891 closeTag = "\\" + firstCloseChar + "\\" + secondCloseChar;
1893 // Build regex with new delimiters
1896 // # tag (followed by space,! or }) or equals or code
1897 + "(?:(?:(\\#)?(\\w+(?=[!\\s\\" + firstCloseChar + "]))" + "|(?:(\\=)|(\\*)))"
1899 + "\\s*((?:[^\\" + firstCloseChar + "]|\\" + firstCloseChar + "(?!\\" + secondCloseChar + "))*?)"
1903 + "|(?:\\/([\\w\\$\\.\\[\\]]+)))"
1907 // Default rTag: # tag equals code params encoding closeBlock
1908 // /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g;
1910 rTag = new RegExp( rTag, "g" );
1918 // Register declarative tag.
1919 registerTags: registerTags = function( name, tagFn ) {
1921 if ( typeof name === "object" ) {
1922 for ( key in name ) {
1923 registerTags( key, name[ key ]);
1926 // Simple single property case.
1927 viewsNs.tags[ name ] = tagFn;
1936 // Register helper function for use in markup.
1937 registerHelpers: registerHelpers = function( name, helper ) {
1938 if ( typeof name === "object" ) {
1939 // Object representation where property name is path and property value is value.
1940 // TODO: We've discussed an "objectchange" event to capture all N property updates here. See TODO note above about propertyChanges.
1942 for ( key in name ) {
1943 registerHelpers( key, name[ key ]);
1946 // Simple single property case.
1947 viewsNs.helpers[ name ] = helper;
1956 encode: function( encoding, text ) {
1958 ? ( tmplEncode[ encoding || "html" ] || tmplEncode.html)( text ) // HTML encoding is the default
1962 encoders: tmplEncode = {
1963 "none": function( text ) {
1966 "html": function( text ) {
1967 // HTML encoding helper: Replace < > & and ' and " by corresponding entities.
1968 // Implementation, from Mike Samuel <msamuel@google.com>
1969 return String( text ).replace( htmlSpecialChar, replacerForHtml );
1971 //TODO add URL encoding, and perhaps other encoding helpers...
1978 renderTag: function( tag, view, encode, content, tagProperties ) {
1979 // This is a tag call, with arguments: "tag", view, encode, content, presenter [, params...]
1982 presenters = viewsNs.presenters;
1983 hash = tagProperties._hash,
1984 tagFn = viewsNs.tags[ tag ];
1990 content = content && view.tmpl.nested[ content - 1 ];
1991 tagProperties.tmpl = tagProperties.tmpl || content || undefined;
1992 // Set the tmpl property to the content of the block tag, unless set as an override property on the tag
1994 if ( presenters && presenters[ tag ]) {
1995 ctx = extend( extend( {}, tagProperties.ctx ), tagProperties );
1999 tagProperties.ctx = ctx;
2000 tagProperties._ctor = tag + (hash ? "=" + hash.slice( 0, -1 ) : "");
2002 tagProperties = extend( extend( {}, tagFn ), tagProperties );
2003 tagFn = viewsNs.tags.each; // Use each to render the layout template against the data
2006 tagProperties._encode = encode;
2007 tagProperties._view = view;
2008 ret = tagFn.apply( tagProperties, args.length > 5 ? slice.call( args, 5 ) : [view.data] );
2009 return ret || (ret === undefined ? "" : ret.toString()); // (If ret is the value 0 or false or null, will render to string)
2017 render: render = function( data, tmpl, context, parentView, path, tagName ) {
2018 // Render template against data as a tree of subviews (nested template), or as a string (top-level template).
2019 // tagName parameter for internal use only. Used for rendering templates registered as tags (which may have associated presenter objects)
2020 var i, l, dataItem, arrayView, content, result = "";
2022 if ( arguments.length === 2 && data.jsViews ) {
2024 context = parentView.ctx;
2025 data = parentView.data;
2027 tmpl = $.template( tmpl );
2029 return ""; // Could throw...
2032 if ( $.isArray( data )) {
2033 // Create a view item for the array, whose child views correspond to each data item.
2034 arrayView = new View( context, path, parentView, data);
2036 for ( i = 0, l = data.length; i < l; i++ ) {
2037 dataItem = data[ i ];
2038 content = dataItem ? tmpl( dataItem, new View( context, path, arrayView, dataItem, tmpl, this )) : "";
2039 result += viewsNs.activeViews ? "<!--item-->" + content + "<!--/item-->" : content;
2042 result += tmpl( data, new View( context, path, parentView, data, tmpl ));
2045 return viewsNs.activeViews
2046 // If in activeView mode, include annotations
2047 ? "<!--tmpl(" + (path || "") + ") " + (tagName ? "tag=" + tagName : tmpl._name) + "-->" + result + "<!--/tmpl-->"
2048 // else return just the string result
2056 template: function( name, tmpl ) {
2058 // Use $.template( name, tmpl ) to cache a named template,
2059 // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc.
2060 // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration.
2063 // Use $.template( name ) to access a cached template.
2064 // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString )
2065 // will return the compiled template, without adding a name reference.
2066 // If templateString is not a selector, $.template( templateString ) is equivalent
2067 // to $.template( null, templateString ). To ensure a string is treated as a template,
2068 // include an HTML element, an HTML comment, or a template comment tag.
2071 // Compile template and associate with name
2072 if ( "" + tmpl === tmpl ) { // type string
2073 // This is an HTML string being passed directly in.
2074 tmpl = compile( tmpl );
2075 } else if ( jQuery && tmpl instanceof $ ) {
2079 if ( jQuery && tmpl.nodeType ) {
2080 // If this is a template block, use cached copy, or generate tmpl function and cache.
2081 tmpl = $.data( tmpl, "tmpl" ) || $.data( tmpl, "tmpl", compile( tmpl.innerHTML ));
2083 viewsNs.templates[ tmpl._name = tmpl._name || name || "_" + autoName++ ] = tmpl;
2087 // Return named compiled template
2089 ? "" + name !== name // not type string
2091 ? name // already compiled
2092 : $.template( null, name ))
2093 : viewsNs.templates[ name ] ||
2094 // If not in map, treat as a selector. (If integrated with core, use quickExpr.exec)
2095 $.template( null, htmlExpr.test( name ) ? name : try$( name ))
2100 viewsNs.setDelimiters( "{{", "}}" );
2106 // Generate a reusable function that will serve to render a template against data
2107 // (Compile AST then build template function)
2109 function parsePath( all, comp, object, viewDataCtx, viewProperty, path, string, quot ) {
2113 ? ("$view." + viewProperty)
2115 :("$data." + object)
2117 : string || (comp || "");
2120 function compile( markup ) {
2126 current = [,,topNode];
2128 function pushPreceedingContent( shift ) {
2131 content.push( markup.substr( loc, shift ).replace( rNewLine,"\\n"));
2135 function parseTag( all, isBlock, tagName, equals, code, params, useEncode, encode, closeBlock, index ) {
2136 // rTag : # tagName equals code params encode closeBlock
2137 // /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g;
2139 // Build abstract syntax tree: [ tagName, params, content, encode ]
2143 quoted = FALSE, // boolean for string content in double qoutes
2144 aposed = FALSE; // or in single qoutes
2146 function parseParams( all, path, paren, comp, eq, comma, apos, quot, rightParen, space, index ) {
2147 // path paren eq comma apos quot rtPrn space
2148 // /(\$?[\w\.\[\]]+)(?:(\()|(===)|(\=))?|(\,\s*)|\\?(\')|\\?(\")|(\))|(\s+)/g
2151 // within single-quoted string
2152 ? ( aposed = !apos, (aposed ? all : '"'))
2154 // within double-quoted string
2155 ? ( quoted = !quot, (quoted ? all : '"'))
2158 ? ( path.replace( rPath, parsePath ) + comp)
2161 ? parenDepth ? "" :( named = TRUE, '\b' + path + ':')
2164 ? (parenDepth++, path.replace( rPath, parsePath ) + '(')
2167 ? (parenDepth--, ")")
2170 ? path.replace( rPath, parsePath )
2177 ? ( named = FALSE, "\b")
2180 : (aposed = apos, quoted = quot, '"');
2183 tagName = tagName || equals;
2184 pushPreceedingContent( index );
2186 if ( viewsNs.allowCode ) {
2187 content.push([ "*", params.replace( rUnescapeQuotes, "$1" )]);
2189 } else if ( tagName ) {
2190 if ( tagName === "else" ) {
2191 current = stack.pop();
2192 content = current[ 2 ];
2197 .replace( rParams, parseParams )
2198 .replace( rBuildHash, function( all, keyValue, index ) {
2199 hash += keyValue + ",";
2203 params = params.slice( 0, -1 );
2206 useEncode ? encode || "none" : "",
2208 "{" + hash + "_hash:'" + hash + "',_path:'" + params + "'}",
2213 stack.push( current );
2216 content.push( newNode );
2217 } else if ( closeBlock ) {
2218 current = stack.pop();
2220 loc = index + all.length; // location marker - parsed up to here
2222 throw "Expected block tag";
2224 content = current[ 2 ];
2226 markup = markup.replace( rEscapeQuotes, "\\$1" );
2227 markup.replace( rTag, parseTag );
2228 pushPreceedingContent( markup.length );
2229 return buildTmplFunction( topNode );
2232 // Build javascript compiled template function, from AST
2233 function buildTmplFunction( nodes ) {
2237 code = "try{var views="
2238 + (jQuery ? "jQuery" : "JsViews")
2239 + '.views,tag=views.renderTag,enc=views.encode,html=views.encoders.html,$ctx=$view && $view.ctx,result=""+\n\n';
2241 for ( i = 0; i < l; i++ ) {
2243 if ( node[ 0 ] === "*" ) {
2244 code = code.slice( 0, i ? -1 : -3 ) + ";" + node[ 1 ] + ( i + 1 < l ? "result+=" : "" );
2245 } else if ( "" + node === node ) { // type string
2246 code += '"' + node + '"+';
2248 var tag = node[ 0 ],
2250 content = node[ 2 ],
2253 paramsOrEmptyString = params + '||"")+';
2256 nested.push( buildTmplFunction( content ));
2259 ? (!encode || encode === "html"
2260 ? "html(" + paramsOrEmptyString
2262 ? ("(" + paramsOrEmptyString)
2263 : ('enc("' + encode + '",' + paramsOrEmptyString)
2265 : 'tag("' + tag + '",$view,"' + ( encode || "" ) + '",'
2266 + (content ? nested.length : '""') // For block tags, pass in the key (nested.length) to the nested content template
2267 + "," + obj + (params ? "," : "") + params + ")+";
2270 ret = new Function( "$data, $view", code.slice( 0, -1) + ";return result;\n\n}catch(e){return views.err(e);}" );
2271 ret.nested = nested;
2275 //========================== Private helper functions, used by code above ==========================
2277 function replacerForHtml( ch ) {
2278 // Original code from Mike Samuel <msamuel@google.com>
2279 return escapeMapForHtml[ ch ]
2280 // Intentional assignment that caches the result of encoding ch.
2281 || ( escapeMapForHtml[ ch ] = "&#" + ch.charCodeAt( 0 ) + ";" );
2284 function try$( selector ) {
2285 // If selector is valid, return jQuery object, otherwise return (invalid) selector string
2287 return $( selector );
2292 (function ($, window, undefined) {
2293 var pos_oo = Number.POSITIVE_INFINITY,
2294 neg_oo = Number.NEGATIVE_INFINITY;
2298 // utility functions
2301 _allCoordinates: function (geom) {
2302 // return array of all positions in all geometries of geom
2304 var geometries = this._flatten(geom),
2308 for (; curGeom < geometries.length; curGeom++) {
2309 var coordinates = geometries[curGeom].coordinates,
2310 isArray = coordinates && $.isArray(coordinates[0]),
2311 isDblArray = isArray && $.isArray(coordinates[0][0]),
2312 isTriArray = isDblArray && $.isArray(coordinates[0][0][0]),
2318 coordinates = [coordinates];
2320 coordinates = [coordinates];
2322 coordinates = [coordinates];
2325 for (i = 0; i < coordinates.length; i++) {
2326 for (j = 0; j < coordinates[i].length; j++) {
2327 for (k = 0; k < coordinates[i][j].length; k++) {
2328 result.push(coordinates[i][j][k]);
2336 _isGeodetic: function( coords ) {
2337 // returns true if the first coordinate it can find is geodetic
2339 while ( $.isArray( coords ) ) {
2340 if ( coords.length > 1 && ! $.isArray( coords[ 0 ] ) ) {
2341 return ( coords[ 0 ] >= -180 && coords[ 0 ] <= 180 && coords[ 1 ] >= -85 && coords[ 1 ] <= 85 );
2343 coords = coords[ 0 ];
2354 center: function (bbox, _ignoreGeo /* Internal Use Only */) {
2355 // Envelope.centre in JTS
2356 // bbox only, use centroid for geom
2357 var wasGeodetic = false;
2358 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2360 bbox = $.geo.proj.fromGeodetic(bbox);
2363 var center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
2364 return wasGeodetic ? $.geo.proj.toGeodetic(center) : center;
2367 expandBy: function (bbox, dx, dy, _ignoreGeo /* Internal Use Only */) {
2368 var wasGeodetic = false;
2369 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2371 bbox = $.geo.proj.fromGeodetic(bbox);
2374 bbox = [bbox[0] - dx, bbox[1] - dy, bbox[2] + dx, bbox[3] + dy];
2375 return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2378 height: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
2379 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2380 bbox = $.geo.proj.fromGeodetic(bbox);
2383 return bbox[3] - bbox[1];
2386 _in: function(bbox1, bbox2) {
2387 return bbox1[0] <= bbox2[0] &&
2388 bbox1[1] <= bbox2[1] &&
2389 bbox1[2] >= bbox2[2] &&
2390 bbox1[3] >= bbox2[3];
2393 _bboxDisjoint: function( bbox1, bbox2 ) {
2394 return bbox2[ 0 ] > bbox1[ 2 ] ||
2395 bbox2[ 2 ] < bbox1[ 0 ] ||
2396 bbox2[ 1 ] > bbox1[ 3 ] ||
2397 bbox2[ 3 ] < bbox1[ 1 ];
2400 reaspect: function (bbox, ratio, _ignoreGeo /* Internal Use Only */ ) {
2402 var wasGeodetic = false;
2403 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2405 bbox = $.geo.proj.fromGeodetic(bbox);
2408 var width = this.width(bbox, true),
2409 height = this.height(bbox, true),
2410 center = this.center(bbox, true),
2413 if (width != 0 && height != 0 && ratio > 0) {
2414 if (width / height > ratio) {
2422 bbox = [center[0] - dx, center[1] - dy, center[0] + dx, center[1] + dy];
2425 return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2428 recenter: function( bbox, center, _ignoreGeo /* Internal Use Only */ ) {
2430 var wasGeodetic = false;
2431 if ( !_ignoreGeo && $.geo.proj ) {
2432 if ( this._isGeodetic( bbox ) ) {
2434 bbox = $.geo.proj.fromGeodetic(bbox);
2437 if ( this._isGeodetic( center ) ) {
2438 center = $.geo.proj.fromGeodetic(center);
2442 var halfWidth = ( bbox[ 2 ] - bbox[ 0 ] ) / 2,
2443 halfHeight = ( bbox[ 3 ] - bbox[ 1 ] ) / 2;
2446 center[ 0 ] - halfWidth,
2447 center[ 1 ] - halfHeight,
2448 center[ 0 ] + halfWidth,
2449 center[ 1 ] + halfHeight
2452 return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2455 scaleBy: function ( bbox, scale, _ignoreGeo /* Internal Use Only */ ) {
2457 var wasGeodetic = false;
2458 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2460 bbox = $.geo.proj.fromGeodetic(bbox);
2463 var c = this.center(bbox, true),
2464 dx = (bbox[2] - bbox[0]) * scale / 2,
2465 dy = (bbox[3] - bbox[1]) * scale / 2;
2467 bbox = [c[0] - dx, c[1] - dy, c[0] + dx, c[1] + dy];
2469 return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
2472 width: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
2473 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
2474 bbox = $.geo.proj.fromGeodetic(bbox);
2477 return bbox[2] - bbox[0];
2481 // geometry functions
2484 // bbox (Geometry.getEnvelope in JTS)
2486 bbox: function ( geom, _ignoreGeo /* Internal Use Only */ ) {
2489 } else if ( geom.bbox ) {
2490 result = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom.bbox ) ) ? $.geo.proj.fromGeodetic( geom.bbox ) : geom.bbox;
2492 result = [ pos_oo, pos_oo, neg_oo, neg_oo ];
2494 var coordinates = this._allCoordinates( geom ),
2497 if ( coordinates.length == 0 ) {
2501 var wasGeodetic = false;
2502 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( coordinates ) ) {
2504 coordinates = $.geo.proj.fromGeodetic( coordinates );
2507 for ( ; curCoord < coordinates.length; curCoord++ ) {
2508 result[0] = Math.min(coordinates[curCoord][0], result[0]);
2509 result[1] = Math.min(coordinates[curCoord][1], result[1]);
2510 result[2] = Math.max(coordinates[curCoord][0], result[2]);
2511 result[3] = Math.max(coordinates[curCoord][1], result[3]);
2515 return wasGeodetic ? $.geo.proj.toGeodetic(result) : result;
2520 centroid: function( geom, _ignoreGeo /* Internal Use Only */ ) {
2521 switch (geom.type) {
2523 return $.extend({}, geom);
2529 coords = $.merge( [ ], geom.type == "Polygon" ? geom.coordinates[0] : geom.coordinates ),
2532 var wasGeodetic = false;
2533 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( coords ) ) {
2535 coords = $.geo.proj.fromGeodetic(coords);
2538 //if (coords[0][0] != coords[coords.length - 1][0] || coords[0][1] != coords[coords.length - 1][1]) {
2539 // coords.push(coords[0]);
2542 for (; i <= coords.length; i++) {
2543 j = i % coords.length;
2544 n = (coords[i - 1][0] * coords[j][1]) - (coords[j][0] * coords[i - 1][1]);
2546 c[0] += (coords[i - 1][0] + coords[j][0]) * n;
2547 c[1] += (coords[i - 1][1] + coords[j][1]) * n;
2551 if (coords.length > 0) {
2552 c[0] = coords[0][0];
2553 c[1] = coords[0][1];
2554 return { type: "Point", coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c };
2564 return { type: "Point", coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c };
2571 contains: function (geom1, geom2) {
2572 if (geom1.type != "Polygon") {
2576 switch (geom2.type) {
2578 return this._containsPolygonPoint(geom1.coordinates, geom2.coordinates);
2581 return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates);
2584 return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates[0]);
2591 _containsPolygonPoint: function (polygonCoordinates, pointCoordinate) {
2592 if (polygonCoordinates.length == 0 || polygonCoordinates[0].length < 4) {
2597 a = polygonCoordinates[0][0],
2602 for (; i < polygonCoordinates[0].length; i++) {
2603 b = polygonCoordinates[0][i];
2605 if ((a[1] <= pointCoordinate[1] && pointCoordinate[1] < b[1]) || (b[1] <= pointCoordinate[1] && pointCoordinate[1] < a[1]) && (pointCoordinate[0] < a[0] || pointCoordinate[0] < b[0])) {
2606 x = a[0] + (b[0] - a[0]) * (pointCoordinate[1] - a[1]) / (b[1] - a[1]);
2608 if (x > pointCoordinate[0]) {
2616 return rayCross % 2 == 1;
2619 _containsPolygonLineString: function (polygonCoordinates, lineStringCoordinates) {
2620 for (var i = 0; i < lineStringCoordinates.length; i++) {
2621 if (!this._containsPolygonPoint(polygonCoordinates, lineStringCoordinates[i])) {
2630 distance: function ( geom1, geom2, _ignoreGeo /* Internal Use Only */ ) {
2631 var geom1CoordinatesProjected = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom1.coordinates ) ) ? $.geo.proj.fromGeodetic(geom1.coordinates) : geom1.coordinates,
2632 geom2CoordinatesProjected = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom2.coordinates ) ) ? $.geo.proj.fromGeodetic(geom2.coordinates) : geom2.coordinates;
2634 switch (geom1.type) {
2636 switch (geom2.type) {
2638 return this._distancePointPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
2640 return this._distanceLineStringPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
2642 return this._containsPolygonPoint(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
2649 switch (geom2.type) {
2651 return this._distanceLineStringPoint(geom1CoordinatesProjected, geom2CoordinatesProjected);
2653 return this._distanceLineStringLineString(geom1CoordinatesProjected, geom2CoordinatesProjected);
2655 return this._containsPolygonLineString(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
2662 switch (geom2.type) {
2664 return this._containsPolygonPoint(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
2666 return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
2668 return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected[0]) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected[0]);
2676 _distancePointPoint: function (coordinate1, coordinate2) {
2677 var dx = coordinate2[0] - coordinate1[0],
2678 dy = coordinate2[1] - coordinate1[1];
2679 return Math.sqrt((dx * dx) + (dy * dy));
2682 _distanceLineStringPoint: function (lineStringCoordinates, pointCoordinate) {
2683 var minDist = pos_oo;
2685 if (lineStringCoordinates.length > 0) {
2686 var a = lineStringCoordinates[0],
2688 apx = pointCoordinate[0] - a[0],
2689 apy = pointCoordinate[1] - a[1];
2691 if (lineStringCoordinates.length == 1) {
2692 return Math.sqrt(apx * apx + apy * apy);
2694 for (var i = 1; i < lineStringCoordinates.length; i++) {
2695 var b = lineStringCoordinates[i],
2699 bpx = pointCoordinate[0] - b[0],
2700 bpy = pointCoordinate[1] - b[1],
2702 d = this._distanceSegmentPoint(abx, aby, apx, apy, bpx, bpy);
2719 return Math.sqrt(minDist);
2722 _distanceSegmentPoint: function (abx, aby, apx, apy, bpx, bpy) {
2723 var dot1 = abx * apx + aby * apy;
2726 return apx * apx + apy * apy;
2729 var dot2 = abx * abx + aby * aby;
2732 return bpx * bpx + bpy * bpy;
2735 return apx * apx + apy * apy - dot1 * dot1 / dot2;
2738 _distanceLineStringLineString: function (lineStringCoordinates1, lineStringCoordinates2) {
2739 var minDist = pos_oo;
2740 for (var i = 0; i < lineStringCoordinates2.length; i++) {
2741 minDist = Math.min(minDist, this._distanceLineStringPoint(lineStringCoordinates1, lineStringCoordinates2[i]));
2748 _buffer: function( geom, distance, _ignoreGeo /* Internal Use Only */ ) {
2749 var wasGeodetic = false,
2750 coords = geom.coordinates;
2752 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom.coordinates ) ) {
2754 coords = $.geo.proj.fromGeodetic( geom.coordinates );
2757 switch ( geom.type ) {
2759 var resultCoords = [],
2764 for ( ; i <= slices; i++ ) {
2765 a = ( i * 360 / slices ) * ( Math.PI / 180 );
2766 resultCoords.push( [
2767 coords[ 0 ] + Math.cos( a ) * distance,
2768 coords[ 1 ] + Math.sin( a ) * distance
2774 coordinates: [ ( wasGeodetic ? $.geo.proj.toGeodetic( resultCoords ) : resultCoords ) ]
2789 _flatten: function (geom) {
2790 // return an array of all basic geometries
2792 var geometries = [],
2794 switch (geom.type) {
2796 $.merge(geometries, this._flatten(geom.geometry));
2799 case "FeatureCollection":
2800 for (; curGeom < geom.features.length; curGeom++) {
2801 $.merge(geometries, this._flatten(geom.features[curGeom].geometry));
2805 case "GeometryCollection":
2806 for (; curGeom < geom.geometries.length; curGeom++) {
2807 $.merge(geometries, this._flatten(geom.geometries[curGeom]));
2812 geometries[0] = geom;
2818 length: function( geom, _ignoreGeo /* Internal Use Only */ ) {
2820 lineStringCoordinates,
2823 switch ( geom.type ) {
2828 lineStringCoordinates = geom.coordinates;
2832 lineStringCoordinates = geom.coordinates[ 0 ];
2836 if ( lineStringCoordinates ) {
2837 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( lineStringCoordinates ) ) {
2838 lineStringCoordinates = $.geo.proj.fromGeodetic( lineStringCoordinates );
2841 for ( ; i < lineStringCoordinates.length; i++ ) {
2842 dx = lineStringCoordinates[ i ][0] - lineStringCoordinates[ i - 1 ][0];
2843 dy = lineStringCoordinates[ i ][1] - lineStringCoordinates[ i - 1 ][1];
2844 sum += Math.sqrt((dx * dx) + (dy * dy));
2850 // return undefined;
2853 area: function( geom, _ignoreGeo /* Internal Use Only */ ) {
2858 switch ( geom.type ) {
2864 polygonCoordinates = geom.coordinates[ 0 ];
2868 if ( polygonCoordinates ) {
2869 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( polygonCoordinates ) ) {
2870 polygonCoordinates = $.geo.proj.fromGeodetic( polygonCoordinates );
2873 for ( ; i <= polygonCoordinates.length; i++) {
2874 j = i % polygonCoordinates.length;
2875 sum += ( polygonCoordinates[ i - 1 ][ 0 ] - polygonCoordinates[ j ][ 0 ] ) * ( polygonCoordinates[ i - 1 ][ 1 ] + polygonCoordinates[ j ][ 1 ] ) / 2;
2878 return Math.abs( sum );
2882 pointAlong: function( geom, percentage, _ignoreGeo /* Internal Use Only */ ) {
2883 var totalLength = 0,
2884 previousPercentageSum = 0,
2886 remainderPercentageSum,
2888 lineStringCoordinates,
2889 segmentLengths = [],
2892 wasGeodetic = false;
2894 switch ( geom.type ) {
2896 return $.extend( { }, geom );
2899 lineStringCoordinates = geom.coordinates;
2903 lineStringCoordinates = geom.coordinates[ 0 ];
2907 if ( lineStringCoordinates ) {
2908 if ( percentage === 0 ) {
2911 coordinates: [ lineStringCoordinates[ 0 ][ 0 ], lineStringCoordinates[ 0 ][ 1 ] ]
2913 } else if ( percentage === 1 ) {
2914 i = lineStringCoordinates.length - 1;
2917 coordinates: [ lineStringCoordinates[ i ][ 0 ], lineStringCoordinates[ i ][ 1 ] ]
2920 if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( lineStringCoordinates ) ) {
2922 lineStringCoordinates = $.geo.proj.fromGeodetic( lineStringCoordinates );
2925 for ( ; i < lineStringCoordinates.length; i++ ) {
2926 dx = lineStringCoordinates[ i ][ 0 ] - lineStringCoordinates[ i - 1 ][ 0 ];
2927 dy = lineStringCoordinates[ i ][ 1 ] - lineStringCoordinates[ i - 1 ][ 1 ];
2928 len = Math.sqrt((dx * dx) + (dy * dy));
2929 segmentLengths.push( len );
2933 for ( i = 0; i < segmentLengths.length && percentageSum < percentage; i++ ) {
2934 previousPercentageSum = percentageSum;
2935 percentageSum += ( segmentLengths[ i ] / totalLength );
2938 remainderPercentageSum = percentage - previousPercentageSum;
2940 c0 = lineStringCoordinates[ i - 1 ];
2941 c1 = lineStringCoordinates[ i ];
2944 c0[ 0 ] + ( remainderPercentageSum * ( c1[ 0 ] - c0[ 0 ] ) ),
2945 c0[ 1 ] + ( remainderPercentageSum * ( c1[ 1 ] - c0[ 1 ] ) )
2950 coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c
2960 _WKT: (function () {
2961 function pointToString(value) {
2962 return "POINT " + pointToUntaggedString(value.coordinates);
2965 function pointToUntaggedString(coordinates) {
2966 if (!(coordinates && coordinates.length)) {
2969 return "(" + coordinates.join(" ") + ")";
2973 function lineStringToString(value) {
2974 return "LINESTRING " + lineStringToUntaggedString(value.coordinates);
2977 function lineStringToUntaggedString(coordinates) {
2978 if (!(coordinates && coordinates.length)) {
2983 for (var i = 0; i < coordinates.length; i++) {
2984 points.push(coordinates[i].join(" "));
2987 return "(" + points + ")";
2991 function polygonToString(value) {
2992 return "POLYGON " + polygonToUntaggedString(value.coordinates);
2995 function polygonToUntaggedString(coordinates) {
2996 if (!(coordinates && coordinates.length)) {
2999 var lineStrings = [];
3001 for (var i = 0; i < coordinates.length; i++) {
3002 lineStrings.push(lineStringToUntaggedString(coordinates[i]));
3005 return "(" + lineStrings + ")";
3009 function multiPointToString(value) {
3010 return "MULTIPOINT " + lineStringToUntaggedString(value.coordinates);
3013 function multiLineStringToString(value) {
3014 return "MULTILINSTRING " + polygonToUntaggedString(value.coordinates);
3017 function multiPolygonToString(value) {
3018 return "MULTIPOLYGON " + multiPolygonToUntaggedString(value.coordinates);
3021 function multiPolygonToUntaggedString(coordinates) {
3022 if (!(coordinates && coordinates.length)) {
3026 for (var i = 0; i < coordinates.length; i++) {
3027 polygons.push(polygonToUntaggedString(coordinates[i]));
3029 return "(" + polygons + ")";
3033 function geometryCollectionToString(value) {
3034 return "GEOMETRYCOLLECTION " + geometryCollectionToUntaggedString(value.geometries);
3037 function geometryCollectionToUntaggedString(geometries) {
3038 if (!(geometries && geometries.length)) {
3041 var geometryText = [];
3042 for (var i = 0; i < geometries.length; i++) {
3043 geometryText.push(stringify(geometries[i]));
3045 return "(" + geometries + ")";
3049 function stringify(value) {
3050 if (!(value && value.type)) {
3053 switch (value.type) {
3055 return pointToString(value);
3058 return lineStringToString(value);
3061 return polygonToString(value);
3064 return multiPointToString(value);
3066 case "MultiLineString":
3067 return multiLineStringToString(value);
3069 case "MultiPolygon":
3070 return multiPolygonToString(value);
3072 case "GeometryCollection":
3073 return geometryCollectionToString(value);
3081 function pointParseUntagged(wkt) {
3082 var pointString = wkt.match( /\(\s*([\d\.-]+)\s+([\d\.-]+)\s*\)/ );
3083 return pointString && pointString.length > 2 ? {
3086 parseFloat(pointString[1]),
3087 parseFloat(pointString[2])
3092 function lineStringParseUntagged(wkt) {
3093 var lineString = wkt.match( /\s*\((.*)\)/ ),
3099 if ( lineString.length > 1 ) {
3100 pointStrings = lineString[ 1 ].match( /[\d\.-]+\s+[\d\.-]+/g );
3102 for ( ; i < pointStrings.length; i++ ) {
3103 pointParts = pointStrings[ i ].match( /\s*([\d\.-]+)\s+([\d\.-]+)\s*/ );
3104 coords[ i ] = [ parseFloat( pointParts[ 1 ] ), parseFloat( pointParts[ 2 ] ) ];
3116 function polygonParseUntagged(wkt) {
3117 var polygon = wkt.match( /\s*\(\s*\((.*)\)\s*\)/ ),
3123 if ( polygon.length > 1 ) {
3124 pointStrings = polygon[ 1 ].match( /[\d\.-]+\s+[\d\.-]+/g );
3126 for ( ; i < pointStrings.length; i++ ) {
3127 pointParts = pointStrings[ i ].match( /\s*([\d\.-]+)\s+([\d\.-]+)\s*/ );
3128 coords[ i ] = [ parseFloat( pointParts[ 1 ] ), parseFloat( pointParts[ 2 ] ) ];
3133 coordinates: [ coords ]
3140 function parse(wkt) {
3143 var typeIndex = wkt.indexOf( " " ),
3144 untagged = wkt.substr( typeIndex + 1 );
3146 switch (wkt.substr(0, typeIndex).toUpperCase()) {
3148 return pointParseUntagged( untagged );
3151 return lineStringParseUntagged( untagged );
3154 return polygonParseUntagged( untagged );
3162 stringify: stringify,
3169 // projection functions
3172 proj: (function () {
3173 var halfPi = 1.5707963267948966192,
3174 quarterPi = 0.7853981633974483096,
3175 radiansPerDegree = 0.0174532925199432958,
3176 degreesPerRadian = 57.295779513082320877,
3177 semiMajorAxis = 6378137;
3180 fromGeodeticPos: function (coordinate) {
3185 semiMajorAxis * coordinate[ 0 ] * radiansPerDegree,
3186 semiMajorAxis * Math.log(Math.tan(quarterPi + coordinate[ 1 ] * radiansPerDegree / 2))
3190 fromGeodetic: function ( coordinates ) {
3191 if ( ! $.geo._isGeodetic( coordinates ) ) {
3195 var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
3196 fromGeodeticPos = this.fromGeodeticPos;
3198 if (!isMultiPointOrLineString && coordinates.length == 4) {
3200 var min = fromGeodeticPos([ coordinates[ 0 ], coordinates[ 1 ] ]),
3201 max = fromGeodeticPos([ coordinates[ 2 ], coordinates[ 3 ] ]);
3202 return [ min[ 0 ], min[ 1 ], max[ 0 ], max[ 1 ] ];
3205 var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
3206 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
3210 if (!isMultiPolygon) {
3211 if (!isMultiLineStringOrPolygon) {
3212 if (!isMultiPointOrLineString) {
3213 coordinates = [ coordinates ];
3215 coordinates = [ coordinates ];
3217 coordinates = [ coordinates ];
3220 for ( i = 0; i < coordinates.length; i++ ) {
3222 for ( j = 0; j < coordinates[ i ].length; j++ ) {
3223 result[ i ][ j ] = [ ];
3224 for ( k = 0; k < coordinates[ i ][ j ].length; k++ ) {
3225 result[ i ][ j ][ k ] = fromGeodeticPos(coordinates[ i ][ j ][ k ]);
3230 return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
3234 toGeodeticPos: function (coordinate) {
3236 (coordinate[ 0 ] / semiMajorAxis) * degreesPerRadian,
3237 (halfPi - 2 * Math.atan(1 / Math.exp(coordinate[ 1 ] / semiMajorAxis))) * degreesPerRadian
3241 toGeodetic: function (coordinates) {
3242 if ( $.geo._isGeodetic( coordinates ) ) {
3246 var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
3247 toGeodeticPos = this.toGeodeticPos;
3249 if (!isMultiPointOrLineString && coordinates.length == 4) {
3251 var min = toGeodeticPos([ coordinates[ 0 ], coordinates[ 1 ] ]),
3252 max = toGeodeticPos([ coordinates[ 2 ], coordinates[ 3 ] ]);
3253 return [ min[ 0 ], min[ 1 ], max[ 0 ], max[ 1 ] ];
3256 var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
3257 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
3260 if (!isMultiPolygon) {
3261 if (!isMultiLineStringOrPolygon) {
3262 if (!isMultiPointOrLineString) {
3263 coordinates = [ coordinates ];
3265 coordinates = [ coordinates ];
3267 coordinates = [ coordinates ];
3270 for ( i = 0; i < coordinates.length; i++ ) {
3272 for ( j = 0; j < coordinates[ i ].length; j++ ) {
3273 result[ i ][ j ] = [ ];
3274 for ( k = 0; k < coordinates[ i ][ j ].length; k++ ) {
3275 result[ i ][ j ][ k ] = toGeodeticPos(coordinates[ i ][ j ][ k ]);
3280 return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
3287 // service types (defined in other files)
3293 (function ($, undefined) {
3295 var _ieVersion = (function () {
3296 var v = 5, div = document.createElement("div"), a = div.all || [];
3297 while (div.innerHTML = "<!--[if gt IE " + (++v) + "]><br><![endif]-->", a[0]) { }
3298 return v > 6 ? v : !v;
3301 $.widget("geo.geographics", {
3309 _$canvas: undefined,
3310 _context: undefined,
3311 _$labelsContainer: undefined,
3315 borderRadius: "8px",
3321 //stroke: undefined,
3324 visibility: "visible",
3329 _create: function () {
3330 this._$elem = this.element;
3331 this._options = this.options;
3333 this._$elem.css({ display: "inline-block", overflow: "hidden", textAlign: "left" });
3335 if (this._$elem.css("position") == "static") {
3336 this._$elem.css("position", "relative");
3339 this._$elem.addClass( "geo-graphics" );
3341 this._width = this._$elem.width();
3342 this._height = this._$elem.height();
3344 if (!(this._width && this._height)) {
3345 this._width = parseInt(this._$elem.css("width"));
3346 this._height = parseInt(this._$elem.css("height"));
3349 var posCss = 'position:absolute;left:0;top:0;margin:0;padding:0;',
3350 sizeCss = 'width:' + this._width + 'px;height:' + this._height + 'px;',
3351 sizeAttr = 'width="' + this._width + '" height="' + this._height + '"';
3353 if (document.createElement('canvas').getContext) {
3354 this._$elem.append('<canvas ' + sizeAttr + ' style="' + posCss + '"></canvas>');
3355 this._$canvas = this._$elem.children(':last');
3356 this._context = this._$canvas[0].getContext("2d");
3357 } else if (_ieVersion <= 8) {
3358 this._trueCanvas = false;
3359 this._$elem.append( '<div ' + sizeAttr + ' style="' + posCss + sizeCss + '"></div>');
3360 this._$canvas = this._$elem.children(':last');
3362 G_vmlCanvasManager.initElement(this._$canvas[0]);
3363 this._context = this._$canvas[0].getContext("2d");
3364 this._$canvas.children().css({ backgroundColor: "transparent", width: this._width, height: this._height });
3367 this._$elem.append('<div class="geo-labels-container" style="' + posCss + sizeCss + '"></div>');
3368 this._$labelsContainer = this._$elem.children(':last');
3371 _setOption: function (key, value) {
3372 if (key == "style") {
3373 value = $.extend({}, this._options.style, value);
3375 $.Widget.prototype._setOption.apply(this, arguments);
3378 destroy: function () {
3379 $.Widget.prototype.destroy.apply(this, arguments);
3380 this._$elem.html("");
3381 this._$elem.removeClass( "geo-graphics" );
3384 clear: function () {
3385 this._context.clearRect(0, 0, this._width, this._height);
3386 this._$labelsContainer.html("");
3389 drawArc: function (coordinates, startAngle, sweepAngle, style) {
3390 style = this._getGraphicStyle(style);
3392 if (style.visibility != "hidden" && style.opacity > 0 && style.widthValue > 0 && style.heightValue > 0) {
3393 var r = Math.min(style.widthValue, style.heightValue) / 2;
3395 startAngle = (startAngle * Math.PI / 180);
3396 sweepAngle = (sweepAngle * Math.PI / 180);
3398 this._context.save();
3399 this._context.translate(coordinates[0], coordinates[1]);
3400 if (style.widthValue > style.heightValue) {
3401 this._context.scale(style.widthValue / style.heightValue, 1);
3403 this._context.scale(1, style.heightValue / style.widthValue);
3406 this._context.beginPath();
3407 this._context.arc(0, 0, r, startAngle, sweepAngle, false);
3409 if (this._trueCanvas) {
3410 this._context.restore();
3414 this._context.fillStyle = style.fill;
3415 this._context.globalAlpha = style.opacity * style.fillOpacity;
3416 this._context.fill();
3419 if (style.doStroke) {
3420 this._context.lineJoin = "round";
3421 this._context.lineWidth = style.strokeWidthValue;
3422 this._context.strokeStyle = style.stroke;
3424 this._context.globalAlpha = style.opacity * style.strokeOpacity;
3425 this._context.stroke();
3428 if (!this._trueCanvas) {
3429 this._context.restore();
3434 drawPoint: function (coordinates, style) {
3435 var style = this._getGraphicStyle(style);
3436 if (style.widthValue == style.heightValue && style.heightValue == style.borderRadiusValue) {
3437 this.drawArc(coordinates, 0, 360, style);
3438 } else if (style.visibility != "hidden" && style.opacity > 0) {
3439 style.borderRadiusValue = Math.min(Math.min(style.widthValue, style.heightValue) / 2, style.borderRadiusValue);
3440 coordinates[0] -= style.widthValue / 2;
3441 coordinates[1] -= style.heightValue / 2;
3442 this._context.beginPath();
3443 this._context.moveTo(coordinates[0] + style.borderRadiusValue, coordinates[1]);
3444 this._context.lineTo(coordinates[0] + style.widthValue - style.borderRadiusValue, coordinates[1]);
3445 this._context.quadraticCurveTo(coordinates[0] + style.widthValue, coordinates[1], coordinates[0] + style.widthValue, coordinates[1] + style.borderRadiusValue);
3446 this._context.lineTo(coordinates[0] + style.widthValue, coordinates[1] + style.heightValue - style.borderRadiusValue);
3447 this._context.quadraticCurveTo(coordinates[0] + style.widthValue, coordinates[1] + style.heightValue, coordinates[0] + style.widthValue - style.borderRadiusValue, coordinates[1] + style.heightValue);
3448 this._context.lineTo(coordinates[0] + style.borderRadiusValue, coordinates[1] + style.heightValue);
3449 this._context.quadraticCurveTo(coordinates[0], coordinates[1] + style.heightValue, coordinates[0], coordinates[1] + style.heightValue - style.borderRadiusValue);
3450 this._context.lineTo(coordinates[0], coordinates[1] + style.borderRadiusValue);
3451 this._context.quadraticCurveTo(coordinates[0], coordinates[1], coordinates[0] + style.borderRadiusValue, coordinates[1]);
3452 this._context.closePath();
3455 this._context.fillStyle = style.fill;
3456 this._context.globalAlpha = style.opacity * style.fillOpacity;
3457 this._context.fill();
3460 if (style.doStroke) {
3461 this._context.lineJoin = "round";
3462 this._context.lineWidth = style.strokeWidthValue;
3463 this._context.strokeStyle = style.stroke;
3465 this._context.globalAlpha = style.opacity * style.strokeOpacity;
3467 this._context.stroke();
3472 drawLineString: function (coordinates, style) {
3473 this._drawLines([coordinates], false, style);
3476 drawPolygon: function (coordinates, style) {
3477 this._drawLines(coordinates, true, style);
3480 drawBbox: function (bbox, style) {
3490 drawLabel: function( coordinates, label ) {
3491 this._$labelsContainer.append( '<div class="geo-label" style="position:absolute; left:' + coordinates[ 0 ] + 'px; top:' + coordinates[ 1 ] + 'px;">' + label + '</div>');
3494 resize: function( ) {
3495 this._width = this._$elem.width();
3496 this._height = this._$elem.height();
3498 if (!(this._width && this._height)) {
3499 this._width = parseInt(this._$elem.css("width"));
3500 this._height = parseInt(this._$elem.css("height"));
3503 if ( this._trueCanvas ) {
3504 this._$canvas[0].width = this._width;
3505 this._$canvas[0].height = this._height;
3509 this._$labelsContainer.css( {
3511 height: this._height
3515 _getGraphicStyle: function (style) {
3516 function safeParse(value) {
3517 value = parseInt(value);
3518 return (+value + '') === value ? +value : value;
3521 style = $.extend({}, this._options.style, style);
3522 style.borderRadiusValue = safeParse(style.borderRadius);
3523 style.fill = style.fill || style.color;
3524 style.doFill = style.fill && style.fillOpacity > 0;
3525 style.stroke = style.stroke || style.color;
3526 style.strokeWidthValue = safeParse(style.strokeWidth);
3527 style.doStroke = style.stroke && style.strokeOpacity > 0 && style.strokeWidthValue > 0;
3528 style.widthValue = safeParse(style.width);
3529 style.heightValue = safeParse(style.height);
3533 _drawLines: function (coordinates, close, style) {
3534 if (!coordinates || !coordinates.length || coordinates[0].length < 2) {
3538 var style = this._getGraphicStyle(style),
3541 if (style.visibility != "hidden" && style.opacity > 0) {
3542 this._context.beginPath();
3543 this._context.moveTo(coordinates[0][0][0], coordinates[0][0][1]);
3545 for (i = 0; i < coordinates.length; i++) {
3546 for (j = 0; j < coordinates[i].length; j++) {
3547 this._context.lineTo(coordinates[i][j][0], coordinates[i][j][1]);
3552 this._context.closePath();
3555 if (close && style.doFill) {
3556 this._context.fillStyle = style.fill;
3557 this._context.globalAlpha = style.opacity * style.fillOpacity;
3558 this._context.fill();
3561 if (style.doStroke) {
3562 this._context.lineCap = this._context.lineJoin = "round";
3563 this._context.lineWidth = style.strokeWidthValue;
3564 this._context.strokeStyle = style.stroke;
3566 this._context.globalAlpha = style.opacity * style.strokeOpacity;
3567 this._context.stroke();
3576 (function ($, undefined) {
3577 var _ieVersion = (function () {
3578 var v = 5, div = document.createElement("div"), a = div.all || [];
3579 while (div.innerHTML = "<!--[if gt IE " + (++v) + "]><br><![endif]-->", a[0]) { }
3580 return v > 6 ? v : !v;
3584 bbox: [-180, -85, 180, 85],
3585 bboxMax: [-180, -85, 180, 85],
3588 "static": "default",
3589 pan: "url(data:image/vnd.microsoft.icon;base64,AAACAAEAICACAAgACAAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAAAEAAAAAAAAAAAAAAgAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAA/AAAAfwAAAP+AAAH/gAAB/8AAA//AAAd/wAAGf+AAAH9gAADbYAAA2yAAAZsAAAGbAAAAGAAAAAAAAA//////////////////////////////////////////////////////////////////////////////////////gH///4B///8Af//+AD///AA///wAH//4AB//8AAf//AAD//5AA///gAP//4AD//8AF///AB///5A////5///8=), move",
3591 drawPoint: "crosshair",
3592 drawLineString: "crosshair",
3593 drawPolygon: "crosshair",
3594 measureLength: "crosshair",
3595 measureArea: "crosshair"
3598 length: "{{=length.toFixed( 2 )}} m",
3599 area: "{{=area.toFixed( 2 )}} sq m"
3610 src: function (view) {
3611 return "http://tile.openstreetmap.org/" + view.zoom + "/" + view.tile.column + "/" + view.tile.row + ".png";
3613 attr: "© OpenStreetMap & contributors, CC-BY-SA"
3620 basePixelSize: 156543.03392799936,
3621 origin: [-20037508.342787, 20037508.342787]
3628 $.widget("geo.geomap", {
3629 // private widget members
3630 _$elem: undefined, //< map div for maps, service div for services
3631 _map: undefined, //< only defined in services
3636 _$resizeContainer: undefined, //< all elements that should match _contentBounds' size
3638 _$eventTarget: undefined,
3639 _$contentFrame: undefined,
3640 _$existingChildren: undefined,
3641 _$attrList: undefined,
3642 _$servicesContainer: undefined,
3644 _$panContainer: undefined, //< all non-service elements that move while panning
3645 _$shapesContainer: undefined,
3646 _$drawContainer: undefined,
3647 _$measureContainer: undefined,
3648 _$measureLabel: undefined,
3652 _currentServices: [], //< internal copy
3655 _pixelSize: undefined,
3656 _centerMax: undefined,
3657 _pixelSizeMax: undefined,
3659 _userGeodetic: true,
3661 _wheelTimeout: null,
3664 _zoomFactor: 2, //< determines what a zoom level means
3666 _fullZoomFactor: 2, //< interactiveScale factor needed to zoom a whole level
3667 _partialZoomFactor: 1.18920711500273, //< interactiveScale factor needed to zoom a fraction of a level (the fourth root of 2)
3669 _mouseDown: undefined,
3671 _toolPan: undefined,
3672 _shiftZoom: undefined,
3674 _current: undefined,
3675 _downDate: undefined,
3676 _moveDate: undefined,
3677 _clickDate: undefined,
3678 _lastMove: undefined,
3679 _lastDrag: undefined,
3681 _windowHandler: null,
3682 _resizeTimeout: null,
3684 _panning: undefined,
3685 _velocity: undefined,
3686 _friction: undefined,
3688 _supportTouch: undefined,
3689 _softDblClick: undefined,
3691 _isDbltap: undefined,
3693 _isMultiTouch: undefined,
3694 _multiTouchAnchor: undefined, //< TouchList
3695 _multiTouchAnchorBbox: undefined, //< bbox
3696 _multiTouchCurrentBbox: undefined, //< bbox
3698 _drawTimeout: null, //< used in drawPoint mode so we don't send two shape events on dbltap
3699 _drawPixels: [], //< an array of coordinate arrays for drawing lines & polygons, in pixel coordinates
3702 _graphicShapes: [], //< an array of objects containing style object refs & GeoJSON object refs
3708 options: $.extend({}, _defaultOptions),
3710 _createWidget: function (options, element) {
3711 this._$elem = $(element);
3713 if (this._$elem.is(".geo-service")) {
3714 var $contentFrame = this._$elem.closest( ".geo-content-frame" );
3715 this._$elem.append('<div class="geo-shapes-container" style="position:absolute; left:0; top:0; width:' + $contentFrame.css( "width" ) + '; height:' + $contentFrame.css( "height" ) + '; margin:0; padding:0;"></div>');
3716 this._$shapesContainer = this._$elem.children(':last');
3717 this._graphicShapes = [];
3718 $.Widget.prototype._createWidget.apply(this, arguments);
3722 this._$elem.addClass("geo-map");
3724 this._initOptions = options || {};
3726 this._forcePosition(this._$elem);
3728 this._$elem.css("text-align", "left");
3730 var size = this._findMapSize();
3731 this._contentBounds = {
3732 x: parseInt(this._$elem.css("padding-left")),
3733 y: parseInt(this._$elem.css("padding-top")),
3734 width: size["width"],
3735 height: size["height"]
3738 this._createChildren();
3740 this._center = this._centerMax = [0, 0];
3742 this.options["pixelSize"] = this._pixelSize = this._pixelSizeMax = 156543.03392799936;
3750 this._isDbltap = false;
3752 this._anchor = [ 0, 0 ];
3753 this._current = [ 0, 0 ];
3754 this._lastMove = [ 0, 0 ];
3755 this._lastDrag = [ 0, 0 ];
3756 this._velocity = [ 0, 0 ];
3758 this._friction = [.8, .8];
3762 this._clickDate = 0;
3764 this._drawPixels = [];
3765 this._drawCoords = [];
3766 this._graphicShapes = [];
3769 $.Widget.prototype._createWidget.apply(this, arguments);
3772 _create: function () {
3773 this._options = this.options;
3775 if (this._$elem.is(".geo-service")) {
3776 this._map = this._$elem.data( "geoMap" );
3777 this._$shapesContainer.geographics( );
3778 this._options["shapeStyle"] = this._$shapesContainer.geographics("option", "style");
3784 this._supportTouch = "ontouchend" in document;
3785 this._softDblClick = this._supportTouch || _ieVersion == 7;
3788 touchStartEvent = this._supportTouch ? "touchstart" : "mousedown",
3789 touchStopEvent = this._supportTouch ? "touchend touchcancel" : "mouseup",
3790 touchMoveEvent = this._supportTouch ? "touchmove" : "mousemove";
3792 $(document).keydown($.proxy(this._document_keydown, this));
3794 this._$eventTarget.dblclick($.proxy(this._eventTarget_dblclick, this));
3796 this._$eventTarget.bind(touchStartEvent, $.proxy(this._eventTarget_touchstart, this));
3798 var dragTarget = (this._$eventTarget[0].setCapture) ? this._$eventTarget : $(document);
3799 dragTarget.bind(touchMoveEvent, $.proxy(this._dragTarget_touchmove, this));
3800 dragTarget.bind(touchStopEvent, $.proxy(this._dragTarget_touchstop, this));
3802 this._$eventTarget.mousewheel($.proxy(this._eventTarget_mousewheel, this));
3804 this._windowHandler = function () {
3805 if (geomap._resizeTimeout) {
3806 clearTimeout(geomap._resizeTimeout);
3808 geomap._resizeTimeout = setTimeout(function () {
3809 if (geomap._created) {
3810 geomap._$elem.geomap("resize");
3815 $(window).resize(this._windowHandler);
3817 this._$drawContainer.geographics({ style: this._initOptions.drawStyle || {} });
3818 this._options["drawStyle"] = this._$drawContainer.geographics("option", "style");
3820 this._$shapesContainer.geographics( { style: this._initOptions.shapeStyle || { } } );
3821 this._options["shapeStyle"] = this._$shapesContainer.geographics("option", "style");
3823 if (this._initOptions) {
3824 if (this._initOptions.tilingScheme) {
3825 this._setOption("tilingScheme", this._initOptions.tilingScheme, false);
3827 if ( this._initOptions.services ) {
3828 // jQuery UI Widget Factory merges user services with our default, we want to clobber the default
3829 this._options[ "services" ] = $.merge( [ ], this._initOptions.services );
3831 if (this._initOptions.bbox) {
3832 this._setOption("bbox", this._initOptions.bbox, false);
3834 if (this._initOptions.center) {
3835 this._setOption("center", this._initOptions.center, false);
3837 if (this._initOptions.zoom !== undefined) {
3838 this._setZoom(this._initOptions.zoom, false, false);
3842 $.template( "geoMeasureLength", this._options[ "measureLabels" ].length );
3843 $.template( "geoMeasureArea", this._options[ "measureLabels" ].area );
3845 this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
3847 this._createServices();
3850 this._created = true;
3853 _setOption: function (key, value, refresh) {
3854 if ( key == "pixelSize" ) {
3858 refresh = (refresh === undefined || refresh);
3860 if ( this._$elem.is( ".geo-map" ) ) {
3861 this._panFinalize();
3866 this._userGeodetic = $.geo.proj && $.geo._isGeodetic( value );
3867 if ( this._userGeodetic ) {
3868 value = $.geo.proj.fromGeodetic( value );
3871 this._setBbox(value, false, refresh);
3872 value = this._getBbox();
3876 this._userGeodetic = $.geo.proj && $.geo._isGeodetic( value );
3877 if ( this._userGeodetic ) {
3878 value = $.geo.proj.fromGeodetic( value );
3881 this._setCenterAndSize( value, this._pixelSize, false, refresh );
3884 case "measureLabels":
3885 value = $.extend( this._options[ "measureLabels" ], value );
3886 $.template( "geoMeasureLength", value.length );
3887 $.template( "geoMeasureArea", value.area );
3891 if (this._$drawContainer) {
3892 this._$drawContainer.geographics("option", "style", value);
3893 value = this._$drawContainer.geographics("option", "style");
3898 if (this._$shapesContainer) {
3899 this._$shapesContainer.geographics("option", "style", value);
3900 value = this._$shapesContainer.geographics("option", "style");
3905 this._resetDrawing( );
3906 this._$eventTarget.css("cursor", this._options["cursors"][value]);
3910 this._setZoom(value, false, refresh);
3914 $.Widget.prototype._setOption.apply(this, arguments);
3919 if ( this._userGeodetic ) {
3920 this._options[ "bbox" ] = $.geo.proj.toGeodetic( this._options[ "bbox" ] );
3921 this._options[ "center" ] = $.geo.proj.toGeodetic( this._center );
3925 case "tilingScheme":
3926 if ( value != null ) {
3927 this._pixelSizeMax = this._getPixelSize( 0 );
3929 value.origin[ 0 ] + this._pixelSizeMax * value.tileWidth / 2,
3930 value.origin[ 1 ] + this._pixelSizeMax * value.tileHeight / 2
3936 this._pixelSizeMax = this._getPixelSize( 0 );
3938 if ( $.geo.proj && $.geo._isGeodetic( value ) ) {
3939 this._centerMax = $.geo.center( $.geo.proj.fromGeodetic( value ) );
3941 this._centerMax = $.geo.center( value );
3946 this._createServices();
3954 this._$shapesContainer.geographics("clear");
3955 this._refreshShapes( this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes );
3961 destroy: function () {
3962 if ( this._$elem.is(".geo-service") ) {
3963 this._$shapesContainer.geographics("destroy");
3964 this._$shapesContainer = undefined;
3966 this._created = false;
3968 $(window).unbind("resize", this._windowHandler);
3970 for ( var i = 0; i < this._currentServices.length; i++ ) {
3971 this._currentServices[ i ].serviceContainer.geomap("destroy");
3972 $.geo["_serviceTypes"][this._currentServices[i].type].destroy(this, this._$servicesContainer, this._currentServices[i]);
3975 this._$shapesContainer.geographics("destroy");
3976 this._$shapesContainer = undefined;
3977 this._$drawContainer.geographics("destroy");
3978 this._$drawContainer = undefined;
3980 this._$existingChildren.detach();
3981 this._$elem.html("");
3982 this._$elem.append(this._$existingChildren);
3983 this._$elem.removeClass("geo-map");
3986 $.Widget.prototype.destroy.apply(this, arguments);
3989 toMap: function (p) {
3991 return this._userGeodetic ? $.geo.proj.toGeodetic(p) : p;
3994 toPixel: function ( p, _center /* Internal Use Only */, _pixelSize /* Internal Use Only */ ) {
3995 return this._toPixel( $.geo.proj ? $.geo.proj.fromGeodetic( p ) : p, _center, _pixelSize );
3998 opacity: function ( value, _serviceContainer ) {
3999 if ( this._$elem.is( ".geo-service" ) ) {
4000 this._$elem.closest( ".geo-map" ).geomap( "opacity", value, this._$elem );
4002 if ( value >= 0 || value <= 1 ) {
4003 for ( var i = 0; i < this._currentServices.length; i++ ) {
4004 var service = this._currentServices[ i ];
4005 if ( !_serviceContainer || service.serviceContainer[ 0 ] == _serviceContainer[ 0 ] ) {
4006 service.style.opacity = value;
4007 $.geo[ "_serviceTypes" ][ service.type ].opacity( this, service );
4014 toggle: function ( value, _serviceContainer ) {
4015 if ( this._$elem.is( ".geo-service" ) ) {
4016 this._$elem.closest( ".geo-map" ).geomap( "toggle", value, this._$elem );
4019 for ( var i = 0; i < this._currentServices.length; i++ ) {
4020 var service = this._currentServices[ i ];
4022 if ( !_serviceContainer || service.serviceContainer[ 0 ] == _serviceContainer[ 0 ] ) {
4023 if ( value === undefined ) {
4024 // toggle visibility
4025 value = ( service.style.visibility !== "visible" );
4028 service.style.visibility = ( value ? "visible" : "hidden" );
4030 service.serviceContainer.toggle( value );
4033 $.geo[ "_serviceTypes" ][ service.type ].refresh( this, service );
4040 zoom: function (numberOfLevels) {
4041 if (numberOfLevels != null) {
4042 this._setZoom(this._options["zoom"] + numberOfLevels, false, true);
4046 refresh: function () {
4050 resize: function () {
4051 var size = this._findMapSize(),
4052 dx = size["width"]/2 - this._contentBounds.width/2,
4053 dy = size["height"]/2 - this._contentBounds.height/2,
4056 this._contentBounds = {
4057 x: parseInt(this._$elem.css("padding-left")),
4058 y: parseInt(this._$elem.css("padding-top")),
4059 width: size["width"],
4060 height: size["height"]
4063 this._$resizeContainer.css( {
4064 width: size["width"],
4065 height: size["height"]
4068 for (i = 0; i < this._currentServices.length; i++) {
4069 $.geo["_serviceTypes"][this._currentServices[i].type].resize(this, this._currentServices[i]);
4072 this._$elem.find( ".geo-graphics" ).css( {
4073 width: size["width"],
4074 height: size["height"]
4075 }).geographics( "resize" );
4077 for (i = 0; i < this._drawPixels.length; i++) {
4078 this._drawPixels[i][0] += dx;
4079 this._drawPixels[i][1] += dy;
4082 this._setCenterAndSize(this._center, this._pixelSize, false, true);
4085 append: function ( shape, style, label, refresh ) {
4086 if ( shape && $.isPlainObject( shape ) ) {
4087 var shapes, arg, i, realStyle, realLabel, realRefresh;
4089 if ( shape.type == "FeatureCollection" ) {
4090 shapes = shape.features;
4092 shapes = $.isArray( shape ) ? shape : [ shape ];
4095 for ( i = 1; i < arguments.length; i++ ) {
4096 arg = arguments[ i ];
4098 if ( typeof arg === "object" ) {
4100 } else if ( typeof arg === "number" || typeof arg === "string" ) {
4102 } else if ( typeof arg === "boolean" ) {
4107 for ( i = 0; i < shapes.length; i++ ) {
4108 if ( shapes[ i ].type != "Point" ) {
4109 var bbox = $.geo.bbox( shapes[ i ] );
4110 if ( $.geo.proj && $.geo._isGeodetic( bbox ) ) {
4111 bbox = $.geo.proj.fromGeodetic( bbox );
4113 $.data( shapes[ i ], "geoBbox", bbox );
4116 this._graphicShapes.push( {
4123 if ( realRefresh === undefined || realRefresh ) {
4129 empty: function ( refresh ) {
4130 for ( var i = 0; i < this._graphicShapes.length; i++ ) {
4131 $.removeData( this._graphicShapes[ i ].shape, "geoBbox" );
4134 this._graphicShapes = [];
4136 if ( refresh === undefined || refresh ) {
4141 find: function ( selector, pixelTolerance ) {
4142 var isPoint = $.isPlainObject( selector ),
4143 searchPixel = isPoint ? this._map.toPixel( selector.coordinates ) : undefined,
4144 mapTol = this._map._pixelSize * pixelTolerance,
4151 for ( ; i < this._graphicShapes.length; i++ ) {
4152 graphicShape = this._graphicShapes[ i ];
4155 if ( graphicShape.shape.type == "Point" ) {
4156 if ( $.geo.distance( graphicShape.shape, selector ) <= mapTol ) {
4157 result.push( graphicShape.shape );
4160 var bbox = $.data( graphicShape.shape, "geoBbox" ),
4173 coordinates: $.geo.proj && $.geo._isGeodetic( selector.coordinates ) ? $.geo.proj.fromGeodetic( selector.coordinates ) : selector.coordinates
4176 if ( $.geo.distance( bboxPolygon, projectedPoint, true ) <= mapTol ) {
4177 geometries = $.geo._flatten( graphicShape.shape );
4178 for ( curGeom = 0; curGeom < geometries.length; curGeom++ ) {
4179 if ( $.geo.distance( geometries[ curGeom ], selector ) <= mapTol ) {
4180 result.push( graphicShape.shape );
4187 result.push( graphicShape.shape );
4191 if ( this._$elem.is( ".geo-map" ) ) {
4192 this._$elem.find( ".geo-service" ).each( function( ) {
4193 result = $.merge( result, $( this ).geomap( "find", selector, pixelTolerance ) );
4200 remove: function ( shape, refresh ) {
4201 for ( var i = 0; i < this._graphicShapes.length; i++ ) {
4202 if ( this._graphicShapes[ i ].shape == shape ) {
4203 $.removeData( shape, "geoBbox" );
4204 var rest = this._graphicShapes.slice( i + 1 );
4205 this._graphicShapes.length = i;
4206 this._graphicShapes.push.apply( this._graphicShapes, rest );
4211 if ( refresh === undefined || refresh ) {
4216 _getBbox: function (center, pixelSize) {
4217 center = center || this._center;
4218 pixelSize = pixelSize || this._pixelSize;
4220 // calculate the internal bbox
4221 var halfWidth = this._contentBounds[ "width" ] / 2 * pixelSize,
4222 halfHeight = this._contentBounds[ "height" ] / 2 * pixelSize;
4223 return [ center[ 0 ] - halfWidth, center[ 1 ] - halfHeight, center[ 0 ] + halfWidth, center[ 1 ] + halfHeight ];
4226 _setBbox: function (value, trigger, refresh) {
4227 var center = [value[0] + (value[2] - value[0]) / 2, value[1] + (value[3] - value[1]) / 2],
4228 pixelSize = Math.max($.geo.width(value, true) / this._contentBounds.width, $.geo.height(value, true) / this._contentBounds.height);
4230 if (this._options["tilingScheme"]) {
4231 var zoom = this._getZoom( center, pixelSize );
4232 pixelSize = this._getPixelSize( zoom );
4234 if ( this._getZoom( center, pixelSize ) < 0 ) {
4235 pixelSize = this._pixelSizeMax;
4239 this._setCenterAndSize(center, pixelSize, trigger, refresh);
4242 _getBboxMax: function () {
4243 // calculate the internal bboxMax
4244 var halfWidth = this._contentBounds["width"] / 2 * this._pixelSizeMax,
4245 halfHeight = this._contentBounds["height"] / 2 * this._pixelSizeMax;
4246 return [this._centerMax[0] - halfWidth, this._centerMax[1] - halfHeight, this._centerMax[0] + halfWidth, this._centerMax[1] + halfHeight];
4249 _getCenter: function () {
4250 return this._center;
4253 _getContentBounds: function () {
4254 return this._contentBounds;
4257 _getServicesContainer: function () {
4258 return this._$servicesContainer;
4261 _getZoom: function ( center, pixelSize ) {
4262 center = center || this._center;
4263 pixelSize = pixelSize || this._pixelSize;
4265 // calculate the internal zoom level, vs. public zoom property
4266 var tilingScheme = this._options["tilingScheme"];
4267 if ( tilingScheme ) {
4268 if ( tilingScheme.pixelSizes != null ) {
4269 var roundedPixelSize = Math.floor(pixelSize * 1000),
4270 levels = tilingScheme.pixelSizes.length,
4273 for ( ; i >= 0; i-- ) {
4274 if ( Math.floor( tilingScheme.pixelSizes[ i ] * 1000 ) >= roundedPixelSize ) {
4281 return Math.max( Math.round( Math.log( tilingScheme.basePixelSize / pixelSize) / Math.log( 2 ) ), 0 );
4284 var ratio = this._contentBounds["width"] / this._contentBounds["height"],
4285 bbox = $.geo.reaspect( this._getBbox( center, pixelSize ), ratio, true ),
4286 bboxMax = $.geo.reaspect(this._getBboxMax(), ratio, true);
4288 return Math.max( Math.round( Math.log($.geo.width(bboxMax, true) / $.geo.width(bbox, true)) / Math.log(this._zoomFactor) ), 0 );
4292 _setZoom: function ( value, trigger, refresh ) {
4293 value = Math.max( value, 0 );
4295 this._setCenterAndSize( this._center, this._getPixelSize( value ), trigger, refresh );
4298 _createChildren: function () {
4299 this._$existingChildren = this._$elem.children().detach();
4301 this._forcePosition(this._$existingChildren);
4303 this._$existingChildren.css("-moz-user-select", "none");
4305 var contentSizeCss = "width:" + this._contentBounds["width"] + "px; height:" + this._contentBounds["height"] + "px; margin:0; padding:0;",
4306 contentPosCss = "position:absolute; left:0; top:0;";
4308 this._$elem.prepend('<div class="geo-event-target geo-content-frame" style="position:absolute; left:' + this._contentBounds.x + 'px; top:' + this._contentBounds.y + 'px;' + contentSizeCss + 'overflow:hidden; -khtml-user-select:none; -moz-user-select:none; -webkit-user-select:none; user-select:none;" unselectable="on"></div>');
4309 this._$eventTarget = this._$contentFrame = this._$elem.children(':first');
4311 this._$contentFrame.append('<div class="geo-services-container" style="' + contentPosCss + contentSizeCss + '"></div>');
4312 this._$servicesContainer = this._$contentFrame.children(':last');
4314 this._$contentFrame.append('<div class="geo-shapes-container" style="' + contentPosCss + contentSizeCss + '"></div>');
4315 this._$shapesContainer = this._$contentFrame.children(':last');
4317 this._$contentFrame.append( '<ul style="position: absolute; bottom: 8px; left: 8px; list-style-type: none; max-width: 50%; padding: 0; margin: 0;"></ul>' );
4318 this._$attrList = this._$contentFrame.children( ":last" );
4320 this._$contentFrame.append('<div class="geo-draw-container" style="' + contentPosCss + contentSizeCss + '"></div>');
4321 this._$drawContainer = this._$contentFrame.children(':last');
4323 this._$contentFrame.append('<div class="geo-measure-container" style="' + contentPosCss + contentSizeCss + '"><div class="geo-measure-label" style="' + contentPosCss + '; display: none;"></div></div>');
4324 this._$measureContainer = this._$contentFrame.children(':last');
4325 this._$measureLabel = this._$measureContainer.children();
4327 this._$panContainer = $( [ this._$shapesContainer[ 0 ], this._$drawContainer[ 0 ], this._$measureContainer[ 0 ] ] );
4329 this._$resizeContainer = $( [ this._$contentFrame[ 0 ], this._$servicesContainer[ 0 ], this._$eventTarget[ 0 ], this._$measureContainer[ 0 ] ] );
4331 this._$contentFrame.append(this._$existingChildren);
4333 if ( ! $("#geo-measure-style").length ) {
4334 $("head").prepend( '<style type="text/css" id="geo-measure-style">.geo-measure-label { margin: 4px 0 0 6px; font-family: sans-serif;' + ( _ieVersion ? 'letter-spacing: 2px; color: #444; filter:progid:DXImageTransform.Microsoft.DropShadow(Color=white, OffX=1, OffY=2, Positive=true);' : 'color: #000; text-shadow: #fff 1px 2px; font-weight: bold;' ) + ' }</style>' );
4338 _createServices: function () {
4341 for ( i = 0; i < this._currentServices.length; i++ ) {
4342 this._currentServices[ i ].serviceContainer.geomap( "destroy" );
4343 $.geo[ "_serviceTypes" ][ this._currentServices[ i ].type ].destroy( this, this._$servicesContainer, this._currentServices[ i ] );
4346 this._currentServices = [ ];
4347 this._$servicesContainer.html( "" );
4348 this._$attrList.html( "" );
4350 for ( i = 0; i < this._options[ "services" ].length; i++ ) {
4351 service = this._currentServices[ i ] = $.extend( { }, this._options[ "services" ][ i ] );
4353 // default the service style property on our copy
4354 service.style = $.extend( {
4355 visibility: "visible",
4359 var idString = service.id ? ' id="' + service.id + '"' : "",
4360 classString = 'class="geo-service ' + ( service["class"] ? service["class"] : '' ) + '"',
4361 scHtml = '<div ' + idString + classString + ' style="position:absolute; left:0; top:0; width:32px; height:32px; margin:0; padding:0; display:' + ( service.style.visibility === "visible" ? "block" : "none" ) + ';"></div>',
4364 this._$servicesContainer.append( scHtml );
4365 serviceContainer = this._$servicesContainer.children( ":last" );
4366 this._currentServices[ i ].serviceContainer = serviceContainer;
4368 $.geo[ "_serviceTypes" ][ service.type ].create( this, serviceContainer, service, i );
4370 serviceContainer.data( "geoMap", this ).geomap();
4372 if ( service.attr ) {
4373 this._$attrList.append( '<li>' + service.attr + '</li>' );
4377 this._$attrList.find( "a" ).css( {
4378 position: "relative",
4383 _refreshDrawing: function ( ) {
4384 this._$drawContainer.geographics("clear");
4386 if ( this._drawPixels.length > 0 ) {
4387 var mode = this._options[ "mode" ],
4388 pixels = this._drawPixels,
4389 coords = this._drawCoords,
4397 case "measureLength":
4398 mode = "drawLineString";
4403 label = $.render( { length: $.geo.length( labelShape, true ) }, "geoMeasureLength" );
4404 labelPixel = $.merge( [], pixels[ pixels.length - 1 ] );
4408 mode = "drawPolygon";
4412 coordinates: [ $.merge( [ ], coords ) ]
4414 labelShape.coordinates[ 0 ].push( coords[ 0 ] );
4416 label = $.render( { area: $.geo.area( labelShape, true ) }, "geoMeasureArea" );
4417 labelPixel = $.merge( [], pixels[ pixels.length - 1 ] );
4418 pixels = [ pixels ];
4422 pixels = [ pixels ];
4426 this._$drawContainer.geographics( mode, pixels );
4429 this._$measureLabel.html( label );
4431 widthOver = this._contentBounds.width - ( this._$measureLabel.outerWidth( true ) + labelPixel[ 0 ] );
4432 heightOver = this._contentBounds.height - ( this._$measureLabel.outerHeight( true ) + labelPixel[ 1 ] );
4434 if ( widthOver < 0 ) {
4435 labelPixel[ 0 ] += widthOver;
4438 if ( heightOver < 0 ) {
4439 labelPixel[ 1 ] += heightOver;
4442 this._$measureLabel.css( {
4443 left: labelPixel[ 0 ],
4444 top: labelPixel[ 1 ]
4450 _resetDrawing: function () {
4451 this._drawPixels = [];
4452 this._drawCoords = [];
4453 this._$drawContainer.geographics("clear");
4454 this._$measureLabel.hide();
4457 _refreshShapes: function (geographics, shapes, styles, labels, center, pixelSize) {
4465 bbox = this._map._getBbox(center, pixelSize);
4467 for (i = 0; i < shapes.length; i++) {
4468 shape = shapes[i].shape || shapes[i];
4469 shape = shape.geometry || shape;
4470 shapeBbox = $.data(shape, "geoBbox");
4472 if ( shapeBbox && $.geo._bboxDisjoint( bbox, shapeBbox ) ) {
4476 style = $.isArray(styles) ? styles[i].style : styles;
4477 label = $.isArray(labels) ? labels[i].label : labels;
4478 hasLabel = ( label !== undefined );
4479 labelPixel = undefined;
4481 switch (shape.type) {
4483 labelPixel = this._map.toPixel( shape.coordinates, center, pixelSize );
4484 this._$shapesContainer.geographics("drawPoint", labelPixel, style);
4487 this._$shapesContainer.geographics("drawLineString", this._map.toPixel(shape.coordinates, center, pixelSize), style);
4489 labelPixel = this._map.toPixel( $.geo.pointAlong( shape, .5 ).coordinates, center, pixelSize );
4493 this._$shapesContainer.geographics("drawPolygon", this._map.toPixel(shape.coordinates, center, pixelSize), style);
4495 labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
4499 for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
4500 this._$shapesContainer.geographics("drawPoint", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
4503 labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
4506 case "MultiLineString":
4507 for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
4508 this._$shapesContainer.geographics("drawLineString", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
4511 labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
4514 case "MultiPolygon":
4515 for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
4516 this._$shapesContainer.geographics("drawPolygon", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
4519 labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
4523 case "GeometryCollection":
4524 this._refreshShapes(geographics, shape.geometries, style, label, center, pixelSize);
4528 if ( hasLabel && labelPixel ) {
4529 this._$shapesContainer.geographics( "drawLabel", labelPixel, label );
4534 _findMapSize: function () {
4535 // really, really attempt to find a size for this thing
4536 // even if it's hidden (look at parents)
4537 var size = { width: 0, height: 0 },
4538 sizeContainer = this._$elem;
4540 while (sizeContainer.size() && !(size["width"] > 0 && size["height"] > 0)) {
4541 size = { width: sizeContainer.width(), height: sizeContainer.height() };
4542 if (size["width"] <= 0 || size["height"] <= 0) {
4543 size = { width: parseInt(sizeContainer.css("width")), height: parseInt(sizeContainer.css("height")) };
4545 sizeContainer = sizeContainer.parent();
4550 _forcePosition: function (elem) {
4551 var cssPosition = elem.css("position");
4552 if (cssPosition != "relative" && cssPosition != "absolute" && cssPosition != "fixed") {
4553 elem.css("position", "relative");
4557 _getPixelSize: function ( zoom ) {
4558 var tilingScheme = this._options["tilingScheme"];
4559 if (tilingScheme != null) {
4561 return tilingScheme.pixelSizes != null ? tilingScheme.pixelSizes[0] : tilingScheme.basePixelSize;
4564 zoom = Math.round(zoom);
4565 zoom = Math.max(zoom, 0);
4566 var levels = tilingScheme.pixelSizes != null ? tilingScheme.pixelSizes.length : tilingScheme.levels;
4567 zoom = Math.min(zoom, levels - 1);
4569 if (tilingScheme.pixelSizes != null) {
4570 return tilingScheme.pixelSizes[zoom];
4572 return tilingScheme.basePixelSize / Math.pow(2, zoom);
4575 var bbox = $.geo.scaleBy( this._getBboxMax(), 1 / Math.pow( this._zoomFactor, zoom ), true );
4576 return Math.max( $.geo.width( bbox, true ) / this._contentBounds.width, $.geo.height( bbox, true ) / this._contentBounds.height );
4580 _getZoomCenterAndSize: function ( anchor, zoomDelta, full ) {
4581 var zoomFactor = ( full ? this._fullZoomFactor : this._partialZoomFactor ),
4582 scale = Math.pow( zoomFactor, -zoomDelta ),
4586 if ( this._options[ "tilingScheme" ] ) {
4587 zoomLevel = this._getZoom(this._center, this._pixelSize * scale);
4588 pixelSize = this._getPixelSize(zoomLevel);
4590 pixelSize = this._pixelSize * scale;
4592 if ( this._getZoom( this._center, pixelSize ) < 0 ) {
4593 pixelSize = this._pixelSizeMax;
4597 var ratio = pixelSize / this._pixelSize,
4598 anchorMapCoord = this._toMap(anchor),
4599 centerDelta = [(this._center[0] - anchorMapCoord[0]) * ratio, (this._center[1] - anchorMapCoord[1]) * ratio],
4600 scaleCenter = [anchorMapCoord[0] + centerDelta[0], anchorMapCoord[1] + centerDelta[1]];
4602 return { pixelSize: pixelSize, center: scaleCenter };
4605 _mouseWheelFinish: function () {
4606 this._wheelTimeout = null;
4608 if (this._wheelLevel != 0) {
4609 var wheelCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, this._options[ "tilingScheme" ] != null );
4611 this._setCenterAndSize(wheelCenterAndSize.center, wheelCenterAndSize.pixelSize, true, true);
4613 this._wheelLevel = 0;
4619 _panEnd: function () {
4621 (this._velocity[0] > 0 ? Math.floor(this._velocity[0] * this._friction[0]) : Math.ceil(this._velocity[0] * this._friction[0])),
4622 (this._velocity[1] > 0 ? Math.floor(this._velocity[1] * this._friction[1]) : Math.ceil(this._velocity[1] * this._friction[1]))
4625 if (Math.abs(this._velocity[0]) < 4 && Math.abs(this._velocity[1]) < 4) {
4626 this._panFinalize();
4629 this._current[0] + this._velocity[0],
4630 this._current[1] + this._velocity[1]
4634 setTimeout($.proxy(this._panEnd, this), 30);
4638 _panFinalize: function () {
4639 if (this._panning) {
4640 this._velocity = [0, 0];
4642 var dx = this._current[0] - this._anchor[0],
4643 dy = this._current[1] - this._anchor[1],
4644 image = this._options[ "axisLayout" ] === "image",
4645 dxMap = -dx * this._pixelSize,
4646 dyMap = ( image ? -1 : 1 ) * dy * this._pixelSize;
4648 this._$panContainer.css({ left: 0, top: 0 });
4650 this._$servicesContainer.find( ".geo-shapes-container" ).css( { left: 0, top: 0 } );
4652 this._setCenterAndSize([this._center[0] + dxMap, this._center[1] + dyMap], this._pixelSize, true, true);
4654 this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
4657 this._anchor = this._current;
4658 this._mouseDown = this._toolPan = this._panning = false;
4662 _panMove: function () {
4663 if ( ! this._options[ "pannable" ] ) {
4667 var dx = this._current[0] - this._lastDrag[0],
4668 dy = this._current[1] - this._lastDrag[1],
4673 if (this._toolPan || dx > 3 || dx < -3 || dy > 3 || dy < -3) {
4674 if (!this._toolPan) {
4675 this._toolPan = true;
4676 this._$eventTarget.css("cursor", this._options["cursors"]["pan"]);
4679 if (this._mouseDown) {
4680 this._velocity = [dx, dy];
4683 if (dx != 0 || dy != 0) {
4684 this._panning = true;
4685 this._lastDrag = this._current;
4688 left: function (index, value) {
4689 return parseInt(value) + dx;
4691 top: function (index, value) {
4692 return parseInt(value) + dy;
4696 for ( i = 0; i < this._currentServices.length; i++ ) {
4697 service = this._currentServices[ i ];
4698 $.geo[ "_serviceTypes" ][ service.type ].interactivePan( this, service, dx, dy );
4700 service.serviceContainer.find( ".geo-shapes-container" ).css( translateObj );
4703 this._$panContainer.css( translateObj );
4705 //this._refreshDrawing();
4710 _refresh: function () {
4714 if ( this._$elem.is( ".geo-map" ) ) {
4715 for ( ; i < this._currentServices.length; i++ ) {
4716 service = this._currentServices[ i ];
4718 if ( !this._mouseDown && $.geo[ "_serviceTypes" ][ service.type ] !== null ) {
4719 $.geo[ "_serviceTypes" ][ service.type ].refresh( this, service );
4720 service.serviceContainer.geomap( "refresh" );
4725 if ( this._$shapesContainer ) {
4726 this._$shapesContainer.geographics( "clear" );
4727 if ( this._graphicShapes.length > 0 ) {
4728 this._refreshShapes( this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes );
4733 _setCenterAndSize: function (center, pixelSize, trigger, refresh) {
4734 if ( ! $.isArray( center ) || center.length != 2 || typeof center[ 0 ] !== "number" || typeof center[ 1 ] !== "number" ) {
4738 // the final call during any extent change
4739 if (this._pixelSize != pixelSize) {
4740 this._$elem.find( ".geo-shapes-container" ).geographics("clear");
4741 for (var i = 0; i < this._currentServices.length; i++) {
4742 var service = this._currentServices[i];
4743 $.geo["_serviceTypes"][service.type].interactiveScale(this, service, center, pixelSize);
4747 this._center = $.merge( [ ], center );
4748 this._options["pixelSize"] = this._pixelSize = pixelSize;
4750 if ( this._userGeodetic ) {
4751 this._options["bbox"] = $.geo.proj.toGeodetic( this._getBbox() );
4752 this._options["center"] = $.geo.proj.toGeodetic( this._center );
4754 this._options["bbox"] = this._getBbox();
4755 this._options["center"] = $.merge( [ ], center );
4758 this._options["zoom"] = this._getZoom();
4760 if (this._drawCoords.length > 0) {
4761 this._drawPixels = this._toPixel(this._drawCoords);
4765 this._trigger("bboxchange", window.event, { bbox: $.merge( [ ], this._options["bbox"] ) });
4770 this._refreshDrawing();
4774 _toMap: function (p, center, pixelSize) {
4775 // ignores $.geo.proj
4777 center = center || this._center;
4778 pixelSize = pixelSize || this._pixelSize;
4780 var isMultiPointOrLineString = $.isArray( p[ 0 ] ),
4781 isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray( p[ 0 ][ 0 ] ),
4782 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray( p[ 0 ][ 0 ][ 0 ] ),
4783 width = this._contentBounds["width"],
4784 height = this._contentBounds["height"],
4785 halfWidth = width / 2 * pixelSize,
4786 halfHeight = height / 2 * pixelSize,
4787 bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight],
4788 xRatio = $.geo.width(bbox, true) / width,
4789 yRatio = $.geo.height(bbox, true) / height,
4791 image = this._options[ "axisLayout" ] === "image",
4795 if ( !isMultiPolygon ) {
4796 if ( !isMultiLineStringOrPolygon ) {
4797 if ( !isMultiPointOrLineString ) {
4805 for ( i = 0; i < p.length; i++ ) {
4807 for ( j = 0; j < p[ i ].length; j++ ) {
4808 result[ i ][ j ] = [ ];
4809 for ( k = 0; k < p[ i ][ j ].length; k++ ) {
4810 yOffset = (p[ i ][ j ][ k ][1] * yRatio);
4811 result[ i ][ j ][ k ] = [
4812 bbox[ 0 ] + ( p[ i ][ j ][ k ][ 0 ] * xRatio ),
4813 image ? bbox[ 1 ] + yOffset : bbox[ 3 ] - yOffset
4819 return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
4822 _toPixel: function (p, center, pixelSize) {
4823 // ignores $.geo.proj
4825 center = center || this._center;
4826 pixelSize = pixelSize || this._pixelSize;
4828 var isMultiPointOrLineString = $.isArray( p[ 0 ] ),
4829 isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray( p[ 0 ][ 0 ] ),
4830 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray( p[ 0 ][ 0 ][ 0 ] ),
4831 width = this._contentBounds["width"],
4832 height = this._contentBounds["height"],
4833 halfWidth = width / 2 * pixelSize,
4834 halfHeight = height / 2 * pixelSize,
4835 bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight],
4836 bboxWidth = $.geo.width(bbox, true),
4837 bboxHeight = $.geo.height(bbox, true),
4838 image = this._options[ "axisLayout" ] === "image",
4839 xRatio = width / bboxWidth,
4840 yRatio = height / bboxHeight,
4844 if ( !isMultiPolygon ) {
4845 if ( !isMultiLineStringOrPolygon ) {
4846 if ( !isMultiPointOrLineString ) {
4854 for ( i = 0; i < p.length; i++ ) {
4856 for ( j = 0; j < p[ i ].length; j++ ) {
4857 result[ i ][ j ] = [ ];
4858 for ( k = 0; k < p[ i ][ j ].length; k++ ) {
4859 result[ i ][ j ][ k ] = [
4860 Math.round( ( p[ i ][ j ][ k ][ 0 ] - bbox[ 0 ] ) * xRatio ),
4861 Math.round( ( image ? p[ i ][ j ][ k ][ 1 ] - bbox[ 1 ] : bbox[ 3 ] - p[ i ][ j ][ k ][ 1 ] ) * yRatio )
4867 return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
4870 _zoomTo: function (coord, zoom, trigger, refresh) {
4871 zoom = zoom < 0 ? 0 : zoom;
4873 var pixelSize = this._getPixelSize( zoom );
4875 this._setCenterAndSize( coord, pixelSize, trigger, refresh );
4878 _document_keydown: function (e) {
4879 var len = this._drawCoords.length;
4880 if (len > 0 && e.which == 27) {
4882 this._resetDrawing();
4885 this._drawCoords[len - 2] = $.merge( [], this._drawCoords[ len - 1 ] );
4886 this._drawPixels[len - 2] = $.merge( [], this._drawPixels[ len - 1 ] );
4888 this._drawCoords.length--;
4889 this._drawPixels.length--;
4891 this._refreshDrawing();
4896 _eventTarget_dblclick_zoom: function(e) {
4897 this._trigger("dblclick", e, { type: "Point", coordinates: this.toMap(this._current) });
4898 if (!e.isDefaultPrevented()) {
4899 var centerAndSize = this._getZoomCenterAndSize(this._current, 1, true );
4900 this._setCenterAndSize(centerAndSize.center, centerAndSize.pixelSize, true, true);
4904 _eventTarget_dblclick: function (e) {
4905 if ( this._options[ "mode" ] === "static" ) {
4909 this._panFinalize();
4911 if (this._drawTimeout) {
4912 window.clearTimeout(this._drawTimeout);
4913 this._drawTimeout = null;
4916 var offset = $(e.currentTarget).offset();
4918 switch (this._options["mode"]) {
4919 case "drawLineString":
4920 if ( this._drawCoords.length > 1 && ! ( this._drawCoords[0][0] == this._drawCoords[1][0] &&
4921 this._drawCoords[0][1] == this._drawCoords[1][1] ) ) {
4922 this._drawCoords.length--;
4923 this._trigger( "shape", e, {
4925 coordinates: this._userGeodetic ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords
4928 this._eventTarget_dblclick_zoom(e);
4930 this._resetDrawing();
4934 if ( this._drawCoords.length > 1 && ! ( this._drawCoords[0][0] == this._drawCoords[1][0] &&
4935 this._drawCoords[0][1] == this._drawCoords[1][1] ) ) {
4936 var endIndex = this._drawCoords.length - 1;
4938 this._drawCoords[endIndex] = $.merge( [], this._drawCoords[0] );
4939 this._trigger( "shape", e, {
4941 coordinates: [ this._userGeodetic ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords ]
4945 this._eventTarget_dblclick_zoom(e);
4947 this._resetDrawing();
4950 case "measureLength":
4952 this._resetDrawing();
4956 this._eventTarget_dblclick_zoom(e);
4963 _eventTarget_touchstart: function (e) {
4964 if ( this._options[ "mode" ] === "static" ) {
4968 if ( !this._supportTouch && e.which != 1 ) {
4972 this._panFinalize();
4973 this._mouseWheelFinish();
4975 var offset = $(e.currentTarget).offset(),
4976 touches = e.originalEvent.changedTouches;
4978 if ( this._supportTouch ) {
4979 this._multiTouchAnchor = $.merge( [ ], touches );
4981 this._isMultiTouch = this._multiTouchAnchor.length > 1;
4983 if ( this._isMultiTouch ) {
4984 this._multiTouchCurrentBbox = [
4985 touches[0].pageX - offset.left,
4986 touches[0].pageY - offset.top,
4987 touches[1].pageX - offset.left,
4988 touches[1].pageY - offset.top
4991 this._multiTouchAnchorBbox = $.merge( [ ], this._multiTouchCurrentBbox );
4993 this._current = $.geo.center( this._multiTouchCurrentBbox, true );
4995 this._multiTouchCurrentBbox = [
4996 touches[0].pageX - offset.left,
4997 touches[0].pageY - offset.top,
5002 this._current = [ touches[0].pageX - offset.left, touches[0].pageY - offset.top ];
5005 this._current = [e.pageX - offset.left, e.pageY - offset.top];
5008 if (this._softDblClick) {
5009 var downDate = $.now();
5010 if (downDate - this._downDate < 750) {
5012 var dx = this._current[0] - this._anchor[0],
5013 dy = this._current[1] - this._anchor[1],
5014 distance = Math.sqrt((dx * dx) + (dy * dy));
5016 this._isTap = false;
5018 this._current = $.merge( [ ], this._anchor );
5022 if (this._isDbltap) {
5023 this._isDbltap = false;
5025 this._isDbltap = this._isTap;
5028 this._isDbltap = false;
5031 this._downDate = downDate;
5034 this._mouseDown = true;
5035 this._anchor = $.merge( [ ], this._current );
5037 if (!this._inOp && e.shiftKey) {
5038 this._shiftZoom = true;
5039 this._$eventTarget.css("cursor", this._options["cursors"]["zoom"]);
5040 } else if ( !this._isMultiTouch && this._options[ "pannable" ] ) {
5043 switch (this._options["mode"]) {
5048 this._lastDrag = this._current;
5050 if (e.currentTarget.setCapture) {
5051 e.currentTarget.setCapture();
5062 _dragTarget_touchmove: function (e) {
5063 if ( this._options[ "mode" ] === "static" ) {
5067 var offset = this._$eventTarget.offset(),
5068 drawCoordsLen = this._drawCoords.length,
5069 touches = e.originalEvent.changedTouches,
5074 if ( this._supportTouch ) {
5075 if ( !this._isMultiTouch && touches[ 0 ].identifier !== this._multiTouchAnchor[ 0 ].identifier ) {
5076 // switch to multitouch
5077 this._mouseDown = false;
5078 this._dragTarget_touchstop( e );
5080 this._isMultiTouch = true;
5082 this._multiTouchAnchor.push( touches[ 0 ] );
5084 this._multiTouchCurrentBbox = [
5085 this._multiTouchCurrentBbox[ 0 ],
5086 this._multiTouchCurrentBbox[ 1 ],
5087 this._multiTouchAnchor[1].pageX - offset.left,
5088 this._multiTouchAnchor[1].pageY - offset.top
5091 this._multiTouchAnchorBbox = $.merge( [ ], this._multiTouchCurrentBbox );
5093 this._mouseDown = true;
5094 this._anchor = this._current = $.geo.center( this._multiTouchCurrentBbox, true );
5099 if ( this._isMultiTouch ) {
5100 for ( ; i < touches.length; i++ ) {
5101 if ( touches[ i ].identifier === this._multiTouchAnchor[ 0 ].identifier ) {
5102 this._multiTouchCurrentBbox[ 0 ] = touches[ i ].pageX - offset.left;
5103 this._multiTouchCurrentBbox[ 1 ] = touches[ i ].pageY - offset.top;
5104 } else if ( touches[ i ].identifier === this._multiTouchAnchor[ 1 ].identifier ) {
5105 this._multiTouchCurrentBbox[ 2 ] = touches[ i ].pageX - offset.left;
5106 this._multiTouchCurrentBbox[ 3 ] = touches[ i ].pageY - offset.top;
5110 current = $.geo.center( this._multiTouchCurrentBbox, true );
5112 var currentWidth = this._multiTouchCurrentBbox[ 2 ] - this._multiTouchCurrentBbox[ 0 ],
5113 anchorWidth = this._multiTouchAnchorBbox[ 2 ] - this._multiTouchAnchorBbox[ 0 ],
5114 ratioWidth = currentWidth / anchorWidth;
5116 this._wheelLevel = Math.abs( Math.floor( ( 1 - ratioWidth ) * 10 ) );
5117 if ( Math.abs( currentWidth ) < Math.abs( anchorWidth ) ) {
5118 this._wheelLevel = - this._wheelLevel;
5121 var pinchCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, false );
5122 this._$elem.find( ".geo-shapes-container" ).geographics("clear");
5124 for ( i = 0; i < this._currentServices.length; i++ ) {
5125 service = this._currentServices[ i ];
5126 $.geo[ "_serviceTypes" ][ service.type ].interactiveScale( this, service, pinchCenterAndSize.center, pinchCenterAndSize.pixelSize );
5129 if (this._graphicShapes.length > 0 && this._graphicShapes.length < 256) {
5130 this._refreshShapes(this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes, pinchCenterAndSize.center, pinchCenterAndSize.pixelSize);
5134 if (this._drawCoords.length > 0) {
5135 this._drawPixels = this._toPixel(this._drawCoords, pinchCenterAndSize.center, pinchCenterAndSize.pixelSize);
5136 this._refreshDrawing();
5139 current = $.geo.center( this._multiTouchCurrentBbox, true );
5141 current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
5144 current = [e.pageX - offset.left, e.pageY - offset.top];
5147 if (current[0] === this._lastMove[0] && current[1] === this._lastMove[1]) {
5154 if ( _ieVersion == 7 ) {
5155 this._isDbltap = this._isTap = false;
5158 if (this._mouseDown) {
5159 this._current = current;
5160 this._moveDate = $.now();
5163 if ( this._isMultiTouch ) {
5164 e.preventDefault( );
5165 this._isDbltap = this._isTap = false;
5169 var mode = this._shiftZoom ? "zoom" : this._options["mode"];
5173 if ( this._mouseDown ) {
5174 this._$drawContainer.geographics( "clear" );
5175 this._$drawContainer.geographics( "drawBbox", [
5182 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
5186 case "drawLineString":
5188 case "measureLength":
5190 if (this._mouseDown || this._toolPan) {
5193 if (drawCoordsLen > 0) {
5194 this._drawCoords[drawCoordsLen - 1] = this._toMap(current);
5195 this._drawPixels[drawCoordsLen - 1] = current;
5197 this._refreshDrawing();
5200 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
5205 if (this._mouseDown || this._toolPan) {
5208 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
5213 this._lastMove = current;
5221 _dragTarget_touchstop: function (e) {
5222 if ( this._options[ "mode" ] === "static" ) {
5226 if (!this._mouseDown && _ieVersion == 7) {
5227 // ie7 doesn't appear to trigger dblclick on this._$eventTarget,
5228 // we fake regular click here to cause soft dblclick
5229 this._eventTarget_touchstart(e);
5232 var mouseWasDown = this._mouseDown,
5233 wasToolPan = this._toolPan,
5234 offset = this._$eventTarget.offset(),
5235 mode = this._shiftZoom ? "zoom" : this._options["mode"],
5236 current, i, clickDate,
5239 if (this._supportTouch) {
5240 current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
5242 current = [e.pageX - offset.left, e.pageY - offset.top];
5245 if (this._softDblClick) {
5247 var dx = current[0] - this._anchor[0],
5248 dy = current[1] - this._anchor[1],
5249 distance = Math.sqrt((dx * dx) + (dy * dy));
5250 if (distance <= 8) {
5251 current = $.merge( [ ], this._anchor );
5256 dx = current[0] - this._anchor[0];
5257 dy = current[1] - this._anchor[1];
5259 this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
5261 this._shiftZoom = this._mouseDown = this._toolPan = false;
5263 if ( this._isMultiTouch ) {
5264 e.preventDefault( );
5265 this._isMultiTouch = false;
5267 var pinchCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, false );
5269 this._setCenterAndSize(pinchCenterAndSize.center, pinchCenterAndSize.pixelSize, true, true);
5271 this._wheelLevel = 0;
5276 if (document.releaseCapture) {
5277 document.releaseCapture();
5281 clickDate = $.now();
5282 this._current = current;
5286 if ( dx > 0 || dy > 0 ) {
5287 var minSize = this._pixelSize * 6,
5288 bboxCoords = this._toMap( [ [
5289 Math.min( this._anchor[ 0 ], current[ 0 ] ),
5290 Math.max( this._anchor[ 1 ], current[ 1 ] )
5292 Math.max( this._anchor[ 0 ], current[ 0 ] ),
5293 Math.min( this._anchor[ 1 ], current[ 1 ] )
5303 if ( ( bbox[2] - bbox[0] ) < minSize && ( bbox[3] - bbox[1] ) < minSize ) {
5304 bbox = $.geo.scaleBy( this._getBbox( $.geo.center( bbox, true ) ), .5, true );
5307 this._setBbox(bbox, true, true);
5310 this._resetDrawing();
5314 if (this._drawTimeout) {
5315 window.clearTimeout(this._drawTimeout);
5316 this._drawTimeout = null;
5320 this._panFinalize();
5322 if (clickDate - this._clickDate > 100) {
5324 this._drawTimeout = setTimeout(function () {
5325 if (geomap._drawTimeout) {
5326 geomap._trigger("shape", e, { type: "Point", coordinates: geomap.toMap(current) });
5327 geomap._inOp = false;
5328 geomap._drawTimeout = null;
5335 case "drawLineString":
5337 case "measureLength":
5340 this._panFinalize();
5342 i = (this._drawCoords.length == 0 ? 0 : this._drawCoords.length - 1);
5344 this._drawCoords[i] = this._toMap(current);
5345 this._drawPixels[i] = current;
5347 if (i < 2 || !(this._drawCoords[i][0] == this._drawCoords[i-1][0] &&
5348 this._drawCoords[i][1] == this._drawCoords[i-1][1])) {
5349 this._drawCoords[i + 1] = this._toMap(current);
5350 this._drawPixels[i + 1] = current;
5353 this._refreshDrawing();
5361 if (clickDate - this._clickDate > 100) {
5362 this._trigger("click", e, { type: "Point", coordinates: this.toMap(current) });
5369 this._clickDate = clickDate;
5371 if (this._softDblClick && this._isDbltap) {
5372 this._isDbltap = this._isTap = false;
5373 this._$eventTarget.trigger("dblclick", e);
5383 _eventTarget_mousewheel: function (e, delta) {
5384 if ( this._options[ "mode" ] === "static" || this._options[ "scroll" ] === "off" ) {
5390 this._panFinalize();
5392 if ( this._mouseDown ) {
5397 if (this._wheelTimeout) {
5398 window.clearTimeout(this._wheelTimeout);
5399 this._wheelTimeout = null;
5401 var offset = $(e.currentTarget).offset();
5402 this._anchor = [e.pageX - offset.left, e.pageY - offset.top];
5405 this._wheelLevel += delta;
5407 var wheelCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, this._options[ "tilingScheme" ] != null ),
5411 this._$elem.find( ".geo-shapes-container" ).geographics("clear");
5413 for ( ; i < this._currentServices.length; i++ ) {
5414 service = this._currentServices[ i ];
5415 $.geo["_serviceTypes"][service.type].interactiveScale(this, service, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
5418 if (this._graphicShapes.length > 0 && this._graphicShapes.length < 256) {
5419 this._refreshShapes(this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
5422 if (this._drawCoords.length > 0) {
5423 this._drawPixels = this._toPixel(this._drawCoords, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
5424 this._refreshDrawing();
5428 this._wheelTimeout = window.setTimeout(function () {
5429 geomap._mouseWheelFinish();
5439 (function ($, undefined) {
5440 $.geo._serviceTypes.tiled = (function () {
5442 create: function (map, serviceContainer, service, index) {
5443 var serviceState = $.data(service, "geoServiceState");
5445 if ( !serviceState ) {
5451 var scHtml = '<div data-geo-service="tiled" style="position:absolute; left:0; top:0; width:8px; height:8px; margin:0; padding:0;"></div>';
5453 serviceContainer.append(scHtml);
5455 serviceState.serviceContainer = serviceContainer.children( ":last" );
5457 $.data(service, "geoServiceState", serviceState);
5460 return serviceState.serviceContainer;
5463 destroy: function (map, serviceContainer, service) {
5464 var serviceState = $.data(service, "geoServiceState");
5466 serviceState.serviceContainer.remove();
5468 $.removeData(service, "geoServiceState");
5471 interactivePan: function ( map, service, dx, dy ) {
5472 var serviceState = $.data( service, "geoServiceState" );
5474 if ( serviceState ) {
5475 this._cancelUnloaded( map, service );
5477 serviceState.serviceContainer.children( ).css( "-moz-transition", "").css( {
5478 webkitTransition: "",
5480 left: function ( index, value ) {
5481 return parseInt( value ) + dx;
5483 top: function ( index, value ) {
5484 return parseInt( value ) + dy;
5488 if ( service && service.style.visibility === "visible" ) {
5489 var pixelSize = map._pixelSize,
5492 serviceContainer = serviceState.serviceContainer,
5493 scaleContainer = serviceContainer.children("[data-pixelSize='" + pixelSize + "']"),
5495 /* same as refresh 1 */
5496 contentBounds = map._getContentBounds(),
5497 mapWidth = contentBounds["width"],
5498 mapHeight = contentBounds["height"],
5500 image = map.options[ "axisLayout" ] === "image",
5501 ySign = image ? +1 : -1,
5503 tilingScheme = map.options["tilingScheme"],
5504 tileWidth = tilingScheme.tileWidth,
5505 tileHeight = tilingScheme.tileHeight,
5506 /* end same as refresh 1 */
5508 halfWidth = mapWidth / 2 * pixelSize,
5509 halfHeight = mapHeight / 2 * pixelSize,
5511 currentPosition = scaleContainer.position(),
5512 scaleOriginParts = scaleContainer.data("scaleOrigin").split(","),
5513 totalDx = parseInt(scaleOriginParts[0]) - currentPosition.left,
5514 totalDy = parseInt(scaleOriginParts[1]) - currentPosition.top,
5516 mapCenterOriginal = map._getCenter(),
5518 mapCenterOriginal[0] + totalDx * pixelSize,
5519 mapCenterOriginal[1] + ySign * totalDy * pixelSize
5522 /* same as refresh 2 */
5523 tileX = Math.floor(((mapCenter[0] - halfWidth) - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
5524 tileY = Math.max( Math.floor(( image ? (mapCenter[1] - halfHeight) - tilingScheme.origin[1] : tilingScheme.origin[1] - (mapCenter[1] + halfHeight)) / (pixelSize * tileHeight)), 0 ),
5525 tileX2 = Math.ceil(((mapCenter[0] + halfWidth) - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
5526 tileY2 = Math.ceil(( image ? (mapCenter[1] + halfHeight) - tilingScheme.origin[1] : tilingScheme.origin[1] - (mapCenter[1] - halfHeight)) / (pixelSize * tileHeight)),
5528 bboxMax = map._getBboxMax(),
5529 pixelSizeAtZero = map._getPixelSize(0),
5530 ratio = pixelSizeAtZero / pixelSize,
5531 fullXAtScale = Math.floor((bboxMax[0] - tilingScheme.origin[0]) / (pixelSizeAtZero * tileWidth)) * ratio,
5532 fullYAtScale = Math.floor((tilingScheme.origin[1] + ySign * bboxMax[3]) / (pixelSizeAtZero * tileHeight)) * ratio,
5534 fullXMinX = tilingScheme.origin[0] + (fullXAtScale * tileWidth) * pixelSize,
5535 fullYMinOrMaxY = tilingScheme.origin[1] + ySign * (fullYAtScale * tileHeight) * pixelSize,
5536 /* end same as refresh 2 */
5538 serviceLeft = Math.round((fullXMinX - (mapCenterOriginal[0] - halfWidth)) / pixelSize),
5539 serviceTop = Math.round(( image ? fullYMinOrMaxY - (mapCenterOriginal[1] - halfHeight) : (mapCenterOriginal[1] + halfHeight) - fullYMinOrMaxY ) / pixelSize),
5541 opacity = service.style.opacity,
5545 for ( x = tileX; x < tileX2; x++ ) {
5546 for ( y = tileY; y < tileY2; y++ ) {
5547 var tileStr = "" + x + "," + y,
5548 $img = scaleContainer.children("[data-tile='" + tileStr + "']").removeAttr("data-dirty");
5550 if ( $img.size( ) === 0 ) {
5551 /* same as refresh 3 */
5553 tilingScheme.origin[0] + (x * tileWidth) * pixelSize,
5554 tilingScheme.origin[1] + ySign * (y * tileHeight) * pixelSize
5558 tilingScheme.origin[0] + ((x + 1) * tileWidth - 1) * pixelSize,
5559 tilingScheme.origin[1] + ySign * ((y + 1) * tileHeight - 1) * pixelSize
5562 tileBbox = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]],
5564 urlProp = ( service.hasOwnProperty("src") ? "src" : "getUrl" ),
5569 zoom: map._getZoom(),
5574 index: Math.abs(y + x)
5576 isFunc = $.isFunction( service[ urlProp ] ),
5580 imageUrl = service[ urlProp ]( urlArgs );
5582 $.template( "geoSrc", service[ urlProp ] );
5583 imageUrl = $.render( urlArgs, "geoSrc" );
5585 /* end same as refresh 3 */
5587 serviceState.loadCount++;
5588 //this._map._requestQueued();
5590 if ( serviceState.reloadTiles && $img.size() > 0 ) {
5591 $img.attr( "src", imageUrl );
5593 /* same as refresh 4 */
5594 var imgMarkup = "<img style='position:absolute; " +
5595 "left:" + (((x - fullXAtScale) * 100) + (serviceLeft - (serviceLeft % tileWidth)) / tileWidth * 100) + "%; " +
5596 "top:" + (((y - fullYAtScale) * 100) + (serviceTop - (serviceTop % tileHeight)) / tileHeight * 100) + "%; ";
5598 if ($("body")[0].filters === undefined) {
5599 imgMarkup += "width: 100%; height: 100%;";
5602 imgMarkup += "margin:0; padding:0; -khtml-user-select:none; -moz-user-select:none; -webkit-user-select:none; user-select:none; display:none;' unselectable='on' data-tile='" + tileStr + "' />";
5604 scaleContainer.append( imgMarkup );
5605 $img = scaleContainer.children(":last");
5608 if ( typeof imageUrl === "string" ) {
5609 serviceObj._loadImage( $img, imageUrl, pixelSize, serviceState, serviceContainer, opacity );
5612 imageUrl.done( function( url ) {
5613 serviceObj._loadImage( $img, url, pixelSize, serviceState, serviceContainer, opacity );
5614 } ).fail( function( ) {
5616 serviceState.loadCount--;
5620 /* end same as refresh 4 */
5628 interactiveScale: function (map, service, center, pixelSize) {
5629 var serviceState = $.data( service, "geoServiceState" );
5631 if ( serviceState && service && service.style.visibility === "visible" ) {
5632 this._cancelUnloaded(map, service);
5634 var serviceContainer = serviceState.serviceContainer,
5636 tilingScheme = map.options["tilingScheme"],
5637 tileWidth = tilingScheme.tileWidth,
5638 tileHeight = tilingScheme.tileHeight;
5641 serviceContainer.children( ).each( function ( i ) {
5642 var $scaleContainer = $(this),
5643 scaleRatio = $scaleContainer.attr("data-pixelSize") / pixelSize,
5644 transitionCss = ""; //"width .25s ease-in, height .25s ease-in, left .25s ease-in, top .25s ease-in";
5646 scaleRatio = Math.round(scaleRatio * 1000) / 1000;
5649 var scaleOriginParts = $scaleContainer.data("scaleOrigin").split(","),
5650 oldMapCoord = map._toMap([scaleOriginParts[0], scaleOriginParts[1]]),
5651 newPixelPoint = map._toPixel(oldMapCoord, center, pixelSize);
5653 $scaleContainer.css( "-moz-transition", transitionCss ).css( {
5654 webkitTransition: transitionCss,
5655 transition: transitionCss,
5656 left: Math.round(newPixelPoint[0]) + "px",
5657 top: Math.round(newPixelPoint[1]) + "px",
5658 width: tileWidth * scaleRatio,
5659 height: tileHeight * scaleRatio
5662 if ( $("body")[0].filters !== undefined ) {
5663 $scaleContainer.children().each( function ( i ) {
5664 $( this ).css( "filter", "progid:DXImageTransform.Microsoft.Matrix(FilterType=bilinear,M11=" + scaleRatio + ",M22=" + scaleRatio + ",sizingmethod='auto expand')" );
5671 refresh: function (map, service) {
5672 var serviceState = $.data( service, "geoServiceState" );
5674 this._cancelUnloaded(map, service);
5676 if ( serviceState && service && service.style.visibility === "visible" && !( serviceState.serviceContainer.is( ":hidden" ) ) ) {
5678 var bbox = map._getBbox(),
5679 pixelSize = map._pixelSize,
5682 $serviceContainer = serviceState.serviceContainer,
5684 contentBounds = map._getContentBounds(),
5685 mapWidth = contentBounds["width"],
5686 mapHeight = contentBounds["height"],
5688 image = map.options[ "axisLayout" ] === "image",
5689 ySign = image ? +1 : -1,
5691 tilingScheme = map.options["tilingScheme"],
5692 tileWidth = tilingScheme.tileWidth,
5693 tileHeight = tilingScheme.tileHeight,
5695 tileX = Math.floor((bbox[0] - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
5696 tileY = Math.max( Math.floor( ( image ? bbox[1] - tilingScheme.origin[1] : tilingScheme.origin[1] - bbox[ 3 ] ) / (pixelSize * tileHeight) ), 0 ),
5697 tileX2 = Math.ceil((bbox[2] - tilingScheme.origin[0]) / (pixelSize * tileWidth)),
5698 tileY2 = Math.ceil( ( image ? bbox[3] - tilingScheme.origin[1] : tilingScheme.origin[1] - bbox[ 1 ] ) / (pixelSize * tileHeight) ),
5700 bboxMax = map._getBboxMax(),
5701 pixelSizeAtZero = map._getPixelSize(0),
5702 ratio = pixelSizeAtZero / pixelSize,
5703 fullXAtScale = Math.floor((bboxMax[0] - tilingScheme.origin[0]) / (pixelSizeAtZero * tileWidth)) * ratio,
5704 fullYAtScale = Math.floor((tilingScheme.origin[1] + ySign * bboxMax[3]) / (pixelSizeAtZero * tileHeight)) * ratio,
5706 fullXMinX = tilingScheme.origin[0] + (fullXAtScale * tileWidth) * pixelSize,
5707 fullYMinOrMaxY = tilingScheme.origin[1] + ySign * (fullYAtScale * tileHeight) * pixelSize,
5709 serviceLeft = Math.round((fullXMinX - bbox[0]) / pixelSize),
5710 serviceTop = Math.round( ( image ? fullYMinOrMaxY - bbox[1] : bbox[3] - fullYMinOrMaxY ) / pixelSize),
5712 scaleContainers = $serviceContainer.children().show(),
5713 scaleContainer = scaleContainers.filter("[data-pixelSize='" + pixelSize + "']").appendTo($serviceContainer),
5715 opacity = service.style.opacity,
5719 if (serviceState.reloadTiles) {
5720 scaleContainers.find("img").attr("data-dirty", "true");
5723 if (!scaleContainer.size()) {
5724 $serviceContainer.append("<div style='position:absolute; left:" + serviceLeft % tileWidth + "px; top:" + serviceTop % tileHeight + "px; width:" + tileWidth + "px; height:" + tileHeight + "px; margin:0; padding:0;' data-pixelSize='" + pixelSize + "'></div>");
5725 scaleContainer = $serviceContainer.children(":last").data("scaleOrigin", (serviceLeft % tileWidth) + "," + (serviceTop % tileHeight));
5727 scaleContainer.css({
5728 left: (serviceLeft % tileWidth) + "px",
5729 top: (serviceTop % tileHeight) + "px"
5730 }).data("scaleOrigin", (serviceLeft % tileWidth) + "," + (serviceTop % tileHeight));
5732 scaleContainer.children().each(function (i) {
5735 tile = $img.attr("data-tile").split(",");
5738 left: Math.round(((parseInt(tile[0]) - fullXAtScale) * 100) + (serviceLeft - (serviceLeft % tileWidth)) / tileWidth * 100) + "%",
5739 top: Math.round(((parseInt(tile[1]) - fullYAtScale) * 100) + (serviceTop - (serviceTop % tileHeight)) / tileHeight * 100) + "%"
5743 $img.fadeTo(0, opacity);
5748 for (x = tileX; x < tileX2; x++) {
5749 for (y = tileY; y < tileY2; y++) {
5750 var tileStr = "" + x + "," + y,
5751 $img = scaleContainer.children("[data-tile='" + tileStr + "']").removeAttr("data-dirty");
5753 if ($img.size() === 0 || serviceState.reloadTiles) {
5755 tilingScheme.origin[0] + (x * tileWidth) * pixelSize,
5756 tilingScheme.origin[1] + ySign * (y * tileHeight) * pixelSize
5760 tilingScheme.origin[0] + ((x + 1) * tileWidth - 1) * pixelSize,
5761 tilingScheme.origin[1] + ySign * ((y + 1) * tileHeight - 1) * pixelSize
5764 tileBbox = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]],
5766 urlProp = ( service.hasOwnProperty( "src" ) ? "src" : "getUrl" ),
5771 zoom: map._getZoom(),
5776 index: Math.abs(y + x)
5778 isFunc = $.isFunction( service[ urlProp ] ),
5782 imageUrl = service[ urlProp ]( urlArgs );
5784 $.template( "geoSrc", service[ urlProp ] );
5785 imageUrl = $.render( urlArgs, "geoSrc" );
5788 serviceState.loadCount++;
5789 //this._map._requestQueued();
5791 if (serviceState.reloadTiles && $img.size() > 0) {
5792 $img.attr("src", imageUrl);
5794 var imgMarkup = "<img style='position:absolute; " +
5795 "left:" + (((x - fullXAtScale) * 100) + (serviceLeft - (serviceLeft % tileWidth)) / tileWidth * 100) + "%; " +
5796 "top:" + (((y - fullYAtScale) * 100) + (serviceTop - (serviceTop % tileHeight)) / tileHeight * 100) + "%; ";
5798 if ($("body")[0].filters === undefined) {
5799 imgMarkup += "width: 100%; height: 100%;";
5802 imgMarkup += "margin:0; padding:0; -khtml-user-select:none; -moz-user-select:none; -webkit-user-select:none; user-select:none; display:none;' unselectable='on' data-tile='" + tileStr + "' />";
5804 scaleContainer.append(imgMarkup);
5805 $img = scaleContainer.children(":last");
5808 if ( typeof imageUrl === "string" ) {
5809 serviceObj._loadImage( $img, imageUrl, pixelSize, serviceState, $serviceContainer, opacity );
5812 imageUrl.done( function( url ) {
5813 serviceObj._loadImage( $img, url, pixelSize, serviceState, $serviceContainer, opacity );
5814 } ).fail( function( ) {
5816 serviceState.loadCount--;
5823 scaleContainers.find("[data-dirty]").remove();
5824 serviceState.reloadTiles = false;
5828 resize: function (map, service) {
5831 opacity: function ( map, service ) {
5832 var serviceState = $.data( service, "geoServiceState" );
5833 serviceState.serviceContainer.find( "img" ).stop( true ).fadeTo( "fast", service.style.opacity );
5836 toggle: function ( map, service ) {
5837 var serviceState = $.data( service, "geoServiceState" );
5838 serviceState.serviceContainer.css( "display", service.style.visibility === "visible" ? "block" : "none" );
5841 _cancelUnloaded: function (map, service) {
5842 var serviceState = $.data( service, "geoServiceState" );
5844 if (serviceState && serviceState.loadCount > 0) {
5845 serviceState.serviceContainer.find("img:hidden").remove();
5846 while (serviceState.loadCount > 0) {
5847 serviceState.loadCount--;
5852 _loadImage: function ( $img, url, pixelSize, serviceState, serviceContainer, opacity ) {
5853 $img.load(function (e) {
5855 $(e.target).fadeTo(0, opacity);
5860 serviceState.loadCount--;
5862 if (serviceState.loadCount <= 0) {
5863 serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
5864 serviceState.loadCount = 0;
5866 }).error(function (e) {
5867 $(e.target).remove();
5868 serviceState.loadCount--;
5870 if (serviceState.loadCount <= 0) {
5871 serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
5872 serviceState.loadCount = 0;
5874 }).attr("src", url);
5879 (function ($, undefined) {
5880 $.geo._serviceTypes.shingled = (function () {
5882 create: function (map, serviceContainer, service, index) {
5883 var serviceState = $.data(service, "geoServiceState");
5885 if ( !serviceState ) {
5890 var scHtml = '<div data-geo-service="shingled" style="position:absolute; left:0; top:0; width:16px; height:16px; margin:0; padding:0;"></div>';
5892 serviceContainer.append(scHtml);
5894 serviceState.serviceContainer = serviceContainer.children(":last");
5895 $.data(service, "geoServiceState", serviceState);
5898 return serviceState.serviceContainer;
5901 destroy: function (map, serviceContainer, service) {
5902 var serviceState = $.data(service, "geoServiceState");
5904 serviceState.serviceContainer.remove();
5906 $.removeData(service, "geoServiceState");
5909 interactivePan: function (map, service, dx, dy) {
5910 var serviceState = $.data(service, "geoServiceState");
5912 if ( serviceState ) {
5913 this._cancelUnloaded(map, service);
5915 var serviceContainer = serviceState.serviceContainer,
5916 pixelSize = map._pixelSize,
5917 scaleContainer = serviceContainer.children("[data-pixelSize='" + pixelSize + "']"),
5918 panContainer = scaleContainer.children("div");
5920 if ( !panContainer.length ) {
5921 scaleContainer.children("img").wrap('<div style="position:absolute; left:0; top:0; width:100%; height:100%;"></div>');
5922 panContainer = scaleContainer.children("div");
5926 left: function (index, value) {
5927 return parseInt(value) + dx;
5929 top: function (index, value) {
5930 return parseInt(value) + dy;
5934 // until pan/zoom rewrite, remove all containers not in this scale
5935 serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
5939 interactiveScale: function (map, service, center, pixelSize) {
5940 var serviceState = $.data(service, "geoServiceState");
5942 if ( serviceState ) {
5943 this._cancelUnloaded(map, service);
5945 var serviceContainer = serviceState.serviceContainer,
5947 contentBounds = map._getContentBounds(),
5948 mapWidth = contentBounds["width"],
5949 mapHeight = contentBounds["height"],
5951 halfWidth = mapWidth / 2,
5952 halfHeight = mapHeight / 2,
5954 bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight];
5956 serviceContainer.children().each(function (i) {
5957 var $scaleContainer = $(this),
5958 scalePixelSize = $scaleContainer.attr("data-pixelSize"),
5959 ratio = scalePixelSize / pixelSize;
5961 $scaleContainer.css( {
5962 width: mapWidth * ratio,
5963 height: mapHeight * ratio } ).children("img").each(function (i) {
5965 imgCenter = $img.data("center"),
5966 x = (Math.round((imgCenter[0] - center[0]) / scalePixelSize) - halfWidth) * ratio,
5967 y = (Math.round((center[1] - imgCenter[1]) / scalePixelSize) - halfHeight) * ratio;
5969 $img.css({ left: x + "px", top: y + "px" });
5975 refresh: function (map, service) {
5976 var serviceState = $.data(service, "geoServiceState");
5978 this._cancelUnloaded(map, service);
5980 if ( serviceState && service && service.style.visibility === "visible" && !( serviceState.serviceContainer.is( ":hidden" ) ) ) {
5982 var bbox = map._getBbox(),
5983 pixelSize = map._pixelSize,
5986 serviceContainer = serviceState.serviceContainer,
5988 contentBounds = map._getContentBounds(),
5989 mapWidth = contentBounds["width"],
5990 mapHeight = contentBounds["height"],
5992 halfWidth = mapWidth / 2,
5993 halfHeight = mapHeight / 2,
5995 scaleContainer = serviceContainer.children('[data-pixelSize="' + pixelSize + '"]'),
5997 opacity = service.style.opacity,
6001 if ( !scaleContainer.size() ) {
6002 serviceContainer.append('<div style="position:absolute; left:' + halfWidth + 'px; top:' + halfHeight + 'px; width:' + mapWidth + 'px; height:' + mapHeight + 'px; margin:0; padding:0;" data-pixelSize="' + pixelSize + '"></div>');
6003 scaleContainer = serviceContainer.children(":last");
6006 scaleContainer.children("img").each(function (i) {
6007 var $thisimg = $(this),
6008 imgCenter = $thisimg.data("center"),
6009 center = map._getCenter(),
6010 x = Math.round((imgCenter[0] - center[0]) / pixelSize) - halfWidth,
6011 y = Math.round((center[1] - imgCenter[1]) / pixelSize) - halfHeight;
6013 $thisimg.css({ left: x + "px", top: y + "px" });
6017 serviceContainer.find("img").attr("data-keepAlive", "0");
6020 var urlProp = ( service.hasOwnProperty("src") ? "src" : "getUrl" ),
6025 zoom: map._getZoom(),
6029 isFunc = $.isFunction( service[ urlProp ] ),
6034 imageUrl = service[ urlProp ]( urlArgs );
6036 $.template( "geoSrc", service[ urlProp ] );
6037 imageUrl = $.render( urlArgs, "geoSrc" );
6040 serviceState.loadCount++;
6041 //this._map._requestQueued();
6043 scaleContainer.append('<img style="position:absolute; left:-' + halfWidth + 'px; top:-' + halfHeight + 'px; width:100%; height:100%; margin:0; padding:0; -khtml-user-select:none; -moz-user-select:none; -webkit-user-select:none; user-select:none; display:none;" unselectable="on" />');
6044 $img = scaleContainer.children(":last").data("center", map._getCenter());
6046 if ( typeof imageUrl === "string" ) {
6047 serviceObj._loadImage( $img, imageUrl, pixelSize, serviceState, serviceContainer, opacity );
6050 imageUrl.done( function( url ) {
6051 serviceObj._loadImage( $img, url, pixelSize, serviceState, serviceContainer, opacity );
6052 } ).fail( function( ) {
6054 serviceState.loadCount--;
6061 resize: function (map, service) {
6062 var serviceState = $.data(service, "geoServiceState");
6064 if ( serviceState && service && service.style.visibility === "visible" ) {
6065 this._cancelUnloaded(map, service);
6067 var serviceContainer = serviceState.serviceContainer,
6069 contentBounds = map._getContentBounds(),
6070 mapWidth = contentBounds["width"],
6071 mapHeight = contentBounds["height"],
6073 halfWidth = mapWidth / 2,
6074 halfHeight = mapHeight / 2,
6076 scaleContainer = serviceContainer.children();
6078 scaleContainer.attr("data-pixelSize", "0");
6079 scaleContainer.css({
6080 left: halfWidth + 'px',
6081 top: halfHeight + 'px'
6086 opacity: function ( map, service ) {
6087 var serviceState = $.data( service, "geoServiceState" );
6088 serviceState.serviceContainer.find( "img" ).stop( true ).fadeTo( "fast", service.style.opacity );
6091 toggle: function (map, service) {
6092 var serviceState = $.data(service, "geoServiceState");
6093 serviceState.serviceContainer.css("display", service.style.visibility === "visible" ? "block" : "none");
6096 _cancelUnloaded: function (map, service) {
6097 var serviceState = $.data(service, "geoServiceState");
6099 if (serviceState && serviceState.loadCount > 0) {
6100 serviceState.serviceContainer.find("img:hidden").remove();
6101 while (serviceState.loadCount > 0) {
6102 serviceState.loadCount--;
6107 _loadImage: function ( $img, url, pixelSize, serviceState, serviceContainer, opacity ) {
6108 $img.load(function (e) {
6110 $(e.target).fadeTo(0, opacity);
6115 serviceState.loadCount--;
6117 if (serviceState.loadCount <= 0) {
6118 serviceContainer.children(':not([data-pixelSize="' + pixelSize + '"])').remove();
6120 var panContainer = serviceContainer.find('[data-pixelSize="' + pixelSize + '"]>div');
6121 if (panContainer.size() > 0) {
6122 var panContainerPos = panContainer.position();
6124 panContainer.children("img").each(function (i) {
6125 var $thisimg = $(this),
6126 x = panContainerPos.left + parseInt($thisimg.css("left")),
6127 y = panContainerPos.top + parseInt($thisimg.css("top"));
6129 $thisimg.css({ left: x + "px", top: y + "px" });
6132 panContainer.remove();
6135 serviceState.loadCount = 0;
6137 }).error(function (e) {
6138 $(e.target).remove();
6139 serviceState.loadCount--;
6141 if (serviceState.loadCount <= 0) {
6142 serviceContainer.children(":not([data-pixelSize='" + pixelSize + "'])").remove();
6143 serviceState.loadCount = 0;
6145 }).attr("src", url);
6150 /*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
6151 * Licensed under the MIT License (LICENSE.txt).
6153 * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
6154 * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
6155 * Thanks to: Seamus Leahy for adding deltaX and deltaY
6164 var types = ['DOMMouseScroll', 'mousewheel'];
6166 if ($.event.fixHooks) {
6167 for ( var i=types.length; i; ) {
6168 $.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
6172 $.event.special.mousewheel = {
6174 if ( this.addEventListener ) {
6175 for ( var i=types.length; i; ) {
6176 this.addEventListener( types[--i], handler, false );
6179 this.onmousewheel = handler;
6183 teardown: function() {
6184 if ( this.removeEventListener ) {
6185 for ( var i=types.length; i; ) {
6186 this.removeEventListener( types[--i], handler, false );
6189 this.onmousewheel = null;
6195 mousewheel: function(fn) {
6196 return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
6199 unmousewheel: function(fn) {
6200 return this.unbind("mousewheel", fn);
6205 function handler(event) {
6206 var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
6207 event = $.event.fix(orgEvent);
6208 event.type = "mousewheel";
6210 // Old school scrollwheel delta
6211 if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; }
6212 if ( orgEvent.detail ) { delta = -orgEvent.detail/3; }
6214 // New school multidimensional scroll (touchpads) deltas
6218 if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
6224 if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
6225 if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
6227 // Add event and delta to the front of the arguments
6228 args.unshift(event, delta, deltaX, deltaY);
6230 return ($.event.dispatch || $.event.handle).apply(this, args);