Saturday, 3 October 2015

Reliable way to get current location using ngCordova

Usual approach

As you can see from ngCordova documentation found here, the use of getCurrentPosition is most likely the easier way of getting current location. It looks like if the enableHighAccuracy to true could increase the possibity of getting a reliable current location.

I was working on my first IONIC app where i wanted to get the current location during the absence of internet.I started testing by trying to get the current location while roaming around and once i had a WIFI connection i was trying to verify captured location.

Results were not consistence and have even tried changing argument values but none gave consistence results. When searching out a but it appears like the results are coming out of a cached GPS values. Below is an approach where by repeatedly using the watchPosition gave better results.

Using watchPosition instead of getCurrentPosition

As there is an accuracy returned from the GeoLocation API the plan is to keep retrying till the desired level of accuracy is obtained. This retry time and interval would need to be configuration so that that i can be controlled a bit.

I have written a factory service for now it has only getCurrentPosition. We can expand it later.

Step 1
Delegate the call to a private method which will attempt to get a reliable location and as its asynchronous, we need to return a promise back so that the calling code can monitor that. 

Code
   return {  
     getCurrentLocation : function (options) {  
       var deferred = $q.defer();  
       callStart = new Date().getTime() / 1000;  
       _getLocationWrapper({callStart:callStart,  
                  deferred:deferred,  
                  othOptions:options,  
                  attempt:1});  
       return deferred.promise;  
     }  
   };  


The "options" that needs to be used for watchPosition are taken as input but are also passed over with "othOptions". Other parameters used are self contained within the service method.

Step 2
A wrapper method is created for handling first invocation of the service method

Code
   function _getLocationWrapper(options) {  
      var locCB=function(){return _getLocation(options);};  
      if ( options.attempt == 1 ) {  
        locCB();  
      } else {  
       setTimeout(locCB, options.othOptions.gps.interval*1000);  
      }  
   }  


Step 3
Calls the $cordovaGeolocation.watchPosition and in the callback of that 

When error, return back the error too.

When success,  

  • Check for the desired accuracy and if achieved then return back same or callback the wrapper again.
  • If timeout is reached but the desired accuracy is not obtained then feedback that as error.

In all the processing feedback to client on the progress/retries using deferred's "notify"


Code
   function _getLocation(options){  
     var callStart = options.callStart;  
     var deferred  = options.deferred;  
     var attempt  = options.attempt;  
     var othOptions = options.othOptions;  
     deferred.notify({attempt: attempt, message:'Searching attempt '+attempt, lastAccuracy : options.lastAccuracy});  
     var getLocOptions = {  
       enableHighAccuracy: othOptions.gps.enableHighAccuracy,  
       timeout: othOptions.gps.timeout * 100,  
       maximumAge: 0  
     };  
     var locWatch = $cordovaGeolocation.watchPosition(getLocOptions);  
     locWatch.then(  
       null,  
       function(err) {  
         locWatch.clearWatch();  
         deferred.reject({err:err});  
       },  
       function(position) {  
         var callEnd = new Date().getTime() / 1000;  
         locWatch.clearWatch();  
         if ( position.coords.accuracy && position.coords.accuracy <= othOptions.gps.accuracy ) {  
           // This is good accuracy then accept it  
           deferred.resolve({status:0, position:position});  
         } else if ( (callEnd - callStart) < othOptions.gps.timeout ) {  
           // Keep trying till the configured wait time. If exceeds then return back.  
           options.attempt++;  
           options.lastAccuracy = Math.round(position.coords.accuracy * 100) / 100;  
           options.minAccuracy = options.minAccuracy || options.lastAccuracy; // Default  
           options.minAccuracy = options.minAccuracy < options.lastAccuracy ? options.lastAccuracy : options.minAccuracy;  
           _getLocationWrapper(options);  
         } else {  
           deferred.reject( {error:{code:-999, message:"Could not get location.<br>Minimum accuracy is "+options.minAccuracy+" mts.<br>Try to check location in open area or try adjusting to acceptable accuracy."}} );  
         }  
       }  
     );  
   }  

Step 4
Finally client invocation 


Code


     var locationService = GSSearchLocationService.getCurrentLocation(  
       {  
         enableHighAccuracy : true,   
         timeout : 120, // In seconds  
         interval : 5,  // Retry interval in seconds  
         accuracy : 15  // Accuracy distance in meters   
       }  
     );  
     locationService.then(  
       function(options) {  
        // put your success callback  
       },  
       function(options) {  
        // put your error callback  
       },  
       function(notificationData) {  
         // Put your retry notification messages  
         // Example showing a Ionic Loading message  
         // $ionicLoading.show({  
         //  template: 'Getting Current Position...'  
         //   + '<br>Attempts : '+notificationData.attempt+' <br>Accurracy(m) : '  
         //   + notificationData.lastAccuracy  
         // });  
       }  
      );  


Download


Full service code can be downloaded from here and kindly modify it accordingly.