1 (function ($, undefined) {
2 var _ieVersion = (function () {
3 var v = 5, div = document.createElement("div"), a = div.all || [];
4 while (div.innerHTML = "<!--[if gt IE " + (++v) + "]><br><![endif]-->", a[0]) { }
9 bbox: [-180, -85, 180, 85],
10 bboxMax: [-180, -85, 180, 85],
14 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",
16 drawPoint: "crosshair",
17 drawLineString: "crosshair",
18 drawPolygon: "crosshair",
19 measureLength: "crosshair",
20 measureArea: "crosshair"
23 length: "{{=length.toFixed( 2 )}} m",
24 area: "{{=area.toFixed( 2 )}} sq m"
35 src: function (view) {
36 return "http://tile.openstreetmap.org/" + view.zoom + "/" + view.tile.column + "/" + view.tile.row + ".png";
38 attr: "© OpenStreetMap & contributors, CC-BY-SA"
45 basePixelSize: 156543.03392799936,
46 origin: [-20037508.342787, 20037508.342787]
53 $.widget("geo.geomap", {
54 // private widget members
55 _$elem: undefined, //< map div for maps, service div for services
56 _map: undefined, //< only defined in services
61 _$resizeContainer: undefined, //< all elements that should match _contentBounds' size
63 _$eventTarget: undefined,
64 _$contentFrame: undefined,
65 _$existingChildren: undefined,
66 _$attrList: undefined,
67 _$servicesContainer: undefined,
69 _$panContainer: undefined, //< all non-service elements that move while panning
70 _$shapesContainer: undefined,
71 _$drawContainer: undefined,
72 _$measureContainer: undefined,
73 _$measureLabel: undefined,
77 _currentServices: [], //< internal copy
80 _pixelSize: undefined,
81 _centerMax: undefined,
82 _pixelSizeMax: undefined,
89 _zoomFactor: 2, //< determines what a zoom level means
91 _fullZoomFactor: 2, //< interactiveScale factor needed to zoom a whole level
92 _partialZoomFactor: 1.18920711500273, //< interactiveScale factor needed to zoom a fraction of a level (the fourth root of 2)
94 _mouseDown: undefined,
97 _shiftZoom: undefined,
100 _downDate: undefined,
101 _moveDate: undefined,
102 _clickDate: undefined,
103 _lastMove: undefined,
104 _lastDrag: undefined,
106 _windowHandler: null,
107 _resizeTimeout: null,
110 _velocity: undefined,
111 _friction: undefined,
113 _supportTouch: undefined,
114 _softDblClick: undefined,
116 _isDbltap: undefined,
118 _isMultiTouch: undefined,
119 _multiTouchAnchor: undefined, //< TouchList
120 _multiTouchAnchorBbox: undefined, //< bbox
121 _multiTouchCurrentBbox: undefined, //< bbox
123 _drawTimeout: null, //< used in drawPoint mode so we don't send two shape events on dbltap
124 _drawPixels: [], //< an array of coordinate arrays for drawing lines & polygons, in pixel coordinates
127 _graphicShapes: [], //< an array of objects containing style object refs & GeoJSON object refs
133 options: $.extend({}, _defaultOptions),
135 _createWidget: function (options, element) {
136 this._$elem = $(element);
138 if (this._$elem.is(".geo-service")) {
139 var $contentFrame = this._$elem.closest( ".geo-content-frame" );
140 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>');
141 this._$shapesContainer = this._$elem.children(':last');
142 this._graphicShapes = [];
143 $.Widget.prototype._createWidget.apply(this, arguments);
147 this._$elem.addClass("geo-map");
149 this._initOptions = options || {};
151 this._forcePosition(this._$elem);
153 this._$elem.css("text-align", "left");
155 var size = this._findMapSize();
156 this._contentBounds = {
157 x: parseInt(this._$elem.css("padding-left")),
158 y: parseInt(this._$elem.css("padding-top")),
159 width: size["width"],
160 height: size["height"]
163 this._createChildren();
165 this._center = this._centerMax = [0, 0];
167 this.options["pixelSize"] = this._pixelSize = this._pixelSizeMax = 156543.03392799936;
175 this._isDbltap = false;
177 this._anchor = [ 0, 0 ];
178 this._current = [ 0, 0 ];
179 this._lastMove = [ 0, 0 ];
180 this._lastDrag = [ 0, 0 ];
181 this._velocity = [ 0, 0 ];
183 this._friction = [.8, .8];
189 this._drawPixels = [];
190 this._drawCoords = [];
191 this._graphicShapes = [];
194 $.Widget.prototype._createWidget.apply(this, arguments);
197 _create: function () {
198 this._options = this.options;
200 if (this._$elem.is(".geo-service")) {
201 this._map = this._$elem.data( "geoMap" );
202 this._$shapesContainer.geographics( );
203 this._options["shapeStyle"] = this._$shapesContainer.geographics("option", "style");
209 this._supportTouch = "ontouchend" in document;
210 this._softDblClick = this._supportTouch || _ieVersion == 7;
213 touchStartEvent = this._supportTouch ? "touchstart" : "mousedown",
214 touchStopEvent = this._supportTouch ? "touchend touchcancel" : "mouseup",
215 touchMoveEvent = this._supportTouch ? "touchmove" : "mousemove";
217 $(document).keydown($.proxy(this._document_keydown, this));
219 this._$eventTarget.dblclick($.proxy(this._eventTarget_dblclick, this));
221 this._$eventTarget.bind(touchStartEvent, $.proxy(this._eventTarget_touchstart, this));
223 var dragTarget = (this._$eventTarget[0].setCapture) ? this._$eventTarget : $(document);
224 dragTarget.bind(touchMoveEvent, $.proxy(this._dragTarget_touchmove, this));
225 dragTarget.bind(touchStopEvent, $.proxy(this._dragTarget_touchstop, this));
227 this._$eventTarget.mousewheel($.proxy(this._eventTarget_mousewheel, this));
229 this._windowHandler = function () {
230 if (geomap._resizeTimeout) {
231 clearTimeout(geomap._resizeTimeout);
233 geomap._resizeTimeout = setTimeout(function () {
234 if (geomap._created) {
235 geomap._$elem.geomap("resize");
240 $(window).resize(this._windowHandler);
242 this._$drawContainer.geographics({ style: this._initOptions.drawStyle || {} });
243 this._options["drawStyle"] = this._$drawContainer.geographics("option", "style");
245 this._$shapesContainer.geographics( { style: this._initOptions.shapeStyle || { } } );
246 this._options["shapeStyle"] = this._$shapesContainer.geographics("option", "style");
248 if (this._initOptions) {
249 if (this._initOptions.tilingScheme) {
250 this._setOption("tilingScheme", this._initOptions.tilingScheme, false);
252 if ( this._initOptions.services ) {
253 // jQuery UI Widget Factory merges user services with our default, we want to clobber the default
254 this._options[ "services" ] = $.merge( [ ], this._initOptions.services );
256 if (this._initOptions.bbox) {
257 this._setOption("bbox", this._initOptions.bbox, false);
259 if (this._initOptions.center) {
260 this._setOption("center", this._initOptions.center, false);
262 if (this._initOptions.zoom !== undefined) {
263 this._setZoom(this._initOptions.zoom, false, false);
267 $.template( "geoMeasureLength", this._options[ "measureLabels" ].length );
268 $.template( "geoMeasureArea", this._options[ "measureLabels" ].area );
270 this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
272 this._createServices();
275 this._created = true;
278 _setOption: function (key, value, refresh) {
279 if ( key == "pixelSize" ) {
283 refresh = (refresh === undefined || refresh);
285 if ( this._$elem.is( ".geo-map" ) ) {
291 this._userGeodetic = $.geo.proj && $.geo._isGeodetic( value );
292 if ( this._userGeodetic ) {
293 value = $.geo.proj.fromGeodetic( value );
296 this._setBbox(value, false, refresh);
297 value = this._getBbox();
301 this._userGeodetic = $.geo.proj && $.geo._isGeodetic( value );
302 if ( this._userGeodetic ) {
303 value = $.geo.proj.fromGeodetic( value );
306 this._setCenterAndSize( value, this._pixelSize, false, refresh );
309 case "measureLabels":
310 value = $.extend( this._options[ "measureLabels" ], value );
311 $.template( "geoMeasureLength", value.length );
312 $.template( "geoMeasureArea", value.area );
316 if (this._$drawContainer) {
317 this._$drawContainer.geographics("option", "style", value);
318 value = this._$drawContainer.geographics("option", "style");
323 if (this._$shapesContainer) {
324 this._$shapesContainer.geographics("option", "style", value);
325 value = this._$shapesContainer.geographics("option", "style");
330 this._resetDrawing( );
331 this._$eventTarget.css("cursor", this._options["cursors"][value]);
335 this._setZoom(value, false, refresh);
339 $.Widget.prototype._setOption.apply(this, arguments);
344 if ( this._userGeodetic ) {
345 this._options[ "bbox" ] = $.geo.proj.toGeodetic( this._options[ "bbox" ] );
346 this._options[ "center" ] = $.geo.proj.toGeodetic( this._center );
351 if ( value != null ) {
352 this._pixelSizeMax = this._getPixelSize( 0 );
354 value.origin[ 0 ] + this._pixelSizeMax * value.tileWidth / 2,
355 value.origin[ 1 ] + this._pixelSizeMax * value.tileHeight / 2
361 this._pixelSizeMax = this._getPixelSize( 0 );
363 if ( $.geo.proj && $.geo._isGeodetic( value ) ) {
364 this._centerMax = $.geo.center( $.geo.proj.fromGeodetic( value ) );
366 this._centerMax = $.geo.center( value );
371 this._createServices();
379 this._$shapesContainer.geographics("clear");
380 this._refreshShapes( this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes );
386 destroy: function () {
387 if ( this._$elem.is(".geo-service") ) {
388 this._$shapesContainer.geographics("destroy");
389 this._$shapesContainer = undefined;
391 this._created = false;
393 $(window).unbind("resize", this._windowHandler);
395 for ( var i = 0; i < this._currentServices.length; i++ ) {
396 this._currentServices[ i ].serviceContainer.geomap("destroy");
397 $.geo["_serviceTypes"][this._currentServices[i].type].destroy(this, this._$servicesContainer, this._currentServices[i]);
400 this._$shapesContainer.geographics("destroy");
401 this._$shapesContainer = undefined;
402 this._$drawContainer.geographics("destroy");
403 this._$drawContainer = undefined;
405 this._$existingChildren.detach();
406 this._$elem.html("");
407 this._$elem.append(this._$existingChildren);
408 this._$elem.removeClass("geo-map");
411 $.Widget.prototype.destroy.apply(this, arguments);
414 toMap: function (p) {
416 return this._userGeodetic ? $.geo.proj.toGeodetic(p) : p;
419 toPixel: function ( p, _center /* Internal Use Only */, _pixelSize /* Internal Use Only */ ) {
420 return this._toPixel( $.geo.proj ? $.geo.proj.fromGeodetic( p ) : p, _center, _pixelSize );
423 opacity: function ( value, _serviceContainer ) {
424 if ( this._$elem.is( ".geo-service" ) ) {
425 this._$elem.closest( ".geo-map" ).geomap( "opacity", value, this._$elem );
427 if ( value >= 0 || value <= 1 ) {
428 for ( var i = 0; i < this._currentServices.length; i++ ) {
429 var service = this._currentServices[ i ];
430 if ( !_serviceContainer || service.serviceContainer[ 0 ] == _serviceContainer[ 0 ] ) {
431 service.style.opacity = value;
432 $.geo[ "_serviceTypes" ][ service.type ].opacity( this, service );
439 toggle: function ( value, _serviceContainer ) {
440 if ( this._$elem.is( ".geo-service" ) ) {
441 this._$elem.closest( ".geo-map" ).geomap( "toggle", value, this._$elem );
444 for ( var i = 0; i < this._currentServices.length; i++ ) {
445 var service = this._currentServices[ i ];
447 if ( !_serviceContainer || service.serviceContainer[ 0 ] == _serviceContainer[ 0 ] ) {
448 if ( value === undefined ) {
450 value = ( service.style.visibility !== "visible" );
453 service.style.visibility = ( value ? "visible" : "hidden" );
455 service.serviceContainer.toggle( value );
458 $.geo[ "_serviceTypes" ][ service.type ].refresh( this, service );
465 zoom: function (numberOfLevels) {
466 if (numberOfLevels != null) {
467 this._setZoom(this._options["zoom"] + numberOfLevels, false, true);
471 refresh: function () {
475 resize: function () {
476 var size = this._findMapSize(),
477 dx = size["width"]/2 - this._contentBounds.width/2,
478 dy = size["height"]/2 - this._contentBounds.height/2,
481 this._contentBounds = {
482 x: parseInt(this._$elem.css("padding-left")),
483 y: parseInt(this._$elem.css("padding-top")),
484 width: size["width"],
485 height: size["height"]
488 this._$resizeContainer.css( {
489 width: size["width"],
490 height: size["height"]
493 for (i = 0; i < this._currentServices.length; i++) {
494 $.geo["_serviceTypes"][this._currentServices[i].type].resize(this, this._currentServices[i]);
497 this._$elem.find( ".geo-graphics" ).css( {
498 width: size["width"],
499 height: size["height"]
500 }).geographics( "resize" );
502 for (i = 0; i < this._drawPixels.length; i++) {
503 this._drawPixels[i][0] += dx;
504 this._drawPixels[i][1] += dy;
507 this._setCenterAndSize(this._center, this._pixelSize, false, true);
510 append: function ( shape, style, label, refresh ) {
511 if ( shape && $.isPlainObject( shape ) ) {
512 var shapes, arg, i, realStyle, realLabel, realRefresh;
514 if ( shape.type == "FeatureCollection" ) {
515 shapes = shape.features;
517 shapes = $.isArray( shape ) ? shape : [ shape ];
520 for ( i = 1; i < arguments.length; i++ ) {
521 arg = arguments[ i ];
523 if ( typeof arg === "object" ) {
525 } else if ( typeof arg === "number" || typeof arg === "string" ) {
527 } else if ( typeof arg === "boolean" ) {
532 for ( i = 0; i < shapes.length; i++ ) {
533 if ( shapes[ i ].type != "Point" ) {
534 var bbox = $.geo.bbox( shapes[ i ] );
535 if ( $.geo.proj && $.geo._isGeodetic( bbox ) ) {
536 bbox = $.geo.proj.fromGeodetic( bbox );
538 $.data( shapes[ i ], "geoBbox", bbox );
541 this._graphicShapes.push( {
548 if ( realRefresh === undefined || realRefresh ) {
554 empty: function ( refresh ) {
555 for ( var i = 0; i < this._graphicShapes.length; i++ ) {
556 $.removeData( this._graphicShapes[ i ].shape, "geoBbox" );
559 this._graphicShapes = [];
561 if ( refresh === undefined || refresh ) {
566 find: function ( selector, pixelTolerance ) {
567 var isPoint = $.isPlainObject( selector ),
568 searchPixel = isPoint ? this._map.toPixel( selector.coordinates ) : undefined,
569 mapTol = this._map._pixelSize * pixelTolerance,
576 for ( ; i < this._graphicShapes.length; i++ ) {
577 graphicShape = this._graphicShapes[ i ];
580 if ( graphicShape.shape.type == "Point" ) {
581 if ( $.geo.distance( graphicShape.shape, selector ) <= mapTol ) {
582 result.push( graphicShape.shape );
585 var bbox = $.data( graphicShape.shape, "geoBbox" ),
598 coordinates: $.geo.proj && $.geo._isGeodetic( selector.coordinates ) ? $.geo.proj.fromGeodetic( selector.coordinates ) : selector.coordinates
601 if ( $.geo.distance( bboxPolygon, projectedPoint, true ) <= mapTol ) {
602 geometries = $.geo._flatten( graphicShape.shape );
603 for ( curGeom = 0; curGeom < geometries.length; curGeom++ ) {
604 if ( $.geo.distance( geometries[ curGeom ], selector ) <= mapTol ) {
605 result.push( graphicShape.shape );
612 result.push( graphicShape.shape );
616 if ( this._$elem.is( ".geo-map" ) ) {
617 this._$elem.find( ".geo-service" ).each( function( ) {
618 result = $.merge( result, $( this ).geomap( "find", selector, pixelTolerance ) );
625 remove: function ( shape, refresh ) {
626 for ( var i = 0; i < this._graphicShapes.length; i++ ) {
627 if ( this._graphicShapes[ i ].shape == shape ) {
628 $.removeData( shape, "geoBbox" );
629 var rest = this._graphicShapes.slice( i + 1 );
630 this._graphicShapes.length = i;
631 this._graphicShapes.push.apply( this._graphicShapes, rest );
636 if ( refresh === undefined || refresh ) {
641 _getBbox: function (center, pixelSize) {
642 center = center || this._center;
643 pixelSize = pixelSize || this._pixelSize;
645 // calculate the internal bbox
646 var halfWidth = this._contentBounds[ "width" ] / 2 * pixelSize,
647 halfHeight = this._contentBounds[ "height" ] / 2 * pixelSize;
648 return [ center[ 0 ] - halfWidth, center[ 1 ] - halfHeight, center[ 0 ] + halfWidth, center[ 1 ] + halfHeight ];
651 _setBbox: function (value, trigger, refresh) {
652 var center = [value[0] + (value[2] - value[0]) / 2, value[1] + (value[3] - value[1]) / 2],
653 pixelSize = Math.max($.geo.width(value, true) / this._contentBounds.width, $.geo.height(value, true) / this._contentBounds.height);
655 if (this._options["tilingScheme"]) {
656 var zoom = this._getZoom( center, pixelSize );
657 pixelSize = this._getPixelSize( zoom );
659 if ( this._getZoom( center, pixelSize ) < 0 ) {
660 pixelSize = this._pixelSizeMax;
664 this._setCenterAndSize(center, pixelSize, trigger, refresh);
667 _getBboxMax: function () {
668 // calculate the internal bboxMax
669 var halfWidth = this._contentBounds["width"] / 2 * this._pixelSizeMax,
670 halfHeight = this._contentBounds["height"] / 2 * this._pixelSizeMax;
671 return [this._centerMax[0] - halfWidth, this._centerMax[1] - halfHeight, this._centerMax[0] + halfWidth, this._centerMax[1] + halfHeight];
674 _getCenter: function () {
678 _getContentBounds: function () {
679 return this._contentBounds;
682 _getServicesContainer: function () {
683 return this._$servicesContainer;
686 _getZoom: function ( center, pixelSize ) {
687 center = center || this._center;
688 pixelSize = pixelSize || this._pixelSize;
690 // calculate the internal zoom level, vs. public zoom property
691 var tilingScheme = this._options["tilingScheme"];
692 if ( tilingScheme ) {
693 if ( tilingScheme.pixelSizes != null ) {
694 var roundedPixelSize = Math.floor(pixelSize * 1000),
695 levels = tilingScheme.pixelSizes.length,
698 for ( ; i >= 0; i-- ) {
699 if ( Math.floor( tilingScheme.pixelSizes[ i ] * 1000 ) >= roundedPixelSize ) {
706 return Math.max( Math.round( Math.log( tilingScheme.basePixelSize / pixelSize) / Math.log( 2 ) ), 0 );
709 var ratio = this._contentBounds["width"] / this._contentBounds["height"],
710 bbox = $.geo.reaspect( this._getBbox( center, pixelSize ), ratio, true ),
711 bboxMax = $.geo.reaspect(this._getBboxMax(), ratio, true);
713 return Math.max( Math.round( Math.log($.geo.width(bboxMax, true) / $.geo.width(bbox, true)) / Math.log(this._zoomFactor) ), 0 );
717 _setZoom: function ( value, trigger, refresh ) {
718 value = Math.max( value, 0 );
720 this._setCenterAndSize( this._center, this._getPixelSize( value ), trigger, refresh );
723 _createChildren: function () {
724 this._$existingChildren = this._$elem.children().detach();
726 this._forcePosition(this._$existingChildren);
728 this._$existingChildren.css("-moz-user-select", "none");
730 var contentSizeCss = "width:" + this._contentBounds["width"] + "px; height:" + this._contentBounds["height"] + "px; margin:0; padding:0;",
731 contentPosCss = "position:absolute; left:0; top:0;";
733 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>');
734 this._$eventTarget = this._$contentFrame = this._$elem.children(':first');
736 this._$contentFrame.append('<div class="geo-services-container" style="' + contentPosCss + contentSizeCss + '"></div>');
737 this._$servicesContainer = this._$contentFrame.children(':last');
739 this._$contentFrame.append('<div class="geo-shapes-container" style="' + contentPosCss + contentSizeCss + '"></div>');
740 this._$shapesContainer = this._$contentFrame.children(':last');
742 this._$contentFrame.append( '<ul style="position: absolute; bottom: 8px; left: 8px; list-style-type: none; max-width: 50%; padding: 0; margin: 0;"></ul>' );
743 this._$attrList = this._$contentFrame.children( ":last" );
745 this._$contentFrame.append('<div class="geo-draw-container" style="' + contentPosCss + contentSizeCss + '"></div>');
746 this._$drawContainer = this._$contentFrame.children(':last');
748 this._$contentFrame.append('<div class="geo-measure-container" style="' + contentPosCss + contentSizeCss + '"><div class="geo-measure-label" style="' + contentPosCss + '; display: none;"></div></div>');
749 this._$measureContainer = this._$contentFrame.children(':last');
750 this._$measureLabel = this._$measureContainer.children();
752 this._$panContainer = $( [ this._$shapesContainer[ 0 ], this._$drawContainer[ 0 ], this._$measureContainer[ 0 ] ] );
754 this._$resizeContainer = $( [ this._$contentFrame[ 0 ], this._$servicesContainer[ 0 ], this._$eventTarget[ 0 ], this._$measureContainer[ 0 ] ] );
756 this._$contentFrame.append(this._$existingChildren);
758 if ( ! $("#geo-measure-style").length ) {
759 $("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>' );
763 _createServices: function () {
766 for ( i = 0; i < this._currentServices.length; i++ ) {
767 this._currentServices[ i ].serviceContainer.geomap( "destroy" );
768 $.geo[ "_serviceTypes" ][ this._currentServices[ i ].type ].destroy( this, this._$servicesContainer, this._currentServices[ i ] );
771 this._currentServices = [ ];
772 this._$servicesContainer.html( "" );
773 this._$attrList.html( "" );
775 for ( i = 0; i < this._options[ "services" ].length; i++ ) {
776 service = this._currentServices[ i ] = $.extend( { }, this._options[ "services" ][ i ] );
778 // default the service style property on our copy
779 service.style = $.extend( {
780 visibility: "visible",
784 var idString = service.id ? ' id="' + service.id + '"' : "",
785 classString = 'class="geo-service ' + ( service["class"] ? service["class"] : '' ) + '"',
786 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>',
789 this._$servicesContainer.append( scHtml );
790 serviceContainer = this._$servicesContainer.children( ":last" );
791 this._currentServices[ i ].serviceContainer = serviceContainer;
793 $.geo[ "_serviceTypes" ][ service.type ].create( this, serviceContainer, service, i );
795 serviceContainer.data( "geoMap", this ).geomap();
797 if ( service.attr ) {
798 this._$attrList.append( '<li>' + service.attr + '</li>' );
802 this._$attrList.find( "a" ).css( {
803 position: "relative",
808 _refreshDrawing: function ( ) {
809 this._$drawContainer.geographics("clear");
811 if ( this._drawPixels.length > 0 ) {
812 var mode = this._options[ "mode" ],
813 pixels = this._drawPixels,
814 coords = this._drawCoords,
822 case "measureLength":
823 mode = "drawLineString";
828 label = $.render( { length: $.geo.length( labelShape, true ) }, "geoMeasureLength" );
829 labelPixel = $.merge( [], pixels[ pixels.length - 1 ] );
833 mode = "drawPolygon";
837 coordinates: [ $.merge( [ ], coords ) ]
839 labelShape.coordinates[ 0 ].push( coords[ 0 ] );
841 label = $.render( { area: $.geo.area( labelShape, true ) }, "geoMeasureArea" );
842 labelPixel = $.merge( [], pixels[ pixels.length - 1 ] );
851 this._$drawContainer.geographics( mode, pixels );
854 this._$measureLabel.html( label );
856 widthOver = this._contentBounds.width - ( this._$measureLabel.outerWidth( true ) + labelPixel[ 0 ] );
857 heightOver = this._contentBounds.height - ( this._$measureLabel.outerHeight( true ) + labelPixel[ 1 ] );
859 if ( widthOver < 0 ) {
860 labelPixel[ 0 ] += widthOver;
863 if ( heightOver < 0 ) {
864 labelPixel[ 1 ] += heightOver;
867 this._$measureLabel.css( {
868 left: labelPixel[ 0 ],
875 _resetDrawing: function () {
876 this._drawPixels = [];
877 this._drawCoords = [];
878 this._$drawContainer.geographics("clear");
879 this._$measureLabel.hide();
882 _refreshShapes: function (geographics, shapes, styles, labels, center, pixelSize) {
890 bbox = this._map._getBbox(center, pixelSize);
892 for (i = 0; i < shapes.length; i++) {
893 shape = shapes[i].shape || shapes[i];
894 shape = shape.geometry || shape;
895 shapeBbox = $.data(shape, "geoBbox");
897 if ( shapeBbox && $.geo._bboxDisjoint( bbox, shapeBbox ) ) {
901 style = $.isArray(styles) ? styles[i].style : styles;
902 label = $.isArray(labels) ? labels[i].label : labels;
903 hasLabel = ( label !== undefined );
904 labelPixel = undefined;
906 switch (shape.type) {
908 labelPixel = this._map.toPixel( shape.coordinates, center, pixelSize );
909 this._$shapesContainer.geographics("drawPoint", labelPixel, style);
912 this._$shapesContainer.geographics("drawLineString", this._map.toPixel(shape.coordinates, center, pixelSize), style);
914 labelPixel = this._map.toPixel( $.geo.pointAlong( shape, .5 ).coordinates, center, pixelSize );
918 this._$shapesContainer.geographics("drawPolygon", this._map.toPixel(shape.coordinates, center, pixelSize), style);
920 labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
924 for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
925 this._$shapesContainer.geographics("drawPoint", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
928 labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
931 case "MultiLineString":
932 for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
933 this._$shapesContainer.geographics("drawLineString", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
936 labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
940 for (mgi = 0; mgi < shape.coordinates.length; mgi++) {
941 this._$shapesContainer.geographics("drawPolygon", this._map.toPixel(shape.coordinates[mgi], center, pixelSize), style);
944 labelPixel = this._map.toPixel( $.geo.centroid( shape ).coordinates, center, pixelSize );
948 case "GeometryCollection":
949 this._refreshShapes(geographics, shape.geometries, style, label, center, pixelSize);
953 if ( hasLabel && labelPixel ) {
954 this._$shapesContainer.geographics( "drawLabel", labelPixel, label );
959 _findMapSize: function () {
960 // really, really attempt to find a size for this thing
961 // even if it's hidden (look at parents)
962 var size = { width: 0, height: 0 },
963 sizeContainer = this._$elem;
965 while (sizeContainer.size() && !(size["width"] > 0 && size["height"] > 0)) {
966 size = { width: sizeContainer.width(), height: sizeContainer.height() };
967 if (size["width"] <= 0 || size["height"] <= 0) {
968 size = { width: parseInt(sizeContainer.css("width")), height: parseInt(sizeContainer.css("height")) };
970 sizeContainer = sizeContainer.parent();
975 _forcePosition: function (elem) {
976 var cssPosition = elem.css("position");
977 if (cssPosition != "relative" && cssPosition != "absolute" && cssPosition != "fixed") {
978 elem.css("position", "relative");
982 _getPixelSize: function ( zoom ) {
983 var tilingScheme = this._options["tilingScheme"];
984 if (tilingScheme != null) {
986 return tilingScheme.pixelSizes != null ? tilingScheme.pixelSizes[0] : tilingScheme.basePixelSize;
989 zoom = Math.round(zoom);
990 zoom = Math.max(zoom, 0);
991 var levels = tilingScheme.pixelSizes != null ? tilingScheme.pixelSizes.length : tilingScheme.levels;
992 zoom = Math.min(zoom, levels - 1);
994 if (tilingScheme.pixelSizes != null) {
995 return tilingScheme.pixelSizes[zoom];
997 return tilingScheme.basePixelSize / Math.pow(2, zoom);
1000 var bbox = $.geo.scaleBy( this._getBboxMax(), 1 / Math.pow( this._zoomFactor, zoom ), true );
1001 return Math.max( $.geo.width( bbox, true ) / this._contentBounds.width, $.geo.height( bbox, true ) / this._contentBounds.height );
1005 _getZoomCenterAndSize: function ( anchor, zoomDelta, full ) {
1006 var zoomFactor = ( full ? this._fullZoomFactor : this._partialZoomFactor ),
1007 scale = Math.pow( zoomFactor, -zoomDelta ),
1011 if ( this._options[ "tilingScheme" ] ) {
1012 zoomLevel = this._getZoom(this._center, this._pixelSize * scale);
1013 pixelSize = this._getPixelSize(zoomLevel);
1015 pixelSize = this._pixelSize * scale;
1017 if ( this._getZoom( this._center, pixelSize ) < 0 ) {
1018 pixelSize = this._pixelSizeMax;
1022 var ratio = pixelSize / this._pixelSize,
1023 anchorMapCoord = this._toMap(anchor),
1024 centerDelta = [(this._center[0] - anchorMapCoord[0]) * ratio, (this._center[1] - anchorMapCoord[1]) * ratio],
1025 scaleCenter = [anchorMapCoord[0] + centerDelta[0], anchorMapCoord[1] + centerDelta[1]];
1027 return { pixelSize: pixelSize, center: scaleCenter };
1030 _mouseWheelFinish: function () {
1031 this._wheelTimeout = null;
1033 if (this._wheelLevel != 0) {
1034 var wheelCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, this._options[ "tilingScheme" ] != null );
1036 this._setCenterAndSize(wheelCenterAndSize.center, wheelCenterAndSize.pixelSize, true, true);
1038 this._wheelLevel = 0;
1044 _panEnd: function () {
1046 (this._velocity[0] > 0 ? Math.floor(this._velocity[0] * this._friction[0]) : Math.ceil(this._velocity[0] * this._friction[0])),
1047 (this._velocity[1] > 0 ? Math.floor(this._velocity[1] * this._friction[1]) : Math.ceil(this._velocity[1] * this._friction[1]))
1050 if (Math.abs(this._velocity[0]) < 4 && Math.abs(this._velocity[1]) < 4) {
1051 this._panFinalize();
1054 this._current[0] + this._velocity[0],
1055 this._current[1] + this._velocity[1]
1059 setTimeout($.proxy(this._panEnd, this), 30);
1063 _panFinalize: function () {
1064 if (this._panning) {
1065 this._velocity = [0, 0];
1067 var dx = this._current[0] - this._anchor[0],
1068 dy = this._current[1] - this._anchor[1],
1069 image = this._options[ "axisLayout" ] === "image",
1070 dxMap = -dx * this._pixelSize,
1071 dyMap = ( image ? -1 : 1 ) * dy * this._pixelSize;
1073 this._$panContainer.css({ left: 0, top: 0 });
1075 this._$servicesContainer.find( ".geo-shapes-container" ).css( { left: 0, top: 0 } );
1077 this._setCenterAndSize([this._center[0] + dxMap, this._center[1] + dyMap], this._pixelSize, true, true);
1079 this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
1082 this._anchor = this._current;
1083 this._mouseDown = this._toolPan = this._panning = false;
1087 _panMove: function () {
1088 if ( ! this._options[ "pannable" ] ) {
1092 var dx = this._current[0] - this._lastDrag[0],
1093 dy = this._current[1] - this._lastDrag[1],
1098 if (this._toolPan || dx > 3 || dx < -3 || dy > 3 || dy < -3) {
1099 if (!this._toolPan) {
1100 this._toolPan = true;
1101 this._$eventTarget.css("cursor", this._options["cursors"]["pan"]);
1104 if (this._mouseDown) {
1105 this._velocity = [dx, dy];
1108 if (dx != 0 || dy != 0) {
1109 this._panning = true;
1110 this._lastDrag = this._current;
1113 left: function (index, value) {
1114 return parseInt(value) + dx;
1116 top: function (index, value) {
1117 return parseInt(value) + dy;
1121 for ( i = 0; i < this._currentServices.length; i++ ) {
1122 service = this._currentServices[ i ];
1123 $.geo[ "_serviceTypes" ][ service.type ].interactivePan( this, service, dx, dy );
1125 service.serviceContainer.find( ".geo-shapes-container" ).css( translateObj );
1128 this._$panContainer.css( translateObj );
1130 //this._refreshDrawing();
1135 _refresh: function () {
1139 if ( this._$elem.is( ".geo-map" ) ) {
1140 for ( ; i < this._currentServices.length; i++ ) {
1141 service = this._currentServices[ i ];
1143 if ( !this._mouseDown && $.geo[ "_serviceTypes" ][ service.type ] !== null ) {
1144 $.geo[ "_serviceTypes" ][ service.type ].refresh( this, service );
1145 service.serviceContainer.geomap( "refresh" );
1150 if ( this._$shapesContainer ) {
1151 this._$shapesContainer.geographics( "clear" );
1152 if ( this._graphicShapes.length > 0 ) {
1153 this._refreshShapes( this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes );
1158 _setCenterAndSize: function (center, pixelSize, trigger, refresh) {
1159 if ( ! $.isArray( center ) || center.length != 2 || typeof center[ 0 ] !== "number" || typeof center[ 1 ] !== "number" ) {
1163 // the final call during any extent change
1164 if (this._pixelSize != pixelSize) {
1165 this._$elem.find( ".geo-shapes-container" ).geographics("clear");
1166 for (var i = 0; i < this._currentServices.length; i++) {
1167 var service = this._currentServices[i];
1168 $.geo["_serviceTypes"][service.type].interactiveScale(this, service, center, pixelSize);
1172 this._center = $.merge( [ ], center );
1173 this._options["pixelSize"] = this._pixelSize = pixelSize;
1175 if ( this._userGeodetic ) {
1176 this._options["bbox"] = $.geo.proj.toGeodetic( this._getBbox() );
1177 this._options["center"] = $.geo.proj.toGeodetic( this._center );
1179 this._options["bbox"] = this._getBbox();
1180 this._options["center"] = $.merge( [ ], center );
1183 this._options["zoom"] = this._getZoom();
1185 if (this._drawCoords.length > 0) {
1186 this._drawPixels = this._toPixel(this._drawCoords);
1190 this._trigger("bboxchange", window.event, { bbox: $.merge( [ ], this._options["bbox"] ) });
1195 this._refreshDrawing();
1199 _toMap: function (p, center, pixelSize) {
1200 // ignores $.geo.proj
1202 center = center || this._center;
1203 pixelSize = pixelSize || this._pixelSize;
1205 var isMultiPointOrLineString = $.isArray( p[ 0 ] ),
1206 isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray( p[ 0 ][ 0 ] ),
1207 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray( p[ 0 ][ 0 ][ 0 ] ),
1208 width = this._contentBounds["width"],
1209 height = this._contentBounds["height"],
1210 halfWidth = width / 2 * pixelSize,
1211 halfHeight = height / 2 * pixelSize,
1212 bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight],
1213 xRatio = $.geo.width(bbox, true) / width,
1214 yRatio = $.geo.height(bbox, true) / height,
1216 image = this._options[ "axisLayout" ] === "image",
1220 if ( !isMultiPolygon ) {
1221 if ( !isMultiLineStringOrPolygon ) {
1222 if ( !isMultiPointOrLineString ) {
1230 for ( i = 0; i < p.length; i++ ) {
1232 for ( j = 0; j < p[ i ].length; j++ ) {
1233 result[ i ][ j ] = [ ];
1234 for ( k = 0; k < p[ i ][ j ].length; k++ ) {
1235 yOffset = (p[ i ][ j ][ k ][1] * yRatio);
1236 result[ i ][ j ][ k ] = [
1237 bbox[ 0 ] + ( p[ i ][ j ][ k ][ 0 ] * xRatio ),
1238 image ? bbox[ 1 ] + yOffset : bbox[ 3 ] - yOffset
1244 return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
1247 _toPixel: function (p, center, pixelSize) {
1248 // ignores $.geo.proj
1250 center = center || this._center;
1251 pixelSize = pixelSize || this._pixelSize;
1253 var isMultiPointOrLineString = $.isArray( p[ 0 ] ),
1254 isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray( p[ 0 ][ 0 ] ),
1255 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray( p[ 0 ][ 0 ][ 0 ] ),
1256 width = this._contentBounds["width"],
1257 height = this._contentBounds["height"],
1258 halfWidth = width / 2 * pixelSize,
1259 halfHeight = height / 2 * pixelSize,
1260 bbox = [center[0] - halfWidth, center[1] - halfHeight, center[0] + halfWidth, center[1] + halfHeight],
1261 bboxWidth = $.geo.width(bbox, true),
1262 bboxHeight = $.geo.height(bbox, true),
1263 image = this._options[ "axisLayout" ] === "image",
1264 xRatio = width / bboxWidth,
1265 yRatio = height / bboxHeight,
1269 if ( !isMultiPolygon ) {
1270 if ( !isMultiLineStringOrPolygon ) {
1271 if ( !isMultiPointOrLineString ) {
1279 for ( i = 0; i < p.length; i++ ) {
1281 for ( j = 0; j < p[ i ].length; j++ ) {
1282 result[ i ][ j ] = [ ];
1283 for ( k = 0; k < p[ i ][ j ].length; k++ ) {
1284 result[ i ][ j ][ k ] = [
1285 Math.round( ( p[ i ][ j ][ k ][ 0 ] - bbox[ 0 ] ) * xRatio ),
1286 Math.round( ( image ? p[ i ][ j ][ k ][ 1 ] - bbox[ 1 ] : bbox[ 3 ] - p[ i ][ j ][ k ][ 1 ] ) * yRatio )
1292 return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
1295 _zoomTo: function (coord, zoom, trigger, refresh) {
1296 zoom = zoom < 0 ? 0 : zoom;
1298 var pixelSize = this._getPixelSize( zoom );
1300 this._setCenterAndSize( coord, pixelSize, trigger, refresh );
1303 _document_keydown: function (e) {
1304 var len = this._drawCoords.length;
1305 if (len > 0 && e.which == 27) {
1307 this._resetDrawing();
1310 this._drawCoords[len - 2] = $.merge( [], this._drawCoords[ len - 1 ] );
1311 this._drawPixels[len - 2] = $.merge( [], this._drawPixels[ len - 1 ] );
1313 this._drawCoords.length--;
1314 this._drawPixels.length--;
1316 this._refreshDrawing();
1321 _eventTarget_dblclick_zoom: function(e) {
1322 this._trigger("dblclick", e, { type: "Point", coordinates: this.toMap(this._current) });
1323 if (!e.isDefaultPrevented()) {
1324 var centerAndSize = this._getZoomCenterAndSize(this._current, 1, true );
1325 this._setCenterAndSize(centerAndSize.center, centerAndSize.pixelSize, true, true);
1329 _eventTarget_dblclick: function (e) {
1330 if ( this._options[ "mode" ] === "static" ) {
1334 this._panFinalize();
1336 if (this._drawTimeout) {
1337 window.clearTimeout(this._drawTimeout);
1338 this._drawTimeout = null;
1341 var offset = $(e.currentTarget).offset();
1343 switch (this._options["mode"]) {
1344 case "drawLineString":
1345 if ( this._drawCoords.length > 1 && ! ( this._drawCoords[0][0] == this._drawCoords[1][0] &&
1346 this._drawCoords[0][1] == this._drawCoords[1][1] ) ) {
1347 this._drawCoords.length--;
1348 this._trigger( "shape", e, {
1350 coordinates: this._userGeodetic ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords
1353 this._eventTarget_dblclick_zoom(e);
1355 this._resetDrawing();
1359 if ( this._drawCoords.length > 1 && ! ( this._drawCoords[0][0] == this._drawCoords[1][0] &&
1360 this._drawCoords[0][1] == this._drawCoords[1][1] ) ) {
1361 var endIndex = this._drawCoords.length - 1;
1363 this._drawCoords[endIndex] = $.merge( [], this._drawCoords[0] );
1364 this._trigger( "shape", e, {
1366 coordinates: [ this._userGeodetic ? $.geo.proj.toGeodetic(this._drawCoords) : this._drawCoords ]
1370 this._eventTarget_dblclick_zoom(e);
1372 this._resetDrawing();
1375 case "measureLength":
1377 this._resetDrawing();
1381 this._eventTarget_dblclick_zoom(e);
1388 _eventTarget_touchstart: function (e) {
1389 if ( this._options[ "mode" ] === "static" ) {
1393 if ( !this._supportTouch && e.which != 1 ) {
1397 this._panFinalize();
1398 this._mouseWheelFinish();
1400 var offset = $(e.currentTarget).offset(),
1401 touches = e.originalEvent.changedTouches;
1403 if ( this._supportTouch ) {
1404 this._multiTouchAnchor = $.merge( [ ], touches );
1406 this._isMultiTouch = this._multiTouchAnchor.length > 1;
1408 if ( this._isMultiTouch ) {
1409 this._multiTouchCurrentBbox = [
1410 touches[0].pageX - offset.left,
1411 touches[0].pageY - offset.top,
1412 touches[1].pageX - offset.left,
1413 touches[1].pageY - offset.top
1416 this._multiTouchAnchorBbox = $.merge( [ ], this._multiTouchCurrentBbox );
1418 this._current = $.geo.center( this._multiTouchCurrentBbox, true );
1420 this._multiTouchCurrentBbox = [
1421 touches[0].pageX - offset.left,
1422 touches[0].pageY - offset.top,
1427 this._current = [ touches[0].pageX - offset.left, touches[0].pageY - offset.top ];
1430 this._current = [e.pageX - offset.left, e.pageY - offset.top];
1433 if (this._softDblClick) {
1434 var downDate = $.now();
1435 if (downDate - this._downDate < 750) {
1437 var dx = this._current[0] - this._anchor[0],
1438 dy = this._current[1] - this._anchor[1],
1439 distance = Math.sqrt((dx * dx) + (dy * dy));
1441 this._isTap = false;
1443 this._current = $.merge( [ ], this._anchor );
1447 if (this._isDbltap) {
1448 this._isDbltap = false;
1450 this._isDbltap = this._isTap;
1453 this._isDbltap = false;
1456 this._downDate = downDate;
1459 this._mouseDown = true;
1460 this._anchor = $.merge( [ ], this._current );
1462 if (!this._inOp && e.shiftKey) {
1463 this._shiftZoom = true;
1464 this._$eventTarget.css("cursor", this._options["cursors"]["zoom"]);
1465 } else if ( !this._isMultiTouch && this._options[ "pannable" ] ) {
1468 switch (this._options["mode"]) {
1473 this._lastDrag = this._current;
1475 if (e.currentTarget.setCapture) {
1476 e.currentTarget.setCapture();
1487 _dragTarget_touchmove: function (e) {
1488 if ( this._options[ "mode" ] === "static" ) {
1492 var offset = this._$eventTarget.offset(),
1493 drawCoordsLen = this._drawCoords.length,
1494 touches = e.originalEvent.changedTouches,
1499 if ( this._supportTouch ) {
1500 if ( !this._isMultiTouch && touches[ 0 ].identifier !== this._multiTouchAnchor[ 0 ].identifier ) {
1501 // switch to multitouch
1502 this._mouseDown = false;
1503 this._dragTarget_touchstop( e );
1505 this._isMultiTouch = true;
1507 this._multiTouchAnchor.push( touches[ 0 ] );
1509 this._multiTouchCurrentBbox = [
1510 this._multiTouchCurrentBbox[ 0 ],
1511 this._multiTouchCurrentBbox[ 1 ],
1512 this._multiTouchAnchor[1].pageX - offset.left,
1513 this._multiTouchAnchor[1].pageY - offset.top
1516 this._multiTouchAnchorBbox = $.merge( [ ], this._multiTouchCurrentBbox );
1518 this._mouseDown = true;
1519 this._anchor = this._current = $.geo.center( this._multiTouchCurrentBbox, true );
1524 if ( this._isMultiTouch ) {
1525 for ( ; i < touches.length; i++ ) {
1526 if ( touches[ i ].identifier === this._multiTouchAnchor[ 0 ].identifier ) {
1527 this._multiTouchCurrentBbox[ 0 ] = touches[ i ].pageX - offset.left;
1528 this._multiTouchCurrentBbox[ 1 ] = touches[ i ].pageY - offset.top;
1529 } else if ( touches[ i ].identifier === this._multiTouchAnchor[ 1 ].identifier ) {
1530 this._multiTouchCurrentBbox[ 2 ] = touches[ i ].pageX - offset.left;
1531 this._multiTouchCurrentBbox[ 3 ] = touches[ i ].pageY - offset.top;
1535 current = $.geo.center( this._multiTouchCurrentBbox, true );
1537 var currentWidth = this._multiTouchCurrentBbox[ 2 ] - this._multiTouchCurrentBbox[ 0 ],
1538 anchorWidth = this._multiTouchAnchorBbox[ 2 ] - this._multiTouchAnchorBbox[ 0 ],
1539 ratioWidth = currentWidth / anchorWidth;
1541 this._wheelLevel = Math.abs( Math.floor( ( 1 - ratioWidth ) * 10 ) );
1542 if ( Math.abs( currentWidth ) < Math.abs( anchorWidth ) ) {
1543 this._wheelLevel = - this._wheelLevel;
1546 var pinchCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, false );
1547 this._$elem.find( ".geo-shapes-container" ).geographics("clear");
1549 for ( i = 0; i < this._currentServices.length; i++ ) {
1550 service = this._currentServices[ i ];
1551 $.geo[ "_serviceTypes" ][ service.type ].interactiveScale( this, service, pinchCenterAndSize.center, pinchCenterAndSize.pixelSize );
1554 if (this._graphicShapes.length > 0 && this._graphicShapes.length < 256) {
1555 this._refreshShapes(this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes, pinchCenterAndSize.center, pinchCenterAndSize.pixelSize);
1559 if (this._drawCoords.length > 0) {
1560 this._drawPixels = this._toPixel(this._drawCoords, pinchCenterAndSize.center, pinchCenterAndSize.pixelSize);
1561 this._refreshDrawing();
1564 current = $.geo.center( this._multiTouchCurrentBbox, true );
1566 current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
1569 current = [e.pageX - offset.left, e.pageY - offset.top];
1572 if (current[0] === this._lastMove[0] && current[1] === this._lastMove[1]) {
1579 if ( _ieVersion == 7 ) {
1580 this._isDbltap = this._isTap = false;
1583 if (this._mouseDown) {
1584 this._current = current;
1585 this._moveDate = $.now();
1588 if ( this._isMultiTouch ) {
1589 e.preventDefault( );
1590 this._isDbltap = this._isTap = false;
1594 var mode = this._shiftZoom ? "zoom" : this._options["mode"];
1598 if ( this._mouseDown ) {
1599 this._$drawContainer.geographics( "clear" );
1600 this._$drawContainer.geographics( "drawBbox", [
1607 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
1611 case "drawLineString":
1613 case "measureLength":
1615 if (this._mouseDown || this._toolPan) {
1618 if (drawCoordsLen > 0) {
1619 this._drawCoords[drawCoordsLen - 1] = this._toMap(current);
1620 this._drawPixels[drawCoordsLen - 1] = current;
1622 this._refreshDrawing();
1625 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
1630 if (this._mouseDown || this._toolPan) {
1633 this._trigger("move", e, { type: "Point", coordinates: this.toMap(current) });
1638 this._lastMove = current;
1646 _dragTarget_touchstop: function (e) {
1647 if ( this._options[ "mode" ] === "static" ) {
1651 if (!this._mouseDown && _ieVersion == 7) {
1652 // ie7 doesn't appear to trigger dblclick on this._$eventTarget,
1653 // we fake regular click here to cause soft dblclick
1654 this._eventTarget_touchstart(e);
1657 var mouseWasDown = this._mouseDown,
1658 wasToolPan = this._toolPan,
1659 offset = this._$eventTarget.offset(),
1660 mode = this._shiftZoom ? "zoom" : this._options["mode"],
1661 current, i, clickDate,
1664 if (this._supportTouch) {
1665 current = [e.originalEvent.changedTouches[0].pageX - offset.left, e.originalEvent.changedTouches[0].pageY - offset.top];
1667 current = [e.pageX - offset.left, e.pageY - offset.top];
1670 if (this._softDblClick) {
1672 var dx = current[0] - this._anchor[0],
1673 dy = current[1] - this._anchor[1],
1674 distance = Math.sqrt((dx * dx) + (dy * dy));
1675 if (distance <= 8) {
1676 current = $.merge( [ ], this._anchor );
1681 dx = current[0] - this._anchor[0];
1682 dy = current[1] - this._anchor[1];
1684 this._$eventTarget.css("cursor", this._options["cursors"][this._options["mode"]]);
1686 this._shiftZoom = this._mouseDown = this._toolPan = false;
1688 if ( this._isMultiTouch ) {
1689 e.preventDefault( );
1690 this._isMultiTouch = false;
1692 var pinchCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, false );
1694 this._setCenterAndSize(pinchCenterAndSize.center, pinchCenterAndSize.pixelSize, true, true);
1696 this._wheelLevel = 0;
1701 if (document.releaseCapture) {
1702 document.releaseCapture();
1706 clickDate = $.now();
1707 this._current = current;
1711 if ( dx > 0 || dy > 0 ) {
1712 var minSize = this._pixelSize * 6,
1713 bboxCoords = this._toMap( [ [
1714 Math.min( this._anchor[ 0 ], current[ 0 ] ),
1715 Math.max( this._anchor[ 1 ], current[ 1 ] )
1717 Math.max( this._anchor[ 0 ], current[ 0 ] ),
1718 Math.min( this._anchor[ 1 ], current[ 1 ] )
1728 if ( ( bbox[2] - bbox[0] ) < minSize && ( bbox[3] - bbox[1] ) < minSize ) {
1729 bbox = $.geo.scaleBy( this._getBbox( $.geo.center( bbox, true ) ), .5, true );
1732 this._setBbox(bbox, true, true);
1735 this._resetDrawing();
1739 if (this._drawTimeout) {
1740 window.clearTimeout(this._drawTimeout);
1741 this._drawTimeout = null;
1745 this._panFinalize();
1747 if (clickDate - this._clickDate > 100) {
1749 this._drawTimeout = setTimeout(function () {
1750 if (geomap._drawTimeout) {
1751 geomap._trigger("shape", e, { type: "Point", coordinates: geomap.toMap(current) });
1752 geomap._inOp = false;
1753 geomap._drawTimeout = null;
1760 case "drawLineString":
1762 case "measureLength":
1765 this._panFinalize();
1767 i = (this._drawCoords.length == 0 ? 0 : this._drawCoords.length - 1);
1769 this._drawCoords[i] = this._toMap(current);
1770 this._drawPixels[i] = current;
1772 if (i < 2 || !(this._drawCoords[i][0] == this._drawCoords[i-1][0] &&
1773 this._drawCoords[i][1] == this._drawCoords[i-1][1])) {
1774 this._drawCoords[i + 1] = this._toMap(current);
1775 this._drawPixels[i + 1] = current;
1778 this._refreshDrawing();
1786 if (clickDate - this._clickDate > 100) {
1787 this._trigger("click", e, { type: "Point", coordinates: this.toMap(current) });
1794 this._clickDate = clickDate;
1796 if (this._softDblClick && this._isDbltap) {
1797 this._isDbltap = this._isTap = false;
1798 this._$eventTarget.trigger("dblclick", e);
1808 _eventTarget_mousewheel: function (e, delta) {
1809 if ( this._options[ "mode" ] === "static" || this._options[ "scroll" ] === "off" ) {
1815 this._panFinalize();
1817 if ( this._mouseDown ) {
1822 if (this._wheelTimeout) {
1823 window.clearTimeout(this._wheelTimeout);
1824 this._wheelTimeout = null;
1826 var offset = $(e.currentTarget).offset();
1827 this._anchor = [e.pageX - offset.left, e.pageY - offset.top];
1830 this._wheelLevel += delta;
1832 var wheelCenterAndSize = this._getZoomCenterAndSize( this._anchor, this._wheelLevel, this._options[ "tilingScheme" ] != null ),
1836 this._$elem.find( ".geo-shapes-container" ).geographics("clear");
1838 for ( ; i < this._currentServices.length; i++ ) {
1839 service = this._currentServices[ i ];
1840 $.geo["_serviceTypes"][service.type].interactiveScale(this, service, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
1843 if (this._graphicShapes.length > 0 && this._graphicShapes.length < 256) {
1844 this._refreshShapes(this._$shapesContainer, this._graphicShapes, this._graphicShapes, this._graphicShapes, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
1847 if (this._drawCoords.length > 0) {
1848 this._drawPixels = this._toPixel(this._drawCoords, wheelCenterAndSize.center, wheelCenterAndSize.pixelSize);
1849 this._refreshDrawing();
1853 this._wheelTimeout = window.setTimeout(function () {
1854 geomap._mouseWheelFinish();