views:

363

answers:

4

I have a database of airports with Latitude and Longitude for each point. I want to run a PHP script to find all airports that are nearby a given airport, with their distance and relative direction.

I.e. for Airport KLDJ (40-37-02.810N 074-14-40.539W)

Airport Nearby
KJFK - John F Kennedy Airport (21.2 nm NE) (40-38-23.104N 073-46-44.132W)

I have used code from http://www.movable-type.co.uk/scripts/latlong.html to find distance, and tried to use it to find bearing, which might not be right.

//BEARING RHUMB LINE
$phi = log(tan($lat2/2+pi/4)/tan($lat1/2+pi/4));
$distance['bearing'] = (rad2deg(atan2($theta, $phi)) +180) % 360;

I basically want to run all points through this script and find the distance, which I have already, but then the direction. (i.e. N, S, W, E, NW, SW, NE, SE)

+1  A: 

You can check, if the latitude of position B is larger or smaller than the latitude of position A to determine if it's in the east or in the west. Same thing with the longitude for north and south. Finally you need a threshold, to detect, in which case the drift is large enough to switch to an intermediate direction.

unset
A: 

Just realized you only need the heading now. My bad.

Jonah Bron
That won't work at all.
KingRadical
But won't you get the answer in degrees and mins and seconds, not miles, which is what I imagine users would want?
MJB
Didn't think of that. It would work if you did an approximate conversion from degrees/mins/seconds to miles before hand.
Jonah Bron
A: 

Heavily borrowing from the techniques here and using data from here, I put together this example

<?php

$chicago = array(
    'lat' => 41.9
  , 'lng' => 87.65
);

$dallas = array(
    'lat' => 32.73
  , 'lng' => 96.97
);

$ftworth = array(
    'lat' => 32.82
  , 'lng' => 97.35
);

$bearing = getBearingBetweenPoints( $dallas, $chicago );

echo "Bearing: $bearing&deg;<br>";
echo "Direction: " . getCompassDirection( $bearing );

function getBearingBetweenPoints( $point1, $point2 )
{
  return getRhumbLineBearing( $point1['lat'], $point2['lng'], $point2['lat'], $point1['lng'] );
}

function getRhumbLineBearing($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;
}

function getCompassDirection( $bearing )
{
  static $cardinals = array( 'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'N' );
  return $cardinals[round( $bearing / 45 )];
}
Peter Bailey
Peter,I tried this with not much luck. Code looked very similar to what I had before, but with a few twists that looked like it might work. As I mentioned above, I am good on distance, but the bearning/heading is throwing me off. As I tried Rhumb line code, I just get weird output. With your's, I am getting 0, or 180 degrees and always North. Any thoughts? Regardless, I appreciate the effort!
Brian H
A: 

Looking at another page from the same website:

$lat = 0;  // latitude of centre of bounding circle in degrees
$lon = 0;  // longitude of centre of bounding circle in degrees
$rad = 0;  // radius of bounding circle in chosen units

// Choose your unit of measure... 
$r = 6371;  // earth's radius in km
$r = 3959;  // earth's radius in miles
// or
$r = 3440;  // earth's radius in nautical miles

// convert point of origin to radians
$lat = deg2rad($lat);
$lon = deg2rad($lon);

// first-cut bounding box (in radians)
$maxLat = $lat + $rad / $r;
$minLat = $lat - $rad / $r;
// compensate for longitude getting smaller with increasing latitude
$maxLon = ( $lon + $rad / $r ) / cos( $lat );
$minLon = ( $lon - $rad / $r ) / cos( $lat );

combined with the query:

$query = 'SELECT
  id
, lat
, lon
, ACOS(SIN('.$lat.') * SIN(RADIANS(lat)) + COS('.$lat.') * COS(RADIANS(lat)) * COS(RADIANS(lon) - '. $lon.')) * '.$r.' AS distance
FROM (
   SELECT
     id
   , lat
   , lon
   FROM MyTable
   WHERE 
     RADIANS(lat) > $minLat AND RADIANS(lat) < '.$maxLat.'
    AND 
     RADIANS(lon) > $minLon AND RADIANS(lon) < '.$maxLon.'
) AS FirstCut 
WHERE ACOS(SIN('.$lat.') * SIN(RADIANS(lat)) + COS('.$lat.') * COS(RADIANS(lat)) * COS(RADIANS(lon) - '. $lon.')) * '.$r.' < '.$rad;

Selects you a list of locations (and their distance from the origin of your circle) within the bounding circle you specified.

The only thing left to do is calculate a bearing for the locations returned. Which can be done with your rhumb line formula.

The above assumes you have your database filled with coordinates in degrees.

If you had you locations stored as geometry objects, you could use MySQL's inbuilt spatial functions. Although I'm quite sure MySQL does only include bounding rectangles.

Jacco
Yeah, I had the query to look at all 20K airports and get the distance. I cross referenced that with some other online tools and my calculations were correct. The problem was my rhumb line formula, I was off by -12 to +18 degrees, so figured I did something wrong, hence coming to the community...though could have used the above a while ago :)
Brian H
It would have helped if you added this information to the question.
Jacco