Shai
Shai

Reputation: 25595

Retrieving last known geolocation - Phonegap

The following code

navigator.geolocation.getCurrentPosition(getGeo_Success, getGeo_Fail, {
    enableHighAccuracy : true,
    maximumAge : Infinity,
    timeout : 15000
});

Retrieves the current GPS position - BUT - there must be a valid GPS signal (and ofcourse, the device's GPS feature should be turned on).

If we look at other applications (say, Maps on Android devices) - it knows how to retrieve the last known position - even if I didn't use an application that updates the geolocation before opening Maps - it shows my position on the map, even if I'm inside a building with no GPS signal at all.

Just to clarify: I'm not interested in the last geolocation my application retrieved, as on the next time I`ll start it, that geolocation will probably be irrelevant.

Question is: How can we achieve this with HTML5/Phonegap? seems like navigator.geolocation only knows to retrieve the current position, even though, maximumAge is set to Infinity (which means, the age of the last cached position is irrelevant, so any hit is okay (or, should be!))

Upvotes: 2

Views: 3711

Answers (3)

Shai
Shai

Reputation: 25595

ANDROID SOLUTION (iPhone solution follows):

This is neat:

I've made use of Android's native LocationManager, which provides a getLastKnownLocation function - the name says it all

Here's the relevant code

1) Add the following java class to your application

package your.package.app.app;

import org.apache.cordova.DroidGap;

import android.content.Context;
import android.location.*;
import android.os.Bundle;
import android.webkit.WebView;

public class GetNativeLocation implements LocationListener {
    private WebView mAppView;
    private DroidGap mGap;
    private Location mostRecentLocation;

    public GetNativeLocation(DroidGap gap, WebView view) {
        mAppView = view;
        mGap = gap;
    }

    public void onLocationChanged(Location location) {
        // TODO Auto-generated method stub
        getLocation();
    }

    public void getLocation() {
        LocationManager lm = 
                        (LocationManager)mGap.
                                        getSystemService(Context.LOCATION_SERVICE);
        Criteria criteria = new Criteria();
        criteria.setAccuracy(Criteria.ACCURACY_FINE);
        String provider = lm.getBestProvider(criteria, true);

        lm.requestLocationUpdates(provider, 1000, 500, this);
        mostRecentLocation = lm
                .getLastKnownLocation(LocationManager.GPS_PROVIDER);
    }

    public void doInit(){
        getLocation();
    }

    public double getLat(){ return mostRecentLocation.getLatitude();}
    public double getLong() { return mostRecentLocation.getLongitude(); }
    public void onProviderDisabled(String arg0) {
        // TODO Auto-generated method stub  
    }

    public void onProviderEnabled(String provider) {
        // TODO Auto-generated method stub  
    }

    public void onStatusChanged(String provider, int status, Bundle extras) {
        // TODO Auto-generated method stub
    }
}

2) Make sure your main class looks as follows:

public class App extends DroidGap {

    // Hold a private member of the class that calls LocationManager
    private GetNativeLocation gLocation;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // This line is important, as System Services are not available
        // prior to initialization
        super.init();

        gLocation = new GetNativeLocation(this, appView);

        // Add the interface so we can invoke the java functions from our .js 
        appView.addJavascriptInterface(gLocation, "NativeLocation");

        try {
            super.loadUrl("file:///android_asset/www/index.html");
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }

    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
    }

    @Override
    protected void onStop() {
        // TODO Auto-generated method stub
        super.onStop();
    }
}

3) on your JS code, just call the native java using:

window.NativeLocation.doInit();
alert(window.NativeLocation.getLat());
alert(window.NativeLocation.getLong());

Thats all folks! :-)

EDIT: iPhone solution:

I wrote a tiny Phonegap plugin that creates an interface to a custom class which is making use of iOS's native CLLocationManager

1) Phonegap plugin (JS)

var NativeLocation = {
    doInit: function(types, success, fail) {
        return Cordova.exec(success, fail, "NativeLocation", "doInit", types);
    },

    getLongitude: function(types, success, fail){
        return Cordova.exec(success, fail, "NativeLocation", "getLongitude", types);
    },

    getLatitude: function(types, success, fail){
        return Cordova.exec(success, fail, "NativeLocation", "getLatitude", types);
    }
}

2) The objective-c class that enables us to invoke 'CCLocationManager's functions *NativeLocation.h*

#import <Foundation/Foundation.h>
#import <Cordova/CDVPlugin.h>
#import <CoreLocation/CoreLocation.h>

@protocol NativeLocationDelegate
@required
- (void)locationUpdate:(CLLocation *)location;
- (void)locationError:(NSError *)error;

@end

@interface NativeLocation : CDVPlugin <CLLocationManagerDelegate> {
    id delegate;
    NSString* callbackID;
    CLLocationManager *lm;
    Boolean bEnabled;
    double nLat;
    double nLon;
}

@property (nonatomic, copy) NSString* callbackID;
@property (nonatomic, retain) CLLocationManager *lm;
@property (nonatomic, readonly) Boolean bEnabled;
@property (nonatomic, assign) id delegate;

- (void) doInit:(NSMutableArray*)arguments
                                 withDict:(NSMutableDictionary*)options;
- (void) getLatitude:(NSMutableArray*)arguments 
                                 withDict:(NSMutableDictionary *)options;
- (void) getLongitude:(NSMutableArray*)arguments 
                                 withDict:(NSMutableDictionary *)options;

@end

NativeLocation.m

#import "NativeLocation.h"

@implementation NativeLocation

@synthesize callbackID;
@synthesize lm;
@synthesize bEnabled;
@synthesize delegate;

- (void)doInit:(NSMutableArray *)arguments 
                                 withDict:(NSMutableDictionary *)options{
    if (self != nil){
        self.lm = [[[CLLocationManager alloc] init] autorelease];
        self.lm.delegate = self;
        if (self.lm.locationServicesEnabled == NO)
            bEnabled = FALSE;
        else bEnabled = TRUE;
    }

    nLat = 0.0;
    nLon = 0.0;

    if (bEnabled == TRUE)
        [self.lm startUpdatingLocation];

    CDVPluginResult* pluginResult = [CDVPluginResult 
                                    resultWithStatus:CDVCommandStatus_OK 
                                    messageAsString[@"OK"
                                    stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];

    if (bEnabled == TRUE){
        [self writeJavascript: [pluginResult 
                               toSuccessCallbackString:self.callbackID]];
    } else {
        [self writeJavascript: [pluginResult 
                               toErrorCallbackString:self.callbackID]];
    }
}

- (void)locationManager:(CLLocationManager *)manager 
                        didUpdateToLocation:(CLLocation *)newLocation 
                        fromLocation:(CLLocation *)oldLocation {
    if ([self.delegate conformsToProtocol:@protocol(NativeLocationDelegate)])
        [self.delegate locationUpdate:newLocation ];
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
    if ([self.delegate conformsToProtocol:@protocol(NativeLocationDelegate)])
        [self.delegate locationError:error];
}

- (void)dealloc {
    [self.lm release];
    [super dealloc];
}

- (void)locationUpdate:(CLLocation *)location {
    CLLocationCoordinate2D cCoord = [location coordinate];
    nLat = cCoord.latitude;
    nLon = cCoord.longitude;

}

- (void)getLatitude:(NSMutableArray *)arguments 
                                      withDict:(NSMutableDictionary *)options{

    self.callbackID = [arguments pop];

    nLat = lm.location.coordinate.latitude;
    nLon = lm.location.coordinate.longitude;

    CDVPluginResult* pluginResult = [CDVPluginResult 
                                    resultWithStatus:CDVCommandStatus_OK 
                                    messageAsDouble:nLat];

    [self writeJavascript: [pluginResult toSuccessCallbackString:self.callbackID]];

}
- (void)getLongitude:(NSMutableArray *)arguments 
                                       withDict:(NSMutableDictionary *)options{

    self.callbackID = [arguments pop];

    nLat = lm.location.coordinate.latitude;
    nLon = lm.location.coordinate.longitude;

    CDVPluginResult* pluginResult = [CDVPluginResult 
                     resultWithStatus:CDVCommandStatus_OK messageAsDouble:nLon];

    [self writeJavascript: [pluginResult toSuccessCallbackString:self.callbackID]];

}

@end

3) And finally, invoking everything from the main .js

function getLongitudeSuccess(result){
    gLongitude = result;
}

function getLatitudeSuccess(result){
    gLatitude = result;
}

function runGPSTimer(){
    var sTmp = "gps";

    theTime = setTimeout('runGPSTimer()', 1000);

    NativeLocation.getLongitude(
                                ["getLongitude"],
                                getLongitudeSuccess,
                                function(error){ alert("error: " + error); }
                                );

    NativeLocation.getLatitude(
                               ["getLatitude"],
                               getLatitudeSuccess,
                               function(error){ alert("error: " + error); }
                               );

Upvotes: 2

phwa4563
phwa4563

Reputation: 456

In my PhoneGap/Sencha Touch 2 Application i created the function

function getLastKnownLocation(){
    if(typeof localStorage.lastKnownPosition == "undefined"){
        localStorage.lastKnownPosition = JSON.stringify(null);
    }

    navigator.geolocation.getCurrentPosition(
        function(position){
            localStorage.lastKnownPosition = JSON.stringify(position);
        }
    );

    return JSON.parse(localStorage.lastKnownPosition);
}

So every time i call the getLastKnownLocation() i get a result immediately. It's a good workaround for my purposes.

Upvotes: 1

Sagiv Ofek
Sagiv Ofek

Reputation: 25270

i suggest in your getGeo_Success function call setLocation([the_location]) to store it on localStorage.
you can assume that each device that uses phoneGap already implements localStorage.
this way, you always have valid location there. and then just pop it whenever you need.

function getLocation() {
  return JSON.parse(localStorage.getItem('location'));
}
function setLocation(location) {
  localStorage.setItem('vibesList', JSON.stringify(vibes));
}

edit: if you want to retrive the latest place the device knows (not the app), you have to do it via background proccess that pulling the current data from the device. this is problematic approach because:

  1. not all the devices allow it.
  2. you will have to write your own native custom plugin to connect with phonegap for that.

Upvotes: 0

Related Questions