482 lines
14 KiB
JavaScript
482 lines
14 KiB
JavaScript
|
// 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;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @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('');
|
||
|
};
|
||
|
|
||
|
})();
|