summaryrefslogtreecommitdiff
path: root/plugins/Mapstraction/js/mxn.js
diff options
context:
space:
mode:
authorEvan Prodromou <evan@status.net>2009-11-17 23:23:13 -0500
committerEvan Prodromou <evan@status.net>2009-11-17 23:23:13 -0500
commitfc08a5c8803fd79d58126528fa4a593ccfb8d512 (patch)
treeac2e2a71db244368ab1ea32142a5cea207557624 /plugins/Mapstraction/js/mxn.js
parentb7660b3d9998af4106795d53ed6bc1a0d77d7d4f (diff)
first pass at Mapstraction plugin
Diffstat (limited to 'plugins/Mapstraction/js/mxn.js')
-rw-r--r--plugins/Mapstraction/js/mxn.js505
1 files changed, 505 insertions, 0 deletions
diff --git a/plugins/Mapstraction/js/mxn.js b/plugins/Mapstraction/js/mxn.js
new file mode 100644
index 000000000..7ade41d51
--- /dev/null
+++ b/plugins/Mapstraction/js/mxn.js
@@ -0,0 +1,505 @@
+// 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('');
+};
+
+})(); \ No newline at end of file