MySQL Great Circle Mesafe (Haversine formül)

7 Cevap php

Ben Boylam ve Enlem değerleri alır ve sonra bir MySQL sorgusu içine girer çalışır bir PHP komut dosyası var. Ben sadece MySQL yapmak istiyorum. İşte benim geçerli PHP kodu bulunuyor:

if ($distance != "Any" && $customer_zip != "") { //get the great circle distance

    //get the origin zip code info
    $zip_sql = "SELECT * FROM zip_code WHERE zip_code = '$customer_zip'";
    $result = mysql_query($zip_sql);
    $row = mysql_fetch_array($result);
    $origin_lat = $row['lat'];
    $origin_lon = $row['lon'];

    //get the range
    $lat_range = $distance/69.172;
    $lon_range = abs($distance/(cos($details[0]) * 69.172));
    $min_lat = number_format($origin_lat - $lat_range, "4", ".", "");
    $max_lat = number_format($origin_lat + $lat_range, "4", ".", "");
    $min_lon = number_format($origin_lon - $lon_range, "4", ".", "");
    $max_lon = number_format($origin_lon + $lon_range, "4", ".", "");
    $sql .= "lat BETWEEN '$min_lat' AND '$max_lat' AND lon BETWEEN '$min_lon' AND '$max_lon' AND ";
    }

Herkes bu tamamen MySQL nasıl biliyor mu? Ben internet biraz göz ettik ama o edebiyatının en güzel kafa karıştırıcı.

7 Cevap

Dan Google Code FAQ - Creating a Store Locator with PHP, MySQL & Google Maps:

Burada, -122 koordine 37 25 millik bir yarıçap içinde en yakın 20 yerleri bulabilirsiniz SQL deyimi var. O satırın enlem / boylam ve hedef enlem / boylam dayalı mesafeyi hesaplar ve daha sonra mesafe değeri en az 25 olan sadece satırlar için sorar, mesafeye göre tüm sorguyu emir ve 20 sonuçlarına sınırlar. Yerine kilometre kilometre göre aramak için, 6371 ile 3959 değiştirin.

SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) 
* cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin(radians(lat)) ) ) AS distance 
FROM markers 
HAVING distance < 25 
ORDER BY distance 
LIMIT 0 , 20;

$greatCircleDistance = acos( cos($latitude0) * cos($latitude1) * cos($longitude0 - $longitude1) + sin($latitude0) * sin($latitude1));

radyan enlem ve boylam ile.

bu yüzden

SELECT 
  acos( 
      cos(radians( $latitude0 ))
    * cos(radians( $latitude1 ))
    * cos(radians( $longitude0 ) - radians( $longitude1 ))
    + sin(radians( $latitude0 )) 
    * sin(radians( $latitude1 ))
  ) AS greatCircleDistance 
 FROM yourTable;

SQL sorgusu

, Km veya mil içinde sonuç almak ortalama Dünya'nın yarıçapı (3959 km, 6371 km veya 3440 deniz mili) ile çarpın sonucu için

The thing you are calculating in your example is a bounding box. If you put your coordinate data in a spatial enabled MySQL column, you can use MySQL's build in functionality to query the data.

SELECT 
  id
FROM spatialEnabledTable
WHERE 
  MBRWithin(ogc_point, GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'))

Eğer koordinatları tabloya yardımcı alanları eklerseniz, sorgu yanıt süresini artırabilirsiniz.

Bu gibi:

CREATE TABLE `Coordinates` (
`id` INT(10) UNSIGNED NOT NULL COMMENT 'id for the object',
`type` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'type',
`sin_lat` FLOAT NOT NULL COMMENT 'sin(lat) in radians',
`cos_cos` FLOAT NOT NULL COMMENT 'cos(lat)*cos(lon) in radians',
`cos_sin` FLOAT NOT NULL COMMENT 'cos(lat)*sin(lon) in radians',
`lat` FLOAT NOT NULL COMMENT 'latitude in degrees',
`lon` FLOAT NOT NULL COMMENT 'longitude in degrees',
INDEX `lat_lon_idx` (`lat`, `lon`)
)    

If you're using TokuDB, you'll get even better performance if you add clustering indexes on either of the predicates, for example, like this:

alter table Coordinates add clustering index c_lat(lat);
alter table Coordinates add clustering index c_lon(lon);

You'll need the basic lat and lon in degrees as well as sin(lat) in radians, cos(lat)*cos(lon) in radians and cos(lat)*sin(lon) in radians for each point. Then you create a mysql function, smth like this:

CREATE FUNCTION `geodistance`(`sin_lat1` FLOAT,
                              `cos_cos1` FLOAT, `cos_sin1` FLOAT,
                              `sin_lat2` FLOAT,
                              `cos_cos2` FLOAT, `cos_sin2` FLOAT)
    RETURNS float
    LANGUAGE SQL
    DETERMINISTIC
    CONTAINS SQL
    SQL SECURITY INVOKER
   BEGIN
   RETURN acos(sin_lat1*sin_lat2 + cos_cos1*cos_cos2 + cos_sin1*cos_sin2);
   END

Bu size mesafeyi verir.

Sınırlayıcı boks (dizin zaten yukarıdaki CREATE TABLE sorguda ilave edilir) yerine onu yavaşlatmak arama yardımcı olabilir böylece enlem / boylam üzerinde bir dizin eklemek için unutmayın.

INDEX `lat_lon_idx` (`lat`, `lon`)

Sadece enlem / boylam koordinatları ile eski bir tablo göz önüne alındığında, bu gibi güncellemek için bir komut dosyası ayarlayabilirsiniz: (php meekrodb kullanarak)

$users = DB::query('SELECT id,lat,lon FROM Old_Coordinates');

foreach ($users as $user)
{
  $lat_rad = deg2rad($user['lat']);
  $lon_rad = deg2rad($user['lon']);

  DB::replace('Coordinates', array(
    'object_id' => $user['id'],
    'object_type' => 0,
    'sin_lat' => sin($lat_rad),
    'cos_cos' => cos($lat_rad)*cos($lon_rad),
    'cos_sin' => cos($lat_rad)*sin($lon_rad),
    'lat' => $user['lat'],
    'lon' => $user['lon']
  ));
}

Then you optimize the actual query to only do the distance calculation when really needed, for example by bounding the circle (well, oval) from inside and outside. For that, you'll need to precalculate several metrics for the query itself:

// assuming the search center coordinates are $lat and $lon in degrees
// and radius in km is given in $distance
$lat_rad = deg2rad($lat);
$lon_rad = deg2rad($lon);
$R = 6371; // earth's radius, km
$distance_rad = $distance/$R;
$distance_rad_plus = $distance_rad * 1.06; // ovality error for outer bounding box
$dist_deg_lat = rad2deg($distance_rad_plus); //outer bounding box
$dist_deg_lon = rad2deg($distance_rad_plus/cos(deg2rad($lat)));
$dist_deg_lat_small = rad2deg($distance_rad/sqrt(2)); //inner bounding box
$dist_deg_lon_small = rad2deg($distance_rad/cos(deg2rad($lat))/sqrt(2));

Bu hazırlıkları göz önüne alındığında, sorgu böyle bir şey (php) gider:

$neighbors = DB::query("SELECT id, type, lat, lon,
       geodistance(sin_lat,cos_cos,cos_sin,%d,%d,%d) as distance
       FROM Coordinates WHERE
       lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d
       HAVING (lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d) OR distance <= %d",
  // center radian values: sin_lat, cos_cos, cos_sin
       sin($lat_rad),cos($lat_rad)*cos($lon_rad),cos($lat_rad)*sin($lon_rad),
  // min_lat, max_lat, min_lon, max_lon for the outside box
       $lat-$dist_deg_lat,$lat+$dist_deg_lat,
       $lon-$dist_deg_lon,$lon+$dist_deg_lon,
  // min_lat, max_lat, min_lon, max_lon for the inside box
       $lat-$dist_deg_lat_small,$lat+$dist_deg_lat_small,
       $lon-$dist_deg_lon_small,$lon+$dist_deg_lon_small,
  // distance in radians
       $distance_rad);

EXPLAIN on the above query might say that it's not using index unless there's enough results to trigger such. The index will be used when there's enough data in the coordinates table. You can add FORCE INDEX (lat_lon_idx) to the SELECT to make it use the index with no regards to the table size, so you can verify with EXPLAIN that it is working correctly.

Yukarıdaki kod örnekleri ile bir çalışma ve minimum hata ile mesafeye göre nesne arama ölçeklenebilir uygulama olmalıdır.

Ben biraz ayrıntılı olarak bu işe zorunda, bu yüzden benim sonucunu paylaşacağım. Bu latitude ve longitude tablolarıyla a zip tablosunu kullanır. Google Maps bağlı değildir; yerine size lat / uzun içeren herhangi bir tabloda uyarlayabilirsiniz.

SELECT zip, primary_city, 
       latitude, longitude, distance_in_mi
  FROM (
SELECT zip, primary_city, latitude, longitude,r,
       (3963.17 * ACOS(COS(RADIANS(latpoint)) 
                 * COS(RADIANS(latitude)) 
                 * COS(RADIANS(longpoint) - RADIANS(longitude)) 
                 + SIN(RADIANS(latpoint)) 
                 * SIN(RADIANS(latitude)))) AS distance_in_mi
 FROM zip
 JOIN (
        SELECT  42.81  AS latpoint,  -70.81 AS longpoint, 50.0 AS r
   ) AS p 
 WHERE latitude  
  BETWEEN latpoint  - (r / 69) 
      AND latpoint  + (r / 69)
   AND longitude 
  BETWEEN longpoint - (r / (69 * COS(RADIANS(latpoint))))
      AND longpoint + (r / (69 * COS(RADIANS(latpoint))))
  ) d
 WHERE distance_in_mi <= r
 ORDER BY distance_in_mi
 LIMIT 30

Bu sorgunun ortasında bu satıra bakın:

    SELECT  42.81  AS latpoint,  -70.81 AS longpoint, 50.0 AS r

Bu lat / uzun noktasının 42.81/-70.81 50.0 kilometre mesafedeki zip tabloda 30 yakın girdileri arar. Eğer bir uygulama içine bu yapı zaman kendi gelin ve arama yarıçapı koymak nerede, bu.

Eğer kilometre yerine mil, çalışmak istiyorsanız değişiklik 69 111.045 ve sorguda değişiklik 3963.17 6378.10 için için.

Burada detaylı bir writeup bulunuyor. Ben Birini yardımcı olur umarım. http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

Benim javascript uygulaması için iyi bir referans olacağını düşündüm:

/*
 * Check to see if the second coord is within the precision ( meters )
 * of the first coord and return accordinly
 */
function checkWithinBound(coord_one, coord_two, precision){

    var distance = 3959000 * Math.acos( 
    Math.cos( degree_to_radian( coord_two.lat ) ) * 
    Math.cos( degree_to_radian( coord_one.lat ) ) * 
    Math.cos( 
            degree_to_radian( coord_one.lng ) - degree_to_radian( coord_two.lng ) 
    ) +
    Math.sin( degree_to_radian( coord_two.lat ) ) * 
    Math.sin( degree_to_radian( coord_one.lat ) ) 
    )
    return distance <= precision;

}

/**
 * Get radian from given degree
 */
function degree_to_radian(degree){
    return degree * (Math.PI / 180);
}

Ben yukarıdaki cevaba yorum, ancak @ Pavel Chuchuva yanıt ile dikkatli olamaz. Her iki koordinatlar aynı ise bu formül bir sonuç döndürmez. Bu durumda, mesafe null, ve böylece satır olduğu gibi bu formül ile iade edilmeyecektir.

Ben MySQL uzman değilim, ama bu benim için çalışıyor gibi görünüyor:

SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distance 
FROM markers HAVING distance < 25 OR distance IS NULL ORDER BY distance LIMIT 0 , 20;

Aşağıdaki bağlantı saygı 3 dilde (PHP, Javascript ve SQL) Bu soruyu cevaplarken, aynı zamanda veri sağlayan değil, sadece iyi bir kaynaktır.

http://www.opengeocode.org/download.php#cityzip