As part of an iPhone SDK project, I have an sqlite database with a table full of geographic locations, each stored as a latitude and longitude value in degrees. I wanted to be able to perform an SQL SELECT on this table and ORDER BY each row’s distance from an arbitrary point. I’ve achieved this by defining a custom sqlite function. This article contains the code for the function, together with instructions on using it.
Here’s the function, together with a convenience macro to convert from degrees to radians. This function is based on an online distance calculator I found which makes use of the spherical law of cosines.
#define DEG2RAD(degrees) (degrees * 0.01745327) // degrees * pi over 180
static void distanceFunc(sqlite3_context *context, int argc, sqlite3_value **argv)
{
// check that we have four arguments (lat1, lon1, lat2, lon2)
assert(argc == 4);
// check that all four arguments are non-null
if (sqlite3_value_type(argv[0]) == SQLITE_NULL || sqlite3_value_type(argv[1]) == SQLITE_NULL || sqlite3_value_type(argv[2]) == SQLITE_NULL || sqlite3_value_type(argv[3]) == SQLITE_NULL) {
sqlite3_result_null(context);
return;
}
// get the four argument values
double lat1 = sqlite3_value_double(argv[0]);
double lon1 = sqlite3_value_double(argv[1]);
double lat2 = sqlite3_value_double(argv[2]);
double lon2 = sqlite3_value_double(argv[3]);
// convert lat1 and lat2 into radians now, to avoid doing it twice below
double lat1rad = DEG2RAD(lat1);
double lat2rad = DEG2RAD(lat2);
// apply the spherical law of cosines to our latitudes and longitudes, and set the result appropriately
// 6378.1 is the approximate radius of the earth in kilometres
sqlite3_result_double(context, acos(sin(lat1rad) * sin(lat2rad) + cos(lat1rad) * cos(lat2rad) * cos(DEG2RAD(lon2) - DEG2RAD(lon1))) * 6378.1);
}
This defines an SQL function distance(Latitude1, Longitude1, Latitude2, Longitude2), which returns the distance (in kilometres) between two points.
To use this function, add the code above to your Xcode project, and then add this line immediately after you call sqlite3_open:
sqlite3_create_function(sqliteDatabasePtr, "distance", 4, SQLITE_UTF8, NULL, &distanceFunc, NULL, NULL);
…where sqliteDatabasePtr is the database pointer returned by your call to sqlite3_open.
Assuming you have a table called Locations, with columns called Latitude and Longitude (both of type double) containing values in degrees, you can then use this function in your SQL like this:
SELECT * FROM Locations ORDER BY distance(Latitude, Longitude, 51.503357, -0.1199)
This example orders the locations in your database based on how far away they are from the London Eye, which is at 51.503357, -0.1199.
Thanks for the code! This helped out a lot! Works like a charm!
Thanks heaps! Does exactly as advertised
All I needed, thanks a lot.
Thx for you’re code, it works well !
I have a question, I use you’re query with two parameters so it’s like that :
“SELECT * FROM spot_en ORDER BY distance(SPOT_LAT, SPOT_LON, ?, ?) LIMIT 0,20″
but when I use the sqlite3_bind_double(selectstmt, 1, lat);
it returns a 21 error, that it says that I misuse the library, do you know if it come from you’re function or it’s a mistake of my code.
Thanks
I am getting an error on distanceFunc. Do I need to declare it?
Dave, I am having a little difficulty deploying your code. I am getting a few errors I can not figure out. I am trying to use it in the iPhone SDK.
Hi Tommy,
Can you post the errors you are seeing? And which version of the SDK are you using?
Dave.
I’m having trouble integrating this code into my project. I am new to Objective-C and C but I assume this is C code. What are the details to properly integrate it? I put the function code into a .c file and also tried creating a header file. (Is a header necessary? How exactly should it look?) I #import the header file in the .m file I will be using it and follow your sqlite3_create_function instructions.
I’ve reduced it to one warning and one error.
The warning “‘distanceFunc’ used but never defined” is in my header file which looks like this…
#import
static void distanceFunc(sqlite3_context *context, int argc, sqlite3_value **argv);
The error is simply “gcc-4.0 failed with exit code 1″
What is my lack of C skills blinding me to!?
Dave,
I finally figured out my issue/
Tommy
Dave, I do have one issue. I got the code to work, but I want to substitute a variable where you are plugging the coordinates for the London Eye in your select statement. My select statement looks like this:
[NSString stringWithFormat:@"select pk, station, dial, city, state from radiostations ORDER BY distance(Latitude, Longitude, %@, %@)",presentLat, presentLng];
Tommy
Does this syntax look correct?
Very cool! Was pretty bummed when i realized sqlite didn’t include math functions, you saved me TONS of time
I was having the same problem with declarations. To use the code you must declare it in your header file…
static void distanceFunc(sqlite3_context *context, int argc, sqlite3_value **argv);
… just an awesome tool ! couldn’t dream better !
Thanks! saved me a tonne of time
This is f’n awesome! THANKS! I had something that seemed like overkill. THis works better. Place a limit on it to return a max number, and I’m good to go.
All I needed, thanks a lot.
That’s awesome, I’m wondering if you have any solution for the problem described here (http://stackoverflow.com/questions/2099090/trouble-with-sqlite-sql-query).
Thanks
Hi Alix,
I’ve posted a comment / suggestion on stackoverflow – hope it’s useful
Dave.
Thank you for an excellent post
Thanks for posting this it really helped me out.
Is there any way to adapt this to javascript? I’m using phonegap to create an application, currently I have an sqlite database full of lats and lons and I want to be able to pull all of the rows from the db that are within say a 25mi radius (optionally converting to km). i have found no documentation on how to create a custom function for sqlite in javascript.
just to pitch in.. if you are using fmdb…
sqlite3_create_function([db sqliteHandle], “distance”, 4, SQLITE_UTF8, NULL, &distanceFunc, NULL, NULL);
see FMDatabase.m
regards,
rupert
Pingback: Calculate POI distance from current location in wp7 « Bazinga!
You’re my new best friend. Thanks, it works just as I needed!
Dave are you able to help me with my stackoverflow question as well?
http://stackoverflow.com/questions/4231177/need-help-with-a-sqlite-statement-returning-distance-using-a-distancefunc-in-obje
Thanks!
Thanks!! I was really looking for this!
Thanks for this post…….
You are my hero.
Whoah! This snippet of code is hell of a masterpiece. Saved me a lot of work. Thank You! Works like a charm.
Unfortunately, no matter where I put this function into my viewController.m file, I get this error: “expected ‘)’ before ‘*’ token”. This is even after putting it above the @ implementation line.
Can anyone provide a few details on how to actually include this function into your Objective-C viewController.m file?
I’d be interested to know about performance with this. How many seconds per 1,000 location records does it take for an iPhone 3, 3GS and 4 to return results?
We have a database of 1,400 locations taking 4 seconds on iPhone 3GS and 9 seconds on iPhone 4. We’re looking to increase the data set to 14,000 locations, but that’ll take an unacceptible 90 seconds on 3GS.
Also, would love to see a JavaScript version of the custom SQL function for those developing HTML5 web apps with SQLite storage and PhoneGap apps.
Hi Dave,
I’m a total newbie to the iphone SDK. I have some knowledge of php/mysql, and javascript/html, that’s about it. Do you mind elaborating on where to put the code you offered in xcode. Can it literally go anywhere? what kind of file type? Do you have a source code file I could use?
If not, no worries, it was worth a shot.
Pingback: Usando Funções Matemáticas no Sqlite3 « WORKAROUNDS
I could hug you. perfect
genius!
Excellent post, it work for me. Thanks!
Thanks for the code-snippet, awesome
though since Apple encourages using the core-data rather than direct database access, I wonder if you can do the same with core-data interface.
You save a lot of time of my work! Thanks a lot!
Thanks a lot!! You saved me lots of time! Great post!!
I have successfully got this to sort my results… now I am trying to also display the kilometre distance in my uitableview.
But I cannot seem to work out how to extract that form the function.
thanks for the great code snippet
The function returns the distance, so you can make it field in your SELECT clause. For example:
SELECT *, distance(Latitude, Longitude, lat, long) AS Distance FROM table ORDER BY Distance
I have done this with the app I am currently working on. Hope this helps.
Holy moly. Amazing code. Genius.
Deliver a truckload of cookies to this man post haste!!
Hello
Nice solution for iphone.
Now i want to same implemetation in Android
Pls help me out
Thanks
Parag
This code is working great for the iPhone with the SDK implemented through Titanium! THANK YOU!!!
I have NOT been able to get it into SQLite for Android. Any clues on how to do the same distance calculation for Android?
Thank you so much!
I started to think that searching based on distance was nog possible with SQLite, but you definitely proved me wrong…
please help, i am facing an issue..
i added the code to my sqlite plugin .m file in xcode and added the sqlite.createfunc statement just after the sqlite open statement in the same .m file.
now, when i query using order by distance…., error is coming, if i remove this order by clause, it give result.
please help
This is really great. It works perfectly in my app. Thanks for the post!