mxn.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. // Auto-load scripts
  2. //
  3. // specify which map providers to load by using
  4. // <script src="mxn.js?(provider1,provider2,[module1,module2])" ...
  5. // in your HTML
  6. //
  7. // for each provider mxn.provider.module.js and mxn.module.js will be loaded
  8. // module 'core' is always loaded
  9. //
  10. // NOTE: if you call without providers
  11. // <script src="mxn.js" ...
  12. // no scripts will be loaded at all and it is then up to you to load the scripts independently
  13. (function() {
  14. var providers = null;
  15. var modules = 'core';
  16. var scriptBase;
  17. var scripts = document.getElementsByTagName('script');
  18. // Determine which scripts we need to load
  19. for (var i = 0; i < scripts.length; i++) {
  20. var match = scripts[i].src.replace(/%20/g , '').match(/^(.*?)mxn\.js(\?\(\[?(.*?)\]?\))?$/);
  21. if (match != null) {
  22. scriptBase = match[1];
  23. if (match[3]) {
  24. var settings = match[3].split(',[');
  25. providers = settings[0].replace(']' , '');
  26. if (settings[1]) modules += ',' + settings[1];
  27. }
  28. break;
  29. }
  30. }
  31. if (providers == null || providers == 'none') return; // Bail out if no auto-load has been found
  32. providers = providers.replace(/ /g, '').split(',');
  33. modules = modules.replace(/ /g, '').split(',');
  34. // Actually load the scripts
  35. for (i = 0; i < modules.length; i++) {
  36. document.write("<script type='text/javascript' src='" + scriptBase + 'mxn.' + modules[i] + '.js' + "'></script>");
  37. for (var j = 0; j < providers.length; j++) document.write("<script type='text/javascript' src='" + scriptBase + 'mxn.' + providers[j] + '.' + modules[i] + '.js' + "'></script>");
  38. }
  39. })();
  40. (function(){
  41. // holds all our implementing functions
  42. var apis = {};
  43. // Our special private methods
  44. /**
  45. * Calls the API specific implementation of a particular method.
  46. * @private
  47. */
  48. var invoke = function(sApiId, sObjName, sFnName, oScope, args){
  49. if(!hasImplementation(sApiId, sObjName, sFnName)) {
  50. throw 'Method ' + sFnName + ' of object ' + sObjName + ' is not supported by API ' + sApiId + '. Are you missing a script tag?';
  51. }
  52. return apis[sApiId][sObjName][sFnName].apply(oScope, args);
  53. };
  54. /**
  55. * Determines whether the specified API provides an implementation for the
  56. * specified object and function name.
  57. * @private
  58. */
  59. var hasImplementation = function(sApiId, sObjName, sFnName){
  60. if(typeof(apis[sApiId]) == 'undefined') {
  61. throw 'API ' + sApiId + ' not loaded. Are you missing a script tag?';
  62. }
  63. if(typeof(apis[sApiId][sObjName]) == 'undefined') {
  64. throw 'Object definition ' + sObjName + ' in API ' + sApiId + ' not loaded. Are you missing a script tag?';
  65. }
  66. return typeof(apis[sApiId][sObjName][sFnName]) == 'function';
  67. };
  68. /**
  69. * @name mxn
  70. * @namespace
  71. */
  72. var mxn = window.mxn = /** @lends mxn */ {
  73. /**
  74. * Registers a set of provider specific implementation functions.
  75. * @function
  76. * @param {String} sApiId The API ID to register implementing functions for.
  77. * @param {Object} oApiImpl An object containing the API implementation.
  78. */
  79. register: function(sApiId, oApiImpl){
  80. if(!apis.hasOwnProperty(sApiId)){
  81. apis[sApiId] = {};
  82. }
  83. mxn.util.merge(apis[sApiId], oApiImpl);
  84. },
  85. /**
  86. * Adds a list of named proxy methods to the prototype of a
  87. * specified constructor function.
  88. * @function
  89. * @param {Function} func Constructor function to add methods to
  90. * @param {Array} aryMethods Array of method names to create
  91. * @param {Boolean} bWithApiArg Optional. Whether the proxy methods will use an API argument
  92. */
  93. addProxyMethods: function(func, aryMethods, bWithApiArg){
  94. for(var i = 0; i < aryMethods.length; i++) {
  95. var sMethodName = aryMethods[i];
  96. if(bWithApiArg){
  97. func.prototype[sMethodName] = new Function('return this.invoker.go(\'' + sMethodName + '\', arguments, { overrideApi: true } );');
  98. }
  99. else {
  100. func.prototype[sMethodName] = new Function('return this.invoker.go(\'' + sMethodName + '\', arguments);');
  101. }
  102. }
  103. },
  104. /*
  105. checkLoad: function(funcDetails){
  106. if(this.loaded[this.api] === false) {
  107. var scope = this;
  108. this.onload[this.api].push( function() { funcDetails.callee.apply(scope, funcDetails); } );
  109. return true;
  110. }
  111. return false;
  112. },
  113. */
  114. /**
  115. * Bulk add some named events to an object.
  116. * @function
  117. * @param {Object} oEvtSrc The event source object.
  118. * @param {String[]} aEvtNames Event names to add.
  119. */
  120. addEvents: function(oEvtSrc, aEvtNames){
  121. for(var i = 0; i < aEvtNames.length; i++){
  122. var sEvtName = aEvtNames[i];
  123. if(sEvtName in oEvtSrc){
  124. throw 'Event or method ' + sEvtName + ' already declared.';
  125. }
  126. oEvtSrc[sEvtName] = new mxn.Event(sEvtName, oEvtSrc);
  127. }
  128. }
  129. };
  130. /**
  131. * Instantiates a new Event
  132. * @constructor
  133. * @param {String} sEvtName The name of the event.
  134. * @param {Object} oEvtSource The source object of the event.
  135. */
  136. mxn.Event = function(sEvtName, oEvtSource){
  137. var handlers = [];
  138. if(!sEvtName){
  139. throw 'Event name must be provided';
  140. }
  141. /**
  142. * Add a handler to the Event.
  143. * @param {Function} fn The handler function.
  144. * @param {Object} ctx The context of the handler function.
  145. */
  146. this.addHandler = function(fn, ctx){
  147. handlers.push({context: ctx, handler: fn});
  148. };
  149. /**
  150. * Remove a handler from the Event.
  151. * @param {Function} fn The handler function.
  152. * @param {Object} ctx The context of the handler function.
  153. */
  154. this.removeHandler = function(fn, ctx){
  155. for(var i = 0; i < handlers.length; i++){
  156. if(handlers[i].handler == fn && handlers[i].context == ctx){
  157. handlers.splice(i, 1);
  158. }
  159. }
  160. };
  161. /**
  162. * Remove all handlers from the Event.
  163. */
  164. this.removeAllHandlers = function(){
  165. handlers = [];
  166. };
  167. /**
  168. * Fires the Event.
  169. * @param {Object} oEvtArgs Event arguments object to be passed to the handlers.
  170. */
  171. this.fire = function(oEvtArgs){
  172. var args = [sEvtName, oEvtSource, oEvtArgs];
  173. for(var i = 0; i < handlers.length; i++){
  174. handlers[i].handler.apply(handlers[i].context, args);
  175. }
  176. };
  177. };
  178. /**
  179. * Creates a new Invoker, a class which helps with on-the-fly
  180. * invocation of the correct API methods.
  181. * @constructor
  182. * @param {Object} aobj The core object whose methods will make cals to go()
  183. * @param {String} asClassName The name of the Mapstraction class to be invoked, normally the same name as aobj's constructor function
  184. * @param {Function} afnApiIdGetter The function on object aobj which will return the active API ID
  185. */
  186. mxn.Invoker = function(aobj, asClassName, afnApiIdGetter){
  187. var obj = aobj;
  188. var sClassName = asClassName;
  189. var fnApiIdGetter = afnApiIdGetter;
  190. var defOpts = {
  191. overrideApi: false, // {Boolean} API ID is overridden by value in first argument
  192. context: null, // {Object} Local vars can be passed from the body of the method to the API method within this object
  193. fallback: null // {Function} If an API implementation doesn't exist this function is run instead
  194. };
  195. /**
  196. * Invoke the API implementation of a specific method.
  197. * @param {String} sMethodName The method name to invoke
  198. * @param {Array} args Arguments to pass on
  199. * @param {Object} oOptions Optional. Extra options for invocation
  200. * @param {Boolean} oOptions.overrideApi When true the first argument is used as the API ID.
  201. * @param {Object} oOptions.context A context object for passing extra information on to the provider implementation.
  202. * @param {Function} oOptions.fallback A fallback function to run if the provider implementation is missing.
  203. */
  204. this.go = function(sMethodName, args, oOptions){
  205. if(typeof(oOptions) == 'undefined'){
  206. oOptions = defOpts;
  207. }
  208. var sApiId = oOptions.overrideApi ? args[0] : fnApiIdGetter.apply(obj);
  209. if(typeof(sApiId) != 'string'){
  210. throw 'API ID not available.';
  211. }
  212. if(typeof(oOptions.context) != 'undefined' && oOptions.context !== null){
  213. // make sure args is an array
  214. args = Array.prototype.slice.apply(args);
  215. args.push(oOptions.context);
  216. }
  217. if(typeof(oOptions.fallback) == 'function' && !hasImplementation(sApiId, sClassName, sMethodName)){
  218. // we've got no implementation but have got a fallback function
  219. return oOptions.fallback.apply(obj, args);
  220. }
  221. else {
  222. return invoke(sApiId, sClassName, sMethodName, obj, args);
  223. }
  224. };
  225. };
  226. /**
  227. * @namespace
  228. */
  229. mxn.util = {
  230. /**
  231. * Merges properties of one object into another recursively.
  232. * @param {Object} oRecv The object receiveing properties
  233. * @param {Object} oGive The object donating properties
  234. */
  235. merge: function(oRecv, oGive){
  236. for (var sPropName in oGive){
  237. if (oGive.hasOwnProperty(sPropName)) {
  238. if(!oRecv.hasOwnProperty(sPropName)){
  239. oRecv[sPropName] = oGive[sPropName];
  240. }
  241. else {
  242. mxn.util.merge(oRecv[sPropName], oGive[sPropName]);
  243. }
  244. }
  245. }
  246. },
  247. /**
  248. * $m, the dollar function, elegantising getElementById()
  249. * @return An HTML element or array of HTML elements
  250. */
  251. $m: function() {
  252. var elements = [];
  253. for (var i = 0; i < arguments.length; i++) {
  254. var element = arguments[i];
  255. if (typeof(element) == 'string') {
  256. element = document.getElementById(element);
  257. }
  258. if (arguments.length == 1) {
  259. return element;
  260. }
  261. elements.push(element);
  262. }
  263. return elements;
  264. },
  265. /**
  266. * loadScript is a JSON data fetcher
  267. * @param {String} src URL to JSON file
  268. * @param {Function} callback Callback function
  269. */
  270. loadScript: function(src, callback) {
  271. var script = document.createElement('script');
  272. script.type = 'text/javascript';
  273. script.src = src;
  274. if (callback) {
  275. if(script.addEventListener){
  276. script.addEventListener('load', callback, true);
  277. }
  278. else if(script.attachEvent){
  279. var done = false;
  280. script.attachEvent("onreadystatechange",function(){
  281. if ( !done && document.readyState === "complete" ) {
  282. done = true;
  283. callback();
  284. }
  285. });
  286. }
  287. }
  288. var h = document.getElementsByTagName('head')[0];
  289. h.appendChild( script );
  290. return;
  291. },
  292. /**
  293. *
  294. * @param {Object} point
  295. * @param {Object} level
  296. */
  297. convertLatLonXY_Yahoo: function(point, level) { //Mercator
  298. var size = 1 << (26 - level);
  299. var pixel_per_degree = size / 360.0;
  300. var pixel_per_radian = size / (2 * Math.PI);
  301. var origin = new YCoordPoint(size / 2 , size / 2);
  302. var answer = new YCoordPoint();
  303. answer.x = Math.floor(origin.x + point.lon * pixel_per_degree);
  304. var sin = Math.sin(point.lat * Math.PI / 180.0);
  305. answer.y = Math.floor(origin.y + 0.5 * Math.log((1 + sin) / (1 - sin)) * -pixel_per_radian);
  306. return answer;
  307. },
  308. /**
  309. * Load a stylesheet from a remote file.
  310. * @param {String} href URL to the CSS file
  311. */
  312. loadStyle: function(href) {
  313. var link = document.createElement('link');
  314. link.type = 'text/css';
  315. link.rel = 'stylesheet';
  316. link.href = href;
  317. document.getElementsByTagName('head')[0].appendChild(link);
  318. return;
  319. },
  320. /**
  321. * getStyle provides cross-browser access to css
  322. * @param {Object} el HTML Element
  323. * @param {String} prop Style property name
  324. */
  325. getStyle: function(el, prop) {
  326. var y;
  327. if (el.currentStyle) {
  328. y = el.currentStyle[prop];
  329. }
  330. else if (window.getComputedStyle) {
  331. y = window.getComputedStyle( el, '').getPropertyValue(prop);
  332. }
  333. return y;
  334. },
  335. /**
  336. * Convert longitude to metres
  337. * http://www.uwgb.edu/dutchs/UsefulData/UTMFormulas.HTM
  338. * "A degree of longitude at the equator is 111.2km... For other latitudes,
  339. * multiply by cos(lat)"
  340. * assumes the earth is a sphere but good enough for our purposes
  341. * @param {Float} lon
  342. * @param {Float} lat
  343. */
  344. lonToMetres: function(lon, lat) {
  345. return lon * (111200 * Math.cos(lat * (Math.PI / 180)));
  346. },
  347. /**
  348. * Convert metres to longitude
  349. * @param {Object} m
  350. * @param {Object} lat
  351. */
  352. metresToLon: function(m, lat) {
  353. return m / (111200 * Math.cos(lat * (Math.PI / 180)));
  354. },
  355. /**
  356. * Convert kilometres to miles
  357. * @param {Float} km
  358. * @returns {Float} miles
  359. */
  360. KMToMiles: function(km) {
  361. return km / 1.609344;
  362. },
  363. /**
  364. * Convert miles to kilometres
  365. * @param {Float} miles
  366. * @returns {Float} km
  367. */
  368. milesToKM: function(miles) {
  369. return miles * 1.609344;
  370. },
  371. // stuff to convert google zoom levels to/from degrees
  372. // assumes zoom 0 = 256 pixels = 360 degrees
  373. // zoom 1 = 256 pixels = 180 degrees
  374. // etc.
  375. /**
  376. *
  377. * @param {Object} pixels
  378. * @param {Object} zoom
  379. */
  380. getDegreesFromGoogleZoomLevel: function(pixels, zoom) {
  381. return (360 * pixels) / (Math.pow(2, zoom + 8));
  382. },
  383. /**
  384. *
  385. * @param {Object} pixels
  386. * @param {Object} degrees
  387. */
  388. getGoogleZoomLevelFromDegrees: function(pixels, degrees) {
  389. return mxn.util.logN((360 * pixels) / degrees, 2) - 8;
  390. },
  391. /**
  392. *
  393. * @param {Object} number
  394. * @param {Object} base
  395. */
  396. logN: function(number, base) {
  397. return Math.log(number) / Math.log(base);
  398. },
  399. /**
  400. * Returns array of loaded provider apis
  401. * @returns {Array} providers
  402. */
  403. getAvailableProviders : function () {
  404. var providers = [];
  405. for (var propertyName in apis){
  406. if (apis.hasOwnProperty(propertyName)) {
  407. providers.push(propertyName);
  408. }
  409. }
  410. return providers;
  411. }
  412. };
  413. /**
  414. * Class for converting between HTML and RGB integer color formats.
  415. * Accepts either a HTML color string argument or three integers for R, G and B.
  416. * @constructor
  417. */
  418. mxn.util.Color = function() {
  419. if(arguments.length == 3) {
  420. this.red = arguments[0];
  421. this.green = arguments[1];
  422. this.blue = arguments[2];
  423. }
  424. else if(arguments.length == 1) {
  425. this.setHexColor(arguments[0]);
  426. }
  427. };
  428. mxn.util.Color.prototype.reHex = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
  429. /**
  430. * Set the color from the supplied HTML hex string.
  431. * @param {String} strHexColor A HTML hex color string e.g. '#00FF88'.
  432. */
  433. mxn.util.Color.prototype.setHexColor = function(strHexColor) {
  434. var match = strHexColor.match(this.reHex);
  435. if(match) {
  436. strHexColor = match[1];
  437. }
  438. else {
  439. throw 'Invalid HEX color format, expected #000, 000, #000000 or 000000';
  440. }
  441. if(strHexColor.length == 3) {
  442. strHexColor = strHexColor.replace(/\w/g, function(str){return str.concat(str);});
  443. }
  444. this.red = parseInt(strHexColor.substr(0,2), 16);
  445. this.green = parseInt(strHexColor.substr(2,2), 16);
  446. this.blue = parseInt(strHexColor.substr(4,2), 16);
  447. };
  448. /**
  449. * Retrieve the color value as an HTML hex string.
  450. * @returns {String} Format '00FF88' - note no preceding #.
  451. */
  452. mxn.util.Color.prototype.getHexColor = function() {
  453. var vals = [this.red.toString(16), this.green.toString(16), this.blue.toString(16)];
  454. for(var i = 0; i < vals.length; i++) {
  455. vals[i] = (vals[i].length == 1) ? '0' + vals[i] : vals[i];
  456. vals[i] = vals[i].toUpperCase();
  457. }
  458. return vals.join('');
  459. };
  460. })();