// Auto-load scripts // // specify which map providers to load by using // <script src="mxn.js?(provider1,provider2,[module1,module2])" ... // in your HTML // // for each provider mxn.provider.module.js and mxn.module.js will be loaded // module 'core' is always loaded // // NOTE: if you call without providers // <script src="mxn.js" ... // no scripts will be loaded at all and it is then up to you to load the scripts independently (function() { var providers = null; var modules = 'core'; var scriptBase; var scripts = document.getElementsByTagName('script'); // Determine which scripts we need to load for (var i = 0; i < scripts.length; i++) { var match = scripts[i].src.replace(/%20/g , '').match(/^(.*?)mxn\.js(\?\(\[?(.*?)\]?\))?$/); if (match != null) { scriptBase = match[1]; if (match[3]) { var settings = match[3].split(',['); providers = settings[0].replace(']' , ''); if (settings[1]) modules += ',' + settings[1]; } break; } } if (providers == null || providers == 'none') return; // Bail out if no auto-load has been found providers = providers.replace(/ /g, '').split(','); modules = modules.replace(/ /g, '').split(','); // Actually load the scripts for (i = 0; i < modules.length; i++) { document.write("<script type='text/javascript' src='" + scriptBase + 'mxn.' + modules[i] + '.js' + "'></script>"); for (var j = 0; j < providers.length; j++) document.write("<script type='text/javascript' src='" + scriptBase + 'mxn.' + providers[j] + '.' + modules[i] + '.js' + "'></script>"); } })(); (function(){ // holds all our implementing functions var apis = {}; // Our special private methods /** * Calls the API specific implementation of a particular method. * @private */ var invoke = function(sApiId, sObjName, sFnName, oScope, args){ if(!hasImplementation(sApiId, sObjName, sFnName)) { throw 'Method ' + sFnName + ' of object ' + sObjName + ' is not supported by API ' + sApiId + '. Are you missing a script tag?'; } return apis[sApiId][sObjName][sFnName].apply(oScope, args); }; /** * Determines whether the specified API provides an implementation for the * specified object and function name. * @private */ var hasImplementation = function(sApiId, sObjName, sFnName){ if(typeof(apis[sApiId]) == 'undefined') { throw 'API ' + sApiId + ' not loaded. Are you missing a script tag?'; } if(typeof(apis[sApiId][sObjName]) == 'undefined') { throw 'Object definition ' + sObjName + ' in API ' + sApiId + ' not loaded. Are you missing a script tag?'; } return typeof(apis[sApiId][sObjName][sFnName]) == 'function'; }; /** * @name mxn * @namespace */ var mxn = window.mxn = /** @lends mxn */ { /** * Registers a set of provider specific implementation functions. * @function * @param {String} sApiId The API ID to register implementing functions for. * @param {Object} oApiImpl An object containing the API implementation. */ register: function(sApiId, oApiImpl){ if(!apis.hasOwnProperty(sApiId)){ apis[sApiId] = {}; } mxn.util.merge(apis[sApiId], oApiImpl); }, /** * Adds a list of named proxy methods to the prototype of a * specified constructor function. * @function * @param {Function} func Constructor function to add methods to * @param {Array} aryMethods Array of method names to create * @param {Boolean} bWithApiArg Optional. Whether the proxy methods will use an API argument */ addProxyMethods: function(func, aryMethods, bWithApiArg){ for(var i = 0; i < aryMethods.length; i++) { var sMethodName = aryMethods[i]; if(bWithApiArg){ func.prototype[sMethodName] = new Function('return this.invoker.go(\'' + sMethodName + '\', arguments, { overrideApi: true } );'); } else { func.prototype[sMethodName] = new Function('return this.invoker.go(\'' + sMethodName + '\', arguments);'); } } }, /* checkLoad: function(funcDetails){ if(this.loaded[this.api] === false) { var scope = this; this.onload[this.api].push( function() { funcDetails.callee.apply(scope, funcDetails); } ); return true; } return false; }, */ /** * Bulk add some named events to an object. * @function * @param {Object} oEvtSrc The event source object. * @param {String[]} aEvtNames Event names to add. */ addEvents: function(oEvtSrc, aEvtNames){ for(var i = 0; i < aEvtNames.length; i++){ var sEvtName = aEvtNames[i]; if(sEvtName in oEvtSrc){ throw 'Event or method ' + sEvtName + ' already declared.'; } oEvtSrc[sEvtName] = new mxn.Event(sEvtName, oEvtSrc); } } }; /** * Instantiates a new Event * @constructor * @param {String} sEvtName The name of the event. * @param {Object} oEvtSource The source object of the event. */ mxn.Event = function(sEvtName, oEvtSource){ var handlers = []; if(!sEvtName){ throw 'Event name must be provided'; } /** * Add a handler to the Event. * @param {Function} fn The handler function. * @param {Object} ctx The context of the handler function. */ this.addHandler = function(fn, ctx){ handlers.push({context: ctx, handler: fn}); }; /** * Remove a handler from the Event. * @param {Function} fn The handler function. * @param {Object} ctx The context of the handler function. */ this.removeHandler = function(fn, ctx){ for(var i = 0; i < handlers.length; i++){ if(handlers[i].handler == fn && handlers[i].context == ctx){ handlers.splice(i, 1); } } }; /** * Remove all handlers from the Event. */ this.removeAllHandlers = function(){ handlers = []; }; /** * Fires the Event. * @param {Object} oEvtArgs Event arguments object to be passed to the handlers. */ this.fire = function(oEvtArgs){ var args = [sEvtName, oEvtSource, oEvtArgs]; for(var i = 0; i < handlers.length; i++){ handlers[i].handler.apply(handlers[i].context, args); } }; }; /** * Creates a new Invoker, a class which helps with on-the-fly * invocation of the correct API methods. * @constructor * @param {Object} aobj The core object whose methods will make cals to go() * @param {String} asClassName The name of the Mapstraction class to be invoked, normally the same name as aobj's constructor function * @param {Function} afnApiIdGetter The function on object aobj which will return the active API ID */ mxn.Invoker = function(aobj, asClassName, afnApiIdGetter){ var obj = aobj; var sClassName = asClassName; var fnApiIdGetter = afnApiIdGetter; var defOpts = { overrideApi: false, // {Boolean} API ID is overridden by value in first argument context: null, // {Object} Local vars can be passed from the body of the method to the API method within this object fallback: null // {Function} If an API implementation doesn't exist this function is run instead }; /** * Invoke the API implementation of a specific method. * @param {String} sMethodName The method name to invoke * @param {Array} args Arguments to pass on * @param {Object} oOptions Optional. Extra options for invocation * @param {Boolean} oOptions.overrideApi When true the first argument is used as the API ID. * @param {Object} oOptions.context A context object for passing extra information on to the provider implementation. * @param {Function} oOptions.fallback A fallback function to run if the provider implementation is missing. */ this.go = function(sMethodName, args, oOptions){ if(typeof(oOptions) == 'undefined'){ oOptions = defOpts; } var sApiId = oOptions.overrideApi ? args[0] : fnApiIdGetter.apply(obj); if(typeof(sApiId) != 'string'){ throw 'API ID not available.'; } if(typeof(oOptions.context) != 'undefined' && oOptions.context !== null){ // make sure args is an array args = Array.prototype.slice.apply(args); args.push(oOptions.context); } if(typeof(oOptions.fallback) == 'function' && !hasImplementation(sApiId, sClassName, sMethodName)){ // we've got no implementation but have got a fallback function return oOptions.fallback.apply(obj, args); } else { return invoke(sApiId, sClassName, sMethodName, obj, args); } }; }; /** * @namespace */ mxn.util = { /** * Merges properties of one object into another recursively. * @param {Object} oRecv The object receiveing properties * @param {Object} oGive The object donating properties */ merge: function(oRecv, oGive){ for (var sPropName in oGive){ if (oGive.hasOwnProperty(sPropName)) { if(!oRecv.hasOwnProperty(sPropName)){ oRecv[sPropName] = oGive[sPropName]; } else { mxn.util.merge(oRecv[sPropName], oGive[sPropName]); } } } }, /** * $m, the dollar function, elegantising getElementById() * @return An HTML element or array of HTML elements */ $m: function() { var elements = []; for (var i = 0; i < arguments.length; i++) { var element = arguments[i]; if (typeof(element) == 'string') { element = document.getElementById(element); } if (arguments.length == 1) { return element; } elements.push(element); } return elements; }, /** * loadScript is a JSON data fetcher * @param {String} src URL to JSON file * @param {Function} callback Callback function */ loadScript: function(src, callback) { var script = document.createElement('script'); script.type = 'text/javascript'; script.src = src; if (callback) { if(script.addEventListener){ script.addEventListener('load', callback, true); } else if(script.attachEvent){ var done = false; script.attachEvent("onreadystatechange",function(){ if ( !done && document.readyState === "complete" ) { done = true; callback(); } }); } } var h = document.getElementsByTagName('head')[0]; h.appendChild( script ); return; }, /** * * @param {Object} point * @param {Object} level */ convertLatLonXY_Yahoo: function(point, level) { //Mercator var size = 1 << (26 - level); var pixel_per_degree = size / 360.0; var pixel_per_radian = size / (2 * Math.PI); var origin = new YCoordPoint(size / 2 , size / 2); var answer = new YCoordPoint(); answer.x = Math.floor(origin.x + point.lon * pixel_per_degree); var sin = Math.sin(point.lat * Math.PI / 180.0); answer.y = Math.floor(origin.y + 0.5 * Math.log((1 + sin) / (1 - sin)) * -pixel_per_radian); return answer; }, /** * Load a stylesheet from a remote file. * @param {String} href URL to the CSS file */ loadStyle: function(href) { var link = document.createElement('link'); link.type = 'text/css'; link.rel = 'stylesheet'; link.href = href; document.getElementsByTagName('head')[0].appendChild(link); return; }, /** * getStyle provides cross-browser access to css * @param {Object} el HTML Element * @param {String} prop Style property name */ getStyle: function(el, prop) { var y; if (el.currentStyle) { y = el.currentStyle[prop]; } else if (window.getComputedStyle) { y = window.getComputedStyle( el, '').getPropertyValue(prop); } return y; }, /** * Convert longitude to metres * http://www.uwgb.edu/dutchs/UsefulData/UTMFormulas.HTM * "A degree of longitude at the equator is 111.2km... For other latitudes, * multiply by cos(lat)" * assumes the earth is a sphere but good enough for our purposes * @param {Float} lon * @param {Float} lat */ lonToMetres: function(lon, lat) { return lon * (111200 * Math.cos(lat * (Math.PI / 180))); }, /** * Convert metres to longitude * @param {Object} m * @param {Object} lat */ metresToLon: function(m, lat) { return m / (111200 * Math.cos(lat * (Math.PI / 180))); }, /** * Convert kilometres to miles * @param {Float} km * @returns {Float} miles */ KMToMiles: function(km) { return km / 1.609344; }, /** * Convert miles to kilometres * @param {Float} miles * @returns {Float} km */ milesToKM: function(miles) { return miles * 1.609344; }, // stuff to convert google zoom levels to/from degrees // assumes zoom 0 = 256 pixels = 360 degrees // zoom 1 = 256 pixels = 180 degrees // etc. /** * * @param {Object} pixels * @param {Object} zoom */ getDegreesFromGoogleZoomLevel: function(pixels, zoom) { return (360 * pixels) / (Math.pow(2, zoom + 8)); }, /** * * @param {Object} pixels * @param {Object} degrees */ getGoogleZoomLevelFromDegrees: function(pixels, degrees) { return mxn.util.logN((360 * pixels) / degrees, 2) - 8; }, /** * * @param {Object} number * @param {Object} base */ logN: function(number, base) { return Math.log(number) / Math.log(base); }, /** * Returns array of loaded provider apis * @returns {Array} providers */ getAvailableProviders : function () { var providers = []; for (var propertyName in apis){ if (apis.hasOwnProperty(propertyName)) { providers.push(propertyName); } } return providers; } }; /** * Class for converting between HTML and RGB integer color formats. * Accepts either a HTML color string argument or three integers for R, G and B. * @constructor */ mxn.util.Color = function() { if(arguments.length == 3) { this.red = arguments[0]; this.green = arguments[1]; this.blue = arguments[2]; } else if(arguments.length == 1) { this.setHexColor(arguments[0]); } }; mxn.util.Color.prototype.reHex = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; /** * Set the color from the supplied HTML hex string. * @param {String} strHexColor A HTML hex color string e.g. '#00FF88'. */ mxn.util.Color.prototype.setHexColor = function(strHexColor) { var match = strHexColor.match(this.reHex); if(match) { strHexColor = match[1]; } else { throw 'Invalid HEX color format, expected #000, 000, #000000 or 000000'; } if(strHexColor.length == 3) { strHexColor = strHexColor.replace(/\w/g, function(str){return str.concat(str);}); } this.red = parseInt(strHexColor.substr(0,2), 16); this.green = parseInt(strHexColor.substr(2,2), 16); this.blue = parseInt(strHexColor.substr(4,2), 16); }; /** * Retrieve the color value as an HTML hex string. * @returns {String} Format '00FF88' - note no preceding #. */ mxn.util.Color.prototype.getHexColor = function() { var vals = [this.red.toString(16), this.green.toString(16), this.blue.toString(16)]; for(var i = 0; i < vals.length; i++) { vals[i] = (vals[i].length == 1) ? '0' + vals[i] : vals[i]; vals[i] = vals[i].toUpperCase(); } return vals.join(''); }; })();