Do the calculation on the database by switching to a database that supports GeoIP.
Do the calculation on the database: you're using MySQL, so the following stored procedure should help
CREATE FUNCTION distance (latA double, lonA double, latB double, LonB double)
RETURNS double DETERMINISTIC
SET @RlatA = radians(latA);
SET @RlonA = radians(lonA);
SET @RlatB = radians(latB);
SET @RlonB = radians(LonB);
SET @deltaLat = @RlatA - @RlatB;
SET @deltaLon = @RlonA - @RlonB;
SET @d = SIN(@deltaLat/2) * SIN(@deltaLat/2) +
COS(@RlatA) * COS(@RlatB) * SIN(@deltaLon/2)*SIN(@deltaLon/2);
RETURN 2 * ASIN(SQRT(@d)) * 6371.01;
If you have an index on latitude and longitude in your database, you can reduce the number of calculations that need to be calculated by working out an initial bounding box in PHP ($minLat, $maxLat, $minLong and $maxLong), and limiting the rows to a subset of your entries based on that (WHERE latitude BETWEEN $minLat AND $maxLat AND longitude BETWEEN $minLong AND $maxLong). Then MySQL only needs to execute the distance calculation for that subset of rows.
FURTHER EDIT (as an explanation for the previous edit)
If you're simply using the SQL statement provided by Jonathon (or a stored procedure to calculate the distance) then SQL still has to look through every record in your database, and to calculate the distance for every record in your database before it can decide whether to return that row or discard it.
Because the calculation is relatively slow to execute, it would be better if you could reduce the set of rows that need to be calculated, eliminating rows that will clearly fall outside of the required distance, so that we're only executing the expensive calculation for a smaller number of rows.
If you consider that what you're doing is basically drawing a circle on a map, centred on your initial point, and with a radius of distance; then the formula simply identifies which rows fall within that circle... but it still has to checking every single row.
Using a bounding box is like drawing a square on the map first with the left, right, top and bottom edges at the appropriate distance from our centre point. Our circle will then be drawn within that box, with the Northmost, Eastmost, Southmost and Westmost points on the circle touching the borders of the box. Some rows will fall outside that box, so SQL doesn't even bother trying to calculate the distance for those rows. It only calculates the distance for those rows that fall within the bounding box to see if they fall within the circle as well.
Within PHP, we can use a very simple calculation that works out the minimum and maximum latitude and longitude based on our distance, then set those values in the WHERE clause of your SQL statement. This is effectively our box, and anything that falls outside of that is automatically discarded without any need to actually calculate its distance.
There's a good explanation of this (with PHP code) on the Movable Type website that should be essential reading for anybody planning to do any GeoPositioning work in PHP.
Thank you very much, I have two questions. 1 - whats a stored procedure? how would i implement that, and then use it? 2 - is this any more efficient or better than Jonathon's answer?
A "stored procedure" is basically a user-defined block of code stored within the database server itself. You can read about MySQL stored procedures at dev.mysql.com/doc/refman/5.1/en/stored-routines.html It basically does the same as Jonathon's code, but instead of all the math within your select statement, you simply reference the new function distance() in your select (passing in the appropriate arguments) as you might do with any of the MySQL built-in functions
Note, the 6371.01 is the radius multiplier in kilometers, change it to 3958 if you want the value in miles
ah right i understand, thats a really good idea thanks. :) I'll look into stored procedures. I really don't understand your edit! ha, I'm not sure whether that's because I don't understand how indexes work or because I just can't get my head around the concept. Could you shed a bit more light on it at all? Sorry to be thick, and if you don't have the time then don't worry at all. :)