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