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 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.
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 sunsequent 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
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.