3 // specify which map providers to load by using
\r
4 // <script src="mxn.js?(provider1,provider2,[module1,module2])" ...
\r
7 // for each provider mxn.provider.module.js and mxn.module.js will be loaded
\r
8 // module 'core' is always loaded
\r
10 // NOTE: if you call without providers
\r
11 // <script src="mxn.js" ...
\r
12 // no scripts will be loaded at all and it is then up to you to load the scripts independently
\r
14 var providers = null;
\r
15 var modules = 'core';
\r
17 var scripts = document.getElementsByTagName('script');
\r
19 // Determine which scripts we need to load
\r
20 for (var i = 0; i < scripts.length; i++) {
\r
21 var match = scripts[i].src.replace(/%20/g , '').match(/^(.*?)mxn\.js(\?\(\[?(.*?)\]?\))?$/);
\r
22 if (match != null) {
\r
23 scriptBase = match[1];
\r
25 var settings = match[3].split(',[');
\r
26 providers = settings[0].replace(']' , '');
\r
27 if (settings[1]) modules += ',' + settings[1];
\r
33 if (providers == null || providers == 'none') return; // Bail out if no auto-load has been found
\r
34 providers = providers.replace(/ /g, '').split(',');
\r
35 modules = modules.replace(/ /g, '').split(',');
\r
37 // Actually load the scripts
\r
38 for (i = 0; i < modules.length; i++) {
\r
39 document.write("<script type='text/javascript' src='" + scriptBase + 'mxn.' + modules[i] + '.js' + "'></script>");
\r
40 for (var j = 0; j < providers.length; j++) document.write("<script type='text/javascript' src='" + scriptBase + 'mxn.' + providers[j] + '.' + modules[i] + '.js' + "'></script>");
\r
46 // holds all our implementing functions
\r
49 // Our special private methods
\r
51 * Calls the API specific implementation of a particular method.
\r
54 var invoke = function(sApiId, sObjName, sFnName, oScope, args){
\r
55 if(!hasImplementation(sApiId, sObjName, sFnName)) {
\r
56 throw 'Method ' + sFnName + ' of object ' + sObjName + ' is not supported by API ' + sApiId + '. Are you missing a script tag?';
\r
58 return apis[sApiId][sObjName][sFnName].apply(oScope, args);
\r
62 * Determines whether the specified API provides an implementation for the
\r
63 * specified object and function name.
\r
66 var hasImplementation = function(sApiId, sObjName, sFnName){
\r
67 if(typeof(apis[sApiId]) == 'undefined') {
\r
68 throw 'API ' + sApiId + ' not loaded. Are you missing a script tag?';
\r
70 if(typeof(apis[sApiId][sObjName]) == 'undefined') {
\r
71 throw 'Object definition ' + sObjName + ' in API ' + sApiId + ' not loaded. Are you missing a script tag?';
\r
73 return typeof(apis[sApiId][sObjName][sFnName]) == 'function';
\r
80 var mxn = window.mxn = /** @lends mxn */ {
\r
83 * Registers a set of provider specific implementation functions.
\r
85 * @param {String} sApiId The API ID to register implementing functions for.
\r
86 * @param {Object} oApiImpl An object containing the API implementation.
\r
88 register: function(sApiId, oApiImpl){
\r
89 if(!apis.hasOwnProperty(sApiId)){
\r
92 mxn.util.merge(apis[sApiId], oApiImpl);
\r
96 * Adds a list of named proxy methods to the prototype of a
\r
97 * specified constructor function.
\r
99 * @param {Function} func Constructor function to add methods to
\r
100 * @param {Array} aryMethods Array of method names to create
\r
101 * @param {Boolean} bWithApiArg Optional. Whether the proxy methods will use an API argument
\r
103 addProxyMethods: function(func, aryMethods, bWithApiArg){
\r
104 for(var i = 0; i < aryMethods.length; i++) {
\r
105 var sMethodName = aryMethods[i];
\r
107 func.prototype[sMethodName] = new Function('return this.invoker.go(\'' + sMethodName + '\', arguments, { overrideApi: true } );');
\r
110 func.prototype[sMethodName] = new Function('return this.invoker.go(\'' + sMethodName + '\', arguments);');
\r
116 checkLoad: function(funcDetails){
\r
117 if(this.loaded[this.api] === false) {
\r
119 this.onload[this.api].push( function() { funcDetails.callee.apply(scope, funcDetails); } );
\r
127 * Bulk add some named events to an object.
\r
129 * @param {Object} oEvtSrc The event source object.
\r
130 * @param {String[]} aEvtNames Event names to add.
\r
132 addEvents: function(oEvtSrc, aEvtNames){
\r
133 for(var i = 0; i < aEvtNames.length; i++){
\r
134 var sEvtName = aEvtNames[i];
\r
135 if(sEvtName in oEvtSrc){
\r
136 throw 'Event or method ' + sEvtName + ' already declared.';
\r
138 oEvtSrc[sEvtName] = new mxn.Event(sEvtName, oEvtSrc);
\r
145 * Instantiates a new Event
\r
147 * @param {String} sEvtName The name of the event.
\r
148 * @param {Object} oEvtSource The source object of the event.
\r
150 mxn.Event = function(sEvtName, oEvtSource){
\r
153 throw 'Event name must be provided';
\r
156 * Add a handler to the Event.
\r
157 * @param {Function} fn The handler function.
\r
158 * @param {Object} ctx The context of the handler function.
\r
160 this.addHandler = function(fn, ctx){
\r
161 handlers.push({context: ctx, handler: fn});
\r
164 * Remove a handler from the Event.
\r
165 * @param {Function} fn The handler function.
\r
166 * @param {Object} ctx The context of the handler function.
\r
168 this.removeHandler = function(fn, ctx){
\r
169 for(var i = 0; i < handlers.length; i++){
\r
170 if(handlers[i].handler == fn && handlers[i].context == ctx){
\r
171 handlers.splice(i, 1);
\r
176 * Remove all handlers from the Event.
\r
178 this.removeAllHandlers = function(){
\r
183 * @param {Object} oEvtArgs Event arguments object to be passed to the handlers.
\r
185 this.fire = function(oEvtArgs){
\r
186 var args = [sEvtName, oEvtSource, oEvtArgs];
\r
187 for(var i = 0; i < handlers.length; i++){
\r
188 handlers[i].handler.apply(handlers[i].context, args);
\r
194 * Creates a new Invoker, a class which helps with on-the-fly
\r
195 * invocation of the correct API methods.
\r
197 * @param {Object} aobj The core object whose methods will make cals to go()
\r
198 * @param {String} asClassName The name of the Mapstraction class to be invoked, normally the same name as aobj's constructor function
\r
199 * @param {Function} afnApiIdGetter The function on object aobj which will return the active API ID
\r
201 mxn.Invoker = function(aobj, asClassName, afnApiIdGetter){
\r
203 var sClassName = asClassName;
\r
204 var fnApiIdGetter = afnApiIdGetter;
\r
206 overrideApi: false, // {Boolean} API ID is overridden by value in first argument
\r
207 context: null, // {Object} Local vars can be passed from the body of the method to the API method within this object
\r
208 fallback: null // {Function} If an API implementation doesn't exist this function is run instead
\r
212 * Invoke the API implementation of a specific method.
\r
213 * @param {String} sMethodName The method name to invoke
\r
214 * @param {Array} args Arguments to pass on
\r
215 * @param {Object} oOptions Optional. Extra options for invocation
\r
216 * @param {Boolean} oOptions.overrideApi When true the first argument is used as the API ID.
\r
217 * @param {Object} oOptions.context A context object for passing extra information on to the provider implementation.
\r
218 * @param {Function} oOptions.fallback A fallback function to run if the provider implementation is missing.
\r
220 this.go = function(sMethodName, args, oOptions){
\r
222 if(typeof(oOptions) == 'undefined'){
\r
223 oOptions = defOpts;
\r
226 var sApiId = oOptions.overrideApi ? args[0] : fnApiIdGetter.apply(obj);
\r
228 if(typeof(sApiId) != 'string'){
\r
229 throw 'API ID not available.';
\r
232 if(typeof(oOptions.context) != 'undefined' && oOptions.context !== null){
\r
233 // make sure args is an array
\r
234 args = Array.prototype.slice.apply(args);
\r
235 args.push(oOptions.context);
\r
238 if(typeof(oOptions.fallback) == 'function' && !hasImplementation(sApiId, sClassName, sMethodName)){
\r
239 // we've got no implementation but have got a fallback function
\r
240 return oOptions.fallback.apply(obj, args);
\r
243 return invoke(sApiId, sClassName, sMethodName, obj, args);
\r
256 * Merges properties of one object into another recursively.
\r
257 * @param {Object} oRecv The object receiveing properties
\r
258 * @param {Object} oGive The object donating properties
\r
260 merge: function(oRecv, oGive){
\r
261 for (var sPropName in oGive){
\r
262 if (oGive.hasOwnProperty(sPropName)) {
\r
263 if(!oRecv.hasOwnProperty(sPropName)){
\r
264 oRecv[sPropName] = oGive[sPropName];
\r
267 mxn.util.merge(oRecv[sPropName], oGive[sPropName]);
\r
274 * $m, the dollar function, elegantising getElementById()
\r
275 * @return An HTML element or array of HTML elements
\r
279 for (var i = 0; i < arguments.length; i++) {
\r
280 var element = arguments[i];
\r
281 if (typeof(element) == 'string') {
\r
282 element = document.getElementById(element);
\r
284 if (arguments.length == 1) {
\r
287 elements.push(element);
\r
293 * loadScript is a JSON data fetcher
\r
294 * @param {String} src URL to JSON file
\r
295 * @param {Function} callback Callback function
\r
297 loadScript: function(src, callback) {
\r
298 var script = document.createElement('script');
\r
299 script.type = 'text/javascript';
\r
302 if(script.addEventListener){
\r
303 script.addEventListener('load', callback, true);
\r
305 else if(script.attachEvent){
\r
307 script.attachEvent("onreadystatechange",function(){
\r
308 if ( !done && document.readyState === "complete" ) {
\r
315 var h = document.getElementsByTagName('head')[0];
\r
316 h.appendChild( script );
\r
322 * @param {Object} point
\r
323 * @param {Object} level
\r
325 convertLatLonXY_Yahoo: function(point, level) { //Mercator
\r
326 var size = 1 << (26 - level);
\r
327 var pixel_per_degree = size / 360.0;
\r
328 var pixel_per_radian = size / (2 * Math.PI);
\r
329 var origin = new YCoordPoint(size / 2 , size / 2);
\r
330 var answer = new YCoordPoint();
\r
331 answer.x = Math.floor(origin.x + point.lon * pixel_per_degree);
\r
332 var sin = Math.sin(point.lat * Math.PI / 180.0);
\r
333 answer.y = Math.floor(origin.y + 0.5 * Math.log((1 + sin) / (1 - sin)) * -pixel_per_radian);
\r
338 * Load a stylesheet from a remote file.
\r
339 * @param {String} href URL to the CSS file
\r
341 loadStyle: function(href) {
\r
342 var link = document.createElement('link');
\r
343 link.type = 'text/css';
\r
344 link.rel = 'stylesheet';
\r
346 document.getElementsByTagName('head')[0].appendChild(link);
\r
351 * getStyle provides cross-browser access to css
\r
352 * @param {Object} el HTML Element
\r
353 * @param {String} prop Style property name
\r
355 getStyle: function(el, prop) {
\r
357 if (el.currentStyle) {
\r
358 y = el.currentStyle[prop];
\r
360 else if (window.getComputedStyle) {
\r
361 y = window.getComputedStyle( el, '').getPropertyValue(prop);
\r
367 * Convert longitude to metres
\r
368 * http://www.uwgb.edu/dutchs/UsefulData/UTMFormulas.HTM
\r
369 * "A degree of longitude at the equator is 111.2km... For other latitudes,
\r
370 * multiply by cos(lat)"
\r
371 * assumes the earth is a sphere but good enough for our purposes
\r
372 * @param {Float} lon
\r
373 * @param {Float} lat
\r
375 lonToMetres: function(lon, lat) {
\r
376 return lon * (111200 * Math.cos(lat * (Math.PI / 180)));
\r
380 * Convert metres to longitude
\r
381 * @param {Object} m
\r
382 * @param {Object} lat
\r
384 metresToLon: function(m, lat) {
\r
385 return m / (111200 * Math.cos(lat * (Math.PI / 180)));
\r
389 * Convert kilometres to miles
\r
390 * @param {Float} km
\r
391 * @returns {Float} miles
\r
393 KMToMiles: function(km) {
\r
394 return km / 1.609344;
\r
398 * Convert miles to kilometres
\r
399 * @param {Float} miles
\r
400 * @returns {Float} km
\r
402 milesToKM: function(miles) {
\r
403 return miles * 1.609344;
\r
406 // stuff to convert google zoom levels to/from degrees
\r
407 // assumes zoom 0 = 256 pixels = 360 degrees
\r
408 // zoom 1 = 256 pixels = 180 degrees
\r
413 * @param {Object} pixels
\r
414 * @param {Object} zoom
\r
416 getDegreesFromGoogleZoomLevel: function(pixels, zoom) {
\r
417 return (360 * pixels) / (Math.pow(2, zoom + 8));
\r
422 * @param {Object} pixels
\r
423 * @param {Object} degrees
\r
425 getGoogleZoomLevelFromDegrees: function(pixels, degrees) {
\r
426 return mxn.util.logN((360 * pixels) / degrees, 2) - 8;
\r
431 * @param {Object} number
\r
432 * @param {Object} base
\r
434 logN: function(number, base) {
\r
435 return Math.log(number) / Math.log(base);
\r
439 * Returns array of loaded provider apis
\r
440 * @returns {Array} providers
\r
442 getAvailableProviders : function () {
\r
443 var providers = [];
\r
444 for (var propertyName in apis){
\r
445 if (apis.hasOwnProperty(propertyName)) {
\r
446 providers.push(propertyName);
\r
455 * Class for converting between HTML and RGB integer color formats.
\r
456 * Accepts either a HTML color string argument or three integers for R, G and B.
\r
459 mxn.util.Color = function() {
\r
460 if(arguments.length == 3) {
\r
461 this.red = arguments[0];
\r
462 this.green = arguments[1];
\r
463 this.blue = arguments[2];
\r
465 else if(arguments.length == 1) {
\r
466 this.setHexColor(arguments[0]);
\r
470 mxn.util.Color.prototype.reHex = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
\r
473 * Set the color from the supplied HTML hex string.
\r
474 * @param {String} strHexColor A HTML hex color string e.g. '#00FF88'.
\r
476 mxn.util.Color.prototype.setHexColor = function(strHexColor) {
\r
477 var match = strHexColor.match(this.reHex);
\r
479 strHexColor = match[1];
\r
482 throw 'Invalid HEX color format, expected #000, 000, #000000 or 000000';
\r
484 if(strHexColor.length == 3) {
\r
485 strHexColor = strHexColor.replace(/\w/g, function(str){return str.concat(str);});
\r
487 this.red = parseInt(strHexColor.substr(0,2), 16);
\r
488 this.green = parseInt(strHexColor.substr(2,2), 16);
\r
489 this.blue = parseInt(strHexColor.substr(4,2), 16);
\r
493 * Retrieve the color value as an HTML hex string.
\r
494 * @returns {String} Format '00FF88' - note no preceding #.
\r
496 mxn.util.Color.prototype.getHexColor = function() {
\r
497 var vals = [this.red.toString(16), this.green.toString(16), this.blue.toString(16)];
\r
498 for(var i = 0; i < vals.length; i++) {
\r
499 vals[i] = (vals[i].length == 1) ? '0' + vals[i] : vals[i];
\r
500 vals[i] = vals[i].toUpperCase();
\r
502 return vals.join('');
\r