org.symbian.tools.wrttools.previewer/preview/script/lib/sapi/Location.js
changeset 37 641b65b14318
child 210 0f7abfd6ae62
equal deleted inserted replaced
35:ec3f1313ae92 37:641b65b14318
       
     1 /**
       
     2  * Location.js
       
     3  * 
       
     4  * Nokia Web Runtime Service API emulation 
       
     5  * WRT v1.1
       
     6  * 
       
     7  * Copyright 2009 Nokia Corporation. All rights reserved.
       
     8 */
       
     9 
       
    10  
       
    11 (function(){
       
    12 	
       
    13 	var provider = 'Service.Location',
       
    14 		Interface = 'ILocation';
       
    15 
       
    16 	/**
       
    17 	 * Landmark service
       
    18 	 */
       
    19 	var LocationService = function(){
       
    20 		this.GetLocation 			= __GetLocation;
       
    21 		this.Trace 					= __Trace;
       
    22 		this.Calculate				= __Calculate;
       
    23 		this.CancelNotification		= __CancelNotification;		
       
    24 	}
       
    25 
       
    26 	device.implementation.extend(provider, Interface, new LocationService() );
       
    27 
       
    28 
       
    29 	/******************************************************/	
       
    30 	/******************************************************/	
       
    31 	/******************************************************/	
       
    32 
       
    33 	var	context = device.implementation.context,
       
    34 		_t = context._t,
       
    35 		method = '',
       
    36 		result = false,
       
    37 		DBase = null;
       
    38 
       
    39 
       
    40 	var transactionIds = new Array();
       
    41 	var tTransactionId = -1;
       
    42 	var isTraceInProgress = false;
       
    43 	var criteriaTrace;
       
    44 	var callbackTrace;
       
    45 
       
    46 	/**
       
    47 	 * Landmarks: GetLocation
       
    48 	 * @param {Object} criteria
       
    49 	 */
       
    50 	function __GetLocation(criteria, callback, flag){	
       
    51 		method = "GetLocation";
       
    52 		//	Async call
       
    53 		flag = flag || false;
       
    54 
       
    55 		if (!criteria) {
       
    56 			criteria = new Object();
       
    57 		}	
       
    58 		
       
    59 		if(typeof criteria.LocationInformationClass == "undefined")
       
    60 			criteria.LocationInformationClass = "BasicLocationInformation"; // Default value of LocationInformationClass is "BasicLocationInformation" if not provided
       
    61 
       
    62 		var result = validateArgument('GetLocation',criteria);
       
    63 		if(result.ErrorCode != 0)
       
    64 			return result;
       
    65 		
       
    66 		if (typeof callback == 'function') {
       
    67 			
       
    68 			var retVal = context.callAsync(this, arguments.callee, criteria, callback,true);
       
    69 			transactionIds.push(retVal.TransactionID);  // all transaction ids are pushed on this variable, because CancelNotification function of SAPI doesn't take TransactioID as input
       
    70 			return retVal;
       
    71 		}
       
    72 		
       
    73 		if(flag)
       
    74 		{
       
    75 			transactionIds.shift();  // Remove oldest TransactionID(FIFO) (Async call)
       
    76 		}
       
    77 		
       
    78 		DBase = context.getData(provider);
       
    79 		var returnValue = DBase[criteria.LocationInformationClass];
       
    80 		locationNotify(criteria.Updateoptions);
       
    81 		return context.Result(returnValue);
       
    82 	}
       
    83 	
       
    84 	/**
       
    85 	 * Location: Trace
       
    86 	 * @param {Object} criteria
       
    87 	 * @param {Function} callback function for async call
       
    88 	 */
       
    89 	function __Trace(criteria, callback){
       
    90 		method = "Trace";
       
    91 
       
    92 		if (!criteria) {
       
    93 			criteria = new Object();
       
    94 		}	
       
    95 		
       
    96 		if(typeof criteria.LocationInformationClass == "undefined")
       
    97 			criteria.LocationInformationClass = "BasicLocationInformation"; // Default value of LocationInformationClass is "BasicLocationInformation" if not provided
       
    98 
       
    99 		if (typeof callback != 'function') { // callback should be valid function
       
   100 			return error(device.implementation.ERR_SERVICE_NOT_SUPPORTED,msg.msgCommandNotFound); 
       
   101 		}
       
   102 		
       
   103 		var result = validateArgument('Trace',criteria);
       
   104 		if(result.ErrorCode != 0)
       
   105 			return result;
       
   106 		
       
   107 		criteriaTrace = criteria;
       
   108 		callbackTrace = callback;
       
   109 		isTraceInProgress = true;
       
   110 		locationNotify(criteria.Updateoptions);
       
   111 
       
   112 		return traceCall(criteria,callback);
       
   113 	}
       
   114 
       
   115 	/**
       
   116 	 * Location: Calculate
       
   117 	 * @param {Object} criteria
       
   118 	 */
       
   119 	function __Calculate(criteria){
       
   120 		method = "Calculate";
       
   121 		if(!criteria || !criteria.MathRequest)
       
   122 			return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcMissingMathReq);
       
   123 			
       
   124 		if(typeof criteria.MathRequest != "string" || (criteria.MathRequest != "FindDistance" && criteria.MathRequest != "FindBearingTo" && criteria.MathRequest != "MoveCoordinates")) // Error check for wrong MathRequest criteria
       
   125 			return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcWrongTypeMathReq);
       
   126 			
       
   127 		if(typeof criteria.DistanceParamSource != "object" || (typeof criteria.DistanceParamSource.Latitude != "number" || typeof criteria.DistanceParamSource.Longitude != "number" || typeof criteria.DistanceParamSource.Altitude != "number"))
       
   128 			return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcMissingArgLocCord);
       
   129 
       
   130 		if(criteria.MathRequest == "FindDistance" || criteria.MathRequest == "FindBearingTo")
       
   131 		{
       
   132 			if(typeof criteria.DistanceParamSource != "object" || (typeof criteria.DistanceParamDestination.Latitude != "number" || typeof criteria.DistanceParamDestination.Longitude != "number" || typeof criteria.DistanceParamDestination.Altitude != "number"))
       
   133 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcMissingArgLocCord);
       
   134 			if (criteria.MathRequest == "FindDistance") {
       
   135 				var dist = LatLon.distHaversine(criteria.DistanceParamDestination.Latitude, criteria.DistanceParamDestination.Longitude, criteria.DistanceParamSource.Latitude, criteria.DistanceParamSource.Longitude)*1000;
       
   136 				if (typeof criteria.DistanceParamDestination.Altitude == "number" && typeof criteria.DistanceParamSource.Altitude == "number") {
       
   137 					var delta = criteria.DistanceParamDestination.Altitude - criteria.DistanceParamSource.Altitude
       
   138 					dist = Math.sqrt(dist * dist + delta * delta);
       
   139 				}
       
   140 				return context.Result(dist);
       
   141 			}
       
   142 			else if (criteria.MathRequest == "FindBearingTo"){
       
   143 				var bearing = LatLon.bearing( criteria.DistanceParamSource.Latitude, criteria.DistanceParamSource.Longitude,criteria.DistanceParamDestination.Latitude, criteria.DistanceParamDestination.Longitude);
       
   144 				return context.Result(bearing);				
       
   145 			}
       
   146 		}
       
   147 		else if(criteria.MathRequest == "MoveCoordinates"){
       
   148 
       
   149 			if(typeof criteria.MoveByThisDistance == "undefined")
       
   150 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcMissingArgMoveDist);
       
   151 			
       
   152 			if(typeof criteria.MoveByThisBearing == "undefined")
       
   153 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcMissingArgMoveBear);
       
   154 			
       
   155 
       
   156 			if(typeof criteria.MoveByThisDistance != "number")
       
   157 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcWrongTypeMoveDist);
       
   158 			
       
   159 			if(typeof criteria.MoveByThisBearing != "number")
       
   160 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcWrongTypeMoveBear);
       
   161 			
       
   162 			var latLon = new LatLon(criteria.DistanceParamSource.Latitude, criteria.DistanceParamSource.Longitude);
       
   163 			var dlatLon = latLon.destPoint(criteria.MoveByThisBearing, criteria.MoveByThisDistance/1000);
       
   164 			var retVal = new Object();
       
   165 			retVal.Longitude = dlatLon.lon;
       
   166 			retVal.Latitude = dlatLon.lat;
       
   167 			retVal.Altitude = criteria.DistanceParamSource.Altitude;
       
   168 			return context.Result(retVal);
       
   169 		}
       
   170 	}
       
   171 			
       
   172 	/**
       
   173 	 * Location: CancelNotification
       
   174 	 * @param {Object} criteria
       
   175 	 */
       
   176 	function __CancelNotification(criteria){
       
   177 		method = "Cancel";
       
   178 		if(!criteria)
       
   179 				return error(device.implementation.ERR_MISSING_ARGUMENT,msg.msgCancelMissingType);
       
   180 		
       
   181 		var arr = new Array();
       
   182 		var i = 0;
       
   183 		var key
       
   184 		for(key in criteria);
       
   185 			arr[i++] = key;
       
   186 
       
   187 		if(!criteria.CancelRequestType && arr.length)
       
   188 				return error(device.implementation.ERR_NOT_FOUND,msg.msgCancelMissingType);
       
   189 		
       
   190 		if(criteria.CancelRequestType != "GetLocCancel" && criteria.CancelRequestType != "TraceCancel")
       
   191 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCancelWrongType);
       
   192 		
       
   193 		if (criteria.CancelRequestType == "GetLocCancel") {
       
   194 			for (var i = 0; i < transactionIds.length; i++) {
       
   195 				clearTimeout(transactionIds[i])
       
   196 			}
       
   197 		}
       
   198 		
       
   199 		if (criteria.CancelRequestType == "TraceCancel")
       
   200 		{
       
   201 			isTraceInProgress = false;
       
   202 			tTransactionId = -1;
       
   203 		}
       
   204 		return context.ErrorResult(device.implementation.ERR_SUCCESS);
       
   205 	}
       
   206 		
       
   207 
       
   208 
       
   209 	
       
   210 	/*******************************
       
   211 	 * helper functions
       
   212 	 *******************************/
       
   213 
       
   214 	/**
       
   215 	 * Location: traceCall
       
   216 	 * @param {} 
       
   217 	 * This function emulates repetitive trace calls,It calls specified callback function after every UpdateInterval untill 
       
   218 	 * CancelNotification is called
       
   219 	 */
       
   220 	function traceCall(){
       
   221 		var tid = setTimeout(function(){
       
   222 		if(!isTraceInProgress)
       
   223 			return;
       
   224 			
       
   225 		DBase = context.getData(provider);
       
   226 		var returnValue = DBase[criteriaTrace.LocationInformationClass];
       
   227 		var result,
       
   228 			eventCode = {completed:2, error:4, progress:9},
       
   229 		code = eventCode.completed;
       
   230 
       
   231 		callbackTrace(tTransactionId,code,context.Result(returnValue,0));
       
   232 		traceCall();
       
   233 		}, criteriaTrace.Updateoptions.UpdateInterval/1000);
       
   234 		if(tTransactionId == -1)
       
   235 			tTransactionId = tid;
       
   236 		return context.AsyncResult(tTransactionId);
       
   237 	}
       
   238 
       
   239 	/**
       
   240 	 * Location: validateArgument
       
   241 	 * @param {string,object} callingMethod and criteria
       
   242 	 * Validates arguments
       
   243 	 */
       
   244 	function validateArgument(fun,criteria)
       
   245 	{
       
   246 		method = fun;
       
   247 		if(typeof criteria.Updateoptions != "undefined")
       
   248 		{
       
   249 			if(typeof criteria.Updateoptions != "object") // Checking for error in UpdateOptions criteria
       
   250 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationBadArg);
       
   251 			
       
   252 			if(typeof criteria.Updateoptions.UpdateInterval != "undefined" && typeof criteria.Updateoptions.UpdateInterval != "number")
       
   253 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationWrongType);
       
   254 			
       
   255 			if(typeof criteria.Updateoptions.UpdateTimeOut != "undefined" && typeof criteria.Updateoptions.UpdateTimeOut != "number")	
       
   256 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationWrongType);
       
   257 
       
   258 			if(typeof criteria.Updateoptions.UpdateMaxAge != "undefined" && typeof criteria.Updateoptions.UpdateMaxAge != "number")	
       
   259 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationWrongType);
       
   260 
       
   261 			if(typeof criteria.Updateoptions.PartialUpdates != "undefined" && typeof criteria.Updateoptions.PartialUpdates != "boolean")	
       
   262 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationWrongType);
       
   263 
       
   264 			if((typeof criteria.Updateoptions.UpdateInterval != "undefined" && criteria.Updateoptions.UpdateInterval  < 0) || 
       
   265 					(typeof criteria.Updateoptions.UpdateTimeOut != "undefined" && criteria.Updateoptions.UpdateTimeOut  < 0) ||
       
   266 					(typeof criteria.Updateoptions.UpdateMaxAge != "undefined" && criteria.Updateoptions.UpdateMaxAge  < 0))
       
   267 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationNegInt);
       
   268 
       
   269 			if(typeof criteria.Updateoptions.UpdateTimeOut != "undefined" && typeof criteria.Updateoptions.UpdateInterval != "undefined" && criteria.Updateoptions.UpdateInterval > criteria.Updateoptions.UpdateTimeOut)
       
   270 			{
       
   271 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgNone);
       
   272 			}
       
   273 
       
   274 			/*if((typeof criteria.Updateoptions.UpdateTimeOut != "undefined" && criteria.Updateoptions.UpdateTimeOut <= 1000000))// || (typeof criteria.Updateoptions.UpdateInterval != "undefined" && criteria.Updateoptions.UpdateInterval <= 1000000))
       
   275 			{
       
   276 				return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgNone);
       
   277 			}*/				
       
   278 		}
       
   279 
       
   280 		if(typeof criteria.LocationInformationClass != "undefined" && criteria.LocationInformationClass != "BasicLocationInformation" && criteria.LocationInformationClass != "GenericLocationInfo") // checking for errors in LocationInformationClass criteria
       
   281 			return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationWrongCat);
       
   282 		
       
   283 		if (/^Trace$/i.test(fun)&&(!criteria.Updateoptions || typeof criteria.Updateoptions.UpdateInterval == "undefined")) {
       
   284 			if(!criteria.Updateoptions)
       
   285 			{
       
   286 				criteria.Updateoptions = new Object();
       
   287 			}
       
   288 			criteria.Updateoptions.UpdateInterval = 1000000;  // Emulation only!! for convenience UpdateInterval is set to 1 second is not specified or if it less than 1 second
       
   289 			context.notify("Using default UpdateInterval(1000000 micro seconds)"); 
       
   290 		}
       
   291 		
       
   292 		return context.ErrorResult(device.implementation.ERR_SUCCESS, "");
       
   293 		
       
   294 	}
       
   295 
       
   296 	/**
       
   297 	 * Location: error
       
   298 	 * @param {number,string} ErrorCode and ErrorString
       
   299 	 * Replaces Error String with method name
       
   300 	 */
       
   301 	function error(code, msg /*, args...*/){
       
   302 
       
   303 		var args = ['location',method].concat([].slice.call(arguments,2));
       
   304 		msg = msg ? _t().arg.apply(msg,args) : undefined;
       
   305 		return context.ErrorResult(code, msg);
       
   306 	}
       
   307 	
       
   308 	function locationNotify(updateoptions) {
       
   309 		if(!updateoptions)
       
   310 			return;
       
   311 		if(typeof updateoptions.UpdateTimeOut != "undefined")
       
   312 			context.notify(_t("%s:: %s : Updateoptions.UpdateTimeOut not implemented in preview").arg(provider, method));
       
   313 
       
   314 		if(typeof updateoptions.UpdateMaxAge != "undefined")
       
   315 			context.notify(_t("%s:: %s : Updateoptions.UpdateMaxAge not implemented in preview").arg(provider, method));
       
   316 
       
   317 		if(typeof updateoptions.PartialUpdates != "undefined")
       
   318 			context.notify(_t("%s:: %s : Updateoptions.PartialUpdates not implemented in preview").arg(provider, method));
       
   319 	}
       
   320 
       
   321 	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
       
   322 	/*  Latitude/longitude spherical geodesy formulae & scripts (c) Chris Veness 2002-2009            */
       
   323 	/*	http://www.movable-type.co.uk/scripts/latlong.html											  */  
       
   324 	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
       
   325 	
       
   326 	/*
       
   327 	 * Use Haversine formula to Calculate distance (in km) between two points specified by 
       
   328 	 * latitude/longitude (in numeric degrees)
       
   329 	 *
       
   330 	 * example usage from form:
       
   331 	 *   result.value = LatLon.distHaversine(lat1.value.parseDeg(), long1.value.parseDeg(), 
       
   332 	 *                                       lat2.value.parseDeg(), long2.value.parseDeg());
       
   333 	 * where lat1, long1, lat2, long2, and result are form fields
       
   334 	 */
       
   335 	
       
   336 	
       
   337 	LatLon.distHaversine = function(lat1, lon1, lat2, lon2) {
       
   338 	  var R = 6371; // earth's mean radius in km
       
   339 	  var dLat = toRad(lat2-lat1);
       
   340 	  var dLon = toRad(lon2-lon1);
       
   341 	  lat1 = toRad(lat1), lat2 = toRad(lat2);
       
   342 	
       
   343 	  var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
       
   344 	          Math.cos(lat1) * Math.cos(lat2) * 
       
   345 	          Math.sin(dLon/2) * Math.sin(dLon/2);
       
   346 	  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
       
   347 	  var d = R * c;
       
   348 	  return d;
       
   349 	}
       
   350 	
       
   351 	
       
   352 	/*
       
   353 	 * ditto using Law of Cosines
       
   354 	 */
       
   355 	LatLon.distCosineLaw = function(lat1, lon1, lat2, lon2) {
       
   356 	  var R = 6371; // earth's mean radius in km
       
   357 	  var d = Math.acos(Math.sin(toRad(lat1))*Math.sin(toRad(lat2)) +
       
   358 	                    Math.cos(toRad(lat1))*Math.cos(toRad(lat2))*Math.cos(toRad(lon2-lon1))) * R;
       
   359 	  return d;
       
   360 	}
       
   361 	
       
   362 	
       
   363 	/*
       
   364 	 * calculate (initial) bearing between two points
       
   365 	 *   see http://williams.best.vwh.net/avform.htm#Crs
       
   366 	 */
       
   367 	LatLon.bearing = function(lat1, lon1, lat2, lon2) {
       
   368 	  lat1 = toRad(lat1); lat2 = toRad(lat2);
       
   369 	  var dLon = toRad(lon2-lon1);
       
   370 
       
   371 	  var y = Math.sin(dLon) * Math.cos(lat2);
       
   372 	  var x = Math.cos(lat1)*Math.sin(lat2) -
       
   373 	          Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
       
   374 	  return toBrng(Math.atan2(y, x));
       
   375 	}
       
   376 	
       
   377 	
       
   378 	/*
       
   379 	 * calculate destination point given start point, initial bearing (deg) and distance (km)
       
   380 	 *   see http://williams.best.vwh.net/avform.htm#LL
       
   381 	 */
       
   382 	LatLon.prototype.destPoint = function(brng, d) {
       
   383 	  var R = 6371; // earth's mean radius in km
       
   384 	  var lat1 = toRad(this.lat), lon1 = toRad(this.lon);
       
   385 	  brng = toRad(brng);
       
   386 	
       
   387 	  var lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) + 
       
   388 	                        Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng) );
       
   389 	  var lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1), 
       
   390 	                               Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
       
   391 	  lon2 = (lon2+Math.PI)%(2*Math.PI) - Math.PI;  // normalise to -180...+180
       
   392 	
       
   393 	  if (isNaN(lat2) || isNaN(lon2)) return null;
       
   394 	  return new LatLon(toDeg(lat2), toDeg(lon2));
       
   395 	}
       
   396 	
       
   397 	
       
   398 	/*
       
   399 	 * construct a LatLon object: arguments in numeric degrees
       
   400 	 *
       
   401 	 * note all LatLong methods expect & return numeric degrees (for lat/long & for bearings)
       
   402 	 */
       
   403 	function LatLon(lat, lon) {
       
   404 	  this.lat = lat;
       
   405 	  this.lon = lon;
       
   406 	}
       
   407 	
       
   408 	
       
   409 	/*
       
   410 	 * represent point {lat, lon} in standard representation
       
   411 	 */
       
   412 	LatLon.prototype.toString = function() {
       
   413 	  return this.lat.toLat() + ', ' + this.lon.toLon();
       
   414 	}
       
   415 	
       
   416 	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
       
   417 	
       
   418 	// extend String object with method for parsing degrees or lat/long values to numeric degrees
       
   419 	//
       
   420 	// this is very flexible on formats, allowing signed decimal degrees, or deg-min-sec suffixed by 
       
   421 	// compass direction (NSEW). A variety of separators are accepted (eg 3º 37' 09"W) or fixed-width 
       
   422 	// format without separators (eg 0033709W). Seconds and minutes may be omitted. (Minimal validation 
       
   423 	// is done).
       
   424 	
       
   425 	function parseDeg (str) {
       
   426 	  if (!isNaN(str)) return Number(str);                 // signed decimal degrees without NSEW
       
   427 	
       
   428 	  var degLL = str.replace(/^-/,'').replace(/[NSEW]/i,'');  // strip off any sign or compass dir'n
       
   429 	  var dms = degLL.split(/[^0-9.]+/);                     // split out separate d/m/s
       
   430 	  for (var i in dms) if (dms[i]=='') dms.splice(i,1);    // remove empty elements (see note below)
       
   431 	  switch (dms.length) {                                  // convert to decimal degrees...
       
   432 	    case 3:                                              // interpret 3-part result as d/m/s
       
   433 	      var deg = dms[0]/1 + dms[1]/60 + dms[2]/3600; break;
       
   434 	    case 2:                                              // interpret 2-part result as d/m
       
   435 	      var deg = dms[0]/1 + dms[1]/60; break;
       
   436 	    case 1:                                              // decimal or non-separated dddmmss
       
   437 	      if (/[NS]/i.test(str)) degLL = '0' + degLL;       // - normalise N/S to 3-digit degrees
       
   438 	      var deg = dms[0].slice(0,3)/1 + dms[0].slice(3,5)/60 + dms[0].slice(5)/3600; break;
       
   439 	    default: return NaN;
       
   440 	  }
       
   441 	  if (/^-/.test(str) || /[WS]/i.test(str)) deg = -deg; // take '-', west and south as -ve
       
   442 	  return deg;
       
   443 	}
       
   444 	// note: whitespace at start/end will split() into empty elements (except in IE)
       
   445 	
       
   446 	
       
   447 	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
       
   448 	
       
   449 	// extend Number object with methods for converting degrees/radians
       
   450 	
       
   451 	function toRad (deg) {  // convert degrees to radians
       
   452 	  return deg * Math.PI / 180;
       
   453 	}
       
   454 	
       
   455 	function toDeg (rad) {  // convert radians to degrees (signed)
       
   456 	  return rad * 180 / Math.PI;
       
   457 	}
       
   458 	
       
   459 	function toBrng (rad) {  // convert radians to degrees (as bearing: 0...360)
       
   460 	  return (toDeg(rad)+360) % 360;
       
   461 	}
       
   462 	
       
   463 	
       
   464 	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
       
   465 	
       
   466 	// extend Number object with methods for presenting bearings & lat/longs
       
   467 	
       
   468 	function toDMS (num) {  // convert numeric degrees to deg/min/sec
       
   469 	  var d = Math.abs(num);  // (unsigned result ready for appending compass dir'n)
       
   470 	  d += 1/7200;  // add ½ second for rounding
       
   471 	  var deg = Math.floor(d);
       
   472 	  var min = Math.floor((d-deg)*60);
       
   473 	  var sec = Math.floor((d-deg-min/60)*3600);
       
   474 	  // add leading zeros if required
       
   475 	  if (deg<100) deg = '0' + deg; if (deg<10) deg = '0' + deg;
       
   476 	  if (min<10) min = '0' + min;
       
   477 	  if (sec<10) sec = '0' + sec;
       
   478 	  return deg + '\u00B0' + min + '\u2032' + sec + '\u2033';
       
   479 	}
       
   480 	
       
   481 	function toLat (deg) {  // convert numeric degrees to deg/min/sec latitude
       
   482 	  return toDMS(deg).slice(1) + (deg<0 ? 'S' : 'N');  // knock off initial '0' for lat!
       
   483 	}
       
   484 	
       
   485 	function toLon (deg) {  // convert numeric degrees to deg/min/sec longitude
       
   486 	  return toDMS(deg) + (deg>0 ? 'E' : 'W');
       
   487 	}
       
   488 	
       
   489 	function toPrecision (num,fig) {  // override toPrecision method with one which displays 
       
   490 	  if (num == 0) return 0;                      // trailing zeros in place of exponential notation
       
   491 	  var scale = Math.ceil(Math.log(num)*Math.LOG10E);
       
   492 	  var mult = Math.pow(10, fig-scale);
       
   493 	  return Math.round(num*mult)/mult;
       
   494 	}
       
   495 
       
   496 
       
   497 	/** 
       
   498 	 * error messages
       
   499 	 * order of %s args: Service name, method name, parameter name 
       
   500 	 */
       
   501 	var msg = {
       
   502 		msgCommandNotFound			: '%s : Command Not found',
       
   503 		msgGetLocationWrongCat		: '%s : %s : wrong category info should be BasicLocationInformation/GenericLocationInfo ',
       
   504 		msgGetLocationBadArg		: '%s : %s : BadArgument - Updateoptions',
       
   505 		msgGetLocationNegInt		: '%s : %s : Negative Time Interval',
       
   506 		msgGetLocationWrongType 	: '%s : %s : UpdateOptions Type mismatch',
       
   507 		msgTraceWrongCat			: '%s : %s : Invalid LocationInformationClass',
       
   508 		msgCalcMissingMathReq 		: '%s : %s : Missing argument - MathRequest',
       
   509 		msgCalcWrongTypeMathReq 	: '%s : %s : Wrong argument - MathRequest',
       
   510 		msgCalcMissingArgLocCord 	: '%s : %s : Missing argument - locationcoordinate',
       
   511 		msgCalcMissingArgMoveDist 	: '%s : %s : Missing argument - MoveByThisDistance',
       
   512 		msgCalcMissingArgMoveBear 	: '%s : %s : Missing argument - MoveByThisBearing',
       
   513 		msgCalcWrongTypeMoveDist  	: '%s : %s : TypeMismatch - MoveByThisDistance',
       
   514 		msgCalcWrongTypeMoveBear 	: '%s : %s : TypeMismatch - MoveByThisBearing',
       
   515 		msgCancelBadArg 			: '%s : %s : BadArgument – cancel type',
       
   516 		msgCancelMissingType 		: '%s : %s : Missing cancel type',
       
   517 		msgCancelWrongType 			: '%s : %s : Wrong cancel type'	,
       
   518 		msgNone						: ''	
       
   519 	};
       
   520 		
       
   521 
       
   522 }) ()
       
   523