I have downloaded the ordnance survey which contains the postcode eastings and northings of all UK postcodes and I would like to perform a mySQL query which calculates and shows the results of a given distance.
Firstly I need to match up all records in properties to the postcode table to show each properties easting and northing joined into the results.
I then need to calculate the distance between the given postcode and each property record and display only the ones which fall within the given distance.
I have found lots of examples for using long/lat but I cannot seem to find any good examples for using the ordance survey easting and northing system. According to the ordenance survey guide I should be getting an answer in metres for using the full 6 digits.
I am also getting this error:
errorErrormessage: BIGINT UNSIGNED value is out of range in '(((mjm.pc.Eastings - 436421) ^ 2) + ((mjm.pc.Northings - 291786) ^ 2))'
When trying to test the query:
$query = "
SELECT * , SQRT( (pc.Eastings - {$e1})^2 + (pc.Northings - {$n1})^2) AS distance
FROM properties p, postcodes pc
WHERE concat( p.POSTCODE1, p.POSTCODE2 )= pc.Postcode HAVING distance <= 10
LIMIT 0 , 30
";
Can someone help me understand what I need to do to make this work please?
^ is the XOR operator, not the square. Can you try this?
SQRT( POW(pc.Eastings - {$e1}, 2) +
POW(pc.Northings - {$n1}, 2)) as distance
Related
I want to fetch 5 rows from table whose "lat" value is nearest to 30. I am developing Google MAP app, where i need the 5 nearest location from Data Base.
My table looks like that,
MySQL provides a Math function that turns negative numbers into absolute values. By using that, you can get the five closest locations whether their lat is slightly lower or higher than 30:
ORDER BY ABS(lat - 30) ASC LIMIT 5
The ASC is optional as it is the default sorting order in all DBMS (thanks Gordon).
On a map "nearest location" should be based on the distance of two points, otherwise lat 30.00, long 75.00 will be the "nearest" location.
A quite exact calculation of the distance between two points (latitude/longitude) is based on the Haversine formula:
DEGREES(ACOS(COS(RADIANS(ref_latitude)) *
COS(RADIANS(latitude)) *
COS(RADIANS(ref_longitude) - RADIANS(longitude)) +
SIN(RADIANS(ref_latitude)) *
SIN(RADIANS(latitude)))) AS distance
latitude = `lat`
longitude = `long`
ref_latitude & ref_longitude = the point you want to find the nearest locations from
`DOUBLE` should be used for calculation
This results in degrees, multiply by 111.195 for an approximate distance in kilometers or by 69.093 for miles.
As you want near locations you might go for a more simple calculation using the Pythagorean theorem
sqrt(power(lat-ref_latitude, 2) +
power((lng-ref_longitude)*cos(radians(ref_latitude)), 2))
Again multiply by 111.195 for kilometers or by 69.093 for miles.
Now simply ORDER BY this distance.
And instead of comparing to all rows in your database you should restrict the number of rows to compare, e.g.
WHERE latitude BETWEEN ref_latitude - 0.2 and ref_latitude + 0.2
AND longitude BETWEEN ref_longitude - 0.2 and ref_longitude + 0.2
Btw, some DBMSes support geospatial extensions like distance functions or geospatial indexes.
As a note, if you want to do this efficiently and you have an index on lat, then the following more complex query should perform better:
select t.*
from ((select t.*
from table t
where lat <= 30
order by lat desc
limit 5
) union all
(select t.*
from table t
where lat > 30
order by lat asc
limit 5
)
) t
order by abs(lat - 30)
limit 5;
The two subqueries can use an index on lat to avoid sorting. The outer query is then only sorting 10 rows.
I have a php script and a database of geolocations.
At the moment my users can enter their postcode, using Yahaa I get their geo location.
Now that I have their geolocation I want to write a script that loads their local businesses in order of distance. I'm guessing to do this I need to load records from the database that have the most similar coordinates to the users current geolocation. I tried this code:
$sql = "SELECT * FROM dirlistings WHERE ABS(latitude - $lat) AND ABS(longitude - $long)";
However it just displays the results in normal order.
Have I missed anything?
I think you want an ORDER clause in there somewhere.
But what you really want is the Haversine formula: MySQL Great Circle Distance (Haversine formula)
It's more complicated than you think.
This is a great article that helped me a lot. Although its written in javascript, you easily change it to php.
http://www.movable-type.co.uk/scripts/latlong.html
Note that circle distance isn't going to be precise enough if you're talking about large distances (thousands of miles, for example), as the earth's surface isn't flat. If you need a better formula for geo-distance calculation, you can use something like this:
$dlat = ((double)$lat) / 57.29577951;
$dlon = ((double)$lon) / 57.29577951;
$sql = "SELECT *
FROM dirlistings
WHERE 3963.0 * acos(sin(latitude / 57.29577951) * sin($dlat) + cos(latitude / 57.29577951) * cos($dlat) * cos($dlon - longitude / 57.29577951)) < MAX_DIST
ORDER BY acos(sin(latitude / 57.29577951) * sin($dlat) + cos(latitude / 57.29577951) * cos($dlat) * cos($dlon - longitude / 57.29577951))
";
The distances here are in miles - make sure to specify correct max distance - and this formula will give very close results for distances of even ten thousand miles. Note though that such computation is quite time- and power-intensive and if you are not dealing with large distances (i.e. nothing more than a couple hundred miles), then you should use a quicker approximation.
You've missed quite a few things. In order to do what you're trying to do, you need to compute (using the Pythagorean theorem) the distance between two points, and then order by that distance.
You can calculate distance ( (lat - $lat)^2 + (lon - $lon)^2 )^0.5 via:
SQRT(
POW(latitude - ' . $lat . ',2)
+
POW(longitude - ' . $lon . ',2)
) AS distance
Then it's as simple as:
ORDER BY distance ASC
Try ORDER BY latitude, longitude at the end. That should do it or approximately do it.
Right I have been trying to work out how to compare a given postcode to a database of say store addresses and have them ordered in terms of which one is closest to the given postcode (or ZIP code I guess).
This is mainly out of interest, rather than me asking you for advice and then selling it to a client :-O
First of all after research I discovered that you have to do distance with Lat/Long so I found an API that converts postcodes/zip codes to lat long and now my DB has a structure such as id, store_name, lat, long, postcode and I can convert a given postcode to a lat long.
But how in SQL do I make a query for the ones closest to a given lat long?
Try something like this:
// get all the zipcodes within the specified radius - default 20
function zipcodeRadius($lat, $lon, $radius)
{
$radius = $radius ? $radius : 20;
$sql = 'SELECT distinct(ZipCode) FROM zipcode WHERE (3958*3.1415926*sqrt((Latitude-'.$lat.')*(Latitude-'.$lat.') + cos(Latitude/57.29578)*cos('.$lat.'/57.29578)*(Longitude-'.$lon.')*(Longitude-'.$lon.'))/180) <= '.$radius.';';
$result = $this->db->query($sql);
// get each result
$zipcodeList = array();
while($row = $this->db->fetch_array($result))
{
array_push($zipcodeList, $row['ZipCode']);
}
return $zipcodeList;
}
UPDATE:
There is some discussion about efficiency. Here is a little benchmark for you with this query. I have a database that contains EVERY zipcode in the US. Some of them are duplicate because of the way zipcodes work (outside the scope of this topic). So I have just under 80k records. I ran a 20 mile radius distance on 90210:
SELECT distinct(ZipCode) FROM zipcodes WHERE (3958*3.1415926*sqrt((Latitude-34.09663010)*(Latitude-34.09663010) + cos(Latitude/57.29578)*cos(34.09663010/57.29578)*(Longitude- -118.41242981)*(Longitude- -118.41242981))/180) <= 20
I got back 366 total records and Query took 0.1770 sec. How much more efficient do you need?
check out this great open source project
Disclaimer: Not my project, and nor am I contributor. Purely a recommendation.
See this answer to a previous question for an example of calculating a bounding box before querying MySQL. This allows the complex formula in the MySQL query to run against a subset of the database entries, rather than against every entry in the table.
I am writing a tool for a game that involves calculating the distance between two coordinates on a toroidal plane 500 units across. That is, [0,0] through [499,499] are valid coordinates, and [0,0] and [499,499] are also right next to each other.
Currently, in my application, I am comparing the distance between a city with an [X,Y] location respective to the user's own [X,Y] location, which they have configured in advance.
To do this, I found this algorithm, which kind of works:
Math.sqrt ( dx * dx + dy * dy );
Because sorting a paged list by distance is a useful thing to be able to do, I implemented this algorithm in a MySQL query and have made it available to my application using the following part of my SELECT statement:
SQRT( POW( ( ".strval($sourceX)." - cityX ) , 2 ) + POW( ( ".strval($sourceY)." - cityY ) , 2 ) ) AS distance
This works fine for many calculations, but does not take into account the fact that [0,0] and [499,499] are kitty-corner to one another.
Is there any way I can tweak this algorithm to generate an accurate distance, given that 0 and 499 are adjacent?
I assume you mean wrapping coordinates and nothing spherical shaped. Like a flat piece of paper where the ends are magically connected to each other.
That means that for a map sized 500x500, the distance in the x (or y) direction is at most 250. (If it would be more than 250 steps, we could better walk 500-x steps backward.)
A simple way to fix this, would be
dx = Math.abs(dx);
dy = Math.abs(dy);
if (dx > 250)
dx = 500 - dx;
if (dy > 250)
dy = 500 - dy;
distance = Math.sqrt ( dx * dx + dy * dy );
Update (torus):
OK, from your own comments, it seems that you do mean the torus -- the surface of a donut -- and not the sphere. (This is a big difference, and you should edit your question: calling it a sphere is wrong.)
For this, the answer is fairly simple -- the cartesian formula you give is more or less correct. However, you need to wrap distances around so that anything greater than or equal to 250=500/2 gets translated down to between 0 and 250.
So the answer is something like (I don't know PHP at all, so this may need to be modified for syntax)
dx1 = min(dx, 500-dx)
dy1 = min(dy, 500-dy);
distance = Math.sqrt(dx1*dx1 + dy1*dy1);
(This assumes that you have defined dx and dy to be the absolute value of the differences.)
Just found this routine which implements the same calculation in a nicely-packaged function.
Original Answer (sphere):
You haven't explained how your (x,y) coordinates map to points on the sphere!
There are (literally) an infinite number of choices, each corresponding to a different map projection, and the formula is different for each of them. Note that no matter what choice you make, the meaning of the two coordinates is very different.
If your (x,y) are really longitude and latitude, for example, there are plenty of canned formulae (i.e., haversine) but you'll have to first translate 0->499 to 0->360 degrees for longitude and -90->90 degrees for latitude. (Note that lon and lat behave very differently on the sphere!)
But I emphasize that any choice you make will distort from the flat geometry that you get if you plot in (x,y) versus the way it really looks on the sphere.
(Finally, if you really mean that the top edge is the same as the bottom and the right the same as the left, then you probably have a torus and not a sphere!)
If you know latitude and longitude of two points - you could use haversine formula to compute distance between two points on sphere.
But as I understood you want formula which is accurate for nearly antipodal points. Haversine formula fails here. In such case you need Vincenty's formula which is accurate even in antipodal cases.
http://en.wikipedia.org/wiki/Great-circle_distance#Formulae
It sounds like you are simply using a special finite Cartesian space that is "tiled". In this case each object does not have a unique position. Instead of (x, y) it is (x + i*w, y + j*h) for all possible integer values i and j and where w and h are the widths and heights of your "window" respectively.
Obviously then the distance is not unique but the minimum distance is which is just min(d(p1,p2))) over all i, j.
If your coordinates are wrapped then you just need to compute it for i=-1,0,1 and j=-1,0,1 then take the smallest one.
That general algorithm is fine for rectangular coordinate systems or very sort distances in spherical coordinates, but it's not appropriate for a spherical coordinate system.
I think a better approach would be based on latitude and longitude, like this:
http://jan.ucc.nau.edu/~cvm/latlongdist.html
MySQL has geo-coding built into it. Why not use that?
http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
Although some of the answers here were very close, the problem was finally solved with this SELECT segment:
SQRT( POW( LEAST( ABS($sourceXstr-cityX), ( 500 +LEAST($sourceXstr,cityX)-GREATEST($sourceXstr,cityX))) , 2 ) + POW( LEAST( ABS($sourceYstr-cityY), ( 500 +LEAST($sourceYstr,cityY)-GREATEST($sourceYstr,cityY))) , 2 ) ) AS distance
I am writing answer if two coordinates are in 2 dimensional plane where ends are not meeting each other which OP has not asked. But it might help someone in future.
If your points are in a 2 dimensional plane, the distance between points (x1, y1) and (x2, y2) is given by the Pythagorean theorem:
d = squareroot( square(x2 - x1) + square(y2 - y1) )
In PHP,
$x1 = $y1 = 2;
$x2 = $y2 = 5;
$distance = sqrt( pow(($x2-$x1),2) + pow(($y2-$y1),2) );
echo $distance; // 4.2426406871193
I have just been developing a postcode distance calculator for my Dads company, where all our customers are kept on file and every time a new potential customer makes an enquiry the system will check against all other customers' postcodes. The problem is that it doesn't yet do the distance calculation. Typing in a DT1 postcode with the distance set to 5 miles says that DT1, 2 and 3 are close by. DT2 says DT1 and 2, DT3 says DT1, 3 and 4. This doesn't make any sense. Yet if I put in BH2 with a distance of 50 miles it will bring back Bournemouth, Dorchester, Portsmouth, Bath, Southampton and a couple of other towns. These are all correct - however I do not know what is going on with the DT postcodes. DT11 doesn't bring back any (under 5 miles), nor does DT10 (except for themselves).
I first of all used the root of (x * x + y * y) - I believe this is called Pythagoras' theorem.. not sure since it have been years since I used that term!!
I have also tried the following:
$sinstuff = sin(deg2rad($latitude))*sin(deg2rad($latitude2));
$cosstuff = cos(deg2rad($latitude))*cos(deg2rad($latitude2))*cos(deg2rad($longitude-$longitude2));
$sumstuff = $sinstuff + $cosstuff;
$acosstuff = rad2deg(acos($sumstuff));
$distance = $acosstuff*60*1.1515; //dunno what the 60 and 1.1515 is all about - I got this formula off a website.
This is the formula which seems to be bringing up the weird results. I also notice that the exact distance between DT11 and DT11 is something like +/- 0.00000989{and lots of other numbers}. This also seems a bit odd since, surely, the distance between DT11 and itself is 0...
Has anyone ever done anything like this successfully before?
I do have a massive batch of files - about 158MB in total - containing every UK postcode in full, with its corresponding latitude and longitude. My aim is, rather than comparing the specified full postcode to every other full postcode, to work out which area codes are less than a certain distance away then compare the specified postcode to all the full postcodes which are within these areas. Is this efficient? Is there a more efficient way?
Any help with this would be greatly appreciated.
Regards,
Richard
PS I know there are some sites out there which tell you how to work out distances but I dont seem to be able to get any of them working correctly (as you can tell from above).
PPS I know I can use the Google API for this - but this is not an option since this is going to be an offline application running on a local installation of wamp server. It is for use at trade shows where we cannot guarantee an internet connection.
You're use the haversine formula:
$l1 ==> latitude1
$o1 ==> longitude1
$l2 ==> latitude2
$o2 ==> longitude2
(taken from http://www.go4expert.com/forums/showthread.php?t=339)
function haversine ($l1, $o1, $l2, $o2)
{
$l1 = deg2rad ($l1);
$sinl1 = sin ($l1);
$l2 = deg2rad ($l2);
$o1 = deg2rad ($o1);
$o2 = deg2rad ($o2);
return (7926 - 26 * $sinl1) * asin (min (1, 0.707106781186548 * sqrt ((1 - (sin ($l2) * $sinl1) - cos ($l1) * cos ($l2) * cos ($o2 - $o1)))));
}
That should give you the distance between both points.
I think the Post Office maintains a Postcode-Address file which lists
every UK postcode and its associated address (not sure if it has
lattitude and longitude information). I've seen copies of this (maybe
not totally up to date) on cover discs on various PC magazines in the
past. I'm sure you have to pay for the latest version, but you might be
able to get a starting point by checking out the cover discs from back
issues.