20

Update 16th November 2012

I would like to raise this question again, offering with a new bounty for a solid, good solution. It seems that only the solution (shubhansh's answer) does not effectively work now. I will explain why.

Firstly, this is the live map I have with radiuses and people, the radiuses are in red and the people are in blue.

enter image description here

As you can see, there are two people in this map with eight radiuses, basically I am getting only the person which is Person A, but I am not getting Person B, I'm guessing that the SQL is not correctly picking it up which I need it to be precise and accurate from the person's radius and the marker radiuses.

It looks like what is picked up are are inside the radiuses, not those who overlap a radius, I need it to be able to pick up any results for any radiuses that overlap each other.

I am looking for a precise and accurate SQL than shubhansh's answer. You may read below to read how exactly I need the query to act and pick up accurate people.

The data, PEOPLE:

+-----------+-----------+--------+
| latitude  | longitude | radius |
+-----------+-----------+--------+
| 51.517395 | -0.053129 | 5.6    |
| 51.506607 | -0.116129 | 0.7    |
+-----------+-----------+--------+

Please note that radius is in kilometers.

+-----------+-----------+-----+
| latitude  | longitude | km  |
+-----------+-----------+-----+
| 51.502117 | -0.103340 | 0.3 |
| 51.498913 | -0.120850 | 0.7 |
| 51.496078 | -0.108919 | 0.7 |
| 51.496506 | -0.095873 | 0.7 |
| 51.503399 | -0.090723 | 0.7 |
| 51.508049 | -0.100336 | 0.7 |
| 51.508797 | -0.112610 | 0.7 |
| 51.505535 | -0.125227 | 0.7 |
| 51.502331 | -0.108061 | 0.7 |
+-----------+-----------+-----+

The current SQL I use:

SELECT ppl.latitude,
       ppl.longitude,
       ppl.radius
FROM 
(
    people ppl
),
(
    SELECT latitude, longitude 
    FROM radiuses
) AS radius
WHERE (POW((ppl.longitude - radius.longitude) * 111.12 * COS(ppl.latitude), 2) + POW((ppl.longitude - radius.longitude) * 111.12, 2)) <= 4
GROUP BY ppl.id

The data for MySQL which you can use to test your query,

INSERT INTO radiuses (id, latitude, longitude, km) VALUES ('1', '51.502117', '-0.103340', '0.3'), ('2', '51.498913', '-0.120850', '0.7'), ('3', '51.496078', '-0.108919', '0.7'), ('4', '51.496506', '-0.095873', '0.7'), ('5', '51.503399', '-0.090723', '0.7'), ('6', '51.508049', '-0.100336', '0.7'), ('7', '51.508797', '-0.112610', '0.7'), ('8', '51.505535', '-0.125227', '0.7'), ('9', '51.502331', '-0.108061', '0.7');

INSERT INTO people (id, latitude, longitude, radius) VALUES ('1', '51.517395', '-0.053129', '5.6'), ('2', '51.506607', '-0.116129', '0.7');

Old summary

Note: all the latitudes and longitudes are just randomly made.

I have a map applet which a user can place his radius of a lat/lng location, with a 1km radius.

Now, there is another user that can put his radiuses, at any location on the map, each with 1km radius (same as the user above).

Like this User A is red and User B is blue.

enter image description here

Basically User A stores his radiuses in a table that looks like this:

+-----------+---------+-----------+-----------+
| radius_id | user_id | latitude  | longitude |
+-----------+---------+-----------+-----------+
|         1 |       1 | 81.802117 | -1.110035 |
|         2 |       1 | 81.798272 | -1.144196 |
|         3 |       1 | 81.726782 | -1.135919 |
+-----------+---------+-----------+-----------+

And User B stores his radius in another table that looks like this - (note: they can only store 1 coordinates per account):

+---------+-----------+-----------+
| user_id | latitude  | longitude |
+---------+-----------+-----------+
|       6 | 81.444126 | -1.244910 |
+---------+-----------+-----------+

I want to be able to pick up those users that fall within the defined radiuses, even if the radius circles are touching, in the map picture. Only marker C would be able to pick up the single radius, when A and B do not.

I'm sure this is possible, but I do not know how to come up with this kind of system in MySQL.

I found this on the Google Developers site it's close but not just what it performs I need.

EDIT: I have found a better one, this is very close, but still not what I am looking for, since it uses 1 bound of latitude and longitude coordinates when I have multiple in a table.

Community
  • 1
  • 1
MacMac
  • 34,294
  • 55
  • 151
  • 222
  • This may be an answer that gives you a hint http://stackoverflow.com/questions/11502469/find-records-with-lattitude-and-logintude/11502530#11502530 you just add a `WHERE distance < 1234` to the query. – fdomig Jul 24 '12 at 10:35

3 Answers3

14

For solving this you need to understand equation of circle, which is something like this For any point (x,y) to fall within circle with center (x1, y1) and radius r units is

(x-x1)^2 + (y - y1)^2 <= r^2

where a^b = a to the power b

Here in your case User B's (latitude, longitude) are the center of circle, User A's (latitude, longitude) are the points (x,y) and radius = 2kms.

But basic problem is of changing degrees of latitudes to longitudes, so here is solution, 1 degree = 111.12 km. So to keep units same on both side of equation, we will convert it to Kms

So our final equation becomes:

((x-x1)*111.12)^2 + ((y-y1)*111.12)^2 = 4      (=2^2) 

SQL statement for the same should look something like this

SELECT A.user_id, A.radius_id, A.latitude, A.logitude
FROM UserA AS A, 
     (SELECT user_id, latitude, longitude 
       FROM UserB 
       WHERE user_id = 8) AS B
WHERE (POW((A.latitude-B.latitude)*111.12, 2) + POW((A.longitude - B.longitude)*111.12, 2)) <= 4
/* **Edit** Here I have used (A.longitude - B.longitude)*111.12, for more accurate results one can replace it with (A.longitude - B.longitude)*111.12*cos(A.latitude)) or (A.longitude - B.longitude)*111.12*cos(B.latitude)) 

And, as i have suggested in the comments that first filter some records based on approximation, so whether one uses A.latitude or B.latitude it will not make much difference */

Hope this will help...

jsist
  • 5,223
  • 3
  • 28
  • 43
  • You can factor out the 111.12^2. Then divide both sides by that amount. WHERE (POW((A.latitude-B.latitude), 2) + POW((A.longitude - B.longitude), 2)) <= 4/(111.12^2). – walrii Jul 20 '12 at 22:20
  • 2
    One degree = 111.12 km only at the equator; as you move towards the poles, latitude remains relatively constant but longitude approaches zero, so this solution will become increasingly inaccurate the farther away from the equator you are. – Ethan Brown Jul 20 '12 at 22:44
  • I think model given above is fine. If one needs more accurate results pertaining to distance, they can use formulas given here. [http://www.movable-type.co.uk/scripts/latlong.html ] and make suitable adjustments in the query given – jsist Jul 25 '12 at 13:24
  • For speeding up the query, I would recommend you to first filter out the solution, just by filtering longitude and latitude +- 2/112.12 degrees and then apply distance formula on the result formula, for speeding up the result. – jsist Jul 25 '12 at 13:28
  • @shubhansh Can I put the defined kilometers which is in the column named `km` from table `UserB`? They are all not 1km, so whereabouts should I put them? Also, I kind of don't understand what you mean by speeding it up by filtering it, exactly how would I filter them out them apply the distance formula on the result? – MacMac Jul 26 '12 at 17:44
  • @Burning the Codeigniter, Yes, you can have a column for radius in table UserB say "radius" and you can as well have custom radius for User A say column "radius" in table UserA(if 1Km default, then replace A.radius by 1), then query will change to SELECT A.user_id, A.radius_id, A.latitude, A.logitude FROM UserA AS A, (SELECT user_id, latitude, longitude FROM UserB WHERE user_id = 8) AS B WHERE (POW((A.latitude-B.latitude)*111.12, 2) + POW((A.longitude - B.longitude)*111.12, 2)) <= POW((A.radius + B.radius), 2); – jsist Jul 27 '12 at 05:17
  • @Burning the Codeigniter, By speeding up i mean, There will be many many points in UserA, and if you are going to apply accurate formula for distance then it is surely going to take hell lot of time. So, solution for that is, First get only those points that falls in the range of approximately close to the UserB coordinates and then apply the distance formula or normal formula(that involves simply POW()) even to those subset of values. This can be achieved by modifying the query a bit. The query is written in next comment(Sufficeint space is not left here) – jsist Jul 27 '12 at 05:38
  • @Burning the Codeigniter, the FROM clause will be "FROM UserA A JOIN UserB B ON ((ABS(A.latitude - B.latitude) <= ((A.radius + B.radius)/112.12)) AND (ABS(A.longitude - B.longitude) <= ((A.radius + B.radius)/112.12)))". Since controversy was on use of 112.12, you can use smaller value say 50 instead of 112.12(since its just approximation). Note here I have assumed that both userA and UserB are on same side of Equator and Prime Meridian(i.e. both are either in North hemisphere or in south hemisphere). For higher precision you need to add some more conditions. – jsist Jul 27 '12 at 06:02
  • @shubhansh Are you sure this formula works, because I cannot have users included in the results when the radiuses touch each other because I have 2 radiuses which overlap each other but they do not get included in the result only those markers that are inside radiuses not the radius - I hope you understand what I mean. – MacMac Jul 27 '12 at 13:44
  • If you want to ommit those on radius, simply replace condition "<=" or ">=" with "<" or ">" whichever is the case. I guess so, that it should work. Moreover, you apply the query and have sample data and test it first. It is not at all going to take more than 1-2 hours of yours to finally come to conclusion(if its working or not). You can use latitudes and longitudes from google earth, feed them to your database and simply come to conclusion by running the query i told you. – jsist Jul 27 '12 at 13:52
  • Okay, it works slightly accurate but I will report back to you if I have any issues. Do you have any SQL query that would perform precision accurate? – MacMac Jul 28 '12 at 14:51
  • I have made an edit in answer by adding comment. Hope, it will surely increase accuracy. – jsist Jul 28 '12 at 16:49
  • @shubhansh I have updated my post in order to require a more effective solution. If you can. – MacMac Nov 17 '12 at 13:17
7

At the heart of your problem is the question "how do I know if two circles overlap". The answer to that is "if the distance between their centers is less than the sum of their radii". So what you're looking for is how to determine the distance between two points.

The other answer is treating latitude and longitude as if they comprise a Cartesian plane. which they do not (longitude tends towards zero as you approach the poles from the equator). Now, as an approximation, it may work just fine for your solution, depending on the accuracy needed by your solution. On the other hand, if you need this to be very accurate, you need the Haversine formula. There's a great description of how to implement it in MySQL here:

http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL

From slide 7 of that presentation, you have the following formula:

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)))

Note that the first number is the mean radius of the earth in miles; change that to 6371 for kilometers.

How you use this calculated distance is going to depend on details not included in your post, such as the number of points you're dealing with, the geographic dispersion, any performance requirements, and whether or not the data is static or being continually updated.

I mention these things because performance is going to be an issue, especially if you have any significant amount of data and/or it's being continuously updated (like user's locations based on their phone's GPS data).

One way you can help with the performance issue is use squares instead of circles, and use the approximation of one degree = 111.12 km. That way you can automatically cull out any points that are obviously far away from one another. Then you're left just calculating the Haversine formula only for the handful of points that fall within the area of interest.

I hope this is helpful in pointing you in the right direction.

Ethan Brown
  • 26,892
  • 4
  • 80
  • 92
6

The essential point of your geometry is that two circles overlap if the distance between their centers is less than the sum of their radii. Since we're doing a comparison, we can use the square of the distance, since that avoids a square root operation. In the original, each radius is fixed at 1, the sum of the two radii is 2, and the square of the sum is 4.

There's a big difference between the original question and the new question. In the first you've got circles of fixed radius and the second you've got circles of varying radius. The constant 4 in the comparison expression [...distance^2...] <= 4 needs to be replaced, since that's an artifact of the fixed radius of the original. To implement this, add the km field into the query. And as you should check, you weren't using ppl.radius in the WHERE filter, so it's hardly surprising that varying that value didn't change your query results.

SELECT ppl.latitude, ppl.longitude, ppl.radius
FROM 
  ( people ppl ),
  ( SELECT latitude, longitude, km FROM radiuses ) AS B
WHERE [...distance^2...] <= POW( ppl.radius + B.km, 2)

I should say that this question took far longer to understand than it should have, because you're calling the entity-that's-not-a-person a "radius", when really you've got a property that ought to be called 'radius' on two different entities. So name that other entity something descriptive.

eh9
  • 7,340
  • 20
  • 43