How to apply multiple Order by in Rails - mysql

In my Rails application I have a table called HealthcareCenter.
I want to list HealthcareCenter based on below order(I have pagination, per page should list 50 records).
Number of recommentations (If user logged in)
Ratings (high rating first)
Distance (first set distance should be < 25km, second distance between 25 and 100, third any distance but ascending order)
I will explain by below image
At image we can see 5 clinics, as like shown in image sql should order the clicnics
I have written
HealthcareCenter.where("healthcare_centers.name LIKE ?", "%#{query}%")
.where(type_id: type_id)
.order("healthcare_centers.overall_rating DESC")
.calculate_distance2a(lat, lon)
.order("distance")
.order("healthcare_centers.name")
.offset(offset)
.limit(limit)
def self.calculate_distance2a(orig_lat, orig_lon, dist = nil)
lat1, lon1, lat2, lon2 = the_square(orig_lat, orig_lon, dist)
self.select(" healthcare_centers.id,
ROUND(
6371 * 2 * ASIN ( SQRT (
POWER( SIN((#{orig_lat} - healthcare_centers.latitude)*pi()/180 / 2),2)
+ COS(#{orig_lat} * pi()/180)
* COS(healthcare_centers.latitude *pi()/180)
* POWER(SIN((#{orig_lon} - healthcare_centers.longitude) *pi()/180 / 2), 2)
) ),2) as distance"
)
.where("healthcare_centers.longitude between #{lon1} and #{lon2}")
.where("healthcare_centers.latitude between #{lat1} and #{lat2}")
.order("distance")
end
This query give me the list of clinics with high rating (because currenly I am checking user not logged in) but gives me greater than 25KM as first result
Because on my DB more than 25KM have high rating
Can you please guide me on how to include those distance condition
1) <= 25
2) > 25 && <= 100
3) > 100 (should be in asc order)
Consider If less than 25KM we have 2 Clicnics.Clinic A 4.3(rating), 2Km distance, Clinic B 3(rating), 1.5km based on rating Clinic A should list on top.

You should calculate distance as a column alias an then
HealthcareCenter.where("healthcare_centers.name LIKE ?", "%#{query}%")
.where(type_id: type_id)
.order("healthcare_centers.overall_rating DESC,
distance,
healthcare_centers.name")

Related

Distance by zip code formula

I found this formula and it works, however what i trying to do is to give ability to filter by distance from his ZIP code.
I found formula that calculates distance between two latitude and longitude coordinates.
(3956 * ACOS(COS(RADIANS(start_lat)) * COS(RADIANS(end_lat)) * COS(RADIANS(end_lon) - RADIANS(start_lon)) + SIN(RADIANS(start_lat)) * SIN(RADIANS(end_lat))))
I have filter on page that sends following info
$_POST["start_latitude"] = 34.023179;
$_POST["start_longitude"] = -118.303965;
$_POST["max_distance"] = 50;
If i do
SELECT (3956 * ACOS(COS(RADIANS({$_POST["start_latitude"]})) * COS(RADIANS(34.018626))
* COS(RADIANS(-118.249978) - RADIANS({$_POST["start_longitude"]}))
+ SIN(RADIANS({$_POST["start_latitude"]})) * SIN(RADIANS(34.018626))))
Will output distance as number of miles 4 miles in this case.
How can i convert this formula for my goal to find places no longer than say 50 miles from coordinates entered? I know all need to be done is change of places in formula, but i am not good with school math.
SELECT place_name FROM places
WHERE place_latitude = ? AND place_longitude = ?
EDIT:
I have places table where i got 1000 records in format
id place_name latitude longitude
1 xxx 432423 -43432
2 yyy 523452 -54353
3 zzz 553453 -53422
etc.
So the formula has to do something like
SELECT place_name FROM places
(CALCULATE each place distance from
$_POST["start_latitude"] and $_POST["start_longitude"]
and select only ones that) < 50
Put the distance formula into the WHERE clause:
SELECT place_name
FROM places
WHERE (3956 * ACOS(COS(RADIANS(:start_latitude)) * COS(RADIANS(latitude)) * COS(RADIANS(-longitude) - RADIANS(:start_longitude)) + SIN(RADIANS(:start_latitude)) * SIN(RADIANS(latitude))))
< :max_distance
This resouce and web service usefull, check it:
http://www.codebump.com/services/PlaceLookup.asmx
function calc_distance($point1, $point2)
{
$radius = 3958; // Earth's radius (miles)
$deg_per_rad = 57.29578; // Number of degrees/radian (for conversion)
$distance = ($radius * pi() * sqrt(
($point1['lat'] - $point2['lat'])
* ($point1['lat'] - $point2['lat'])
+ cos($point1['lat'] / $deg_per_rad) // Convert these to
* cos($point2['lat'] / $deg_per_rad) // radians for cos()
* ($point1['long'] - $point2['long'])
* ($point1['long'] - $point2['long'])
) / 180);
return $distance; // Returned using the units used for $radius.
}
EDIT
Check the page :
http://www.mssqltips.com/sqlservertip/2690/calculate-the-geographical-distance-between-two-cities-in-sql-server/
--
DECLARE #98001 GEOGRAPHY;
DECLARE #Patzip GEOGRAPHY;
SELECT #98001 = Coordinates FROM ZipCodeLKUP INNER JOIN
Facility ON ZipCodeLKUP.ZipCode = Facility.ZipCode
Where Facility.ZipCode=98001
SELECT #Patzip = Coordinates FROM ZipCodeLKUP INNER JOIN
HIDIMV_year ON ZipCodeLKUP.ZipCode = HIDIMV_year .Patzip
where PATZIP in ('98001', '98466','97202')
SELECT #98001.STDistance(#Patzip)/1000 AS [Distance in KM]

Oracle circle distance search: missing results

EDIT: (If u want see the old problematic, see at the bottom of this question)
With your helps i modified my statement:
SELECT *
FROM (
SELECT
dest.ZC_ZIP,
dest.ZC_LOCATION_NAME,
ACOS(
SIN(RADIANS(src.ZC_LAT)) * SIN(RADIANS(dest.ZC_LAT))
+ COS(RADIANS(src.ZC_LAT)) * COS(RADIANS(dest.ZC_LAT))
* COS(RADIANS(src.ZC_LON) - RADIANS(dest.ZC_LON))
) * 6371 AS DISTANCE
FROM USER.ZC_COORDINATES dest
CROSS JOIN USER.ZC_COORDINATES src
WHERE src.ZC_ID =
(SELECT
ZC_ID
FROM USER.ZC_COORDINATES
WHERE ZC_ZIP = '64289'
GROUP BY ZC_ID
)
AND
(dest.ZC_ID <> src.ZC_ID OR dest.ZC_ID = src.ZC_ID)
)
HAVING DISTANCE <= 25 /* km */
ORDER BY DISTANCE;
After this i get the error: "Radians" are invalid identifier.
Reason: Oracle doenst implements that function (MySQL does!).
So i search and found this Code and use it:
CREATE OR REPLACE FUNCTION calc_distance(pLat1 NUMBER, pLon1 NUMBER, pLat2 NUMBER, pLon2 NUMBER)
RETURN NUMBER IS
-- r is the spherical radius of earth in Kilometers
cSpherRad CONSTANT NUMBER := 6371;
-- The spherical radius of earth in miles is 3956
a NUMBER;
vLat NUMBER;
vLat1Rad NUMBER;
vLat2Rad NUMBER;
vLon NUMBER;
vLon1Rad NUMBER;
vLon2Rad NUMBER;
BEGIN
/*
Most computers require the arguments of trigonometric functions to be
expressed in radians. To convert lon1, lat1 and lon2,lat2 from
degrees,minutes, seconds to radians, first convert them to decimal
degrees. To convert decimal degrees to radians, multiply the number
of degrees by pi/180 = 0.017453293 radians/degrees.
*/
vLat1Rad := pLat1 * 0.017453293;
vLat2Rad := pLat2 * 0.017453293;
vLon1Rad := pLon1 * 0.017453293;
vLon2Rad := pLon2 * 0.017453293;
vLon := vLon2Rad - vLon1Rad;
vLat := vLat2Rad - vLat1Rad;
a := POWER(SIN(vLat/2),2) + COS(vLat1Rad) * COS(vLat2Rad) * POWER(SIN(vLon/2),2);
/*
The intermediate result c is the great circle distance in radians.
Inverse trigonometric functions return results expressed in radians.
To express c in decimal degrees, multiply the number of radians by
180/pi = 57.295780 degrees/radian.
The great circle distance d will be in the same units as r.
*/
RETURN ROUND(cSpherRad * 2 * ATAN2(SQRT(a), SQRT(1-a)),1);
EXCEPTION
WHEN OTHERS THEN
RETURN 999;
END calc_distance;
/
Successfully compilation.
Now the correct modification without group by errors:
SELECT *
FROM (
SELECT
dest.ZC_ZIP AS ZIP,
dest.ZC_LOCATION_NAME AS LOCNAME,
calc_distance(src.ZC_LAT, src.ZC_LON, dest.ZC_LAT, dest.ZC_LON) AS DISTANCE
FROM BASE.ZC_COORDINATES dest
CROSS JOIN BASE.ZC_COORDINATES src
WHERE src.ZC_ID =
(SELECT
ZC_ID
FROM BASE.ZC_COORDINATES
WHERE ZC_ZIP = '64289'
GROUP BY ZC_ID
)
AND
(dest.ZC_ID <> src.ZC_ID OR dest.ZC_ID = src.ZC_ID)
)
HAVING DISTANCE <= 25 /* km */
GROUP BY ZIP, LOCNAME, DISTANCE;
--ORDER BY DISTANCE;
Whats the problem now?
Ok, on my old locals MySQL system i get these sql results (correct solution):
zc_zip zc_location_name distance
64291 Darmstadt 0
64297 Darmstadt 0
64289 Darmstadt 0
64283 Darmstadt 0
64285 Darmstadt 0
64295 Darmstadt 0
64293 Darmstadt 0
64287 Darmstadt 0
64347 Griesheim, Hessen 5.385545333978872
64331 Weiterstadt 5.671376373674798
64367 M├╝hltal, Hessen 6.992565870106159
64319 Pfungstadt 7.7621346384241585
64380 Roßdorf bei Darmstadt 8.134881711148836
64372 Ober-Ramstadt 8.421977582053422
64390 Erzhausen, Hessen 9.419234655429722
64572 B├╝ttelborn 9.739076077060767
64409 Messel 9.962635340560048
63329 Egelsbach, Hessen 11.274247321555363
64342 Seeheim-Jugenheim 11.743554066395413
64560 Riedstadt 12.135040456984065
64404 Bickenbach 12.597317271640899
64521 Groß-Gerau 12.637599535794854
64397 Modautal 13.260629389533909
64846 Groß-Zimmern 13.270265030251164
63225 Langen (Hessen) 13.745513110307494
Thats i get from the oracle system:
ZIP LOCNAME DISTANCE
64293 Darmstadt 0
64283 Darmstadt 0
64295 Darmstadt 0
64285 Darmstadt 0
64297 Darmstadt 0
64287 Darmstadt 0
64289 Darmstadt 0
64291 Darmstadt 0
You see, some reults are missing.
I think the problem is the "GROUP BY" line in the oracle statement, which are needed.
So i cant delete this line. But how i can get my other results? I'm realy confused :(
(Old problematic)
At the moment i program a circle zip search for my employee.
My tool works under construction on my MySQL Database (local).
Now i migrate my OpenGeoDB into our oracle database (successfully) and want run my sql statement which works perfectly on my local MySQL database.
The full error message:
ORA-00904: "DISTANCE": invalid indentifier
00904. 00000 - "%s: invalid identifier"
*Cause:
*Action:
Error in line: 20 col: 7
My SQL Statement:
SELECT
dest.zc_zip,
dest.zc_location_name,
ACOS(
SIN(RADIANS(src.zc_lat)) * SIN(RADIANS(dest.zc_lat))
+ COS(RADIANS(src.zc_lat)) * COS(RADIANS(dest.zc_lat))
* COS(RADIANS(src.zc_lon) - RADIANS(dest.zc_lon))
) * 6371 as distance
FROM USER.ZC_COORDINATES dest
CROSS JOIN USER.ZC_COORDINATES src
WHERE src.zc_id =
(
SELECT zc_id
FROM USER.zip_coordinates
WHERE zc_zip = '64289' /* Platzhalter fuer PLZ */
GROUP BY zc_zip
)
AND (dest.zc_id <> src.zc_id OR dest.zc_id = src.zc_id)
HAVING distance <= 25 /* km */
ORDER BY distance;
What I do to solve this problem?:
I switch the word "distance" with the full math calc, but after this, Oracle call invalid identifier "Radians"
Thats my problem because I dont know how I can solve this problem.
Have someone any ideas to solve that problem?
Thanks.
Oracle won't let you use a column computed in the SELECT list in a following ORDER BY. The following is a reasonable workaround:
SELECT *
FROM (SELECT dest.ZC_ZIP,
dest.ZC_LOCATION_NAME,
ACOS(SIN(RADIANS(src.ZC_LAT)) * SIN(RADIANS(dest.ZC_LAT))
+ COS(RADIANS(src.ZC_LAT)) * COS(RADIANS(dest.ZC_LAT))
* COS(RADIANS(src.ZC_LON) - RADIANS(dest.ZC_LON))
) * 6371 AS DISTANCE
FROM USER.ZC_COORDINATES dest
CROSS JOIN USER.ZC_COORDINATES src
WHERE src.ZC_ID = (SELECT ZC_ID
FROM USER.ZIP_COORDINATES
WHERE ZC_ZIP = '64289'
GROUP BY ZC_ZIP) AND
(dest.ZC_ID <> src.ZC_ID OR
dest.ZC_ID = src.ZC_ID))
HAVING DISTANCE <= 25 /* km */
ORDER BY DISTANCE;
Oracle also doesn't provide a function for converting degrees to radians but it's easy enough to add your own:
CREATE OR REPLACE FUNCTION RADIANS(nDegrees IN NUMBER) RETURN NUMBER DETERMINISTIC IS
BEGIN
-- radians = degrees / (180 / pi)
-- RETURN nDegrees / (180 / ACOS(-1)); -- but 180/pi is a constant, so...
RETURN nDegrees / 57.29577951308232087679815481410517033235;
END RADIANS;
Share and enjoy.
my last answer and solution for this question.
And sorry for the later answer. To much business.
A big thanks to #Bob Jarvis and #Ollie Jones for their help and experience.
What are the problems?
The different between MySQL and Oracle - Statements
Migration problem with float numbers from MySQL to Oracle with "Oracle SQL Developer"
Not existing function radians on Oracle
Solutions:
Float number migration:
To fix that problem I do that:
Use phpMyAdmin (PMA)
1. SQL File Select
2. Only data export, no structure or something
3. 1 line for 1 data set
4. Import into existing table on oracle serv
Missing radiant function:
Use this following statement:
CREATE OR REPLACE FUNCTION RADIANS(nDEGREES IN NUMBER)
RETURN NUMBER DETERMINISTIC IS
BEGIN
RETURN nDegrees / 57.29577951;
END RADIANS;
/
SQL-Statement (MySQL one):
SELECT
dest.zc_zip,
dest.zc_location_name,
ROUND(ACOS(
SIN(RADIANS(src.zc_lat)) * SIN(RADIANS(dest.zc_lat))
+ COS(RADIANS(src.zc_lat)) * COS(RADIANS(dest.zc_lat))
* COS(RADIANS(src.zc_lon) - RADIANS(dest.zc_lon))
) * 6371,
1) as distance
FROM zip_coordinates dest
CROSS JOIN zip_coordinates src
WHERE src.zc_id =
(
SELECT zc_id
FROM zip_coordinates
WHERE zc_zip = '/zip/' /* placeholder for zip */
GROUP BY zc_zip
)
AND dest.zc_lat >= src.zc_lat -((/circle/-0.5)/111.045)
AND dest.zc_lat <= src.zc_lat +((/circle/-0.5)/111.045)
AND dest.zc_lon >= src.zc_lon -((/circle/-0.5)/(111.045 * COS(RADIANS(src.ZC_LAT))))
AND dest.zc_lon <= src.zc_lon +((/circle/-0.5)/(111.045 * COS(RADIANS(src.ZC_LAT))))
HAVING distance <= (/circle/-0.5) /* km */ /* placeholder for radius */
ORDER BY distance, dest.zc_zip;
SQL-Statement (Oracle one):
SELECT *
FROM (
SELECT
dest.ZC_ZIP AS ZIP,
dest.ZC_LOCATION_NAME AS LOCNAME,
ROUND(
(6371* ACOS(
COS(RADIANS(dest.ZC_LAT))
* COS(RADIANS(src.ZC_LAT))
* COS(RADIANS(src.ZC_LON) - RADIANS(dest.ZC_LON))
+ SIN(RADIANS(dest.ZC_LAT))
* SIN(RADIANS(src.ZC_LAT))
)
), 1
) AS DISTANCE
FROM ORACLE_USER.ZC_COORDINATES dest
CROSS JOIN ORACLE_USER.ZC_COORDINATES src
WHERE src.ZC_ID =
(SELECT
ZC_ID
FROM ORACLE_USER.ZC_COORDINATES
WHERE ZC_ZIP = '/zip/'
GROUP BY ZC_ID
)
AND dest.ZC_LAT >= src.ZC_LAT -((/circle/-0.5)/111.045)
AND dest.ZC_LAT <= src.ZC_LAT +((/circle/-0.5)/111.045)
AND dest.ZC_LON >= src.ZC_LON -((/circle/-0.5)/(111.045 * COS(RADIANS(src.ZC_LAT))))
AND dest.ZC_LON <= src.ZC_LON +((/circle/-0.5)/(111.045 * COS(RADIANS(src.ZC_LAT))))
)
HAVING DISTANCE <= (/circle/-0.5) /* km */
GROUP BY ZIP, LOCNAME, DISTANCE
ORDER BY DISTANCE;

Determine longitudes and latitudes within a range

I have locations in my database. A location has the attributes latitude and longitude (taken from google maps, example: 48.809591).
Is there any query that could help me retrieve the locations within a range of another location?
Example:
I have the location A with latitude = 48.809591, and longitude = 2.124009 and want to retrieve all location objects in my database that are within 5 miles of location A
My first thought was to retrieve the locations in a square where location.latitude < A.latitude + 5 miles and location.latitude > A.latitude - 5 miles and location.longitude < A.longitude + 5 miles and location.longitude > A.longitude - 5 miles, and then remove the irrelevant locations from the returned array with the help of something like http://www.movable-type.co.uk/scripts/latlong.html
Any ideas?
Just in case you're using MySQL as your DBMS1, you may be interested in checking out the following presentation:
Geo/Spatial Search with MySQL2 by Alexander Rubin
The author describes how you can use the Haversine Formula in MySQL to order spatial data by proximity and limit the results to a defined radius. More importantly, he also describes how to avoid a full table scan for such queries, using traditional indexes on the latitude and longitude columns.
1 Even if you aren't, this is still interesting and applicable.
2 There is also a pdf version of the presentation.
The calculation you want, i think, is called the great circle distance:
http://en.wikipedia.org/wiki/Great-circle_distance
You would need a distance function.
For SQL Server it would look something like this (note that distance is in kilometers),
CREATE FUNCTION distance
(
#startLatitude float,
#startLongitude float,
#endLatitude float,
#endLongitude float
)
RETURNS float
AS
BEGIN
DECLARE #distance float;
set #distance =
6371 * 2 * atn2(sqrt(power(sin(pi() / 180 * (#endLatitude - #startLatitude) / 2), 2) +
power(cos(#startLatitude * pi() / 180), 2) *
power(sin(pi() / 180 * (#endLongitude - #startLongitude) / 2), 2)),
sqrt(1 - power(sin(pi() / 180 * (#endLatitude - #startLatitude) / 2), 2) +
power(cos(#startLatitude * pi() / 180), 2) *
power(sin(pi() / 180 * (#endLongitude - #startLongitude) / 2), 2)));
RETURN #distance
END

Find nearest latitude/longitude with an SQL query

I have latitude and longitude and I want to pull the record from the database, which has nearest latitude and longitude by the distance, if that distance gets longer than specified one, then don't retrieve it.
Table structure:
id
latitude
longitude
place name
city
country
state
zip
sealevel
SELECT latitude, longitude, SQRT(
POW(69.1 * (latitude - [startlat]), 2) +
POW(69.1 * ([startlng] - longitude) * COS(latitude / 57.3), 2)) AS distance
FROM TableName HAVING distance < 25 ORDER BY distance;
where [starlat] and [startlng] is the position where to start measuring the distance.
Google's solution:
Creating the Table
When you create the MySQL table, you want to pay particular attention to the lat and lng attributes. With the current zoom capabilities of Google Maps, you should only need 6 digits of precision after the decimal. To keep the storage space required for your table at a minimum, you can specify that the lat and lng attributes are floats of size (10,6). That will let the fields store 6 digits after the decimal, plus up to 4 digits before the decimal, e.g. -123.456789 degrees. Your table should also have an id attribute to serve as the primary key.
CREATE TABLE `markers` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`name` VARCHAR( 60 ) NOT NULL ,
`address` VARCHAR( 80 ) NOT NULL ,
`lat` FLOAT( 10, 6 ) NOT NULL ,
`lng` FLOAT( 10, 6 ) NOT NULL
) ENGINE = MYISAM ;
Populating the Table
After creating the table, it's time to populate it with data. The sample data provided below is for about 180 pizzarias scattered across the United States. In phpMyAdmin, you can use the IMPORT tab to import various file formats, including CSV (comma-separated values). Microsoft Excel and Google Spreadsheets both export to CSV format, so you can easily transfer data from spreadsheets to MySQL tables through exporting/importing CSV files.
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Frankie Johnnie & Luigo Too','939 W El Camino Real, Mountain View, CA','37.386339','-122.085823');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Amici\'s East Coast Pizzeria','790 Castro St, Mountain View, CA','37.38714','-122.083235');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Kapp\'s Pizza Bar & Grill','191 Castro St, Mountain View, CA','37.393885','-122.078916');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Round Table Pizza: Mountain View','570 N Shoreline Blvd, Mountain View, CA','37.402653','-122.079354');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Tony & Alba\'s Pizza & Pasta','619 Escuela Ave, Mountain View, CA','37.394011','-122.095528');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Oregano\'s Wood-Fired Pizza','4546 El Camino Real, Los Altos, CA','37.401724','-122.114646');
Finding Locations with MySQL
To find locations in your markers table that are within a certain radius distance of a given latitude/longitude, you can use a SELECT statement based on the Haversine formula. The Haversine formula is used generally for computing great-circle distances between two pairs of coordinates on a sphere. An in-depth mathemetical explanation is given by Wikipedia and a good discussion of the formula as it relates to programming is on Movable Type's site.
Here's the SQL statement that will find the closest 20 locations that are within a radius of 25 miles to the 37, -122 coordinate. It calculates the distance based on the latitude/longitude of that row and the target latitude/longitude, and then asks for only rows where the distance value is less than 25, orders the whole query by distance, and limits it to 20 results. To search by kilometers instead of miles, replace 3959 with 6371.
SELECT
id,
(
3959 *
acos(cos(radians(37)) *
cos(radians(lat)) *
cos(radians(lng) -
radians(-122)) +
sin(radians(37)) *
sin(radians(lat )))
) AS distance
FROM markers
HAVING distance < 28
ORDER BY distance LIMIT 0, 20;
This one is to find latitudes and longitudes in a distance less than 28 miles.
Another one is to find them in a distance between 28 and 29 miles:
SELECT
id,
(
3959 *
acos(cos(radians(37)) *
cos(radians(lat)) *
cos(radians(lng) -
radians(-122)) +
sin(radians(37)) *
sin(radians(lat )))
) AS distance
FROM markers
HAVING distance < 29 and distance > 28
ORDER BY distance LIMIT 0, 20;
https://developers.google.com/maps/articles/phpsqlsearch_v3#creating-the-map
The original answers to the question are good, but newer versions of mysql (MySQL 5.7.6 on) support geo queries, so you can now use built in functionality rather than doing complex queries.
You can now do something like:
select *, ST_Distance_Sphere( point ('input_longitude', 'input_latitude'),
point(longitude, latitude)) * .000621371192
as `distance_in_miles`
from `TableName`
having `distance_in_miles` <= 'input_max_distance'
order by `distance_in_miles` asc
The results are returned in meters. So if you want in KM simply use .001 instead of .000621371192 (which is for miles).
MySql docs are here
Here is my full solution implemented in PHP.
This solution uses the Haversine formula as presented in http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL.
It should be noted that the Haversine formula experiences weaknesses around the poles. This answer shows how to implement the vincenty Great Circle Distance formula to get around this, however I chose to just use Haversine because it's good enough for my purposes.
I'm storing latitude as DECIMAL(10,8) and longitude as DECIMAL(11,8). Hopefully this helps!
showClosest.php
<?PHP
/**
* Use the Haversine Formula to display the 100 closest matches to $origLat, $origLon
* Only search the MySQL table $tableName for matches within a 10 mile ($dist) radius.
*/
include("./assets/db/db.php"); // Include database connection function
$db = new database(); // Initiate a new MySQL connection
$tableName = "db.table";
$origLat = 42.1365;
$origLon = -71.7559;
$dist = 10; // This is the maximum distance (in miles) away from $origLat, $origLon in which to search
$query = "SELECT name, latitude, longitude, 3956 * 2 *
ASIN(SQRT( POWER(SIN(($origLat - latitude)*pi()/180/2),2)
+COS($origLat*pi()/180 )*COS(latitude*pi()/180)
*POWER(SIN(($origLon-longitude)*pi()/180/2),2)))
as distance FROM $tableName WHERE
longitude between ($origLon-$dist/cos(radians($origLat))*69)
and ($origLon+$dist/cos(radians($origLat))*69)
and latitude between ($origLat-($dist/69))
and ($origLat+($dist/69))
having distance < $dist ORDER BY distance limit 100";
$result = mysql_query($query) or die(mysql_error());
while($row = mysql_fetch_assoc($result)) {
echo $row['name']." > ".$row['distance']."<BR>";
}
mysql_close($db);
?>
./assets/db/db.php
<?PHP
/**
* Class to initiate a new MySQL connection based on $dbInfo settings found in dbSettings.php
*
* #example $db = new database(); // Initiate a new database connection
* #example mysql_close($db); // close the connection
*/
class database{
protected $databaseLink;
function __construct(){
include "dbSettings.php";
$this->database = $dbInfo['host'];
$this->mysql_user = $dbInfo['user'];
$this->mysql_pass = $dbInfo['pass'];
$this->openConnection();
return $this->get_link();
}
function openConnection(){
$this->databaseLink = mysql_connect($this->database, $this->mysql_user, $this->mysql_pass);
}
function get_link(){
return $this->databaseLink;
}
}
?>
./assets/db/dbSettings.php
<?php
$dbInfo = array(
'host' => "localhost",
'user' => "root",
'pass' => "password"
);
?>
It may be possible to increase performance by using a MySQL stored procedure as suggested by the "Geo-Distance-Search-with-MySQL" article posted above.
I have a database of ~17,000 places and the query execution time is 0.054 seconds.
Just in case you are lazy like me, here's a solution amalgamated from this and other answers on SO.
set #orig_lat=37.46;
set #orig_long=-122.25;
set #bounding_distance=1;
SELECT
*
,((ACOS(SIN(#orig_lat * PI() / 180) * SIN(`lat` * PI() / 180) + COS(#orig_lat * PI() / 180) * COS(`lat` * PI() / 180) * COS((#orig_long - `long`) * PI() / 180)) * 180 / PI()) * 60 * 1.1515) AS `distance`
FROM `cities`
WHERE
(
`lat` BETWEEN (#orig_lat - #bounding_distance) AND (#orig_lat + #bounding_distance)
AND `long` BETWEEN (#orig_long - #bounding_distance) AND (#orig_long + #bounding_distance)
)
ORDER BY `distance` ASC
limit 25;
Easy one ;)
SELECT * FROM `WAYPOINTS` W ORDER BY
ABS(ABS(W.`LATITUDE`-53.63) +
ABS(W.`LONGITUDE`-9.9)) ASC LIMIT 30;
Just replace the coordinates with your required ones. The values have to be stored as double. This ist a working MySQL 5.x example.
Cheers
Try this, it show the nearest points to provided coordinates (within 50 km). It works perfectly:
SELECT m.name,
m.lat, m.lon,
p.distance_unit
* DEGREES(ACOS(COS(RADIANS(p.latpoint))
* COS(RADIANS(m.lat))
* COS(RADIANS(p.longpoint) - RADIANS(m.lon))
+ SIN(RADIANS(p.latpoint))
* SIN(RADIANS(m.lat)))) AS distance_in_km
FROM <table_name> AS m
JOIN (
SELECT <userLat> AS latpoint, <userLon> AS longpoint,
50.0 AS radius, 111.045 AS distance_unit
) AS p ON 1=1
WHERE m.lat
BETWEEN p.latpoint - (p.radius / p.distance_unit)
AND p.latpoint + (p.radius / p.distance_unit)
AND m.lon BETWEEN p.longpoint - (p.radius / (p.distance_unit * COS(RADIANS(p.latpoint))))
AND p.longpoint + (p.radius / (p.distance_unit * COS(RADIANS(p.latpoint))))
ORDER BY distance_in_km
Just change <table_name>. <userLat> and <userLon>
You can read more about this solution here: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
You're looking for things like the haversine formula. See here as well.
There's other ones but this is the most commonly cited.
If you're looking for something even more robust, you might want to look at your databases GIS capabilities. They're capable of some cool things like telling you whether a point (City) appears within a given polygon (Region, Country, Continent).
Check this code based on the article Geo-Distance-Search-with-MySQL:
Example: find the 10 nearest hotels to my current location in a 10 miles radius:
#Please notice that (lat,lng) values mustn't be negatives to perform all calculations
set #my_lat=34.6087674878572;
set #my_lng=58.3783670308302;
set #dist=10; #10 miles radius
SELECT dest.id, dest.lat, dest.lng, 3956 * 2 * ASIN(SQRT(POWER(SIN((#my_lat -abs(dest.lat)) * pi()/180 / 2),2) + COS(#my_lat * pi()/180 ) * COS(abs(dest.lat) * pi()/180) * POWER(SIN((#my_lng - abs(dest.lng)) * pi()/180 / 2), 2))
) as distance
FROM hotel as dest
having distance < #dist
ORDER BY distance limit 10;
#Also notice that distance are expressed in terms of radius.
Find nearest Users to my:
Distance in meters
Based in Vincenty's formula
i have User table:
+----+-----------------------+---------+--------------+---------------+
| id | email | name | location_lat | location_long |
+----+-----------------------+---------+--------------+---------------+
| 13 | xxxxxx#xxxxxxxxxx.com | Isaac | 17.2675625 | -97.6802361 |
| 14 | xxxx#xxxxxxx.com.mx | Monse | 19.392702 | -99.172596 |
+----+-----------------------+---------+--------------+---------------+
sql:
-- my location: lat 19.391124 -99.165660
SELECT
(ATAN(
SQRT(
POW(COS(RADIANS(users.location_lat)) * SIN(RADIANS(users.location_long) - RADIANS(-99.165660)), 2) +
POW(COS(RADIANS(19.391124)) * SIN(RADIANS(users.location_lat)) -
SIN(RADIANS(19.391124)) * cos(RADIANS(users.location_lat)) * cos(RADIANS(users.location_long) - RADIANS(-99.165660)), 2)
)
,
SIN(RADIANS(19.391124)) *
SIN(RADIANS(users.location_lat)) +
COS(RADIANS(19.391124)) *
COS(RADIANS(users.location_lat)) *
COS(RADIANS(users.location_long) - RADIANS(-99.165660))
) * 6371000) as distance,
users.id
FROM users
ORDER BY distance ASC
radius of the earth : 6371000 ( in meters)
simpledb.execSQL("CREATE TABLE IF NOT EXISTS " + tablename + "(id INTEGER PRIMARY KEY AUTOINCREMENT,lat double,lng double,address varchar)");
simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2891001','70.780154','craftbox');");
simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2901396','70.7782428','kotecha');");//22.2904718 //70.7783906
simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2863155','70.772108','kkv Hall');");
simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.275993','70.778076','nana mava');");
simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2667148','70.7609386','Govani boys hostal');");
double curentlat=22.2667258; //22.2677258
double curentlong=70.76096826;//70.76096826
double curentlat1=curentlat+0.0010000;
double curentlat2=curentlat-0.0010000;
double curentlong1=curentlong+0.0010000;
double curentlong2=curentlong-0.0010000;
try{
Cursor c=simpledb.rawQuery("select * from '"+tablename+"' where (lat BETWEEN '"+curentlat2+"' and '"+curentlat1+"') or (lng BETWEEN '"+curentlong2+"' and '"+curentlong1+"')",null);
Log.d("SQL ", c.toString());
if(c.getCount()>0)
{
while (c.moveToNext())
{
double d=c.getDouble(1);
double d1=c.getDouble(2);
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
It sounds like you want to do a nearest neighbour search with some bound on the distance. SQL does not support anything like this as far as I am aware and you would need to use an alternative data structure such as an R-tree or kd-tree.
MS SQL Edition here:
DECLARE #SLAT AS FLOAT
DECLARE #SLON AS FLOAT
SET #SLAT = 38.150785
SET #SLON = 27.360249
SELECT TOP 10 [LATITUDE], [LONGITUDE], SQRT(
POWER(69.1 * ([LATITUDE] - #SLAT), 2) +
POWER(69.1 * (#SLON - [LONGITUDE]) * COS([LATITUDE] / 57.3), 2)) AS distance
FROM [TABLE] ORDER BY 3
Sounds like you should just use PostGIS, SpatialLite, SQLServer2008, or Oracle Spatial. They can all answer this question for you with spatial SQL.
+----+-----------------------+---------+--------------+---------------+
| id | email | name | location_lat | location_long |
+----+-----------------------+---------+--------------+---------------+
| 7 | test#gmail.com | rembo | 23.0249256 | 72.5269697 |
| 25 | test1#gmail.com. | Rajnis | 23.0233221 | 72.5342112 |
+----+-----------------------+---------+--------------+---------------+
$lat = 23.02350629;
$long = 72.53230239;
DB::
SELECT
("
SELECT
*
FROM
(
SELECT
,
(
( ( acos( sin(( ". $ lat ." * pi() / 180)) * sin(( lat * pi() / 180)) + cos(( ". $ lat ." pi() / 180 )) * cos(( lat * pi() / 180)) * cos((( ". $ long ." - LONG) * pi() / 180))) ) * 180 / pi() ) * 60 * 1.1515 * 1.609344
)
as distance
FROM
users
)
users
WHERE
distance <= 2");
In extreme cases this approach fails, but for performance, I've skipped the trigonometry and simply calculated the diagonal squared.
Mysql query for search coordinates with distance limit and where condition
SELECT id, ( 3959 * acos( cos( radians('28.5850154') ) * cos( radians(latitude) ) * cos( radians( longitude ) - radians('77.07207489999999') ) + sin( radians('28.5850154') ) * sin( radians( latitude ) ) ) ) AS distance FROM `vendors` HAVING distance < 5;
This problem is not very hard at all, but it gets more complicated if you need to optimize it.
What I mean is, do you have 100 locations in your database or 100 million? It makes a big difference.
If the number of locations is small, get them out of SQL and into code by just doing ->
Select * from Location
Once you get them into code, calculate the distance between each lat/lon and your original with the Haversine formula and sort it.

Fastest Way to Find Distance Between Two Lat/Long Points

I currently have just under a million locations in a mysql database all with longitude and latitude information.
I am trying to find the distance between one point and many other points via a query. It's not as fast as I want it to be especially with 100+ hits a second.
Is there a faster query or possibly a faster system other than mysql for this? I'm using this query:
SELECT
name,
( 3959 * acos( cos( radians(42.290763) ) * cos( radians( locations.lat ) )
* cos( radians(locations.lng) - radians(-71.35368)) + sin(radians(42.290763))
* sin( radians(locations.lat)))) AS distance
FROM locations
WHERE active = 1
HAVING distance < 10
ORDER BY distance;
Note: The provided distance is in Miles. If you need Kilometers, use 6371 instead of 3959.
Create your points using Point values of Geometry data types in MyISAM table. As of Mysql 5.7.5, InnoDB tables now also support SPATIAL indices.
Create a SPATIAL index on these points
Use MBRContains() to find the values:
SELECT *
FROM table
WHERE MBRContains(LineFromText(CONCAT(
'('
, #lon + 10 / ( 111.1 / cos(RADIANS(#lat)))
, ' '
, #lat + 10 / 111.1
, ','
, #lon - 10 / ( 111.1 / cos(RADIANS(#lat)))
, ' '
, #lat - 10 / 111.1
, ')' )
,mypoint)
, or, in MySQL 5.1 and above:
SELECT *
FROM table
WHERE MBRContains
(
LineString
(
Point (
#lon + 10 / ( 111.1 / COS(RADIANS(#lat))),
#lat + 10 / 111.1
),
Point (
#lon - 10 / ( 111.1 / COS(RADIANS(#lat))),
#lat - 10 / 111.1
)
),
mypoint
)
This will select all points approximately within the box (#lat +/- 10 km, #lon +/- 10km).
This actually is not a box, but a spherical rectangle: latitude and longitude bound segment of the sphere. This may differ from a plain rectangle on the Franz Joseph Land, but quite close to it on most inhabited places.
Apply additional filtering to select everything inside the circle (not the square)
Possibly apply additional fine filtering to account for the big circle distance (for large distances)
Not a MySql specific answer, but it'll improve the performance of your sql statement.
What you're effectively doing is calculating the distance to every point in the table, to see if it's within 10 units of a given point.
What you can do before you run this sql, is create four points that draw a box 20 units on a side, with your point in the center i.e.. (x1,y1 ) . . . (x4, y4), where (x1,y1) is (givenlong + 10 units, givenLat + 10units) . . . (givenLong - 10units, givenLat -10 units).
Actually, you only need two points, top left and bottom right call them (X1, Y1) and (X2, Y2)
Now your SQL statement use these points to exclude rows that definitely are more than 10u from your given point, it can use indexes on the latitudes & longitudes, so will be orders of magnitude faster than what you currently have.
e.g.
select . . .
where locations.lat between X1 and X2
and locations.Long between y1 and y2;
The box approach can return false positives (you can pick up points in the corners of the box that are > 10u from the given point), so you still need to calculate the distance of each point. However this again will be much faster because you have drastically limited the number of points to test to the points within the box.
I call this technique "Thinking inside the box" :)
EDIT: Can this be put into one SQL statement?
I have no idea what mySql or Php is capable of, sorry.
I don't know where the best place is to build the four points, or how they could be passed to a mySql query in Php. However, once you have the four points, there's nothing stopping you combining your own SQL statement with mine.
select name,
( 3959 * acos( cos( radians(42.290763) )
* cos( radians( locations.lat ) )
* cos( radians( locations.lng ) - radians(-71.35368) )
+ sin( radians(42.290763) )
* sin( radians( locations.lat ) ) ) ) AS distance
from locations
where active = 1
and locations.lat between X1 and X2
and locations.Long between y1 and y2
having distance < 10 ORDER BY distance;
I know with MS SQL I can build a SQL statement that declares four floats (X1, Y1, X2, Y2) and calculates them before the "main" select statement, like I said, I've no idea if this can be done with MySql. However I'd still be inclined to build the four points in C# and pass them as parameters to the SQL query.
Sorry I can't be more help, if anyone can answer the MySQL & Php specific portions of this, feel free to edit this answer to do so.
I needed to solve similar problem (filtering rows by distance from single point) and by combining original question with answers and comments, I came up with solution which perfectly works for me on both MySQL 5.6 and 5.7.
SELECT
*,
(6371 * ACOS(COS(RADIANS(56.946285)) * COS(RADIANS(Y(coordinates)))
* COS(RADIANS(X(coordinates)) - RADIANS(24.105078)) + SIN(RADIANS(56.946285))
* SIN(RADIANS(Y(coordinates))))) AS distance
FROM places
WHERE MBRContains
(
LineString
(
Point (
24.105078 + 15 / (111.320 * COS(RADIANS(56.946285))),
56.946285 + 15 / 111.133
),
Point (
24.105078 - 15 / (111.320 * COS(RADIANS(56.946285))),
56.946285 - 15 / 111.133
)
),
coordinates
)
HAVING distance < 15
ORDER By distance
coordinates is field with type POINT and has SPATIAL index
6371 is for calculating distance in kilometres
56.946285 is latitude for central point
24.105078 is longitude for central point
15 is maximum distance in kilometers
In my tests, MySQL uses SPATIAL index on coordinates field to quickly select all rows which are within rectangle and then calculates actual distance for all filtered places to exclude places from rectangles corners and leave only places inside circle.
This is visualisation of my result:
Gray stars visualise all points on map, yellow stars are ones returned by MySQL query. Gray stars inside corners of rectangle (but outside circle) were selected by MBRContains() and then deselected by HAVING clause.
The following MySQL function was posted on this blog post. I haven't tested it much, but from what I gathered from the post, if your latitude and longitude fields are indexed, this may work well for you:
DELIMITER $$
DROP FUNCTION IF EXISTS `get_distance_in_miles_between_geo_locations` $$
CREATE FUNCTION get_distance_in_miles_between_geo_locations(
geo1_latitude decimal(10,6), geo1_longitude decimal(10,6),
geo2_latitude decimal(10,6), geo2_longitude decimal(10,6))
returns decimal(10,3) DETERMINISTIC
BEGIN
return ((ACOS(SIN(geo1_latitude * PI() / 180) * SIN(geo2_latitude * PI() / 180)
+ COS(geo1_latitude * PI() / 180) * COS(geo2_latitude * PI() / 180)
* COS((geo1_longitude - geo2_longitude) * PI() / 180)) * 180 / PI())
* 60 * 1.1515);
END $$
DELIMITER ;
Sample usage:
Assuming a table called places with fields latitude & longitude:
SELECT get_distance_in_miles_between_geo_locations(-34.017330, 22.809500,
latitude, longitude) AS distance_from_input FROM places;
if you are using MySQL 5.7.*, then you can use st_distance_sphere(POINT, POINT).
Select st_distance_sphere(POINT(-2.997065, 53.404146 ), POINT(58.615349, 23.56676 ))/1000 as distcance
SELECT * FROM (SELECT *,(((acos(sin((43.6980168*pi()/180)) *
sin((latitude*pi()/180))+cos((43.6980168*pi()/180)) *
cos((latitude*pi()/180)) * cos(((7.266903899999988- longitude)*
pi()/180))))*180/pi())*60*1.1515 ) as distance
FROM wp_users WHERE 1 GROUP BY ID limit 0,10) as X
ORDER BY ID DESC
This is the distance calculation query between to points in MySQL, I have used it in a long database, it it working perfect! Note: do the changes (database name, table name, column etc) as per your requirements.
set #latitude=53.754842;
set #longitude=-2.708077;
set #radius=20;
set #lng_min = #longitude - #radius/abs(cos(radians(#latitude))*69);
set #lng_max = #longitude + #radius/abs(cos(radians(#latitude))*69);
set #lat_min = #latitude - (#radius/69);
set #lat_max = #latitude + (#radius/69);
SELECT * FROM postcode
WHERE (longitude BETWEEN #lng_min AND #lng_max)
AND (latitude BETWEEN #lat_min and #lat_max);
source
select
(((acos(sin(('$latitude'*pi()/180)) * sin((`lat`*pi()/180))+cos(('$latitude'*pi()/180))
* cos((`lat`*pi()/180)) * cos((('$longitude'- `lng`)*pi()/180))))*180/pi())*60*1.1515)
AS distance
from table having distance<22;
A MySQL function which returns the number of metres between the two coordinates:
CREATE FUNCTION DISTANCE_BETWEEN (lat1 DOUBLE, lon1 DOUBLE, lat2 DOUBLE, lon2 DOUBLE)
RETURNS DOUBLE DETERMINISTIC
RETURN ACOS( SIN(lat1*PI()/180)*SIN(lat2*PI()/180) + COS(lat1*PI()/180)*COS(lat2*PI()/180)*COS(lon2*PI()/180-lon1*PI()/180) ) * 6371000
To return the value in a different format, replace the 6371000 in the function with the radius of Earth in your choice of unit. For example, kilometres would be 6371 and miles would be 3959.
To use the function, just call it as you would any other function in MySQL. For example, if you had a table city, you could find the distance between every city to every other city:
SELECT
`city1`.`name`,
`city2`.`name`,
ROUND(DISTANCE_BETWEEN(`city1`.`latitude`, `city1`.`longitude`, `city2`.`latitude`, `city2`.`longitude`)) AS `distance`
FROM
`city` AS `city1`
JOIN
`city` AS `city2`
The full code with details about how to install as MySQL plugin are here: https://github.com/lucasepe/lib_mysqludf_haversine
I posted this last year as comment. Since kindly #TylerCollier suggested me to post as answer, here it is.
Another way is to write a custom UDF function that returns the haversine distance from two points. This function can take in input:
lat1 (real), lng1 (real), lat2 (real), lng2 (real), type (string - optinal - 'km', 'ft', 'mi')
So we can write something like this:
SELECT id, name FROM MY_PLACES WHERE haversine_distance(lat1, lng1, lat2, lng2) < 40;
to fetch all records with a distance less then 40 kilometers. Or:
SELECT id, name FROM MY_PLACES WHERE haversine_distance(lat1, lng1, lat2, lng2, 'ft') < 25;
to fetch all records with a distance less then 25 feet.
The core function is:
double
haversine_distance( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error ) {
double result = *(double*) initid->ptr;
/*Earth Radius in Kilometers.*/
double R = 6372.797560856;
double DEG_TO_RAD = M_PI/180.0;
double RAD_TO_DEG = 180.0/M_PI;
double lat1 = *(double*) args->args[0];
double lon1 = *(double*) args->args[1];
double lat2 = *(double*) args->args[2];
double lon2 = *(double*) args->args[3];
double dlon = (lon2 - lon1) * DEG_TO_RAD;
double dlat = (lat2 - lat1) * DEG_TO_RAD;
double a = pow(sin(dlat * 0.5),2) +
cos(lat1*DEG_TO_RAD) * cos(lat2*DEG_TO_RAD) * pow(sin(dlon * 0.5),2);
double c = 2.0 * atan2(sqrt(a), sqrt(1-a));
result = ( R * c );
/*
* If we have a 5th distance type argument...
*/
if (args->arg_count == 5) {
str_to_lowercase(args->args[4]);
if (strcmp(args->args[4], "ft") == 0) result *= 3280.8399;
if (strcmp(args->args[4], "mi") == 0) result *= 0.621371192;
}
return result;
}
A fast, simple and accurate (for smaller distances) approximation can be done with a spherical projection. At least in my routing algorithm I get a 20% boost compared to the correct calculation. In Java code it looks like:
public double approxDistKm(double fromLat, double fromLon, double toLat, double toLon) {
double dLat = Math.toRadians(toLat - fromLat);
double dLon = Math.toRadians(toLon - fromLon);
double tmp = Math.cos(Math.toRadians((fromLat + toLat) / 2)) * dLon;
double d = dLat * dLat + tmp * tmp;
return R * Math.sqrt(d);
}
Not sure about MySQL (sorry!).
Be sure you know about the limitation (the third param of assertEquals means the accuracy in kilometers):
float lat = 24.235f;
float lon = 47.234f;
CalcDistance dist = new CalcDistance();
double res = 15.051;
assertEquals(res, dist.calcDistKm(lat, lon, lat - 0.1, lon + 0.1), 1e-3);
assertEquals(res, dist.approxDistKm(lat, lon, lat - 0.1, lon + 0.1), 1e-3);
res = 150.748;
assertEquals(res, dist.calcDistKm(lat, lon, lat - 1, lon + 1), 1e-3);
assertEquals(res, dist.approxDistKm(lat, lon, lat - 1, lon + 1), 1e-2);
res = 1527.919;
assertEquals(res, dist.calcDistKm(lat, lon, lat - 10, lon + 10), 1e-3);
assertEquals(res, dist.approxDistKm(lat, lon, lat - 10, lon + 10), 10);
Here is a very detailed description of Geo Distance Search with MySQL a solution based on implementation of Haversine Formula to mysql. The complete solution description with theory, implementation and further performance optimization. Although the spatial optimization part didn't work correct in my case.
http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
Have a read of Geo Distance Search with MySQL, a solution
based on implementation of Haversine Formula to MySQL. This is a complete solution
description with theory, implementation and further performance optimization.
Although the spatial optimization part didn't work correctly in my case.
I noticed two mistakes in this:
the use of abs in the select statement on p8. I just omitted abs and it worked.
the spatial search distance function on p27 does not convert to radians or multiply longitude by cos(latitude), unless his spatial data is loaded with this in consideration (cannot tell from context of article), but his example on p26 indicates that his spatial data POINT is not loaded with radians or degrees.
$objectQuery = "SELECT table_master.*, ((acos(sin((" . $latitude . "*pi()/180)) * sin((`latitude`*pi()/180))+cos((" . $latitude . "*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((" . $longitude . "- `longtude`)* pi()/180))))*180/pi())*60*1.1515 as distance FROM `table_post_broadcasts` JOIN table_master ON table_post_broadcasts.master_id = table_master.id WHERE table_master.type_of_post ='type' HAVING distance <='" . $Radius . "' ORDER BY distance asc";
Using mysql
SET #orig_lon = 1.027125;
SET #dest_lon = 1.027125;
SET #orig_lat = 2.398441;
SET #dest_lat = 2.398441;
SET #kmormiles = 6371;-- for distance in miles set to : 3956
SELECT #kmormiles * ACOS(LEAST(COS(RADIANS(#orig_lat)) *
COS(RADIANS(#dest_lat)) * COS(RADIANS(#orig_lon - #dest_lon)) +
SIN(RADIANS(#orig_lat)) * SIN(RADIANS(#dest_lat)),1.0)) as distance;
See: https://andrew.hedges.name/experiments/haversine/
See: https://stackoverflow.com/a/24372831/5155484
See: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
NOTE: LEAST is used to avoid null values as a comment suggested on https://stackoverflow.com/a/24372831/5155484
I really liked #Māris Kiseļovs solution, but I like many others may have the Lat and lng's POINTS reversed from his example. In generalising it I though I would share it. In my case I need to find all the start_points that are within a certain radius of an end_point.
I hope this helps someone.
SELECT #LAT := ST_X(end_point), #LNG := ST_Y(end_point) FROM routes WHERE route_ID = 280;
SELECT
*,
(6371e3 * ACOS(COS(RADIANS(#LAT)) * COS(RADIANS(ST_X(start_point)))
* COS(RADIANS(ST_Y(start_point)) - RADIANS(#LNG)) + SIN(RADIANS(#LAT))
* SIN(RADIANS(ST_X(start_point))))) AS distance
FROM routes
WHERE MBRContains
(
LineString
(
Point (
#LNG + 15 / (111.320 * COS(RADIANS(#LAT))),
#LAT + 15 / 111.133
),
Point (
#LNG - 15 / (111.320 * COS(RADIANS(#LAT))),
#LAT - 15 / 111.133
)
),
POINT(ST_Y(end_point),ST_X(end_point))
)
HAVING distance < 100
ORDER By distance;