mxn.core.js 44 KB

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