Core Location is a framework
in the iOS SDK that provides the location of the device. Depending on
the iPhone and its current state (within cell service, inside a
building, and so forth), any of three technologies can be used: GPS,
cellular, or WiFi. GPS is the most accurate of these technologies and
will be used first by Core Location if GPS hardware is present. If the
device does not have GPS hardware, or if obtaining the current location
with GPS fails, Core Location falls back to cellular and then to WiFi.
Location Manager
Core
Location is simple to understand and to use despite the powerful array
of technologies behind it. (Some of it had to be launched into space on
rockets!) Most of the functionality of Core Location is available from
the Location Manager, which is an instance of the CLLocationManager
class. You use the Location Manager to specify the frequency and
accuracy of the location updates you are looking for, and to turn on and
off receiving those updates.
To use a location manager, you create an instance of
the manager, specify a location manager delegate that will receive
location updates, and start the updating, like this:
CLLocationManager *locManager = [[CLLocationManager alloc] init];
locManager.delegate = self;
[locManager startUpdatingLocation];
When the application is done receiving updates (a
single update is often sufficient), stop location updates with location
manager’s stopUpdatingLocation method.
Location Manager Delegate
The location manager delegate protocol defines the
methods for receiving location updates. There are two methods in the
delegate relating to location: locationManager:didUpdateToLocation:fromLocation and locationManager:didFailWithError.
The locationManager:didUpdateToLocation:fromLocation method’s arguments are the location manager object instance and two CLLocation objects, one for the new location, and one for the previous location. The CLLocation instances provide a coordinate property that is a structure containing longitude and latitude expressed in CLLocationDegrees. CLLocationDegrees is just an alias for a floating-point number of type double.
We’ve already mentioned that different approaches to
geolocating have different inherit accuracies and that each approach may
be more or less accurate depending on the number of points (satellites,
cell towers, WiFi hotspots) it has available to use in its
calculations. CLLocation passes this confidence measure along in the horizontalAccuracy property.
The location’s accuracy is provided as a circle, and
the true location could lie anywhere within that circle. The circle is
defined by the coordinate property as the center of the circle, and the horizontalAccuracy property as the radius of the circle in meters. The larger the horizontalAccuracy property, the larger the circle defined by it will be, so the less confidence there is in the accuracy of the location. If the horizontalAccuracy property is negative, it is an indication that the coordinate is completely invalid and should be ignored.
In addition to longitude and latitude, each CLLocation provides altitude above or below sea level in meters. The altitude property is a CLLocationDistance, which is also just an alias for a floating-point number of type double.
A positive number is an altitude above sea level, and a negative number
is below sea level. There is another confidence factor, this one called
verticalAccuracy, that indicates how accurate the altitude is. A positive verticalAccuracy indicates that the altitude could be off, plus or minus, by that many meters. A negative verticalAccuracy means the altitude is invalid.
An implementation of the location manager delegate’s locationManager:didUpdateToLocation:fromLocation method that logs the longitude, latitude, and altitude is shown in Listing 1.
Listing 1.
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
NSString *coordinateDesc = @"Not Available";
NSString *altitudeDesc = @"Not Available";
if (newLocation.horizontalAccuracy >= 0) {
coordinateDesc = [NSString stringWithFormat:@"%f, %f +/- %f meters",
newLocation.coordinate.latitude,
newLocation.coordinate.longitude,
newLocation.horizontalAccuracy];
}
if (newLocation.verticalAccuracy >= 0) {
altitudeDesc = [NSString stringWithFormat:@"%f +/- %f meters",
newLocation.altitude, newLocation.verticalAccuracy];
}
NSLog(@"Latitude/Longitude: %@ Altitude: %@", coordinateDesc,
altitudeDesc);
}
|
The resulting log output looks like this:
Latitude/Longitude: 35.904392, -79.055735 +/- 76.356886 meters Altitude: 28.000000 +/- 113.175757 meters
Watch Out!
CLLocation also provides a property speed,
which is based on comparing the current location with the prior
location and comparing the time and distance variance between them.
Given the rate at which Core Location updates, the speed property is not very accurate unless the rate of travel is fairly constant.
Handling Location Errors
When your application begins tracking the user’s location, a warning will be displayed on the user’s screen, as shown in Figure 1.
If the user chooses to disallow location services,
iOS will not prevent your application from running but will generate
errors from the location manager.
When an error occurs, the location manager delegate’s locationManager:didFailWithError
is called, letting you know the device cannot return location updates. A
distinction is made as to the cause of the failure. If the user denies
permission to the application, the error argument is kCLErrorDenied; if Core Location tries but cannot determine the location, the error is kCLErrorLocationUnknown; and if no source of trying to retrieve the location is available, the error is kCLErrorNetwork.
Usually Core Location will continue to try to determine the location
after an error, but after a user denial, it won’t, and it is good form
to stop the location manager with location manager’s stopUpdatingLocation method and release the instance. An implementation of locationManager:didFailWithError is shown in Listing 2.
Listing 2.
- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error {
if (error.code == kCLErrorLocationUnknown) {
NSLog(@"Currently unable to retrieve location.");
} else if (error.code == kCLErrorNetwork) {
NSLog(@"Network used to retrieve location is unavailable.");
} else if (error.code == kCLErrorDenied) {
NSLog(@"Permission to retrieve location is denied.");
[locMan stopUpdatingLocation];
[locMan release];
locMan = nil;
}
}
|
Watch Out!
It
is important to keep in mind that the location manager delegate will
not immediately receive a location; it usually takes a number of seconds
for the device to pinpoint the location, and the first time it is used
by an application, Core Location first asks the user’s permission. You
should have a design in place for what the application will do while
waiting for an initial location, and what to do if location information
is unavailable because the user didn’t grant permission or the
geolocation process failed. A common strategy that works for many
applications is to fall back to a user-entered ZIP code.
Location Accuracy and Update Filter
It is possible to tailor the accuracy of the location
to the needs of the application. An application that needs only the
user’s country, for example, does not need 10-meter accuracy from Core
Location and will get a much faster answer by asking for a more
approximate location. This is done before you start the location updates
by setting the location manager’s desiredAccuracy property. desiredAccuracy is an enumerated type, CLLocationAccuracy. Five constants are available with varying levels of precision (with current consumer technology, the first two are the same): kCLLocationAccuracyBest, kCLLocationAccuracyNearestTenMeters, kCLLocationNearestHundredMeters, kCLLocationKilometer, kCLLocationAccuracyThreeKilometers.
After updates on a location manager are started,
updates continue to come into the location manager delegate until they
are stopped. You cannot control the frequency of these updates directly,
but you can control it indirectly with location manager’s distanceFilter property. The distanceFilter
property is set before starting updates and specifies the distance in
meters the device must travel (horizontally, not vertically) before
another update is sent to the delegate.
For example, starting the location manager with
settings suitable for following a walker’s progress on a long hike might
look like this:
CLLocationManager *locManager = [[CLLocationManager alloc] init];
locManager.delegate = self;
locManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
locManager.distanceFilter = 200;
[locManager startUpdatingLocation];
Watch Out!
Each of the three methods of locating the device
(GPS, cellular, and WiFi) can put a serious drain on the device’s
battery. The more accurate an application asks the device to be in
determining location, and the shorter the distance filter, the more
battery the application will use. Be aware of the device’s battery life
and only request location updates as accurately and as frequently as the
application needs them. Stop location manager updates whenever possible
to preserve the battery life of the device.
By the Way
The iPhone Simulator can provide just one location update: Apple HQ in Cupertino, California.