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