I have this code to find the distance between two sets of GPS coordinates, I got the code from elsewhere on the net.
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;
}
}
The ouput returned looks like this: 25.44049 but if I do
$distance_output = distance(-50.12345, 100.1235,-60.12345,120.12345,'km');
echo $distance_output . '<br />;
echo $distance_output - 15.12345;
it outputs like this:
15.12345
1.23639038e10
These are just made up numbers, but you can see the output of distance() looks like a number but then when I subtract the same number from it, it spits out a wierd exponential number. Any ideas?
Thanks a lot
Why is PHP printing my number in scientific notation, when I specified it as .000021?
Use number_format()
I tried your code (you just missed a simple quote), and the output is 1042.1216629565 and 1026.9982129565, seems ok to me.
If I substract 1042.1216629565 to 1042.1216629565, the output becomes 3.092281986028E-11 : note the minus character after the e.
This number is equal to 0.00000000003092281986028, not zero. This difference is common with floating point computation. It can be explained by rounding errors in value binary coding.
See wikipedia : http://en.wikipedia.org/wiki/Floating_point and particularly section about rounding.
If you want to display the result, use number_format()
If you want to compare two floating point values, you'll have to do use something like that :
$epsilon = 1e-6;
if (abs($value1-$value2) <= $epsilon){
}
Related
I've been making search functionality that takes in two fields, Start Postcode and End Postcode. The search is supposed to query the database and collect any records that fall into the radius for both postcodes. I can retrieve the records but after, I need to get the distance between the postcodes. I know how to use the GMaps API to do this but I need to do it locally with PHP for performance reasons (Looping over multiple records etc). It would be very bad for performance to do this with an API. Any suggestions?
Just to be clear: I need a code solution to calculate distance between two postcodes without API use. Is this possible and if so how?
PHP is preferred but even just the logical idea represented in any language is okay for me.
If you have latitude and longtitude you can calculate the distance between two points without api just using this code:
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;
}
}
echo distance(32.9697, -96.80322, 29.46786, -98.53506, "M") . " Miles<br>";
echo distance(32.9697, -96.80322, 29.46786, -98.53506, "K") . " Kilometers<br>";
echo distance(32.9697, -96.80322, 29.46786, -98.53506, "N") . " Nautical Miles<br>";
Store the Lat Lon when you save the record and then work it out from that. It does mean a call to Google Maps API on save though.
I know this is a bit late, but it may help other people in future.
First, you can get Postcode -> Lat/long databases for free.
Here's a useful site which has plenty of geo mapping data like that:
https://www.freemaptools.com/download-uk-postcode-lat-lng.htm
Before you use such calculations though, you should think about what you want - straight-line 'As the crow flies' distances or driving distances. They are not the same!
To make a point, have a look at the Google Map: https://www.google.co.uk/maps/#51.4226247,-3.8874075,8.88z
The straight-line distance (calculated using Lat/Long) between, for example, Ilfracombe and Swansea is going to bear not relationship to the driving distance where you hav eto go up to the Severn bridge.
Calculating straight line distances - cheap and quick but inaccurate.
Calculating driving distances - accurate but very difficult to do yourself - an API, and its costs are necessary if you need this
I have seen all past examples of this error but the examples are for a date function. My problem is different because I am using maths function and it throws me this error:
A non well formed numeric value encountered on line 257
Below is my code snippet - I am doing my code on cakephp3.x
foreach($query as $row)
{
$lat=$row->lat.'<br>';
$lng=$row->lng.'<br>';
$lat1=$formlatitude;
$lon1=$formlongitude;
$lat2=$lat;
$lon2=$lng;
$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);
$miles=$miles * 1.609344;
//echo $miles;
//Although distance is calculated in kms only
if($miles<10)
{
echo $miles;
}
}
I have values of variables like
$lat1=23.02650164397716
And so on - they are decimals. The error occurs on this line:
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
You are setting
$lat=$row->lat.'<br>';
followed by:
$lat2=$lat;
So now regardless of what $lat1 contains, $lat2 is a string with HTML in it, not a number.
Next up, you try to use $lat2 as a number in:
deg2rad($lat2)
It'll probably work if you use:
$lat2=$row->lat;
instead.
I think the problem you're facing could be better addressed by making sure you know what's in each variable. Using a name like lat to refer to a string in one place and a number in another is likely to cause issues as time goes on.
I am trying to find the actual longitude direction on a route.
For example the route could be:
37.635118, -75.058594 (US EAST COAST) to 38.740377, -123.222656 (US WEST COAST)
In this case, the direction would be WEST, as EAST would be across the world.
Using the following distance calculator i would like to return the actual direction as well, but i am uncertain as to how i can do that.
function getdistance($lat1, $lon1, $lat2, $lon2) {
$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;
$res = round(($miles * 1.609344));
return $res;
}
Does anyone know how i would go about getting the W/E direction from the coordinates above?
Thank you
So i realized it was alot simpler than i thought.
$lon1-$lon2 will return either a positive or negative, and from that, its easy to find that any positive number is east, and negative is west.
I have a function that calculates the distance between two GPS coordinates. I then get all the coordinates from the database and loop through them all to get the distance between the current one and the previous one, then add that to an array for the specific GPS device. For some reason it is return NaN. I have tried casting it as a double, an int, and rounding the number.
Here is my PHP code:
function distance($lat1, $lon1, $lat2, $lon2) {
$lat1 = round($lat1, 3);
$lon1 = round($lon1, 3);
$lat2 = round($lat2, 3);
$lon2 = round($lon2, 3);
$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;
if($miles < 0) $miles = $miles * -1;
return ($miles * 1.609344);
}
$this->db->query("SELECT * FROM `gps_loc` WHERE `imeiN`='" . $sql . "' AND `updatetime`>=$timeLimit ORDER BY `_id` DESC");
$dist = array();
$dist2 = array();
while($row = $this->db->getResults()) {
$dist2[$row['imeiN']] = 0;
$dist[$row['imeiN']][]["lat"] = $row['lat'];
$dist[$row['imeiN']][count($dist[$row['imeiN']]) - 1]["lng"] = $row['lon'];
}
foreach($dist as $key=>$d) {
$a = 0;
$b = 0;
foreach($dist[$key] as $n) {
if($a > 0) {
$dist2[$key] += $this->distance($n['lat'], $n['lng'], $dist[$key][$a - 1]['lat'], $dist[$key][$a - 1]['lng']);
}
$a++;
}
}
echo json_encode($dist2);
The range of sin() and cos() is between -1 and 1. Therefore in your first calculation of $dist the result range is -2 to 2. You then pass this to acos(), whose argument must be between -1 and 1. Thus acos(2) for example gives NaN. Everything else from there gives NaN as well.
I'm not sure what the formula should be exactly, but that's where your NaN is coming from. Double-check your trigonometry.
The algo will produce NaN if points are too close to each other. In that case $dist gets value 1. acos(1) is NaN. All subsequent calculations produce NaN too.
You round coordinates as the first step, so it makes more probable that the values become equal after rounding, and produce NaN.
The values you are pulling from the database may be strings, which would cause this issue.
You may also want to check the issues that Kolink raised in his post.
Is that the spherical law of cosines you're using? I'd switch to the Haversine formula:
function distance($lat1, $lon1, $lat2, $lon2)
{
$radius = 3959; //approximate mean radius of the earth in miles, can change to any unit of measurement, will get results back in that unit
$delta_Rad_Lat = deg2rad($lat2 - $lat1); //Latitude delta in radians
$delta_Rad_Lon = deg2rad($lon2 - $lon1); //Longitude delta in radians
$rad_Lat1 = deg2rad($lat1); //Latitude 1 in radians
$rad_Lat2 = deg2rad($lat2); //Latitude 2 in radians
$sq_Half_Chord = sin($delta_Rad_Lat / 2) * sin($delta_Rad_Lat / 2) + cos($rad_Lat1) * cos($rad_Lat2) * sin($delta_Rad_Lon / 2) * sin($delta_Rad_Lon / 2); //Square of half the chord length
$ang_Dist_Rad = 2 * asin(sqrt($sq_Half_Chord)); //Angular distance in radians
$distance = $radius * $ang_Dist_Rad;
return $distance;
}
You should be able to change the earth's radius to any form of measurement from radius in light years to radius in nanometers and get the proper number back out for the unit used.
Thanks for all the responses here - as a result I made a function which combines to computations and tests for NaN in each, if both are not NaN - it averages the calculation, if one is NaN and the other is not - it uses the one that's valid and gives error report for the coordinates that failed one of the calculation:
function distance_slc($lat1, $lon1, $lat2, $lon2) {
$earth_radius = 3960.00; # in miles
$distance = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($lon2-$lon1)) ;
$distance = acos($distance);
$distance = rad2deg($distance);
$distance = $distance * 60 * 1.1515;
$distance1 = round($distance, 4);
// use a second method as well and average
$radius = 3959; //approximate mean radius of the earth in miles, can change to any unit of measurement, will get results back in that unit
$delta_Rad_Lat = deg2rad($lat2 - $lat1); //Latitude delta in radians
$delta_Rad_Lon = deg2rad($lon2 - $lon1); //Longitude delta in radians
$rad_Lat1 = deg2rad($lat1); //Latitude 1 in radians
$rad_Lat2 = deg2rad($lat2); //Latitude 2 in radians
$sq_Half_Chord = sin($delta_Rad_Lat / 2) * sin($delta_Rad_Lat / 2) + cos($rad_Lat1) * cos($rad_Lat2) * sin($delta_Rad_Lon / 2) * sin($delta_Rad_Lon / 2); //Square of half the chord length
$ang_Dist_Rad = 2 * asin(sqrt($sq_Half_Chord)); //Angular distance in radians
$distance2 = $radius * $ang_Dist_Rad;
//echo "distance=$distance and distance2=$distance2\n";
$avg_distance=-1;
$distance1=acos(2);
if((!is_nan($distance1)) && (!is_nan($distance2))){
$avg_distance=($distance1+$distance2)/2;
} else {
if(!is_nan($distance1)){
$avg_distance=$distance1;
try{
throw new Exception("distance1=NAN with lat1=$lat1 lat2=$lat2 lon1=$lon1 lon2=$lon2");
} catch(Exception $e){
trigger_error($e->getMessage());
trigger_error($e->getTraceAsString());
}
}
if(!is_nan($distance2)){
$avg_distance=$distance2;
try{
throw new Exception("distance1=NAN with lat1=$lat1 lat2=$lat2 lon1=$lon1 lon2=$lon2");
} catch(Exception $e){
trigger_error($e->getMessage());
trigger_error($e->getTraceAsString());
}
}
}
return $avg_distance;
}
HTH someone in the future as well.
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.