1 (function(){ 2 3 /** 4 * @exports mxn.util.$m as $m 5 */ 6 var $m = mxn.util.$m; 7 8 /** 9 * Initialise our provider. This function should only be called 10 * from within mapstraction code, not exposed as part of the API. 11 * @private 12 */ 13 var init = function() { 14 this.invoker.go('init', [ this.currentElement, this.api ]); 15 this.applyOptions(); 16 }; 17 18 /** 19 * Mapstraction instantiates a map with some API choice into the HTML element given 20 * @name mxn.Mapstraction 21 * @constructor 22 * @param {String} element The HTML element to replace with a map 23 * @param {String} api The API to use, one of 'google', 'googlev3', 'yahoo', 'microsoft', 'openstreetmap', 'multimap', 'map24', 'openlayers', 'mapquest'. If omitted, first loaded provider implementation is used. 24 * @param {Bool} debug optional parameter to turn on debug support - this uses alert panels for unsupported actions 25 * @exports Mapstraction as mxn.Mapstraction 26 */ 27 var Mapstraction = mxn.Mapstraction = function(element, api, debug) { 28 if (!api){ 29 api = mxn.util.getAvailableProviders()[0]; 30 } 31 32 /** 33 * The name of the active API. 34 * @name mxn.Mapstraction#api 35 * @type {String} 36 */ 37 this.api = api; 38 39 this.maps = {}; 40 41 /** 42 * The DOM element containing the map. 43 * @name mxn.Mapstraction#currentElement 44 * @property 45 * @type {DOMElement} 46 */ 47 this.currentElement = $m(element); 48 49 this.eventListeners = []; 50 51 /** 52 * The array of all layers that have been added to the map. 53 */ 54 this.tileLayers = []; 55 56 /** 57 * The markers currently loaded. 58 * @name mxn.Mapstraction#markers 59 * @property 60 * @type {Array} 61 */ 62 this.markers = []; 63 64 /** 65 * The polylines currently loaded. 66 * @name mxn.Mapstraction#polylines 67 * @property 68 * @type {Array} 69 */ 70 this.polylines = []; 71 72 this.images = []; 73 this.controls = []; 74 this.loaded = {}; 75 this.onload = {}; 76 //this.loaded[api] = true; // FIXME does this need to be true? -ajturner 77 this.onload[api] = []; 78 79 /** 80 * The original element value passed to the constructor. 81 * @name mxn.Mapstraction#element 82 * @property 83 * @type {String|DOMElement} 84 */ 85 this.element = element; 86 87 /** 88 * Options defaults. 89 * @name mxn.Mapstraction#options 90 * @property {Object} 91 */ 92 this.options = { 93 enableScrollWheelZoom: false, 94 enableDragging: true 95 }; 96 97 this.addControlsArgs = {}; 98 99 // set up our invoker for calling API methods 100 this.invoker = new mxn.Invoker(this, 'Mapstraction', function(){ return this.api; }); 101 102 // Adding our events 103 mxn.addEvents(this, [ 104 105 /** 106 * Map has loaded 107 * @name mxn.Mapstraction#load 108 * @event 109 */ 110 'load', 111 112 /** 113 * Map is clicked {location: LatLonPoint} 114 * @name mxn.Mapstraction#click 115 * @event 116 */ 117 'click', 118 119 /** 120 * Map is panned 121 * @name mxn.Mapstraction#endPan 122 * @event 123 */ 124 'endPan', 125 126 /** 127 * Zoom is changed 128 * @name mxn.Mapstraction#changeZoom 129 * @event 130 */ 131 'changeZoom', 132 133 /** 134 * Marker is added {marker: Marker} 135 * @name mxn.Mapstraction#markerAdded 136 * @event 137 */ 138 'markerAdded', 139 140 /** 141 * Marker is removed {marker: Marker} 142 * @name mxn.Mapstraction#markerRemoved 143 * @event 144 */ 145 'markerRemoved', 146 147 /** 148 * Polyline is added {polyline: Polyline} 149 * @name mxn.Mapstraction#polylineAdded 150 * @event 151 */ 152 'polylineAdded', 153 154 /** 155 * Polyline is removed {polyline: Polyline} 156 * @name mxn.Mapstraction#polylineRemoved 157 * @event 158 */ 159 'polylineRemoved' 160 ]); 161 162 // finally initialize our proper API map 163 init.apply(this); 164 }; 165 166 // Map type constants 167 Mapstraction.ROAD = 1; 168 Mapstraction.SATELLITE = 2; 169 Mapstraction.HYBRID = 3; 170 Mapstraction.PHYSICAL = 4; 171 172 // methods that have no implementation in mapstraction core 173 mxn.addProxyMethods(Mapstraction, [ 174 /** 175 * Adds a large map panning control and zoom buttons to the map 176 * @name mxn.Mapstraction#addLargeControls 177 * @function 178 */ 179 'addLargeControls', 180 181 /** 182 * Adds a map type control to the map (streets, aerial imagery etc) 183 * @name mxn.Mapstraction#addMapTypeControls 184 * @function 185 */ 186 'addMapTypeControls', 187 188 /** 189 * Adds a GeoRSS or KML overlay to the map 190 * some flavors of GeoRSS and KML are not supported by some of the Map providers 191 * @name mxn.Mapstraction#addOverlay 192 * @function 193 * @param {String} url GeoRSS or KML feed URL 194 * @param {Boolean} autoCenterAndZoom Set true to auto center and zoom after the feed is loaded 195 */ 196 'addOverlay', 197 198 /** 199 * Adds a small map panning control and zoom buttons to the map 200 * @name mxn.Mapstraction#addSmallControls 201 * @function 202 */ 203 'addSmallControls', 204 205 /** 206 * Applies the current option settings 207 * @name mxn.Mapstraction#applyOptions 208 * @function 209 */ 210 'applyOptions', 211 212 /** 213 * Gets the BoundingBox of the map 214 * @name mxn.Mapstraction#getBounds 215 * @function 216 * @returns {BoundingBox} The bounding box for the current map state 217 */ 218 'getBounds', 219 220 /** 221 * Gets the central point of the map 222 * @name mxn.Mapstraction#getCenter 223 * @function 224 * @returns {LatLonPoint} The center point of the map 225 */ 226 'getCenter', 227 228 /** 229 * Gets the imagery type for the map. 230 * The type can be one of: 231 * mxn.Mapstraction.ROAD 232 * mxn.Mapstraction.SATELLITE 233 * mxn.Mapstraction.HYBRID 234 * mxn.Mapstraction.PHYSICAL 235 * @name mxn.Mapstraction#getMapType 236 * @function 237 * @returns {Number} 238 */ 239 'getMapType', 240 241 /** 242 * Returns a ratio to turn distance into pixels based on current projection 243 * @name mxn.Mapstraction#getPixelRatio 244 * @function 245 * @returns {Float} ratio 246 */ 247 'getPixelRatio', 248 249 /** 250 * Returns the zoom level of the map 251 * @name mxn.Mapstraction#getZoom 252 * @function 253 * @returns {Integer} The zoom level of the map 254 */ 255 'getZoom', 256 257 /** 258 * Returns the best zoom level for bounds given 259 * @name mxn.Mapstraction#getZoomLevelForBoundingBox 260 * @function 261 * @param {BoundingBox} bbox The bounds to fit 262 * @returns {Integer} The closest zoom level that contains the bounding box 263 */ 264 'getZoomLevelForBoundingBox', 265 266 /** 267 * Displays the coordinates of the cursor in the HTML element 268 * @name mxn.Mapstraction#mousePosition 269 * @function 270 * @param {String} element ID of the HTML element to display the coordinates in 271 */ 272 'mousePosition', 273 274 /** 275 * Resize the current map to the specified width and height 276 * (since it is actually on a child div of the mapElement passed 277 * as argument to the Mapstraction constructor, the resizing of this 278 * mapElement may have no effect on the size of the actual map) 279 * @name mxn.Mapstraction#resizeTo 280 * @function 281 * @param {Integer} width The width the map should be. 282 * @param {Integer} height The width the map should be. 283 */ 284 'resizeTo', 285 286 /** 287 * Sets the map to the appropriate location and zoom for a given BoundingBox 288 * @name mxn.Mapstraction#setBounds 289 * @function 290 * @param {BoundingBox} bounds The bounding box you want the map to show 291 */ 292 'setBounds', 293 294 /** 295 * setCenter sets the central point of the map 296 * @name mxn.Mapstraction#setCenter 297 * @function 298 * @param {LatLonPoint} point The point at which to center the map 299 * @param {Object} options Optional parameters 300 * @param {Boolean} options.pan Whether the map should move to the locations using a pan or just jump straight there 301 */ 302 'setCenter', 303 304 /** 305 * Centers the map to some place and zoom level 306 * @name mxn.Mapstraction#setCenterAndZoom 307 * @function 308 * @param {LatLonPoint} point Where the center of the map should be 309 * @param {Integer} zoom The zoom level where 0 is all the way out. 310 */ 311 'setCenterAndZoom', 312 313 /** 314 * Sets the imagery type for the map 315 * The type can be one of: 316 * mxn.Mapstraction.ROAD 317 * mxn.Mapstraction.SATELLITE 318 * mxn.Mapstraction.HYBRID 319 * mxn.Mapstraction.PHYSICAL 320 * @name mxn.Mapstraction#setMapType 321 * @function 322 * @param {Number} type 323 */ 324 'setMapType', 325 326 /** 327 * Sets the zoom level for the map 328 * MS doesn't seem to do zoom=0, and Gg's sat goes closer than it's maps, and MS's sat goes closer than Y!'s 329 * TODO: Mapstraction.prototype.getZoomLevels or something. 330 * @name mxn.Mapstraction#setZoom 331 * @function 332 * @param {Number} zoom The (native to the map) level zoom the map to. 333 */ 334 'setZoom', 335 336 /** 337 * Turns a Tile Layer on or off 338 * @name mxn.Mapstraction#toggleTileLayer 339 * @function 340 * @param {tile_url} url of the tile layer that was created. 341 */ 342 'toggleTileLayer' 343 ]); 344 345 /** 346 * Sets the current options to those specified in oOpts and applies them 347 * @param {Object} oOpts Hash of options to set 348 */ 349 Mapstraction.prototype.setOptions = function(oOpts){ 350 mxn.util.merge(this.options, oOpts); 351 this.applyOptions(); 352 }; 353 354 /** 355 * Sets an option and applies it. 356 * @param {String} sOptName Option name 357 * @param vVal Option value 358 */ 359 Mapstraction.prototype.setOption = function(sOptName, vVal){ 360 this.options[sOptName] = vVal; 361 this.applyOptions(); 362 }; 363 364 /** 365 * Enable scroll wheel zooming 366 * @deprecated Use setOption instead. 367 */ 368 Mapstraction.prototype.enableScrollWheelZoom = function() { 369 this.setOption('enableScrollWheelZoom', true); 370 }; 371 372 /** 373 * Enable/disable dragging of the map 374 * @param {Boolean} on 375 * @deprecated Use setOption instead. 376 */ 377 Mapstraction.prototype.dragging = function(on) { 378 this.setOption('enableDragging', on); 379 }; 380 381 /** 382 * Change the current api on the fly 383 * @param {String} api The API to swap to 384 * @param element 385 */ 386 Mapstraction.prototype.swap = function(element,api) { 387 if (this.api === api) { 388 return; 389 } 390 391 var center = this.getCenter(); 392 var zoom = this.getZoom(); 393 394 this.currentElement.style.visibility = 'hidden'; 395 this.currentElement.style.display = 'none'; 396 397 this.currentElement = $m(element); 398 this.currentElement.style.visibility = 'visible'; 399 this.currentElement.style.display = 'block'; 400 401 this.api = api; 402 this.onload[api] = []; 403 404 if (this.maps[this.api] === undefined) { 405 init.apply(this); 406 407 for (var i = 0; i < this.markers.length; i++) { 408 this.addMarker(this.markers[i], true); 409 } 410 411 for (var j = 0; j < this.polylines.length; j++) { 412 this.addPolyline( this.polylines[j], true); 413 } 414 415 this.setCenterAndZoom(center,zoom); 416 } 417 else { 418 419 //sync the view 420 this.setCenterAndZoom(center,zoom); 421 422 //TODO synchronize the markers and polylines too 423 // (any overlays created after api instantiation are not sync'd) 424 } 425 426 this.addControls(this.addControlsArgs); 427 428 }; 429 430 /** 431 * Returns the loaded state of a Map Provider 432 * @param {String} api Optional API to query for. If not specified, returns state of the originally created API 433 */ 434 Mapstraction.prototype.isLoaded = function(api){ 435 if (api === null) { 436 api = this.api; 437 } 438 return this.loaded[api]; 439 }; 440 441 /** 442 * Set the debugging on or off - shows alert panels for functions that don't exist in Mapstraction 443 * @param {Boolean} debug true to turn on debugging, false to turn it off 444 */ 445 Mapstraction.prototype.setDebug = function(debug){ 446 if(debug !== null) { 447 this.debug = debug; 448 } 449 return this.debug; 450 }; 451 452 /** 453 * Set the api call deferment on or off - When it's on, mxn.invoke will queue up provider API calls until 454 * runDeferred is called, at which time everything in the queue will be run in the order it was added. 455 * @param {Boolean} set deferred to true to turn on deferment 456 */ 457 Mapstraction.prototype.setDefer = function(deferred){ 458 this.loaded[this.api] = !deferred; 459 }; 460 461 /** 462 * Run any queued provider API calls for the methods defined in the provider's implementation. 463 * For example, if defferable in mxn.[provider].core.js is set to {getCenter: true, setCenter: true} 464 * then any calls to map.setCenter or map.getCenter will be queued up in this.onload. When the provider's 465 * implementation loads the map, it calls this.runDeferred and any queued calls will be run. 466 */ 467 Mapstraction.prototype.runDeferred = function(){ 468 while(this.onload[this.api].length > 0) { 469 this.onload[this.api].shift().apply(this); //run deferred calls 470 } 471 }; 472 473 ///////////////////////// 474 // 475 // Event Handling 476 // 477 // FIXME need to consolidate some of these handlers... 478 // 479 /////////////////////////// 480 481 // Click handler attached to native API 482 Mapstraction.prototype.clickHandler = function(lat, lon, me) { 483 this.callEventListeners('click', { 484 location: new LatLonPoint(lat, lon) 485 }); 486 }; 487 488 // Move and zoom handler attached to native API 489 Mapstraction.prototype.moveendHandler = function(me) { 490 this.callEventListeners('moveend', {}); 491 }; 492 493 /** 494 * Add a listener for an event. 495 * @param {String} type Event type to attach listener to 496 * @param {Function} func Callback function 497 * @param {Object} caller Callback object 498 */ 499 Mapstraction.prototype.addEventListener = function() { 500 var listener = {}; 501 listener.event_type = arguments[0]; 502 listener.callback_function = arguments[1]; 503 504 // added the calling object so we can retain scope of callback function 505 if(arguments.length == 3) { 506 listener.back_compat_mode = false; 507 listener.callback_object = arguments[2]; 508 } 509 else { 510 listener.back_compat_mode = true; 511 listener.callback_object = null; 512 } 513 this.eventListeners.push(listener); 514 }; 515 516 /** 517 * Call listeners for a particular event. 518 * @param {String} sEventType Call listeners of this event type 519 * @param {Object} oEventArgs Event args object to pass back to the callback 520 */ 521 Mapstraction.prototype.callEventListeners = function(sEventType, oEventArgs) { 522 oEventArgs.source = this; 523 for(var i = 0; i < this.eventListeners.length; i++) { 524 var evLi = this.eventListeners[i]; 525 if(evLi.event_type == sEventType) { 526 // only two cases for this, click and move 527 if(evLi.back_compat_mode) { 528 if(evLi.event_type == 'click') { 529 evLi.callback_function(oEventArgs.location); 530 } 531 else { 532 evLi.callback_function(); 533 } 534 } 535 else { 536 var scope = evLi.callback_object || this; 537 evLi.callback_function.call(scope, oEventArgs); 538 } 539 } 540 } 541 }; 542 543 544 //////////////////// 545 // 546 // map manipulation 547 // 548 ///////////////////// 549 550 551 /** 552 * addControls adds controls to the map. You specify which controls to add in 553 * the associative array that is the only argument. 554 * addControls can be called multiple time, with different args, to dynamically change controls. 555 * 556 * args = { 557 * pan: true, 558 * zoom: 'large' || 'small', 559 * overview: true, 560 * scale: true, 561 * map_type: true, 562 * } 563 * @param {array} args Which controls to switch on 564 */ 565 Mapstraction.prototype.addControls = function( args ) { 566 this.addControlsArgs = args; 567 this.invoker.go('addControls', arguments); 568 }; 569 570 /** 571 * Adds a marker pin to the map 572 * @param {Marker} marker The marker to add 573 * @param {Boolean} old If true, doesn't add this marker to the markers array. Used by the "swap" method 574 */ 575 Mapstraction.prototype.addMarker = function(marker, old) { 576 marker.mapstraction = this; 577 marker.api = this.api; 578 marker.location.api = this.api; 579 marker.map = this.maps[this.api]; 580 var propMarker = this.invoker.go('addMarker', arguments); 581 marker.setChild(propMarker); 582 if (!old) { 583 this.markers.push(marker); 584 } 585 this.markerAdded.fire({'marker': marker}); 586 }; 587 588 /** 589 * addMarkerWithData will addData to the marker, then add it to the map 590 * @param {Marker} marker The marker to add 591 * @param {Object} data A data has to add 592 */ 593 Mapstraction.prototype.addMarkerWithData = function(marker, data) { 594 marker.addData(data); 595 this.addMarker(marker); 596 }; 597 598 /** 599 * addPolylineWithData will addData to the polyline, then add it to the map 600 * @param {Polyline} polyline The polyline to add 601 * @param {Object} data A data has to add 602 */ 603 Mapstraction.prototype.addPolylineWithData = function(polyline, data) { 604 polyline.addData(data); 605 this.addPolyline(polyline); 606 }; 607 608 /** 609 * removeMarker removes a Marker from the map 610 * @param {Marker} marker The marker to remove 611 */ 612 Mapstraction.prototype.removeMarker = function(marker) { 613 var current_marker; 614 for(var i = 0; i < this.markers.length; i++){ 615 current_marker = this.markers[i]; 616 if(marker == current_marker) { 617 this.invoker.go('removeMarker', arguments); 618 marker.onmap = false; 619 this.markers.splice(i, 1); 620 this.markerRemoved.fire({'marker': marker}); 621 break; 622 } 623 } 624 }; 625 626 /** 627 * removeAllMarkers removes all the Markers on a map 628 */ 629 Mapstraction.prototype.removeAllMarkers = function() { 630 var current_marker; 631 while(this.markers.length > 0) { 632 current_marker = this.markers.pop(); 633 this.invoker.go('removeMarker', [current_marker]); 634 } 635 }; 636 637 /** 638 * Declutter the markers on the map, group together overlapping markers. 639 * @param {Object} opts Declutter options 640 */ 641 Mapstraction.prototype.declutterMarkers = function(opts) { 642 if(this.loaded[this.api] === false) { 643 var me = this; 644 this.onload[this.api].push( function() { 645 me.declutterMarkers(opts); 646 } ); 647 return; 648 } 649 650 var map = this.maps[this.api]; 651 652 switch(this.api) 653 { 654 // case 'yahoo': 655 // 656 // break; 657 // case 'google': 658 // 659 // break; 660 // case 'openstreetmap': 661 // 662 // break; 663 // case 'microsoft': 664 // 665 // break; 666 // case 'openlayers': 667 // 668 // break; 669 case 'multimap': 670 /* 671 * Multimap supports quite a lot of decluttering options such as whether 672 * to use an accurate of fast declutter algorithm and what icon to use to 673 * represent a cluster. Using all this would mean abstracting all the enums 674 * etc so we're only implementing the group name function at the moment. 675 */ 676 map.declutterGroup(opts.groupName); 677 break; 678 // case 'mapquest': 679 // 680 // break; 681 // case 'map24': 682 // 683 // break; 684 case ' dummy': 685 break; 686 default: 687 if(this.debug) { 688 alert(this.api + ' not supported by Mapstraction.declutterMarkers'); 689 } 690 } 691 }; 692 693 /** 694 * Add a polyline to the map 695 * @param {Polyline} polyline The Polyline to add to the map 696 * @param {Boolean} old If true replaces an existing Polyline 697 */ 698 Mapstraction.prototype.addPolyline = function(polyline, old) { 699 polyline.api = this.api; 700 polyline.map = this.maps[this.api]; 701 var propPoly = this.invoker.go('addPolyline', arguments); 702 polyline.setChild(propPoly); 703 if(!old) { 704 this.polylines.push(polyline); 705 } 706 this.polylineAdded.fire({'polyline': polyline}); 707 }; 708 709 // Private remove implementation 710 var removePolylineImpl = function(polyline) { 711 this.invoker.go('removePolyline', arguments); 712 polyline.onmap = false; 713 this.polylineRemoved.fire({'polyline': polyline}); 714 }; 715 716 /** 717 * Remove the polyline from the map 718 * @param {Polyline} polyline The Polyline to remove from the map 719 */ 720 Mapstraction.prototype.removePolyline = function(polyline) { 721 var current_polyline; 722 for(var i = 0; i < this.polylines.length; i++){ 723 current_polyline = this.polylines[i]; 724 if(polyline == current_polyline) { 725 this.polylines.splice(i, 1); 726 removePolylineImpl.call(this, polyline); 727 break; 728 } 729 } 730 }; 731 732 /** 733 * Removes all polylines from the map 734 */ 735 Mapstraction.prototype.removeAllPolylines = function() { 736 var current_polyline; 737 while(this.polylines.length > 0) { 738 current_polyline = this.polylines.pop(); 739 removePolylineImpl.call(this, current_polyline); 740 } 741 }; 742 743 var collectPoints = function(bMarkers, bPolylines, predicate) { 744 var points = []; 745 746 if (bMarkers) { 747 for (var i = 0; i < this.markers.length; i++) { 748 var mark = this.markers[i]; 749 if (!predicate || predicate(mark)) { 750 points.push(mark.location); 751 } 752 } 753 } 754 755 if (bPolylines) { 756 for(i = 0; i < this.polylines.length; i++) { 757 var poly = this.polylines[i]; 758 if (!predicate || predicate(poly)) { 759 for (var j = 0; j < poly.points.length; j++) { 760 points.push(poly.points[j]); 761 } 762 } 763 } 764 } 765 766 return points; 767 }; 768 769 /** 770 * Sets the center and zoom of the map to the smallest bounding box 771 * containing all markers and polylines 772 */ 773 Mapstraction.prototype.autoCenterAndZoom = function() { 774 var points = collectPoints.call(this, true, true); 775 776 this.centerAndZoomOnPoints(points); 777 }; 778 779 /** 780 * centerAndZoomOnPoints sets the center and zoom of the map from an array of points 781 * 782 * This is useful if you don't want to have to add markers to the map 783 */ 784 Mapstraction.prototype.centerAndZoomOnPoints = function(points) { 785 var bounds = new BoundingBox(90, 180, -90, -180); 786 787 for (var i = 0, len = points.length; i < len; i++) { 788 bounds.extend(points[i]); 789 } 790 791 this.setBounds(bounds); 792 }; 793 794 /** 795 * Sets the center and zoom of the map to the smallest bounding box 796 * containing all visible markers and polylines 797 * will only include markers and polylines with an attribute of "visible" 798 */ 799 Mapstraction.prototype.visibleCenterAndZoom = function() { 800 var predicate = function(obj) { 801 return obj.getAttribute("visible"); 802 }; 803 var points = collectPoints.call(this, true, true, predicate); 804 805 this.centerAndZoomOnPoints(points); 806 }; 807 808 /** 809 * Automatically sets center and zoom level to show all polylines 810 * @param {Number} padding Optional number of kilometers to pad around polyline 811 */ 812 Mapstraction.prototype.polylineCenterAndZoom = function(padding) { 813 padding = padding || 0; 814 815 var points = collectPoints.call(this, false, true); 816 817 if (padding > 0) { 818 var padPoints = []; 819 for (var i = 0; i < points.length; i++) { 820 var point = points[i]; 821 822 var kmInOneDegreeLat = point.latConv(); 823 var kmInOneDegreeLon = point.lonConv(); 824 825 var latPad = padding / kmInOneDegreeLat; 826 var lonPad = padding / kmInOneDegreeLon; 827 828 var ne = new LatLonPoint(point.lat + latPad, point.lon + lonPad); 829 var sw = new LatLonPoint(point.lat - latPad, point.lon - lonPad); 830 831 padPoints.push(ne, sw); 832 } 833 points = points.concat(padPoints); 834 } 835 836 this.centerAndZoomOnPoints(points); 837 }; 838 839 /** 840 * addImageOverlay layers an georeferenced image over the map 841 * @param {id} unique DOM identifier 842 * @param {src} url of image 843 * @param {opacity} opacity 0-100 844 * @param {west} west boundary 845 * @param {south} south boundary 846 * @param {east} east boundary 847 * @param {north} north boundary 848 */ 849 Mapstraction.prototype.addImageOverlay = function(id, src, opacity, west, south, east, north) { 850 851 var b = document.createElement("img"); 852 b.style.display = 'block'; 853 b.setAttribute('id',id); 854 b.setAttribute('src',src); 855 b.style.position = 'absolute'; 856 b.style.zIndex = 1; 857 b.setAttribute('west',west); 858 b.setAttribute('south',south); 859 b.setAttribute('east',east); 860 b.setAttribute('north',north); 861 862 var oContext = { 863 imgElm: b 864 }; 865 866 this.invoker.go('addImageOverlay', arguments, { context: oContext }); 867 }; 868 869 Mapstraction.prototype.setImageOpacity = function(id, opacity) { 870 if (opacity < 0) { 871 opacity = 0; 872 } 873 if (opacity >= 100) { 874 opacity = 100; 875 } 876 var c = opacity / 100; 877 var d = document.getElementById(id); 878 if(typeof(d.style.filter)=='string'){ 879 d.style.filter='alpha(opacity:'+opacity+')'; 880 } 881 if(typeof(d.style.KHTMLOpacity)=='string'){ 882 d.style.KHTMLOpacity=c; 883 } 884 if(typeof(d.style.MozOpacity)=='string'){ 885 d.style.MozOpacity=c; 886 } 887 if(typeof(d.style.opacity)=='string'){ 888 d.style.opacity=c; 889 } 890 }; 891 892 Mapstraction.prototype.setImagePosition = function(id) { 893 var imgElement = document.getElementById(id); 894 var oContext = { 895 latLng: { 896 top: imgElement.getAttribute('north'), 897 left: imgElement.getAttribute('west'), 898 bottom: imgElement.getAttribute('south'), 899 right: imgElement.getAttribute('east') 900 }, 901 pixels: { top: 0, right: 0, bottom: 0, left: 0 } 902 }; 903 904 this.invoker.go('setImagePosition', arguments, { context: oContext }); 905 906 imgElement.style.top = oContext.pixels.top.toString() + 'px'; 907 imgElement.style.left = oContext.pixels.left.toString() + 'px'; 908 imgElement.style.width = (oContext.pixels.right - oContext.pixels.left).toString() + 'px'; 909 imgElement.style.height = (oContext.pixels.bottom - oContext.pixels.top).toString() + 'px'; 910 }; 911 912 Mapstraction.prototype.addJSON = function(json) { 913 var features; 914 if (typeof(json) == "string") { 915 features = eval('(' + json + ')'); 916 } else { 917 features = json; 918 } 919 features = features.features; 920 var map = this.maps[this.api]; 921 var html = ""; 922 var item; 923 var polyline; 924 var marker; 925 var markers = []; 926 927 if(features.type == "FeatureCollection") { 928 this.addJSON(features.features); 929 } 930 931 for (var i = 0; i < features.length; i++) { 932 item = features[i]; 933 switch(item.geometry.type) { 934 case "Point": 935 html = "<strong>" + item.title + "</strong><p>" + item.description + "</p>"; 936 marker = new Marker(new LatLonPoint(item.geometry.coordinates[1],item.geometry.coordinates[0])); 937 markers.push(marker); 938 this.addMarkerWithData(marker,{ 939 infoBubble : html, 940 label : item.title, 941 date : "new Date(\""+item.date+"\")", 942 iconShadow : item.icon_shadow, 943 marker : item.id, 944 iconShadowSize : item.icon_shadow_size, 945 icon : item.icon, 946 iconSize : item.icon_size, 947 iconAnchor : item.icon_anchor, 948 category : item.source_id, 949 draggable : false, 950 hover : false 951 }); 952 break; 953 case "Polygon": 954 var points = []; 955 polyline = new Polyline(points); 956 mapstraction.addPolylineWithData(polyline,{ 957 fillColor : item.poly_color, 958 date : "new Date(\""+item.date+"\")", 959 category : item.source_id, 960 width : item.line_width, 961 opacity : item.line_opacity, 962 color : item.line_color, 963 polygon : true 964 }); 965 markers.push(polyline); 966 break; 967 default: 968 // console.log("Geometry: " + features.items[i].geometry.type); 969 } 970 } 971 return markers; 972 }; 973 974 /** 975 * Adds a Tile Layer to the map 976 * 977 * Requires providing a parameterized tile url. Use {Z}, {X}, and {Y} to specify where the parameters 978 * should go in the URL. 979 * 980 * For example, the OpenStreetMap tiles are: 981 * m.addTileLayer("http://tile.openstreetmap.org/{Z}/{X}/{Y}.png", 1.0, "OSM", 1, 19, true); 982 * 983 * @param {tile_url} template url of the tiles. 984 * @param {opacity} opacity of the tile layer - 0 is transparent, 1 is opaque. (default=0.6) 985 * @param {copyright_text} copyright text to use for the tile layer. (default=Mapstraction) 986 * @param {min_zoom} Minimum (furtherest out) zoom level that tiles are available (default=1) 987 * @param {max_zoom} Maximum (closest) zoom level that the tiles are available (default=18) 988 * @param {map_type} Should the tile layer be a selectable map type in the layers palette (default=false) 989 */ 990 Mapstraction.prototype.addTileLayer = function(tile_url, opacity, copyright_text, min_zoom, max_zoom, map_type) { 991 if(!tile_url) { 992 return; 993 } 994 995 opacity = opacity || 0.6; 996 copyright_text = copyright_text || "Mapstraction"; 997 min_zoom = min_zoom || 1; 998 max_zoom = max_zoom || 18; 999 map_type = map_type || false; 1000 1001 return this.invoker.go('addTileLayer', [ tile_url, opacity, copyright_text, min_zoom, max_zoom, map_type] ); 1002 }; 1003 1004 /** 1005 * addFilter adds a marker filter 1006 * @param {field} name of attribute to filter on 1007 * @param {operator} presently only "ge" or "le" 1008 * @param {value} the value to compare against 1009 */ 1010 Mapstraction.prototype.addFilter = function(field, operator, value) { 1011 if (!this.filters) { 1012 this.filters = []; 1013 } 1014 this.filters.push( [field, operator, value] ); 1015 }; 1016 1017 /** 1018 * Remove the specified filter 1019 * @param {Object} field 1020 * @param {Object} operator 1021 * @param {Object} value 1022 */ 1023 Mapstraction.prototype.removeFilter = function(field, operator, value) { 1024 if (!this.filters) { 1025 return; 1026 } 1027 1028 var del; 1029 for (var f=0; f<this.filters.length; f++) { 1030 if (this.filters[f][0] == field && 1031 (! operator || (this.filters[f][1] == operator && this.filters[f][2] == value))) { 1032 this.filters.splice(f,1); 1033 f--; //array size decreased 1034 } 1035 } 1036 }; 1037 1038 /** 1039 * Delete the current filter if present; otherwise add it 1040 * @param {Object} field 1041 * @param {Object} operator 1042 * @param {Object} value 1043 */ 1044 Mapstraction.prototype.toggleFilter = function(field, operator, value) { 1045 if (!this.filters) { 1046 this.filters = []; 1047 } 1048 1049 var found = false; 1050 for (var f = 0; f < this.filters.length; f++) { 1051 if (this.filters[f][0] == field && this.filters[f][1] == operator && this.filters[f][2] == value) { 1052 this.filters.splice(f,1); 1053 f--; //array size decreased 1054 found = true; 1055 } 1056 } 1057 1058 if (! found) { 1059 this.addFilter(field, operator, value); 1060 } 1061 }; 1062 1063 /** 1064 * removeAllFilters 1065 */ 1066 Mapstraction.prototype.removeAllFilters = function() { 1067 this.filters = []; 1068 }; 1069 1070 /** 1071 * doFilter executes all filters added since last call 1072 * Now supports a callback function for when a marker is shown or hidden 1073 * @param {Function} showCallback 1074 * @param {Function} hideCallback 1075 * @returns {Int} count of visible markers 1076 */ 1077 Mapstraction.prototype.doFilter = function(showCallback, hideCallback) { 1078 var map = this.maps[this.api]; 1079 var visibleCount = 0; 1080 var f; 1081 if (this.filters) { 1082 switch (this.api) { 1083 case 'multimap': 1084 /* TODO polylines aren't filtered in multimap */ 1085 var mmfilters = []; 1086 for (f=0; f<this.filters.length; f++) { 1087 mmfilters.push( new MMSearchFilter( this.filters[f][0], this.filters[f][1], this.filters[f][2] )); 1088 } 1089 map.setMarkerFilters( mmfilters ); 1090 map.redrawMap(); 1091 break; 1092 case ' dummy': 1093 break; 1094 default: 1095 var vis; 1096 for (var m=0; m<this.markers.length; m++) { 1097 vis = true; 1098 for (f = 0; f < this.filters.length; f++) { 1099 if (! this.applyFilter(this.markers[m], this.filters[f])) { 1100 vis = false; 1101 } 1102 } 1103 if (vis) { 1104 visibleCount ++; 1105 if (showCallback){ 1106 showCallback(this.markers[m]); 1107 } 1108 else { 1109 this.markers[m].show(); 1110 } 1111 } 1112 else { 1113 if (hideCallback){ 1114 hideCallback(this.markers[m]); 1115 } 1116 else { 1117 this.markers[m].hide(); 1118 } 1119 } 1120 1121 this.markers[m].setAttribute("visible", vis); 1122 } 1123 break; 1124 } 1125 } 1126 return visibleCount; 1127 }; 1128 1129 Mapstraction.prototype.applyFilter = function(o, f) { 1130 var vis = true; 1131 switch (f[1]) { 1132 case 'ge': 1133 if (o.getAttribute( f[0] ) < f[2]) { 1134 vis = false; 1135 } 1136 break; 1137 case 'le': 1138 if (o.getAttribute( f[0] ) > f[2]) { 1139 vis = false; 1140 } 1141 break; 1142 case 'eq': 1143 if (o.getAttribute( f[0] ) == f[2]) { 1144 vis = false; 1145 } 1146 break; 1147 } 1148 1149 return vis; 1150 }; 1151 1152 /** 1153 * getAttributeExtremes returns the minimum/maximum of "field" from all markers 1154 * @param {field} name of "field" to query 1155 * @returns {array} of minimum/maximum 1156 */ 1157 Mapstraction.prototype.getAttributeExtremes = function(field) { 1158 var min; 1159 var max; 1160 for (var m=0; m<this.markers.length; m++) { 1161 if (! min || min > this.markers[m].getAttribute(field)) { 1162 min = this.markers[m].getAttribute(field); 1163 } 1164 if (! max || max < this.markers[m].getAttribute(field)) { 1165 max = this.markers[m].getAttribute(field); 1166 } 1167 } 1168 for (var p=0; m<this.polylines.length; m++) { 1169 if (! min || min > this.polylines[p].getAttribute(field)) { 1170 min = this.polylines[p].getAttribute(field); 1171 } 1172 if (! max || max < this.polylines[p].getAttribute(field)) { 1173 max = this.polylines[p].getAttribute(field); 1174 } 1175 } 1176 1177 return [min, max]; 1178 }; 1179 1180 /** 1181 * getMap returns the native map object that mapstraction is talking to 1182 * @returns the native map object mapstraction is using 1183 */ 1184 Mapstraction.prototype.getMap = function() { 1185 // FIXME in an ideal world this shouldn't exist right? 1186 return this.maps[this.api]; 1187 }; 1188 1189 1190 ////////////////////////////// 1191 // 1192 // LatLonPoint 1193 // 1194 ///////////////////////////// 1195 1196 /** 1197 * LatLonPoint is a point containing a latitude and longitude with helper methods 1198 * @name mxn.LatLonPoint 1199 * @constructor 1200 * @param {double} lat is the latitude 1201 * @param {double} lon is the longitude 1202 * @exports LatLonPoint as mxn.LatLonPoint 1203 */ 1204 var LatLonPoint = mxn.LatLonPoint = function(lat, lon) { 1205 this.lat = Number(lat); // force to be numeric 1206 this.lon = Number(lon); 1207 this.lng = this.lon; // lets be lon/lng agnostic 1208 1209 this.invoker = new mxn.Invoker(this, 'LatLonPoint'); 1210 }; 1211 1212 mxn.addProxyMethods(LatLonPoint, [ 1213 /** 1214 * Retrieve the lat and lon values from a proprietary point. 1215 * @name mxn.LatLonPoint#fromProprietary 1216 * @function 1217 * @param {String} apiId The API ID of the proprietary point. 1218 * @param {Object} point The proprietary point. 1219 */ 1220 'fromProprietary', 1221 1222 /** 1223 * Converts the current LatLonPoint to a proprietary one for the API specified by apiId. 1224 * @name mxn.LatLonPoint#toProprietary 1225 * @function 1226 * @param {String} apiId The API ID of the proprietary point. 1227 * @returns A proprietary point. 1228 */ 1229 'toProprietary' 1230 ], true); 1231 1232 /** 1233 * toString returns a string represntation of a point 1234 * @returns a string like '51.23, -0.123' 1235 * @type String 1236 */ 1237 LatLonPoint.prototype.toString = function() { 1238 return this.lat + ', ' + this.lon; 1239 }; 1240 1241 /** 1242 * distance returns the distance in kilometers between two points 1243 * @param {LatLonPoint} otherPoint The other point to measure the distance from to this one 1244 * @returns the distance between the points in kilometers 1245 * @type double 1246 */ 1247 LatLonPoint.prototype.distance = function(otherPoint) { 1248 // Uses Haversine formula from http://www.movable-type.co.uk 1249 var rads = Math.PI / 180; 1250 var diffLat = (this.lat-otherPoint.lat) * rads; 1251 var diffLon = (this.lon-otherPoint.lon) * rads; 1252 var a = Math.sin(diffLat / 2) * Math.sin(diffLat / 2) + 1253 Math.cos(this.lat*rads) * Math.cos(otherPoint.lat*rads) * 1254 Math.sin(diffLon/2) * Math.sin(diffLon/2); 1255 return 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)) * 6371; // Earth's mean radius in km 1256 }; 1257 1258 /** 1259 * equals tests if this point is the same as some other one 1260 * @param {LatLonPoint} otherPoint The other point to test with 1261 * @returns true or false 1262 * @type boolean 1263 */ 1264 LatLonPoint.prototype.equals = function(otherPoint) { 1265 return this.lat == otherPoint.lat && this.lon == otherPoint.lon; 1266 }; 1267 1268 /** 1269 * Returns latitude conversion based on current projection 1270 * @returns {Float} conversion 1271 */ 1272 LatLonPoint.prototype.latConv = function() { 1273 return this.distance(new LatLonPoint(this.lat + 0.1, this.lon))*10; 1274 }; 1275 1276 /** 1277 * Returns longitude conversion based on current projection 1278 * @returns {Float} conversion 1279 */ 1280 LatLonPoint.prototype.lonConv = function() { 1281 return this.distance(new LatLonPoint(this.lat, this.lon + 0.1))*10; 1282 }; 1283 1284 1285 ////////////////////////// 1286 // 1287 // BoundingBox 1288 // 1289 ////////////////////////// 1290 1291 /** 1292 * BoundingBox creates a new bounding box object 1293 * @name mxn.BoundingBox 1294 * @constructor 1295 * @param {double} swlat the latitude of the south-west point 1296 * @param {double} swlon the longitude of the south-west point 1297 * @param {double} nelat the latitude of the north-east point 1298 * @param {double} nelon the longitude of the north-east point 1299 * @exports BoundingBox as mxn.BoundingBox 1300 */ 1301 var BoundingBox = mxn.BoundingBox = function(swlat, swlon, nelat, nelon) { 1302 //FIXME throw error if box bigger than world 1303 this.sw = new LatLonPoint(swlat, swlon); 1304 this.ne = new LatLonPoint(nelat, nelon); 1305 }; 1306 1307 /** 1308 * getSouthWest returns a LatLonPoint of the south-west point of the bounding box 1309 * @returns the south-west point of the bounding box 1310 * @type LatLonPoint 1311 */ 1312 BoundingBox.prototype.getSouthWest = function() { 1313 return this.sw; 1314 }; 1315 1316 /** 1317 * getNorthEast returns a LatLonPoint of the north-east point of the bounding box 1318 * @returns the north-east point of the bounding box 1319 * @type LatLonPoint 1320 */ 1321 BoundingBox.prototype.getNorthEast = function() { 1322 return this.ne; 1323 }; 1324 1325 /** 1326 * isEmpty finds if this bounding box has zero area 1327 * @returns whether the north-east and south-west points of the bounding box are the same point 1328 * @type boolean 1329 */ 1330 BoundingBox.prototype.isEmpty = function() { 1331 return this.ne == this.sw; // is this right? FIXME 1332 }; 1333 1334 /** 1335 * contains finds whether a given point is within a bounding box 1336 * @param {LatLonPoint} point the point to test with 1337 * @returns whether point is within this bounding box 1338 * @type boolean 1339 */ 1340 BoundingBox.prototype.contains = function(point){ 1341 return point.lat >= this.sw.lat && point.lat <= this.ne.lat && point.lon >= this.sw.lon && point.lon <= this.ne.lon; 1342 }; 1343 1344 /** 1345 * toSpan returns a LatLonPoint with the lat and lon as the height and width of the bounding box 1346 * @returns a LatLonPoint containing the height and width of this bounding box 1347 * @type LatLonPoint 1348 */ 1349 BoundingBox.prototype.toSpan = function() { 1350 return new LatLonPoint( Math.abs(this.sw.lat - this.ne.lat), Math.abs(this.sw.lon - this.ne.lon) ); 1351 }; 1352 1353 1354 1355 /** 1356 * extend extends the bounding box to include the new point 1357 */ 1358 BoundingBox.prototype.extend = function(point) { 1359 if (this.sw.lat > point.lat) { 1360 this.sw.lat = point.lat; 1361 } 1362 if (this.sw.lon > point.lon) { 1363 this.sw.lon = point.lon; 1364 } 1365 if (this.ne.lat < point.lat) { 1366 this.ne.lat = point.lat; 1367 } 1368 if (this.ne.lon < point.lon) { 1369 this.ne.lon = point.lon; 1370 } 1371 return; 1372 }; 1373 1374 ////////////////////////////// 1375 // 1376 // Marker 1377 // 1378 /////////////////////////////// 1379 1380 /** 1381 * Marker create's a new marker pin 1382 * @name mxn.Marker 1383 * @constructor 1384 * @param {LatLonPoint} point the point on the map where the marker should go 1385 * @exports Marker as mxn.Marker 1386 */ 1387 var Marker = mxn.Marker = function(point) { 1388 this.api = null; 1389 this.location = point; 1390 this.onmap = false; 1391 this.proprietary_marker = false; 1392 this.attributes = []; 1393 this.invoker = new mxn.Invoker(this, 'Marker', function(){return this.api;}); 1394 mxn.addEvents(this, [ 1395 'openInfoBubble', // Info bubble opened 1396 'closeInfoBubble', // Info bubble closed 1397 'click' // Marker clicked 1398 ]); 1399 }; 1400 1401 mxn.addProxyMethods(Marker, [ 1402 /** 1403 * Retrieve the settings from a proprietary marker. 1404 * @name mxn.Marker#fromProprietary 1405 * @function 1406 * @param {String} apiId The API ID of the proprietary point. 1407 * @param {Object} marker The proprietary marker. 1408 */ 1409 'fromProprietary', 1410 1411 /** 1412 * Hide the marker. 1413 * @name mxn.Marker#hide 1414 * @function 1415 */ 1416 'hide', 1417 1418 /** 1419 * Open the marker's info bubble. 1420 * @name mxn.Marker#openBubble 1421 * @function 1422 */ 1423 'openBubble', 1424 1425 /** 1426 * Closes the marker's info bubble. 1427 * @name mxn.Marker#closeBubble 1428 * @function 1429 */ 1430 'closeBubble', 1431 1432 /** 1433 * Show the marker. 1434 * @name mxn.Marker#show 1435 * @function 1436 */ 1437 'show', 1438 1439 /** 1440 * Converts the current Marker to a proprietary one for the API specified by apiId. 1441 * @name mxn.Marker#toProprietary 1442 * @function 1443 * @param {String} apiId The API ID of the proprietary marker. 1444 * @returns A proprietary marker. 1445 */ 1446 'toProprietary', 1447 1448 /** 1449 * Updates the Marker with the location of the attached proprietary marker on the map. 1450 * @name mxn.Marker#update 1451 * @function 1452 */ 1453 'update' 1454 ]); 1455 1456 Marker.prototype.setChild = function(some_proprietary_marker) { 1457 this.proprietary_marker = some_proprietary_marker; 1458 some_proprietary_marker.mapstraction_marker = this; 1459 this.onmap = true; 1460 }; 1461 1462 Marker.prototype.setLabel = function(labelText) { 1463 this.labelText = labelText; 1464 }; 1465 1466 /** 1467 * addData conviniently set a hash of options on a marker 1468 * @param {Object} options An object literal hash of key value pairs. Keys are: label, infoBubble, icon, iconShadow, infoDiv, draggable, hover, hoverIcon, openBubble, groupName. 1469 */ 1470 Marker.prototype.addData = function(options){ 1471 for(var sOptKey in options) { 1472 if(options.hasOwnProperty(sOptKey)){ 1473 switch(sOptKey) { 1474 case 'label': 1475 this.setLabel(options.label); 1476 break; 1477 case 'infoBubble': 1478 this.setInfoBubble(options.infoBubble); 1479 break; 1480 case 'icon': 1481 if(options.iconSize && options.iconAnchor) { 1482 this.setIcon(options.icon, options.iconSize, options.iconAnchor); 1483 } 1484 else if(options.iconSize) { 1485 this.setIcon(options.icon, options.iconSize); 1486 } 1487 else { 1488 this.setIcon(options.icon); 1489 } 1490 break; 1491 case 'iconShadow': 1492 if(options.iconShadowSize) { 1493 this.setShadowIcon(options.iconShadow, [ options.iconShadowSize[0], options.iconShadowSize[1] ]); 1494 } 1495 else { 1496 this.setIcon(options.iconShadow); 1497 } 1498 break; 1499 case 'infoDiv': 1500 this.setInfoDiv(options.infoDiv[0],options.infoDiv[1]); 1501 break; 1502 case 'draggable': 1503 this.setDraggable(options.draggable); 1504 break; 1505 case 'hover': 1506 this.setHover(options.hover); 1507 this.setHoverIcon(options.hoverIcon); 1508 break; 1509 case 'hoverIcon': 1510 this.setHoverIcon(options.hoverIcon); 1511 break; 1512 case 'openBubble': 1513 this.openBubble(); 1514 break; 1515 case 'closeBubble': 1516 this.closeBubble(); 1517 break; 1518 case 'groupName': 1519 this.setGroupName(options.groupName); 1520 break; 1521 default: 1522 // don't have a specific action for this bit of 1523 // data so set a named attribute 1524 this.setAttribute(sOptKey, options[sOptKey]); 1525 break; 1526 } 1527 } 1528 } 1529 }; 1530 1531 /** 1532 * Sets the html/text content for a bubble popup for a marker 1533 * @param {String} infoBubble the html/text you want displayed 1534 */ 1535 Marker.prototype.setInfoBubble = function(infoBubble) { 1536 this.infoBubble = infoBubble; 1537 }; 1538 1539 /** 1540 * Sets the text and the id of the div element where to the information 1541 * useful for putting information in a div outside of the map 1542 * @param {String} infoDiv the html/text you want displayed 1543 * @param {String} div the element id to use for displaying the text/html 1544 */ 1545 Marker.prototype.setInfoDiv = function(infoDiv,div){ 1546 this.infoDiv = infoDiv; 1547 this.div = div; 1548 }; 1549 1550 /** 1551 * Sets the icon for a marker 1552 * @param {String} iconUrl The URL of the image you want to be the icon 1553 */ 1554 Marker.prototype.setIcon = function(iconUrl, iconSize, iconAnchor) { 1555 this.iconUrl = iconUrl; 1556 if(iconSize) { 1557 this.iconSize = iconSize; 1558 } 1559 if(iconAnchor) { 1560 this.iconAnchor = iconAnchor; 1561 } 1562 }; 1563 1564 /** 1565 * Sets the size of the icon for a marker 1566 * @param {Array} iconSize The array size in pixels of the marker image: [ width, height ] 1567 */ 1568 Marker.prototype.setIconSize = function(iconSize){ 1569 if(iconSize) { 1570 this.iconSize = iconSize; 1571 } 1572 }; 1573 1574 /** 1575 * Sets the anchor point for a marker 1576 * @param {Array} iconAnchor The array offset in pixels of the anchor point from top left: [ right, down ] 1577 */ 1578 Marker.prototype.setIconAnchor = function(iconAnchor){ 1579 if(iconAnchor) { 1580 this.iconAnchor = iconAnchor; 1581 } 1582 }; 1583 1584 /** 1585 * Sets the icon for a marker 1586 * @param {String} iconUrl The URL of the image you want to be the icon 1587 */ 1588 Marker.prototype.setShadowIcon = function(iconShadowUrl, iconShadowSize){ 1589 this.iconShadowUrl = iconShadowUrl; 1590 if(iconShadowSize) { 1591 this.iconShadowSize = iconShadowSize; 1592 } 1593 }; 1594 1595 Marker.prototype.setHoverIcon = function(hoverIconUrl){ 1596 this.hoverIconUrl = hoverIconUrl; 1597 }; 1598 1599 /** 1600 * Sets the draggable state of the marker 1601 * @param {Bool} draggable set to true if marker should be draggable by the user 1602 */ 1603 Marker.prototype.setDraggable = function(draggable) { 1604 this.draggable = draggable; 1605 }; 1606 1607 /** 1608 * Sets that the marker info is displayed on hover 1609 * @param {Boolean} hover set to true if marker should display info on hover 1610 */ 1611 Marker.prototype.setHover = function(hover) { 1612 this.hover = hover; 1613 }; 1614 1615 /** 1616 * Markers are grouped up by this name. declutterGroup makes use of this. 1617 */ 1618 Marker.prototype.setGroupName = function(sGrpName) { 1619 this.groupName = sGrpName; 1620 }; 1621 1622 /** 1623 * Set an arbitrary key/value pair on a marker 1624 * @param {String} key 1625 * @param value 1626 */ 1627 Marker.prototype.setAttribute = function(key,value) { 1628 this.attributes[key] = value; 1629 }; 1630 1631 /** 1632 * getAttribute: gets the value of "key" 1633 * @param {String} key 1634 * @returns value 1635 */ 1636 Marker.prototype.getAttribute = function(key) { 1637 return this.attributes[key]; 1638 }; 1639 1640 1641 /////////////// 1642 // Polyline /// 1643 /////////////// 1644 1645 /** 1646 * Instantiates a new Polyline. 1647 * @name mxn.Polyline 1648 * @constructor 1649 * @param {Point[]} points Points that make up the Polyline. 1650 * @exports Polyline as mxn.Polyline 1651 */ 1652 var Polyline = mxn.Polyline = function(points) { 1653 this.api = null; 1654 this.points = points; 1655 this.attributes = []; 1656 this.onmap = false; 1657 this.proprietary_polyline = false; 1658 this.pllID = "mspll-"+new Date().getTime()+'-'+(Math.floor(Math.random()*Math.pow(2,16))); 1659 this.invoker = new mxn.Invoker(this, 'Polyline', function(){return this.api;}); 1660 }; 1661 1662 mxn.addProxyMethods(Polyline, [ 1663 1664 /** 1665 * Retrieve the settings from a proprietary polyline. 1666 * @name mxn.Polyline#fromProprietary 1667 * @function 1668 * @param {String} apiId The API ID of the proprietary polyline. 1669 * @param {Object} polyline The proprietary polyline. 1670 */ 1671 'fromProprietary', 1672 1673 /** 1674 * Hide the polyline. 1675 * @name mxn.Polyline#hide 1676 * @function 1677 */ 1678 'hide', 1679 1680 /** 1681 * Show the polyline. 1682 * @name mxn.Polyline#show 1683 * @function 1684 */ 1685 'show', 1686 1687 /** 1688 * Converts the current Polyline to a proprietary one for the API specified by apiId. 1689 * @name mxn.Polyline#toProprietary 1690 * @function 1691 * @param {String} apiId The API ID of the proprietary polyline. 1692 * @returns A proprietary polyline. 1693 */ 1694 'toProprietary', 1695 1696 /** 1697 * Updates the Polyline with the path of the attached proprietary polyline on the map. 1698 * @name mxn.Polyline#update 1699 * @function 1700 */ 1701 'update' 1702 ]); 1703 1704 /** 1705 * addData conviniently set a hash of options on a polyline 1706 * @param {Object} options An object literal hash of key value pairs. Keys are: color, width, opacity, closed, fillColor. 1707 */ 1708 Polyline.prototype.addData = function(options){ 1709 for(var sOpt in options) { 1710 if(options.hasOwnProperty(sOpt)){ 1711 switch(sOpt) { 1712 case 'color': 1713 this.setColor(options.color); 1714 break; 1715 case 'width': 1716 this.setWidth(options.width); 1717 break; 1718 case 'opacity': 1719 this.setOpacity(options.opacity); 1720 break; 1721 case 'closed': 1722 this.setClosed(options.closed); 1723 break; 1724 case 'fillColor': 1725 this.setFillColor(options.fillColor); 1726 break; 1727 default: 1728 this.setAttribute(sOpt, options[sOpt]); 1729 break; 1730 } 1731 } 1732 } 1733 }; 1734 1735 Polyline.prototype.setChild = function(some_proprietary_polyline) { 1736 this.proprietary_polyline = some_proprietary_polyline; 1737 this.onmap = true; 1738 }; 1739 1740 /** 1741 * in the form: #RRGGBB 1742 * Note map24 insists on upper case, so we convert it. 1743 */ 1744 Polyline.prototype.setColor = function(color){ 1745 this.color = (color.length==7 && color[0]=="#") ? color.toUpperCase() : color; 1746 }; 1747 1748 /** 1749 * Stroke width of the polyline 1750 * @param {Integer} width 1751 */ 1752 Polyline.prototype.setWidth = function(width){ 1753 this.width = width; 1754 }; 1755 1756 /** 1757 * A float between 0.0 and 1.0 1758 * @param {Float} opacity 1759 */ 1760 Polyline.prototype.setOpacity = function(opacity){ 1761 this.opacity = opacity; 1762 }; 1763 1764 /** 1765 * Marks the polyline as a closed polygon 1766 * @param {Boolean} bClosed 1767 */ 1768 Polyline.prototype.setClosed = function(bClosed){ 1769 this.closed = bClosed; 1770 }; 1771 1772 /** 1773 * Fill color for a closed polyline as HTML color value e.g. #RRGGBB 1774 * @param {String} sFillColor HTML color value #RRGGBB 1775 */ 1776 Polyline.prototype.setFillColor = function(sFillColor) { 1777 this.fillColor = sFillColor; 1778 }; 1779 1780 1781 /** 1782 * Set an arbitrary key/value pair on a polyline 1783 * @param {String} key 1784 * @param value 1785 */ 1786 Polyline.prototype.setAttribute = function(key,value) { 1787 this.attributes[key] = value; 1788 }; 1789 1790 /** 1791 * Gets the value of "key" 1792 * @param {String} key 1793 * @returns value 1794 */ 1795 Polyline.prototype.getAttribute = function(key) { 1796 return this.attributes[key]; 1797 }; 1798 1799 /** 1800 * Simplifies a polyline, averaging and reducing the points 1801 * @param {Number} tolerance (1.0 is a good starting point) 1802 */ 1803 Polyline.prototype.simplify = function(tolerance) { 1804 var reduced = []; 1805 1806 // First point 1807 reduced[0] = this.points[0]; 1808 1809 var markerPoint = 0; 1810 1811 for (var i = 1; i < this.points.length-1; i++){ 1812 if (this.points[i].distance(this.points[markerPoint]) >= tolerance) 1813 { 1814 reduced[reduced.length] = this.points[i]; 1815 markerPoint = i; 1816 } 1817 } 1818 1819 // Last point 1820 reduced[reduced.length] = this.points[this.points.length-1]; 1821 1822 // Revert 1823 this.points = reduced; 1824 }; 1825 1826 /////////////// 1827 // Radius // 1828 /////////////// 1829 1830 /** 1831 * Creates a new radius object for drawing circles around a point, does a lot of initial calculation to increase load time 1832 * @name mxn.Radius 1833 * @constructor 1834 * @param {LatLonPoint} center LatLonPoint of the radius 1835 * @param {Number} quality Number of points that comprise the approximated circle (20 is a good starting point) 1836 * @exports Radius as mxn.Radius 1837 */ 1838 var Radius = mxn.Radius = function(center, quality) { 1839 this.center = center; 1840 var latConv = center.latConv(); 1841 var lonConv = center.lonConv(); 1842 1843 // Create Radian conversion constant 1844 var rad = Math.PI / 180; 1845 this.calcs = []; 1846 1847 for(var i = 0; i < 360; i += quality){ 1848 this.calcs.push([Math.cos(i * rad) / latConv, Math.sin(i * rad) / lonConv]); 1849 } 1850 }; 1851 1852 /** 1853 * Returns polyline of a circle around the point based on new radius 1854 * @param {Radius} radius 1855 * @param {Color} color 1856 * @returns {Polyline} Polyline 1857 */ 1858 Radius.prototype.getPolyline = function(radius, color) { 1859 var points = []; 1860 1861 for(var i = 0; i < this.calcs.length; i++){ 1862 var point = new LatLonPoint( 1863 this.center.lat + (radius * this.calcs[i][0]), 1864 this.center.lon + (radius * this.calcs[i][1]) 1865 ); 1866 points.push(point); 1867 } 1868 1869 // Add first point 1870 points.push(points[0]); 1871 1872 var line = new Polyline(points); 1873 line.setColor(color); 1874 1875 return line; 1876 }; 1877 1878 1879 })(); 1880