I have a database of geocoded entries. I need to determine which two entries are the furthest apart from a subset of the total entries. For example, I select a list of 10 entries then, from that list, determine which two places represent the greatest distance within that list.
I cannot wrap my head around how to approach this. I've considered using radians even, but nothing seems to meet the requirement.
FYI, LAMP stack going here...
The following query will calculate the distance between all your points and return the two with the biggest distance:
SELECT coor1.longitude as lon1,
coor1.latitude as lat1,
coor2.longitude as lon2,
coor2.latitude as lat2,
(ACOS(
COS(RADIANS(coor1.latitude)) *
COS(RADIANS(coor1.longitude)) *
COS(RADIANS(coor2.latitude)) *
COS(RADIANS(coor2.longitude)) +
COS(RADIANS(coor1.latitude)) *
SIN(RADIANS(coor1.longitude)) *
COS(RADIANS(coor2.latitude)) *
SIN(RADIANS(coor2.longitude)) +
SIN(RADIANS(coor1.latitude)) *
SIN(RADIANS(coor2.latitude))
) * 6378 --- Use 3963.1 for miles
)
AS DistanceKM
FROM coordinates coor1,
coordinates coor2
WHERE NOT (coor1.longitude = coor2.longitude AND coor1.latitude = coor2.latitude)
ORDER BY DistanceKM DESC
LIMIT 1; --- Only the biggest
Now I recommending doing those calculations before hand and storing the result in a separate table.
By the looks of it, this could be solved by first finding the convex hull of the points (using Graham's scan, for instance), and then doing rotating calipers for the diameter on that.
Brute force approach:
Find the center of your list of ten by averaging the latitude and longitude values.
For each (latitude,longitude) pair in your database, use the great circle formula to calculate distance from the center from step (1)
Pick greatest two distances.
Obvious optimization: break the world in to N "squares" (e.g., 10 degrees longitude, 10 degrees latitude) and pre-compute the great circle distance between the centers of of each pairing. Store this in the database. Now you can quickly look for the furthest away "squares" and only check (latitude,longitude) pairs inside those tiles.
Here is the algorithm implemented in PHP for the distance between two points based on latitude and longitude.
Note that if the "subset of total entries" is large, you quickly have quite a few computations to do. If that's the case, you may want to consider pre-calculating distances between city pairs.
EDIT: Why 10 degree optimization does not work:
Take four squares as shown below
-------------------
| | |
| A | B |
| | |
|_______1|________|
| |2 |
| C | D |
| | |
|_______3|________|
By only measuring the centers of the squares and comparing those distances, you get A and D are further apart than A and C. However, cities 1 and 3 are clearly further apart than 1 and 2.
Related
Right now I have a table of 100 million inserts:
CREATE TABLE o (
id int UNIQUE,
latitude FLOAT(10, 8),
longitude FLOAT(11, 8)
);
On my back end I am receiving a user lat/long and trying to return everything within x distance of that.
Instead of doing the distance formula on every single result I was thinking I could possibly calculate the maximum lat/long for X distance.
So we are sort of creating a square by finding the max lat/min lat, max long/min long.
Once we have these max values we would do the query on this range of values thus making our subset significantly smaller to then do the actual distance formula on (i.e., finding the values within X distance).
So my question to you is:
What makes me run faster?
Option 1)
Distance formula on 100 million entries to get the set.
Option 2)
Instead of doing the distance formula on the set of 100 million entries we calculate the min/max lat/long.
Select the values in that range from the table of 100 million entries
Do the distance formula on our new smaller set.
Option 3)
Something exists already for this in SQL
If option 2 is faster the next issue is actually solving that math problem.
If you want to look at that continue reading:
Lat/Long distance formula
dlon = lon2 - lon1
dlat = lat2 - lat1
a = (sin(dlat/2))^2 + cos(lat1) * cos(lat2) * (sin(dlon/2))^2
c = 2 * atan2(sqrt(a), sqrt(1-a))
d = R * c
Obviously we can rearrange this because D (assume 1 mile), and R (is the radius of the earth) is a set value so we get D/R = C.
The problem then comes in to how do we calculate C/2 = atan2(sqrt(a), sqrt(1-a))?
1 -- 100M rows is a lot to scan and test. It's OK do do once in a while, but it is too slow to do a lot.
2 -- Using a pseudo-square bounding box and doing
WHERE latitude BETWEEN ...
AND longitude BETWEEN ...
is a good first step. The latitude range is a simple constant times X; the longitude range also divides by cos(latitude).
But the problem comes when you try to find just those rows in the square. Any combination of index on latitude and/or longitude, either separately or together, will only partially filter. That is, it will ignore longitude and give you everything within the latitude range, or vice versa. That might get you down to 100,000 rows to check the distance against. That's a lot better than 100,000,000, but not as good as you would hope for.
3 -- http://mysql.rjweb.org/doc.php/latlng Does get down to the square, or very close. It is designed to scale. I have tested only 3M rows, not 100M, but it should work fine.
The main trick is to partition on latitude, then have longitude be the first column in the PRIMARY KEY so that InnoDB will cluster the nearby rows nearby in the partition(s). If you look for all rows within X miles (or km) it might look at (and compute the great-circle-distance) for about twice as many rows as necessary, not 100K. If you want to find the nearest 100 items, it might touch about 400 (4x).
As for SPATIAL index, you might want to upgrade to 5.7.6, which is when ST_Distance_Sphere() and ST_MakeEnvelope() were added. (MakeEnvelope is only marginally more convenient than building a Polygon yourself -- it has flat-earth syndrome.)
I am having some trouble with figuring out how to do this. What I have is a list of 160K locations on an Access table with lat and long coordinates for each. I am trying to find out how to create a column that compares 1 item on the list to the rest of the items to bring back the closest distance in miles.
I've figured out how to use the haversine formula to make a 1 to 1 comparison but I am lost in trying to automate the rest.
This is basically what I want to try to produce...
Loc_ID Loc_Lat Loc_Long Min_Miles_Away
1 33.537214 -81.687378 674.48
4 42.16584 -87.845117 11.83
5 41.99558 -87.869057 11.83
6 41.85325 -89.486883 83.75
Explanation to the table...
Location 1 is closest to location 5 (674.48 miles apart)
Location 4 is closest to location 5 (11.83 miles apart)
Location 5 is closest to location 4 (11.83 miles apart)
Location 6 is closest to location 5 (83.75 miles apart)
Any help would be appreciated.
You can do a cartesian join, i.e. a join without a where. It will join each row with every other row. You can do that by simply writing the SQL into the SQL view of the query.
SELECT *
FROM locations a, locations b
Next you can calculate the distance (I guess you have that code already, so just insert the function) on that table.
Finally you can group by MIN.
SELECT loc_id, loc_lat, loc_long, MIN(calulated_distance) as min_miles_away
FROM myCalculatedQuery
I doing an iPhone application which has an external database in this case i am using mySql.
i wan to store coordinates into the database and later i want to search the database using a coordinates to return only the coordinates that are near to the coordinates i am querying.
Example
-----------------
Database |
-----------------
1- Coordinate A |
2- Coordinate B |
3- Coordinate C |
-----------------
search using Coordinate D lets say coordinate A and Coordinate C are near to Coordinate D lets say with 5KM radius distance then my query result to show
Coordinates A, Coordinates C
and ignore Coordinate B as it is not near to Coordinate D
Does anyone have any idea on What format should my database be in ?
How to query the database for near by coordinates ?
Store your coordinates as 2 columns, each a float. So you'd have your table structure like:
lon float(X,Y)
lat float(X,Y)
Where X and Y are your desired precision.
For searching you can use the Haversine distance formula for geo / spatial searching with a fixed distance (your 5KM)
Lets say I have a table venues with following columns:
id
user_id
name
latitude
longitude
The latitude and longitude are kept as FLOAT(10,6) values. As different users add venues, there are venue duplicates. How can I select all the duplicates from the table in range up to lets say 50 metres (as it might be hard to achieve as the longitudial meter equivalents are different at different latitudes, so this is absolutely aproximate)? The query should select all venues: VenueA and VenueB (there might be VenueC, VenueD, etc) so that I can compare them. It should filter out venues that are actually one per location in the range (I care only for duplicates).
I was looking for an answer but had to settle with answering myself.
SELECT s1.id, s1.name, s2.id, s2.name FROM venues s1, venues s2
WHERE s2.id > s1.id AND
(POW(s1.latitude - s2.latitude, 2) + POW(s1.longitude - s2.longitude, 2) < 0.001)
The first condition is to select only half of matrix as order of similar venues is not important. The second one is simplified distance calculator. As user185631 suggested haversine formula should do the trick if you need more precision but I didn't need it as I was looking for duplicates with the same coordinates but couldn't settle with s1.latitude = s2.latitude AND s1.longitude = s2.longitude due to float/decimal corruption in my DB.
Of course checking this at insert would be better but if you get corrupt DB you need to clean it somehow. Please also note that this query is heavy on server if your tables are big.
Create a function which computes distances between lat/lons. For small/less accurate distance (which is the case here) you can use the Equirectangular approximation (see section here: http://www.movable-type.co.uk/scripts/latlong.html). If the distance is less than your chosen threshold (50m), then it is a duplicate.
Determine what 50 meters is in terms of lat and long. Then plus and minus that to your starting location to come up with a max and min for both lat and long. Then...
SELECT id FROM venues WHERE latitude < (your max latitude) AND latitude > (your min latitude) AND longitude < (your max longitude) AND longitude > (your min longitude);
Converting meters to lat/long is very tricky as it depends on where the starting point is on the globe. See the middle section of the page here: http://www.uwgb.edu/dutchs/usefuldata/utmformulas.htm
I'm using MySQL to pull lat longs from a database and check whether or not they are in a particular neighborhood.
Everything works great, except if the point is on the border between two neighborhoods. Then, the point is included in both neighborhoods. It gets duplicated. What's the best way to handle points that are on borders?
Each point should be counted in only one neighborhood-- not both. Also, I don't want, for example, neighborhood A to get all of the border cases, but the bordering neighborhood, neighborhood B, to get zero cases.
Imagine that the point 30.3030, -70.7070 lies on the border between Newport and Oldport. Unfortunately, the point gets counted twice. It gets recorded as being in both Oldport and Newport.
type | latitude | longitude | neighborhood
small | 30.3030 | -70.7070 | Newport
small | 30.3030 | -70.7070 | Oldport
small | 30.3344 | -70.7274 | Anotherport
I use the select statement below:
SELECT t.type, t.latitude, t.longitude, s.neighborhoods
FROM my_type_table t, neighborhood_shapes s
WHERE myWithin(POINTFROMTEXT( CONCAT( 'POINT(', t.latitude, ' ', t.longitude, ')' ) ) , s.neighborhood_polygons )) = 1
my_type_table has columns:
type (VARCHAR)
latitude (decimal)
longitude (decimal)
...and neighborhood_shapes has columns:
neighborhoods (VARCHAR)
neighborhood_polygons (geometry)
I use the myWithin function to test whether the point is in one neighborhood or another. Here's a link to it: myWithin function at mySQL forum . The function returns 1 when the point is in the polygon and 0 when it isn't in the polygon.
How would you solve this problem? Any advice?
Okay, I figured it out. There was a very small overlap in some of the polygons. So, when the function ran it put the point in both neighborhoods.
Thank you.
-Laxmidi