I am using the following scripting that I found on the net to grab all postal codes between a given set coordinates.
When using it my concern is that when some postal codes being grab are greater than the distance entered; not by much - about 20 KM off.
function GetPostalCodes($latitude, $longitude, $range) {
$radius = 3959;
$north = rad2deg(asin(sin(deg2rad($latitude)) * cos($range / $radius) + cos(deg2rad($latitude)) * sin($range / $radius) * cos(deg2rad(0))));
$south = rad2deg(asin(sin(deg2rad($latitude)) * cos($range / $radius) + cos(deg2rad($latitude)) * sin($range / $radius) * cos(deg2rad(180))));
$east = rad2deg(deg2rad($longitude) + atan2(sin(deg2rad(90)) * sin($range / $radius) * cos(deg2rad($latitude)), cos($range / $radius) - sin(deg2rad($latitude)) * sin(deg2rad($north))));
$west = rad2deg(deg2rad($longitude) + atan2(sin(deg2rad(270)) * sin($range / $radius) * cos(deg2rad($latitude)), cos($range / $radius) - sin(deg2rad($latitude)) * sin(deg2rad($north))));
$return = DBSelectAllArrays("SELECT postal FROM postalcodes WHERE (latitude <= $north AND latitude >= $south AND longitude <= $east AND longitude >= $west)");
krsort($return);
if (empty($return)) return false;
return $return;
}
Is there something I am missing to get a more accurate result?
Given your comments:
$radius = 6371.0; // mean radius of Earth in km
This is taken from wikipedia, but I've seen it within a +/- 3km tolerance from other sources.
I began to question whether you were using great circle distance calculations, but this is more important for accuracy over longer distances due to the curvature of the earths surface.
Tim, you started by using a bounding box (rectangle) and then with the Haversine formula, you'll get a radius (circle), which generally is much better if you just want people within a certain distance. You don't state your purpose, but if you're looking for people who may travel a certain distance to you, you may want to consider metropolitan areas, which vary in shape. If so, look at: Canadian Metro Areas data
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
So I am trying to get the distance between two point based on Latitude and Longitude calculated and printed to the screen, it 'works' but the correct answer is way off. And by way off I mean 187 kms off in my particular case. I am not sure why, and I feel as though I am doing something really silly but I can't seem to locate the problem. Here's what I have so far:
/**
* Calculates Geographical Distance from Latitude and Longitude Pairs
*
* #param array $pair1 Array of first Pair
* #param array $pair2 Array of second Pair
*
* #return string
*/
private function _calculateGeographicalDistanceFromLatLng($pair1, $pair2)
{
$pi80 = M_PI / 180;
$pair1[0] *= $pi80;
$pair1[1] *= $pi80;
$pair2[0] *= $pi80;
$pair2[1] *= $pi80;
$r = 6372.797; // radius of Earth in km
$dlat = $pair2[0] - $pair1[0];
$dlng = $pair2[1] - $pair1[1];
$a = sin($dlat / 2) * sin($dlat / 2) + cos($pair1[0]) * cos($pair2[0]) * sin($dlng / 2) * sin($dlng / 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
$km = $r * $c / 1000;
if ($this->_unit == "all") {
$miles = $km * 0.621371192;
return array("KM" => round($km, 2), "Mile" => round($miles, 2));
} elseif ($this->_unit == "mile") {
$miles = $km * 0.621371192;
return round($miles, 2);
} else {
return round($km, 2);
}
}
When I try and have it echo the correct answer with different options, the answer is absolutely way off.
$df = new distanceAdvice("Geographic");
$result = $df->findDistance(array("53.219383", "6.566502"), array("52.090737", "5.121420"));
if (isset($result['error'])) {
echo $result['error']['msg'];
} else {
echo "The geographical distance between the two points based on Latitude and Longitude is: " . $result . " Kilometer.<br />";
}
According to the documentation, to calculate distance between 2 points you should use computeDistanceBetween(LatLngFrom, LatLngTo)
Google handled all those Mercator Projection stuff for you so I guess, rather than writing your own, you should use this API.
I know your pain about this. I've had to encode this great circle distance formula into Excel VBA for some NASA geolocation work I've volunteered for. There is confusing information about the proper formula to use on the web when you do a google search. There is the Haversine formula, and the Spherical Law of Cosines formula. Also, the ATAN2 formula is implemented slightly differently at times [some libraries do atan2(dy, dx) while other libraries (like Excel) do atan2(dx, dy]].
For the Haversine formula (see https://en.wikipedia.org/wiki/Haversine_formula), try changing the line below. The Haversine formula is not supposed to use ATAN2, and unfortunately some first results on Google searches provide the wrong formula:
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
with
$c = 2 * asin(sqrt($a));
That is the proper Haversine formula. There may be an edge case that needs to be accounted for ... my formula in Excel included:
$c = 2 * asin(min(1,sqrt($a)));
And a source for that came from the U.S. Census (though its link is no longer valid) so here is a posting that references it:
http://www.cs.nyu.edu/visual/home/proj/tiger/gisfaq.html
Also, different distance calculators you will find online use different Earth radius values, since the earth is actually not round; even the term "sea level" is not consistent and "round" across the entire Earth. So you may still find your distance calculation is slightly different from whatever you are using as a reference just because of different Earth radius values.
I have serious performance issues with a distance calculation script.
I have approximately 3000 locations (and this will eventually be doubled) in a database. The database structure is quite complex (categories, subcategories) but with time(); I saw that these query's didn't took much time.
I have a $_GET of latitude and longitude of the user and I use this calculation to determine if the location is within a certain radius:
function distance($lat1, $lon1, $lat2, $lon2, $unit) {
$theta = $lon1 - $lon2;
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;
$unit = strtoupper($unit);
if ($unit == "K") {
return ($miles * 1.609344);
} else if ($unit == "N") {
return ($miles * 0.8684);
} else {
return $miles;
}
}
// some sql queries to get the lat/lon from the locations
if ((distance($_GET["lat"], $_GET["long"], $row3["content"], $row4["content"], "K") . "") < 10) {
//push to multidimensional array
}
$row3["content"] and $row4["content"] are the latitude and longitude values. For 3000 locations, this calculation takes up to 13 seconds!
I read this:
Fastest Way to Find Distance Between Two Lat/Long Points
I think the option to draw a box, based on the $_GET of latitude and longitude could perhaps already remove the current calculation. In the sql queries I can already filter out the locations outside the 10 km range.
But I have 2 questions:
If I change the SQL to something like this: ... WHERE LAT >= x1 AND <= x2, does this affect the time of the query?
In the explanation the writer talks about "units". I've been playing around with the lat/lon values, but how do I actually calculate x1, x2, y1, y2 where the $_GET value is a point in the center with a distance of 10 km?
Thank you.
I was able to reduce the calculation time from 13 seconds to 1 second!
I did this by filtering out with mysql the locations that were not within a 10 km bounding box of my lat/long coordinates.
I used this code:
$rad = 10; // radius of bounding circle in kilometers
$R = 6371; // earth's mean radius, km
// first-cut bounding box (in degrees)
$maxLat = $_GET['lat'] + rad2deg($rad/$R);
$minLat = $_GET['lat'] - rad2deg($rad/$R);
// compensate for degrees longitude getting smaller with increasing latitude
$maxLon = $_GET['long'] + rad2deg($rad/$R/cos(deg2rad($_GET['lat'])));
$minLon = $_GET['long'] - rad2deg($rad/$R/cos(deg2rad($_GET['lat'])));
and I changed my mysql to this:
$sql2 = "SELECT ....WHERE LAT BETWEEN '".$minLat."' AND '".$maxLat."'";
$sql3 = "SELECT ....WHERE LON BETWEEN '".$minLon."' AND '".$maxLon."'";
The rest of my code and calculation is exact the same, but instead of doing 3000 calculations, mysql sweeps out the majority.
I don't know if this approach is 100% mathematically correct, but as far as I see it works very fast with minor changes to my initial coding so for my project it's great.
And of course, the source: http://www.movable-type.co.uk/scripts/latlong-db.html
I have a map whose South-West and North-East bounds I take and use it to get places between them.
Because of the date-line sometimes the bounds doesn't come as they should as Explained here.
So I thought of working on:
Get which side the North-East lat lng is compared to the South-West,
if its on the right side its fine, but if it's on the left, I have to
do something.
So to know the side, I calculated the bearing(the angle between the line connecting these SW and NE points and a vertical line). So, if the bearing is between 0-90 its proper or else not.
But the problem is:
Here the top point qualifies for a North-East with both lat and lng
positive: 78.52379, 158.70952
and
The bottom point qualifies for a South-West with both lat and lng
negative: -32.1087, -150.3139
Still the map tries to connect the points in reverse direction(may be tries for least distance) and give the bearing as 342 which I will consider as a improper points and try to reverse 1 of them :(
Looks like this an expected way to calculate the bearing, if so is there a way to solve/achieve what I wanted?
EDIT:
function _getBearing($lat1, $lon1, $lat2, $lon2) {
//difference in longitudinal coordinates
$dLon = deg2rad($lon2) - deg2rad($lon1);
//difference in the phi of latitudinal coordinates
$dPhi = log(tan(deg2rad($lat2) / 2 + pi() / 4) / tan(deg2rad($lat1) / 2 + pi() / 4));
//we need to recalculate $dLon if it is greater than pi
if(abs($dLon) > pi()) {
if($dLon > 0) {
$dLon = (2 * pi() - $dLon) * -1;
}
else {
$dLon = 2 * pi() + $dLon;
}
}
//return the angle, normalized
return (rad2deg(atan2($dLon, $dPhi)) + 360) % 360;
}
I have a list of ISO 3166 country codes (240) and a list of 8 countries/territories/regions (Australia, Denmark, Netherlands, Qatar, South Africa, UAE, UK & USA). I would like to go through the list of country codes and, for each one, work out which is the closest one from the list of 8. The metric (geographical distance, straight-line distance, driving time, etc.) isn't particularly important as it doesn't need to be perfect, just reasonable.
The list of 8 places is subject to regular change so it's impractical to do the task manually. I've tried using the Google Maps API but have so far been unsuccessful. The ideal solution would be in PHP and would result in an array with the country code as the index and closest country (from the list of 8) as the value. Any help appreciated!
The geonames project has public data that you can use
http://www.geonames.org/
Now you can get the distance between geographical coorindates.
function distance($lat1, $lng1, $lat2, $lng2, $miles = true)
{
$pi80 = M_PI / 180;
$lat1 *= $pi80;
$lng1 *= $pi80;
$lat2 *= $pi80;
$lng2 *= $pi80;
$r = 6372.797; // mean radius of Earth in km
$dlat = $lat2 - $lat1;
$dlng = $lng2 - $lng1;
$a = sin($dlat / 2) * sin($dlat / 2) + cos($lat1) * cos($lat2) * sin($dlng / 2) * sin($dlng / 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
$km = $r * $c;
return ($miles ? ($km * 0.621371192) : $km);
}
For your use case you could use the capital of the countries. For big countries you should possibly add some more countries near the boarders.
That is one solution. You could also store geometric shapes that approximate the country in a database and make exact queries...
However the starting point is the data. What do you actually want? What data do you have?
If efficiency is a problem I would recommend build a graph where every country is stored and linked with its border countries. If you only look at the border countries this greatly reduces computational effort.
I am writing a site that basically looks for places within a 25 mile radius of a lat and long using php and mysql.
I am wondering how something like this would work?
I would pass a lat and long to the scrip and have it pull out only locations that are within 25 miles of the lat and long from my Database of locations.
What is the best way to do this?
EDIT:
I found this code for calculating the distance between 2 points.
function distance($lat1, $lon1, $lat2, $lon2, $unit) {
$theta = $lon1 - $lon2;
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;
$unit = strtoupper($unit);
if ($unit == "K") {
return ($miles * 1.609344);
} else if ($unit == "N") {
return ($miles * 0.8684);
} else {
return $miles;
}
}
Is ther a way to do this calc in the MYSQL look up so I can only return if miles =< 25?
Calculating the distance using that function there is pretty computationally expensive, because it involves a whole bunch of transcendental functions. This is going to be problematic when you have a large number of rows to filter on.
Here's an alternative, an approximation that's way less computationally expensive:
Approximate distance in miles:
sqrt(x * x + y * y)
where x = 69.1 * (lat2 - lat1)
and y = 53.0 * (lon2 - lon1)
You can improve the accuracy of this approximate distance calculation by adding the cosine math function:
Improved approximate distance in miles:
sqrt(x * x + y * y)
where x = 69.1 * (lat2 - lat1)
and y = 69.1 * (lon2 - lon1) * cos(lat1/57.3)
Source: http://www.meridianworlddata.com/Distance-Calculation.asp
I ran a bunch of tests with randomly generated datasets.
The difference in accuracy for the 3 algorithms is minimal, especially at short distances
The slowest algorithm is, of course, the one with the trig functions (the one on your question). It is 4x slower than the other two.
Definitely not worth it. Just go with an approximation.
Code is here: http://pastebin.org/424186
To use this on MySQL, create a stored procedure that takes coordinate arguments and returns the distance, then you can do something like:
SELECT columns
FROM table
WHERE DISTANCE(col_x, col_y, target_x, target_y) < 25
You may want to take a look at this solution - a somewhat brilliat workaround.
You can do it easily in two steps:
Find all locations within 25 miles in each direction of the point. This will look like: WHERE lat BETWEEN $lat1 AND $lat2 AND lng BETWEEN $lng1 AND $lng2
Then loop through each result and check to see if it really is within 25 miles using your code. (i.e., Filter out those locations that are in the corners of the square.)
For the first part, here's some code I have laying around (don't remember the source):
$lat_range = $radius / ((6076 / 5280) * 60);
$lng_range = $radius / (((cos(($city['lat'] * 3.141592653589 / 180)) * 6076) / 5280) * 60);
Basically just use ($lat - $lat_range, $lat + $lat_range) and ($lng - $lng_range, $lng + $lng_range) Radius is in miles.
Obviously you can clean up the math a bit.
Edit: I forgot to mention that you would need to tweak it a bit if you need to support locations near the equator, international date line, etc. Obviously for North America, it would be fine as-is.