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