Kevin Ferrell

How to Implement a High Performance Location Finder Interface Using CoreLocation & SQLite

A friend of mine, Molly Moran, is in the process of building a new iPhone app to track hunt clubs (as in fox hunting!). One of the features that she wants to implement is a UITableView that lists nearby clubs based on the user’s GPS location. She has a database of all the clubs in the U.S. along with their approximate latitude/longitude coordinates so its just a matter of using CoreLocation to find the user’s location and then finding all the clubs that are within a given range (20 miles, 50 miles, etc.).

Initially, she had started to implement the view by looping through the list of clubs and then comparing each club’s location against the current GPS location using CoreLocation:


[CLLocation distanceFromLocation:CLLocation]

The problem with this approach is that each club must be compared against the user’s location one at a time. There are several hundred clubs in the list already and it could grow as additional countries are added to the app. The app’s performance could become unacceptable over time using this approach so I wanted to see if I could find a better solution for finding nearby clubs.

I came up with an idea of using a SQLite database to store the club location data so that it could be queried using standard SQL. If we could find the range of acceptable lat/long coordinates, the SQLite engine would be able to filter nearby clubs much more quickly than a linear list scan.  To find the range of acceptable lat/long coordinate, I calculate a point 50 miles (or whatever range the use entered) north, east, south, and west of the user’s current location. I then use these points to effectively create a bounding box of lat/long coordinates within a 50-mile range of the user. The figure below depicts the bounding box in red:

CoreLocation Queries

CoreLocation Queries

In order to calculate the maximum lat/log coordinates for the bounding box, you must first convert miles into meters and then calculate the bearing for points north, east, south, and west. The bearing must be expressed in radians to calculate lat/long coordinates:


double searchRange = self.miles * 1609.04;

double northBearingInRadians = 0;

double eastBearingInRadians = 90 * M_PI / 180;

double southBearingInRadians = 180 * M_PI / 180;

double westBearingInRadians = 270 * M_PI / 180;

CLLocation *northlocation = [self.currentLocation newLocationAtDistance:searchRange alongBearingInRadians:northBearingInRadians];

NSLog(@"North Location: %f,%f",northlocation.coordinate.latitude, northlocation.coordinate.longitude);

self.maxLat = northlocation.coordinate.latitude;

CLLocation *eastlocation = [self.currentLocation newLocationAtDistance:searchRange alongBearingInRadians:eastBearingInRadians];

NSLog(@"East Location: %f,%f",eastlocation.coordinate.latitude, eastlocation.coordinate.longitude);

self.maxLong = eastlocation.coordinate.longitude;

CLLocation *southlocation = [self.currentLocation newLocationAtDistance:searchRange alongBearingInRadians:southBearingInRadians];

NSLog(@"South Location: %f,%f",southlocation.coordinate.latitude, southlocation.coordinate.longitude);

self.minLat = southlocation.coordinate.latitude;

CLLocation *westlocation = [self.currentLocation newLocationAtDistance:searchRange alongBearingInRadians:westBearingInRadians];

NSLog(@"West Location: %f,%f",westlocation.coordinate.latitude, westlocation.coordinate.longitude);

self.minLong = westlocation.coordinate.longitude;

You’ll notice in the queries above that we’re using an extension of the CLLocation class to calculate the new location at a specific distance and bearing for the user’s current location:


- (CLLocation *)newLocationAtDistance:(CLLocationDistance)atDistance alongBearingInRadians:(double)bearingInRadians;

This extension was developed by Dave Addey and can be found on his website. You’re now able to query a database of locations (hunt clubs in this case) to determine if they’re within the bounding range of the query:


FMDatabase *database = [ClubsDatabase getDatabase];

[database open];

NSString *locationQuery = @"SELECT name, city, state, region, country, lat, lon FROM clubs WHERE (lat <= %f AND lat >= %f) AND (lon <= %f AND lon >= %f)";

FMResultSet *results = [database executeQueryWithFormat:locationQuery,self.maxLat, self.minLat, self.maxLong, self.minLong];

You can then loop over the result set and display it within your user interface. This approach is much faster than a linear list scan of the location list since it utilizes the query engine of SQLite to determine whether the lat/long values are within the acceptable range instead of performing a mathematical calculation for each potential location match. Further performance benefits can be gained through database indexes of the SQLite columns that store the lat/long coordinates as well.

There is one issue with the approach described above. The lat/long bounding box query will return locations that are slightly outside of the user-selected range if they occur in the corners of the box. In the figure above, the green circle represents an actual 50 mile radius from the user. The red box depicts the bounding box used by the SQLite query to return a result set. If a location exists in the corners of the box between the green circle and the red border, they will appear in the result set erroneously. This wasn’t an issue for Hunt Clubs app since each club occupies a region and not a specific coordinate. However, if its an issue for your app you can add a second layer of filtering once the SQLite query results are returned and then use the [CLLocation distanceFromLocation:CLLocation] method to ensure the results are within the selected range.

Hopefully other developers will find this method of ranging search useful in their own app!

Discussion Closed

New comments are not allowed on this post.