Mysql distance function - mysql

I have the problems with my function which needs to calculate distance between given coordinates. As I can see problem is with negative values, and I am running out of ideas how to solve this, so if someone can help me I will really appreciate it!
DELIMITER $$
CREATE DEFINER=`sfff_user`#`%` FUNCTION `GetUserDistance`(lat VARCHAR (20), lon VARCHAR (20), userLat VARCHAR (20), userLon VARCHAR (20)) RETURNS int(11)
BEGIN
DECLARE distance INT (11);
IF ISNULL(lat) OR ISNULL(lon) OR lat = '' OR lon = '' THEN
RETURN 0;
ELSE
SELECT
3956 * 2 * ASIN(SQRT(POWER(SIN((lat - ABS(userLat)) * PI() / 180 / 2), 2) + COS(lat * PI() / 180) * COS(ABS(userLat) * PI() / 180) * POWER(SIN((lon - userLon) * PI() / 180 / 2), 2)))
INTO
distance;
RETURN distance;
END IF;
END
For example result for this call:
select GetUserDistance(44, 21, 44, 21) as distance; is 0 which is ok
But look at this:
select GetUserDistance('-15.4167', '28.2833', '-15.4167', '28.2833') as distance;
is 2129 which is insane!
So if you can take a look it would be verry nice to have correct function, since I am dying to solve this :(
Thanks.

According to this link, the formula should be without ABS:
3956 * 2 * ASIN(SQRT(POWER(SIN((lat - userLat) * PI() / 180 / 2), 2) + COS(lat * PI() / 180) * COS(ABS(userLat) * PI() / 180) * POWER(SIN((lon - userLon) * PI() / 180 / 2), 2)))

For safe side you should CAST VARCHAR to DECIMAL and as #Scharron said no need of ABS try:
CREATE FUNCTION `GetUserDistance`(arg_lat VARCHAR (20), arg_lon VARCHAR (20), arg_userLat VARCHAR (20), arg_userLon VARCHAR (20)) RETURNS int(11) NO SQL
BEGIN
DECLARE distance INT (11);
DECLARE lat, lon, userLat, userLon DECIMAL(14,4);
SET lat = CAST(arg_lat AS DECIMAL(14,4));
SET lon = CAST(arg_lon AS DECIMAL(14,4));
SET userLat = CAST(arg_userLat AS DECIMAL(14,4));
SET userLon = CAST(arg_userLon AS DECIMAL(14,4));
IF ISNULL(lat) OR ISNULL(lon) OR lat = '' OR lon = '' THEN
RETURN 0;
ELSE
SELECT
3956 * 2 * ASIN(SQRT(POWER(SIN((lat - userLat) * PI() / 180 / 2), 2) + COS(lat * PI() / 180) * COS(ABS(userLat) * PI() / 180) * POWER(SIN((lon - userLon) * PI() / 180 / 2), 2)))
INTO
distance;
RETURN distance;
END IF;
END

Related

PostgreSQL - column doesn't exist (WHERE with AS)

i am a bit newbie to PostgreSQL, but i have a few experiences with MySQL.
The Postgres is showing me and error - COLUMN doesn't exist, but this is a "virtual column", created by AS.
Code, which is working very well in MySQL:
SELECT place.*, 3956 * 2 * ASIN(SQRT( POWER(SIN((place.lattitude - $1) * pi() / 180 / 2), 2) + COS($2 * pi() / 180) * COS(place.lattitude * pi() / 180) *POWER(SIN(($3 - place.longitude) * pi() / 180 / 2), 2) )) AS "distance" FROM place WHERE place.longitude BETWEEN $4 AND $5 AND place.lattitude BETWEEN $6 AND $7 HAVING "distance" < $8 ORDER BY "distance" LIMIT 10
But Postgres is showing Column "distance" does not exist.
How can I rewrite it? (Please write full SQL query, not "How to")
$number is variable (against SQL injection)
Thanks
Use a subquery:
SELECT p.*
FROM (SELECT place.*, 3956 * 2 * ASIN(SQRT( POWER(SIN((place.lattitude - $1) * pi() / 180 / 2), 2) + COS($2 * pi() / 180) * COS(place.lattitude * pi() / 180) *POWER(SIN(($3 - place.longitude) * pi() / 180 / 2), 2) )) AS "distance"
FROM place
WHERE place.longitude BETWEEN $4 AND $5 AND
place.lattitude BETWEEN $6 AND $7
) p
WHERE "distance" < $8
ORDER BY "distance"
LIMIT 10;
You don't want to use a subquery in MySQL because it materializes the intermediate result. Other databases are smarter in how they optimize queries, and do not necessarily materialize subqueries.

Find all nearest zip codes with lat and long from a particular latitude and longitude in sql query

I've a database table in which there are 4 columns.
1. id
2. person_name
3. country
4. zip_code
Now I want all the zip codes with their real latitude and longitude which come in a given radius of 10 mile from a given lat long.
suppose my latitude and longitudes are (19.24947300,72.85681400) and distance is 10 mile, then what SQL query should I make to return all nearest zip codes and their lat longs.
I only have the following query
SELECT ((ACOS(SIN($lat * PI() / 180) * SIN(lat * PI() / 180) + COS($lat * PI()
/ 180) * COS(lat * PI() / 180) * COS(($lon – lon) * PI() / 180)) * 180 / PI()) * 60 *
1.1515) AS `distance` FROM `members` HAVING `distance`<=’10’ ORDER BY `distance` ASC
But it requires the lat longs of all zip codes in the table but I want them in run time.
I'm not sure I understand what you mean by
But it requires the lat longs of all zip codes in the table but I want them in run time.
Since you want a SQL query, I assume that you have a table with every zip code's latitude and longitude.
You probably shouldn't do all that math in your SQL query. I would first pull every zip code that is in a 10 mile x 10 mile square centered in your queried position and then, if you really need just those that are positively in a 10 mile radius, check in your program (I assume you are using PHP, since you have variables starting with $).
SELECT zip, latitude, longitude FROM zip_positions
WHERE latitude > 19.24947300 - DISTANCE and latitude < 19.24947300 + DISTANCE
AND longitude > 72.85681400 - DISTANCE and longitude < 72.85681400 + DISTANCE
where DISTANCE is a rough equivalent to 10 miles in latitude/longitude that you would have to calculate.
Then you would, if you need it, check in PHP (or whatever language you're using) every result to see if it is indeed less than 10 miles from the point queried. I can't help you as to how to precisely convert latitude and longitude to distance in miles.
I assume this abandoned question was resolved, but let me share my opinions here in case someone else is wrestling with this one. I present 3 options for letting closet zip from DB using lat long as the lookup:
DB LOOKUP OPTION 1 (QUICK AND DIRTY):
SET #lat = 41.675868;
SET #long = -73.864484;
SET #tol = .05;
SELECT * FROM mytable.ZipCodes
WHERE latitude >= #lat AND latitude <= #lat + #tol
AND longitude >= #long AND longitude <= #long + #tol
ORDER BY (ABS(latitude-longitude) - ABS(#lat-#long)) ASC
LIMIT 1
OPTION #2 (VARIATION ON #1)
$qry = "SELECT zipcode,cityname,stateabbr FROM dealer-site.ZipCodes WHERE (latitude >= {$lat} AND latitude <= {$lat}+1) AND (ABS(longitude) >= ABS({$long}) AND ABS(longitude) < ABS({$long})+1) ORDER BY latitude ASC LIMIT 1”;
OPTION #3 BEST METHOD CALCULATE . I WOULD TRY TO USE THIS:
SET #latitude = 41.675868;
SET #longitude = -73.864484;
SELECT
*
FROM
(SELECT
*,
(((ACOS(SIN((#latitude * PI() / 180)) *
SIN((latitude * PI() / 180)) + COS((#latitude * PI() / 180)) * COS((latitude * PI() / 180)) * COS(((#longitude - longitude) * PI() / 180)))) * 180 / PI()) * 60 * 1.1515 * 1.609344) AS distance
FROM
mytable.ZipCodes) ZipCodes
WHERE
distance <= 5
LIMIT 10
PHP MYSQL (LEGACY CALL) :
$qry = "SELECT zipcode
FROM (SELECT *,
(((ACOS(SIN(({$lat} * PI() / 180)) *
SIN((`latitude` * PI() / 180)) + COS(({$lat} * PI() / 180)) * COS((`latitude` * PI() / 180)) *
COS((({$long} - `longitude`) * PI() / 180)))) * 180 / PI()) * 60 * 1.1515 * 1.609344)
AS distance
FROM
`MyDB`.ZipCodes) ZipCodes
WHERE distance <= 5
LIMIT 1
";

Null returns for distance

Total SQL newb here. I've created two SQL tables, one containing information about hotels and the other containing information about attractions. I'm trying to create a query where I can input a postcode of an attraction, and have the database return the distance of hotels within a 1, 5 and 10 mile radius.
SELECT
hotels.*,
attractions.*,
( ( ACOS( SIN(hotels.Hotel_Lat * PI() / 180)
* SIN(attractions.Attraction_Lat * PI() / 180)
+
COS(hotels.Hotel_Long * PI() / 180)
* COS(attractions.Attraction_Long * PI() / 180)
* COS((hotels.Hotel_Long - attractions.Attraction_Long) * PI() / 180)
) * 180 / PI()
) * 60 * 1.1515
) as distance
FROM hotels
JOIN attractions
This query returns 'Null' for distances. My tables are populated with lat longs as well as postcodes. Any ideas why please? I have negative value longitudes if that makes a difference?
Using MySQL Community server 5.6.17.
Sample Data - There are 7 rows of data in hotels.
Hotel_Name | Hotel_Address | Hotel_Lat | Hotel_Long
a ST158DH 52.906438 -2.145523
b ST161LF 52.827959 -2.129709
Attraction_Name | Attraction_Postcode | Attraction_Lat | Attraction_Long
a ST180BA 52.839509 -2.056964
b ST180TG 52.832820 -2.091124
There are four attractions in total.
Using the Haversine formula, which is well behaved even at very small distances, try this code (or equivalent for MySQL):
select
Hotel_Name
,Hotel_Address
,Attraction_Name
,Attraction_Address
,round(2 * atn2(sqrt(a),sqrt(1-a)), 4) as DeltaRad
,round(2 * atn2(sqrt(a),sqrt(1-a)) * (180.0 / pi()), 2) as DeltaDeg
,round(2 * atn2(sqrt(a),sqrt(1-a)) * (180.0 / pi()) * 60, 2) as DeltaNMs
,round(2 * atn2(sqrt(a),sqrt(1-a)) * 6371.0, 2) as DeltaKMs
from (
select
Hotel_Name
,Hotel_Address
,Attraction_Name
,Attraction_Address
, SinHalfDeltaLatRad * SinHalfDeltaLatRad
+ CosCosLat
* SinHalfDeltaLngRad * SinHalfDeltaLngRad as a
from (
SELECT
Hotel_Name
,Hotel_Address
,Attraction_Name
,Attraction_Address
,sin((h.Lat - a.Lat) * PI() / 180.0 / 2.0) as SinHalfDeltaLatRad
,sin((h.Lng - a.Lng) * PI() / 180.0 / 2.0) as SinHalfDeltaLngRad
,cos(a.Lat) * cos(a.Lat) as CosCosLat
FROM hotels as h
cross JOIN attractions as a
) t
) t
Yielding for your sample data:
Hotel_Name Hotel_Address Attraction_Name Attraction_Address DeltaRad DeltaDeg DeltaNMs DeltaKMs
-------------------- ------------- -------------------- ------------------ ---------------------- ---------------------- ---------------------- ----------------------
HotelA ST158DH AttractionA ST180BA 0.0018 0.1 6.02 11.15
HotelB ST161LF AttractionA ST180BA 0.0011 0.06 3.75 6.94
HotelA ST158DH AttractionB ST180TG 0.0015 0.09 5.2 9.63
HotelB ST161LF AttractionB ST180TG 0.0006 0.03 1.97 3.64

Explanation of SQL query

I am having some issues with this SQL query. This actually comes from a PROCEDURE that is dealing with spatial calculations. The procedure finds locations within a certain radius. Really I am having a hard time understanding what is going on. I just need to rewrite it for my purposes if someone could help explain a couple of things. I will be forever grateful to whoever attempts to help. Thanks so much in advance!
Link To Source: http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
Full Procedure - I Just Need Some Explanation Help On The Actual SELECT Statement - Is Here Just For Some Clarification So Whoever Is Reading Can Get A Better Understanding
CREATE PROCEDURE geodist (IN userid int, IN dist int)BEGINdeclare mylon double; declare mylat double;declare lon1 float; declare lon2 float;declare lat1 float; declare lat2 float;
-- get the original lon and lat for the userid
select longitude, latitude into mylon, mylat from users5where id=userid limit 1;
-- calculate lon and lat for the rectangle:
set lon1 = mylon-dist/abs(cos(radians(mylat))*69);set lon2 = mylon+dist/abs(cos(radians(mylat))*69);set lat1 = mylat-(dist/69); set lat2 = mylat+(dist/69);
-- run the query:
SELECT destination.*,3956 * 2 * ASIN(SQRT( POWER(SIN((orig.lat -dest.lat) * pi()/180 / 2), 2) +COS(orig.lat * pi()/180) * COS(dest.lat * pi()/180) *POWER(SIN((orig.lon -dest.lon) * pi()/180 / 2), 2) )) asdistance FROM users destination, users originWHERE origin.id=userid
and destination.longitude between lon1 and lon2 and destination.latitude between lat1 and lat2
having distance < dist ORDER BY Distance limit 10;END $$
Full Query The Part I Need a Little Clarification On:
SELECT destination.*,3956 * 2 * ASIN(SQRT( POWER(SIN((orig.lat -dest.lat) * pi()/180 / 2), 2) +COS(orig.lat * pi()/180) * COS(dest.lat * pi()/180) *POWER(SIN((orig.lon -dest.lon) * pi()/180 / 2), 2) )) as distance FROM users destination, users origin WHERE origin.id=userid
and locations.longitude between lon1 and lon2 and locations.latitude between lat1 and lat2
having distance < dist ORDER BY Distance;
First Part:
SELECT destination.*
Question: What is with the period and * after the word destination? Sure we are selecting destination but is this concatenating? Like prefixing the word destination to every column in the table?
Next Part:
3956 * 2 * ASIN(SQRT( POWER(SIN((orig.lat -dest.lat) * pi()/180 / 2), 2) +COS(orig.lat * pi()/180) * COS(dest.lat * pi()/180) *POWER(SIN((orig.lon -dest.lon) * pi()/180 / 2), 2) )) as distance
Question: Is this just performing the calculation and storing it as the keyword destination? The keyword "as" is throwing me off here. Is it searching the database for a column destination?
Third Part:
FROM users destination, users origin WHERE origin.id=userid
Question: a little confused on where the query is headed. users destination? users origin? What is this grabbing?
Last Part:
having distance < dist ORDER BY Distance;
Question: Confused about the "having" keyword and where having distance comes into play. Is it trying to grab distance from the table because this is something that is obviously calculated on the fly.
First Part
* selects all columns. You can specify all columns for a particular table using the dot notation as you would use to specify an individual column for a table. For example:
SELECT t1.* FROM t1 NATURAL JOIN t2
Will only SELECT columns from t1 even though t2 is used in the JOIN.
Next Part
This just does a mathematical calculation on some columns and aliases the result in distance (not "destination"). You can reference the result as distance in the result set. The AS keyword sets the column alias, but it is optional.
Third Part
destination and origin are aliases. They are leaving off the optional AS keyword. It would be the same to write:
FROM users AS destination, users AS origin
The users table is being renamed for the duration of the query so it can be referenced by the alias but also to avoid the collision of two of the same table name in the query which would be invalid.
Last Part
distance is an alias for the mathematical calculation above. HAVING is like WHERE, but it has to be used for aggregation (e.g. GROUP BY). I could be wrong, but I don't think it's necessary in this query.
I feel like I'm missing something here... Why doesn't the code use the 'mylon' and 'mylat' in the math part of the final query?
So, this:
SELECT destination.*,
3956 * 2 * ASIN(SQRT( POWER(SIN((orig.lat -
destination.lat) * pi()/180 / 2), 2) +
COS(orig.lat * pi()/180) * COS(destination.lat * pi()/180) *
POWER(SIN((orig.lon -destination.lon) * pi()/180 / 2), 2) ))
as distance
FROM users AS destination, users AS orig
WHERE origin.id=userid
AND destination.longitude BETWEEN lon1 AND lon2
AND destination.latitude BETWEEN lat1 AND lat2
HAVING distance < dist ORDER BY Distance limit 10;
becomes this:
SELECT destination.*,
3956 * 2 * ASIN(SQRT( POWER(SIN((mylat -
dest.lat) * pi()/180 / 2), 2) +
COS(mylat * pi()/180) * COS(dest.lat * pi()/180) *
POWER(SIN((mylon -dest.lon) * pi()/180 / 2), 2) ))
as distance
FROM users AS destination
WHERE
destination.longitude BETWEEN lon1 AND lon2
AND destination.latitude BETWEEN lat1 AND lat2
HAVING distance < dist ORDER BY Distance limit 10;
edit: my best guess is that "origin.id=userid" should read "origin.id=destination.id". Am I correct in thinking that this join is done to filter out all users who are outside of the "distance box" -- in order to avoid calculating the actual geo distance?

haversine distance - MySQL and PHP return different results for same formula

distance (straight)
from: (lat) 48.73233 (long) 2.36618
to: lat() 48.84647 (long) 2.41026
equals some: 13096.16 meters
If I use PHP formula, I get proper result.
But when I translate same PHP formula directly into MySQL query - I get 5904.2757 etc.
Here is the code:
php:
$distance = atan2(sqrt(pow(sin((($to_lat - $from_lat) * M_PI / 180) / 2), 2) +
cos(($from_lat * M_PI / 180)) * cos(($to_lat * M_PI / 180)) *
pow(sin((($to_long - $from_long) * M_PI / 180) / 2), 2)), sqrt(1 - (pow(sin((($to_lat - $from_lat) * M_PI / 180) / 2), 2) +
cos(($from_lat * M_PI / 180)) * cos(($to_lat * M_PI / 180)) *
pow(sin((($to_long - $from_long) * M_PI / 180) / 2), 2)))) * 2 * $radiusOfEarth;
mysql:
atan2(sqrt(pow(sin(((ap.Latitude - $from_lat) * pi() / 180) / 2), 2) +
cos(($from_lat * pi() / 180)) * cos((ap.Latitude * pi() / 180)) *
pow(sin(((ap.Longitude - $from_long) * pi() / 180) / 2), 2)), sqrt(1 - (pow(sin(((ap.Latitude - $from_lat) * pi() / 180) / 2), 2) +
cos(($from_lat * pi() / 180)) * cos((ap.Latitude * pi() / 180)) *
pow(sin(((ap.Longitude - $from_long) * pi() / 180) / 2), 2)))) * 2 * 6371000 as Distance
The exact thing you want.
SELECT ((ACOS(SIN(48.73233 * PI() / 180) * SIN(48.84647 * PI() / 180) + COS(48.73233 * PI() / 180) *
COS(48.84647 * PI() / 180) * COS((2.36618 - 2.41026) * PI() / 180)) * 180 / PI()) * 60 *1.1515 * 1.609344 *1000)
AS distance FROM dual;