teaching machines

CS 491 Lecture 26 – Geocoding and Location-aware Apps

December 10, 2013 by . Filed under cs491 mobile, fall 2013, lectures.

This is the final week of the semester, and you are hastily completing your projects. I’m occupied too, so this last exercise will be terse.

The only feature we haven’t discussed in class is supporting reverse geocoded checkin posts. If someone goes to a business or other physical landmark, they should be able to—with one button push—submit a post that says something like, “I’m at 1600 Pennsylvania Ave.” or “I’m at Subway.”

Below is a quick rundown of how we can solve the two subproblems of this feature: turning a latitude/longitude pair into a readable address and querying the device’s current latitude/longitude.

Exercise

Android

Geocoding

Geocoding on Android can sometimes be done using the Android API:

Geocoder geocoder = new Geocoder(context);
List<Address> addresses = geocoder.getFromLocation(latitude, longitude, maxResultCount);

The Android specification does not require implementers to really support this operation, however. You can check if geocoding is possible with a static method:

if (Geocoder.isPresent()) {
  ...
}

Even if the geocoder is present, there are still some issues where geocoding fails and you see a “Service not Available” exception. You might be better off using Google’s web-based geocoding API.

Geocoding may need the INTERNET permission, but it’s not documented. Execute it outside the UI thread, perhaps in an AsyncTask. Once again, whether we need to do this or not is not documented.

Location Updates

Setting up your device to receive location updates can be done through a system service. As with all system-wide services, you first need to grab a service manager:

LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

Through the service manager, we can request to be notified when the device’s location changes:

locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minMillisBetweenUpdate, minMetersBetweenUpdates, listener);

Mobile devices can get location information from a variety of sources. GPS_PROVIDER means we want to use satellites to identify the device’s location, but GPS fails inside of buildings. NETWORK_PROVIDER lets us use wireless access points and cell towers to identify the device’s location. These sources are less accurate but perhaps more persistent.

Requesting location updates more often than we need to is a good way to drain the device’s battery. Therefore, requestLocationUpdates lets you specify thresholds that minimize how frequently updates happen. Also, for most applications, at the very least you should stop requesting updates in onPause, when the app has lost focus:

locationManager.removeUpdates(listener);

The listener that you register will need to implement the LocationListener interface. If we only care about location changes, we can focus our attention on onLocationChanged:

@Override
public void onLocationChanged(final Location location) {
  // ...
}

iOS

Geocoding

Like Android, iOS provides a geocoding API. To reverse geocode a location to a placename, we can use the CLGeocoder class (where CL is short for Core Location):

CLLocation *location = [[CLLocation alloc] initWithLatitude:44.799 longitude:-91.4993];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
  // ...       
}];

Unlike Android, you do not need to schedule this on its own thread. reverseGeocodeLocation will run asynchronously and then execute the block you pass in on the UI thread.

Location Updates

To get notifications about where the device is located, we ask the CLLocationManager to start tracking location changes. We’ll register our view controller as its delegate. When the location changes, the manager will send to its delegate a locationManager:didUpdateLocations: message.

Before we can use any object starting with CL*, we’ve got to add the Core Location framework to our app. Do so by going to Project Properties / Build Phases / Link Binary with Libraries. Click on + and select CoreLocation.framework. You’ll need to #import the CoreLocation/CoreLocation.h header in your code too, so that the compiler knows of this framework’s types and their legal use.

Now, let’s get a CLLocationManager and register our view controller as its delegate:

@property (strong, nonatomic, readwrite) CLLocationManager *locationManager;

- (void)viewDidLoad {
    self.locationManager = [[CLLocationManager alloc] init];
    [self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
    self.locationManager.delegate = self;
}

Then, we mark our view controller as satisfying the CLLocationManagerDelegate protocol in the header for our view controller:

@interface ViewController : UIViewController <CLLocationManagerDelegate>

At which point, we can implement the required callback method:

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
    CLLocation *location = [locations lastObject];
    // ...
}

Notice that your callback gets locations, plural. The manager may have collected several locations since the last call, and it will send all pending locations to your callback.

To kick of the listening process, we need to engage the locationManager:

[self.locationManager startUpdatingLocation];

I want to get updates only when my app is live, so I put this in viewWillAppear. Likewise, I stopped requesting updates in viewWillDisappear:

[self.locationManager stopUpdatingLocation];

Haiku

Greeks had stars in space
Ha! We just look at our screens
Until the batter—