4 * Nokia Web Runtime Service API emulation
\r
7 * Copyright 2009 Nokia Corporation. All rights reserved.
\r
13 var provider = 'Service.Location',
\r
14 Interface = 'ILocation';
\r
19 var LocationService = function(){
\r
20 this.GetLocation = __GetLocation;
\r
21 this.Trace = __Trace;
\r
22 this.Calculate = __Calculate;
\r
23 this.CancelNotification = __CancelNotification;
\r
26 device.implementation.extend(provider, Interface, new LocationService() );
\r
29 /******************************************************/
\r
30 /******************************************************/
\r
31 /******************************************************/
\r
33 var context = device.implementation.context,
\r
40 var transactionIds = new Array();
\r
41 var tTransactionId = -1;
\r
42 var isTraceInProgress = false;
\r
47 * Landmarks: GetLocation
\r
48 * @param {Object} criteria
\r
50 function __GetLocation(criteria, callback, flag){
\r
51 method = "GetLocation";
\r
53 flag = flag || false;
\r
56 criteria = new Object();
\r
59 if(typeof criteria.LocationInformationClass == "undefined")
\r
60 criteria.LocationInformationClass = "BasicLocationInformation"; // Default value of LocationInformationClass is "BasicLocationInformation" if not provided
\r
62 var result = validateArgument('GetLocation',criteria);
\r
63 if(result.ErrorCode != 0)
\r
66 if (typeof callback == 'function') {
\r
68 var retVal = context.callAsync(this, arguments.callee, criteria, callback,true);
\r
69 transactionIds.push(retVal.TransactionID); // all transaction ids are pushed on this variable, because CancelNotification function of SAPI doesn't take TransactioID as input
\r
75 transactionIds.shift(); // Remove oldest TransactionID(FIFO) (Async call)
\r
78 DBase = context.getData(provider);
\r
79 var returnValue = DBase[criteria.LocationInformationClass];
\r
80 locationNotify(criteria.Updateoptions);
\r
81 return context.Result(returnValue);
\r
86 * @param {Object} criteria
\r
87 * @param {Function} callback function for async call
\r
89 function __Trace(criteria, callback){
\r
93 criteria = new Object();
\r
96 if(typeof criteria.LocationInformationClass == "undefined")
\r
97 criteria.LocationInformationClass = "BasicLocationInformation"; // Default value of LocationInformationClass is "BasicLocationInformation" if not provided
\r
99 if (typeof callback != 'function') { // callback should be valid function
\r
100 return error(device.implementation.ERR_SERVICE_NOT_SUPPORTED,msg.msgCommandNotFound);
\r
103 var result = validateArgument('Trace',criteria);
\r
104 if(result.ErrorCode != 0)
\r
107 criteriaTrace = criteria;
\r
108 callbackTrace = callback;
\r
109 isTraceInProgress = true;
\r
110 locationNotify(criteria.Updateoptions);
\r
112 return traceCall(criteria,callback);
\r
116 * Location: Calculate
\r
117 * @param {Object} criteria
\r
119 function __Calculate(criteria){
\r
120 method = "Calculate";
\r
121 if(!criteria || !criteria.MathRequest)
\r
122 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcMissingMathReq);
\r
124 if(typeof criteria.MathRequest != "string" || (criteria.MathRequest != "FindDistance" && criteria.MathRequest != "FindBearingTo" && criteria.MathRequest != "MoveCoordinates")) // Error check for wrong MathRequest criteria
\r
125 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcWrongTypeMathReq);
\r
127 if(typeof criteria.DistanceParamSource != "object" || (typeof criteria.DistanceParamSource.Latitude != "number" || typeof criteria.DistanceParamSource.Longitude != "number" || typeof criteria.DistanceParamSource.Altitude != "number"))
\r
128 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcMissingArgLocCord);
\r
130 if(criteria.MathRequest == "FindDistance" || criteria.MathRequest == "FindBearingTo")
\r
132 if(typeof criteria.DistanceParamSource != "object" || (typeof criteria.DistanceParamDestination.Latitude != "number" || typeof criteria.DistanceParamDestination.Longitude != "number" || typeof criteria.DistanceParamDestination.Altitude != "number"))
\r
133 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcMissingArgLocCord);
\r
134 if (criteria.MathRequest == "FindDistance") {
\r
135 var dist = LatLon.distHaversine(criteria.DistanceParamDestination.Latitude, criteria.DistanceParamDestination.Longitude, criteria.DistanceParamSource.Latitude, criteria.DistanceParamSource.Longitude)*1000;
\r
136 if (typeof criteria.DistanceParamDestination.Altitude == "number" && typeof criteria.DistanceParamSource.Altitude == "number") {
\r
137 var delta = criteria.DistanceParamDestination.Altitude - criteria.DistanceParamSource.Altitude
\r
138 dist = Math.sqrt(dist * dist + delta * delta);
\r
140 return context.Result(dist);
\r
142 else if (criteria.MathRequest == "FindBearingTo"){
\r
143 var bearing = LatLon.bearing( criteria.DistanceParamSource.Latitude, criteria.DistanceParamSource.Longitude,criteria.DistanceParamDestination.Latitude, criteria.DistanceParamDestination.Longitude);
\r
144 return context.Result(bearing);
\r
147 else if(criteria.MathRequest == "MoveCoordinates"){
\r
149 if(typeof criteria.MoveByThisDistance == "undefined")
\r
150 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcMissingArgMoveDist);
\r
152 if(typeof criteria.MoveByThisBearing == "undefined")
\r
153 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcMissingArgMoveBear);
\r
156 if(typeof criteria.MoveByThisDistance != "number")
\r
157 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcWrongTypeMoveDist);
\r
159 if(typeof criteria.MoveByThisBearing != "number")
\r
160 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCalcWrongTypeMoveBear);
\r
162 var latLon = new LatLon(criteria.DistanceParamSource.Latitude, criteria.DistanceParamSource.Longitude);
\r
163 var dlatLon = latLon.destPoint(criteria.MoveByThisBearing, criteria.MoveByThisDistance/1000);
\r
164 var retVal = new Object();
\r
165 retVal.Longitude = dlatLon.lon;
\r
166 retVal.Latitude = dlatLon.lat;
\r
167 retVal.Altitude = criteria.DistanceParamSource.Altitude;
\r
168 return context.Result(retVal);
\r
173 * Location: CancelNotification
\r
174 * @param {Object} criteria
\r
176 function __CancelNotification(criteria){
\r
179 return error(device.implementation.ERR_MISSING_ARGUMENT,msg.msgCancelMissingType);
\r
181 var arr = new Array();
\r
184 for(key in criteria);
\r
187 if(!criteria.CancelRequestType && arr.length)
\r
188 return error(device.implementation.ERR_NOT_FOUND,msg.msgCancelMissingType);
\r
190 if(criteria.CancelRequestType != "GetLocCancel" && criteria.CancelRequestType != "TraceCancel")
\r
191 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgCancelWrongType);
\r
193 if (criteria.CancelRequestType == "GetLocCancel") {
\r
194 for (var i = 0; i < transactionIds.length; i++) {
\r
195 clearTimeout(transactionIds[i])
\r
199 if (criteria.CancelRequestType == "TraceCancel")
\r
201 isTraceInProgress = false;
\r
202 tTransactionId = -1;
\r
204 return context.ErrorResult(device.implementation.ERR_SUCCESS);
\r
210 /*******************************
\r
212 *******************************/
\r
215 * Location: traceCall
\r
217 * This function emulates repetitive trace calls,It calls specified callback function after every UpdateInterval untill
\r
218 * CancelNotification is called
\r
220 function traceCall(){
\r
221 var tid = setTimeout(function(){
\r
222 if(!isTraceInProgress)
\r
225 DBase = context.getData(provider);
\r
226 var returnValue = DBase[criteriaTrace.LocationInformationClass];
\r
228 eventCode = {completed:2, error:4, progress:9},
\r
229 code = eventCode.completed;
\r
231 callbackTrace(tTransactionId,code,context.Result(returnValue,0));
\r
233 }, criteriaTrace.Updateoptions.UpdateInterval/1000);
\r
234 if(tTransactionId == -1)
\r
235 tTransactionId = tid;
\r
236 return context.AsyncResult(tTransactionId);
\r
240 * Location: validateArgument
\r
241 * @param {string,object} callingMethod and criteria
\r
242 * Validates arguments
\r
244 function validateArgument(fun,criteria)
\r
247 if(typeof criteria.Updateoptions != "undefined")
\r
249 if(typeof criteria.Updateoptions != "object") // Checking for error in UpdateOptions criteria
\r
250 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationBadArg);
\r
252 if(typeof criteria.Updateoptions.UpdateInterval != "undefined" && typeof criteria.Updateoptions.UpdateInterval != "number")
\r
253 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationWrongType);
\r
255 if(typeof criteria.Updateoptions.UpdateTimeOut != "undefined" && typeof criteria.Updateoptions.UpdateTimeOut != "number")
\r
256 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationWrongType);
\r
258 if(typeof criteria.Updateoptions.UpdateMaxAge != "undefined" && typeof criteria.Updateoptions.UpdateMaxAge != "number")
\r
259 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationWrongType);
\r
261 if(typeof criteria.Updateoptions.PartialUpdates != "undefined" && typeof criteria.Updateoptions.PartialUpdates != "boolean")
\r
262 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationWrongType);
\r
264 if((typeof criteria.Updateoptions.UpdateInterval != "undefined" && criteria.Updateoptions.UpdateInterval < 0) ||
\r
265 (typeof criteria.Updateoptions.UpdateTimeOut != "undefined" && criteria.Updateoptions.UpdateTimeOut < 0) ||
\r
266 (typeof criteria.Updateoptions.UpdateMaxAge != "undefined" && criteria.Updateoptions.UpdateMaxAge < 0))
\r
267 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationNegInt);
\r
269 if(typeof criteria.Updateoptions.UpdateTimeOut != "undefined" && typeof criteria.Updateoptions.UpdateInterval != "undefined" && criteria.Updateoptions.UpdateInterval > criteria.Updateoptions.UpdateTimeOut)
\r
271 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgNone);
\r
274 /*if((typeof criteria.Updateoptions.UpdateTimeOut != "undefined" && criteria.Updateoptions.UpdateTimeOut <= 1000000))// || (typeof criteria.Updateoptions.UpdateInterval != "undefined" && criteria.Updateoptions.UpdateInterval <= 1000000))
\r
276 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgNone);
\r
280 if(typeof criteria.LocationInformationClass != "undefined" && criteria.LocationInformationClass != "BasicLocationInformation" && criteria.LocationInformationClass != "GenericLocationInfo") // checking for errors in LocationInformationClass criteria
\r
281 return error(device.implementation.ERR_BAD_ARGUMENT_TYPE,msg.msgGetLocationWrongCat);
\r
283 if (/^Trace$/i.test(fun)&&(!criteria.Updateoptions || typeof criteria.Updateoptions.UpdateInterval == "undefined")) {
\r
284 if(!criteria.Updateoptions)
\r
286 criteria.Updateoptions = new Object();
\r
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
\r
289 context.notify("Using default UpdateInterval(1000000 micro seconds)");
\r
292 return context.ErrorResult(device.implementation.ERR_SUCCESS, "");
\r
298 * @param {number,string} ErrorCode and ErrorString
\r
299 * Replaces Error String with method name
\r
301 function error(code, msg /*, args...*/){
\r
303 var args = ['location',method].concat([].slice.call(arguments,2));
\r
304 msg = msg ? _t().arg.apply(msg,args) : undefined;
\r
305 return context.ErrorResult(code, msg);
\r
308 function locationNotify(updateoptions) {
\r
311 if(typeof updateoptions.UpdateTimeOut != "undefined")
\r
312 context.notify(_t("%s:: %s : Updateoptions.UpdateTimeOut not implemented in preview").arg(provider, method));
\r
314 if(typeof updateoptions.UpdateMaxAge != "undefined")
\r
315 context.notify(_t("%s:: %s : Updateoptions.UpdateMaxAge not implemented in preview").arg(provider, method));
\r
317 if(typeof updateoptions.PartialUpdates != "undefined")
\r
318 context.notify(_t("%s:: %s : Updateoptions.PartialUpdates not implemented in preview").arg(provider, method));
\r
321 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
\r
322 /* Latitude/longitude spherical geodesy formulae & scripts (c) Chris Veness 2002-2009 */
\r
323 /* http://www.movable-type.co.uk/scripts/latlong.html */
\r
324 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
\r
327 * Use Haversine formula to Calculate distance (in km) between two points specified by
\r
328 * latitude/longitude (in numeric degrees)
\r
330 * example usage from form:
\r
331 * result.value = LatLon.distHaversine(lat1.value.parseDeg(), long1.value.parseDeg(),
\r
332 * lat2.value.parseDeg(), long2.value.parseDeg());
\r
333 * where lat1, long1, lat2, long2, and result are form fields
\r
337 LatLon.distHaversine = function(lat1, lon1, lat2, lon2) {
\r
338 var R = 6371; // earth's mean radius in km
\r
339 var dLat = toRad(lat2-lat1);
\r
340 var dLon = toRad(lon2-lon1);
\r
341 lat1 = toRad(lat1), lat2 = toRad(lat2);
\r
343 var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
\r
344 Math.cos(lat1) * Math.cos(lat2) *
\r
345 Math.sin(dLon/2) * Math.sin(dLon/2);
\r
346 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
\r
353 * ditto using Law of Cosines
\r
355 LatLon.distCosineLaw = function(lat1, lon1, lat2, lon2) {
\r
356 var R = 6371; // earth's mean radius in km
\r
357 var d = Math.acos(Math.sin(toRad(lat1))*Math.sin(toRad(lat2)) +
\r
358 Math.cos(toRad(lat1))*Math.cos(toRad(lat2))*Math.cos(toRad(lon2-lon1))) * R;
\r
364 * calculate (initial) bearing between two points
\r
365 * see http://williams.best.vwh.net/avform.htm#Crs
\r
367 LatLon.bearing = function(lat1, lon1, lat2, lon2) {
\r
368 lat1 = toRad(lat1); lat2 = toRad(lat2);
\r
369 var dLon = toRad(lon2-lon1);
\r
371 var y = Math.sin(dLon) * Math.cos(lat2);
\r
372 var x = Math.cos(lat1)*Math.sin(lat2) -
\r
373 Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
\r
374 return toBrng(Math.atan2(y, x));
\r
379 * calculate destination point given start point, initial bearing (deg) and distance (km)
\r
380 * see http://williams.best.vwh.net/avform.htm#LL
\r
382 LatLon.prototype.destPoint = function(brng, d) {
\r
383 var R = 6371; // earth's mean radius in km
\r
384 var lat1 = toRad(this.lat), lon1 = toRad(this.lon);
\r
385 brng = toRad(brng);
\r
387 var lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) +
\r
388 Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng) );
\r
389 var lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1),
\r
390 Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
\r
391 lon2 = (lon2+Math.PI)%(2*Math.PI) - Math.PI; // normalise to -180...+180
\r
393 if (isNaN(lat2) || isNaN(lon2)) return null;
\r
394 return new LatLon(toDeg(lat2), toDeg(lon2));
\r
399 * construct a LatLon object: arguments in numeric degrees
\r
401 * note all LatLong methods expect & return numeric degrees (for lat/long & for bearings)
\r
403 function LatLon(lat, lon) {
\r
410 * represent point {lat, lon} in standard representation
\r
412 LatLon.prototype.toString = function() {
\r
413 return this.lat.toLat() + ', ' + this.lon.toLon();
\r
416 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
\r
418 // extend String object with method for parsing degrees or lat/long values to numeric degrees
\r
420 // this is very flexible on formats, allowing signed decimal degrees, or deg-min-sec suffixed by
\r
421 // compass direction (NSEW). A variety of separators are accepted (eg 3º 37' 09"W) or fixed-width
\r
422 // format without separators (eg 0033709W). Seconds and minutes may be omitted. (Minimal validation
\r
425 function parseDeg (str) {
\r
426 if (!isNaN(str)) return Number(str); // signed decimal degrees without NSEW
\r
428 var degLL = str.replace(/^-/,'').replace(/[NSEW]/i,''); // strip off any sign or compass dir'n
\r
429 var dms = degLL.split(/[^0-9.]+/); // split out separate d/m/s
\r
430 for (var i in dms) if (dms[i]=='') dms.splice(i,1); // remove empty elements (see note below)
\r
431 switch (dms.length) { // convert to decimal degrees...
\r
432 case 3: // interpret 3-part result as d/m/s
\r
433 var deg = dms[0]/1 + dms[1]/60 + dms[2]/3600; break;
\r
434 case 2: // interpret 2-part result as d/m
\r
435 var deg = dms[0]/1 + dms[1]/60; break;
\r
436 case 1: // decimal or non-separated dddmmss
\r
437 if (/[NS]/i.test(str)) degLL = '0' + degLL; // - normalise N/S to 3-digit degrees
\r
438 var deg = dms[0].slice(0,3)/1 + dms[0].slice(3,5)/60 + dms[0].slice(5)/3600; break;
\r
439 default: return NaN;
\r
441 if (/^-/.test(str) || /[WS]/i.test(str)) deg = -deg; // take '-', west and south as -ve
\r
444 // note: whitespace at start/end will split() into empty elements (except in IE)
\r
447 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
\r
449 // extend Number object with methods for converting degrees/radians
\r
451 function toRad (deg) { // convert degrees to radians
\r
452 return deg * Math.PI / 180;
\r
455 function toDeg (rad) { // convert radians to degrees (signed)
\r
456 return rad * 180 / Math.PI;
\r
459 function toBrng (rad) { // convert radians to degrees (as bearing: 0...360)
\r
460 return (toDeg(rad)+360) % 360;
\r
464 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
\r
466 // extend Number object with methods for presenting bearings & lat/longs
\r
468 function toDMS (num) { // convert numeric degrees to deg/min/sec
\r
469 var d = Math.abs(num); // (unsigned result ready for appending compass dir'n)
\r
470 d += 1/7200; // add ½ second for rounding
\r
471 var deg = Math.floor(d);
\r
472 var min = Math.floor((d-deg)*60);
\r
473 var sec = Math.floor((d-deg-min/60)*3600);
\r
474 // add leading zeros if required
\r
475 if (deg<100) deg = '0' + deg; if (deg<10) deg = '0' + deg;
\r
476 if (min<10) min = '0' + min;
\r
477 if (sec<10) sec = '0' + sec;
\r
478 return deg + '\u00B0' + min + '\u2032' + sec + '\u2033';
\r
481 function toLat (deg) { // convert numeric degrees to deg/min/sec latitude
\r
482 return toDMS(deg).slice(1) + (deg<0 ? 'S' : 'N'); // knock off initial '0' for lat!
\r
485 function toLon (deg) { // convert numeric degrees to deg/min/sec longitude
\r
486 return toDMS(deg) + (deg>0 ? 'E' : 'W');
\r
489 function toPrecision (num,fig) { // override toPrecision method with one which displays
\r
490 if (num == 0) return 0; // trailing zeros in place of exponential notation
\r
491 var scale = Math.ceil(Math.log(num)*Math.LOG10E);
\r
492 var mult = Math.pow(10, fig-scale);
\r
493 return Math.round(num*mult)/mult;
\r
499 * order of %s args: Service name, method name, parameter name
\r
502 msgCommandNotFound : '%s : Command Not found',
\r
503 msgGetLocationWrongCat : '%s : %s : wrong category info should be BasicLocationInformation/GenericLocationInfo ',
\r
504 msgGetLocationBadArg : '%s : %s : BadArgument - Updateoptions',
\r
505 msgGetLocationNegInt : '%s : %s : Negative Time Interval',
\r
506 msgGetLocationWrongType : '%s : %s : UpdateOptions Type mismatch',
\r
507 msgTraceWrongCat : '%s : %s : Invalid LocationInformationClass',
\r
508 msgCalcMissingMathReq : '%s : %s : Missing argument - MathRequest',
\r
509 msgCalcWrongTypeMathReq : '%s : %s : Wrong argument - MathRequest',
\r
510 msgCalcMissingArgLocCord : '%s : %s : Missing argument - locationcoordinate',
\r
511 msgCalcMissingArgMoveDist : '%s : %s : Missing argument - MoveByThisDistance',
\r
512 msgCalcMissingArgMoveBear : '%s : %s : Missing argument - MoveByThisBearing',
\r
513 msgCalcWrongTypeMoveDist : '%s : %s : TypeMismatch - MoveByThisDistance',
\r
514 msgCalcWrongTypeMoveBear : '%s : %s : TypeMismatch - MoveByThisBearing',
\r
515 msgCancelBadArg : '%s : %s : BadArgument – cancel type',
\r
516 msgCancelMissingType : '%s : %s : Missing cancel type',
\r
517 msgCancelWrongType : '%s : %s : Wrong cancel type' ,
\r