MySQL ग्रेट सर्किल दूरी (हैवरसाइन सूत्र)


184

मुझे एक काम करने वाली PHP स्क्रिप्ट मिली है जो देशांतर और अक्षांश मान प्राप्त करती है और फिर उन्हें MySQL क्वेरी में इनपुट करती है। मैं इसे पूरी तरह से MySQL बनाना चाहता हूं। यहाँ मेरा वर्तमान PHP कोड है:

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 ";
    }

क्या किसी को पता है कि यह पूरी तरह से MySQL कैसे बना सकता है? मैंने इंटरनेट को थोड़ा बढ़ाया है, लेकिन इस पर अधिकांश साहित्य बहुत भ्रमित करने वाला है।


4
नीचे दिए गए सभी बेहतरीन जवाबों के आधार पर, यहाँ कार्रवाई में
हैवरसाइन

उस माइकल को साझा करने के लिए धन्यवाद
निक वुडहम्स

stackoverflow.com/a/40272394/1281385 यह सुनिश्चित करने के लिए कि अनुक्रमणिका को हिट करने का एक उदाहरण है
exussum

जवाबों:


357

से गूगल कोड पूछे जाने वाले प्रश्न - पीएचपी, MySQL और गूगल मैप्स के साथ स्टोर लोकेटर बनाना :

यहां SQL स्टेटमेंट है जो निकटतम 20 स्थानों को ढूंढेगा जो कि 25 मील के दायरे में 37, -122 में समन्वय करते हैं। यह उस पंक्ति के अक्षांश / देशांतर और लक्ष्य अक्षांश / देशांतर के आधार पर दूरी की गणना करता है, और उसके बाद केवल उन पंक्तियों के लिए पूछता है जहाँ दूरी का मान 25 से कम है, दूरी के आधार पर पूरी क्वेरी का आदेश देता है, और इसे 20 परिणामों तक सीमित करता है। मील के बजाय किलोमीटर से खोजने के लिए, 3959 को 6371 से बदलें।

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;

2
sql कथन वास्तव में अच्छा है। लेकिन मैं इस कथन में अपने निर्देशांक कहाँ से पारित कर सकता हूँ? मैं कहीं भी निर्देशांक पारित नहीं कर सकते देखने के लिए
मान

32
37 और -122 को अपने निर्देशांक से बदलें।
पावल चुचुवा

5
मुझे आश्चर्य है कि इस प्रदर्शन के निहितार्थ के बारे में अगर लाखों स्थानों (+ हजारों आगंतुक) हैं ...
हलाल 1zgür

12
आप इस प्रदर्शन में बताए अनुसार बेहतर प्रदर्शन के लिए क्वेरी को संकीर्ण कर सकते हैं: tr.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
maliayas

2
@FosAvance हाँ, यदि आपके पास markersid, lan और lng फ़ील्ड्स के साथ तालिका है तो यह क्वेरी काम करेगी ।
पावेल चुचुवा

32

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

रेडियन में अक्षांश और देशांतर के साथ।

इसलिए

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

आपकी SQL क्वेरी है

किलोमीटर या मील में अपने परिणाम प्राप्त करने के लिए, पृथ्वी के औसत त्रिज्या ( 3959मील, 6371किमी या) के साथ परिणाम गुणा करें3440 समुद्री मील)

आपके उदाहरण में आप जिस चीज की गणना कर रहे हैं वह एक बाउंडिंग बॉक्स है। यदि आप अपने समन्वित डेटा को स्थानिक सक्षम MySQL कॉलम में रखते हैं , तो आप डेटा को क्वेरी करने के लिए कार्यक्षमता में MySQL के निर्माण का उपयोग कर सकते हैं ।

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

13

यदि आप निर्देशांक तालिका में सहायक क्षेत्र जोड़ते हैं, तो आप क्वेरी की प्रतिक्रिया समय में सुधार कर सकते हैं।

ऐशे ही:

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`)
)    

यदि आप TokuDB का उपयोग कर रहे हैं, तो आप और भी बेहतर प्रदर्शन प्राप्त करेंगे यदि आप किसी भी विधेय पर क्लस्टरिंग इंडेक्स जोड़ते हैं, उदाहरण के लिए, इस तरह:

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

आपको मूल अक्षांश और डिग्री के साथ-साथ रेडियन में पाप (lat), रेडियन में cos (lat) * cos (lon) और प्रत्येक बिंदु के लिए radians में cos (lat) * sin (lon) की आवश्यकता होगी। फिर आप एक mysql फ़ंक्शन बनाएं, इस तरह से smth करें:

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

इससे आपको दूरी मिलती है।

Lat / lon पर एक इंडेक्स जोड़ना न भूलें, ताकि बाउंडिंग बॉक्सिंग इसे धीमा करने के बजाय खोज में मदद कर सके (इंडेक्स पहले से क्रिएट टेबल क्वेरी में ऊपर जोड़ा गया है)।

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

केवल लेट / लोन निर्देशांक वाली एक पुरानी तालिका को देखते हुए, आप इसे इस तरह से अपडेट करने के लिए एक स्क्रिप्ट सेट कर सकते हैं: (meprodb का उपयोग करके php)

$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']
  ));
}

फिर आप वास्तविक क्वेरी का अनुकूलन केवल उस दूरी की गणना के लिए करते हैं जब वास्तव में जरूरत होती है, उदाहरण के लिए सर्कल (अच्छी तरह से, अंडाकार) को अंदर और बाहर से बांधकर। उसके लिए, आपको स्वयं क्वेरी के लिए कई मीट्रिक तैयार करने होंगे:

// 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));

उन तैयारियों को देखते हुए, क्वेरी कुछ इस तरह से होती है (php):

$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);

उपर्युक्त प्रश्न पर यह कह सकते हैं कि जब तक कि इस तरह के ट्रिगर करने के लिए पर्याप्त परिणाम न हों, यह सूचकांक का उपयोग नहीं कर रहा है। निर्देशांक तालिका में पर्याप्त डेटा होने पर सूचकांक का उपयोग किया जाएगा। आप तालिका के आकार के संबंध में सूचकांक का उपयोग करने के लिए चयन करने के लिए FORCE INDEX (lat_lon_idx) को जोड़ सकते हैं, ताकि आप EXPLAIN से सत्यापित कर सकें कि यह सही ढंग से काम कर रहा है।

उपरोक्त कोड नमूनों के साथ आपके पास न्यूनतम त्रुटि के साथ दूरी द्वारा ऑब्जेक्ट खोज का एक कार्यशील और मापनीय कार्यान्वयन होना चाहिए।


10

मुझे इस पर कुछ विस्तार से काम करना पड़ा है, इसलिए मैं अपना परिणाम साझा करूँगा। यह zipटेबल के साथ latitudeऔर longitudeतालिकाओं का उपयोग करता है । यह Google मानचित्र पर निर्भर नहीं करता है; बल्कि आप इसे किसी भी तालिका में लाट / लंबे समय के लिए अनुकूलित कर सकते हैं।

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

इस पंक्ति को उस क्वेरी के बीच में देखें:

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

यह zipअक्षांश के 50.0 मील / 42.81 / -70.81 मील के भीतर तालिका में 30 निकटतम प्रविष्टियों की खोज करता है। जब आप इसे एक ऐप में बनाते हैं, तो आप अपनी बात रखते हैं और त्रिज्या खोजते हैं।

आप मील की दूरी के बजाय किलोमीटर की दूरी पर, परिवर्तन में काम करना चाहते हैं 69के लिए 111.045और परिवर्तन 3963.17करने के लिए6378.10 क्वेरी में।

यहाँ एक विस्तृत लेखन है। मुझे उम्मीद है कि यह किसी की मदद करता है। http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/


3

मैंने एक प्रक्रिया लिखी है जो उसी की गणना कर सकती है, लेकिन आपको संबंधित तालिका में अक्षांश और देशांतर दर्ज करना होगा।

drop procedure if exists select_lattitude_longitude;

delimiter //

create procedure select_lattitude_longitude(In CityName1 varchar(20) , In CityName2 varchar(20))

begin

    declare origin_lat float(10,2);
    declare origin_long float(10,2);

    declare dest_lat float(10,2);
    declare dest_long float(10,2);

    if CityName1  Not In (select Name from City_lat_lon) OR CityName2  Not In (select Name from City_lat_lon) then 

        select 'The Name Not Exist or Not Valid Please Check the Names given by you' as Message;

    else

        select lattitude into  origin_lat from City_lat_lon where Name=CityName1;

        select longitude into  origin_long  from City_lat_lon where Name=CityName1;

        select lattitude into  dest_lat from City_lat_lon where Name=CityName2;

        select longitude into  dest_long  from City_lat_lon where Name=CityName2;

        select origin_lat as CityName1_lattitude,
               origin_long as CityName1_longitude,
               dest_lat as CityName2_lattitude,
               dest_long as CityName2_longitude;

        SELECT 3956 * 2 * ASIN(SQRT( POWER(SIN((origin_lat - dest_lat) * pi()/180 / 2), 2) + COS(origin_lat * pi()/180) * COS(dest_lat * pi()/180) * POWER(SIN((origin_long-dest_long) * pi()/180 / 2), 2) )) * 1.609344 as Distance_In_Kms ;

    end if;

end ;

//

delimiter ;

3

मैं उपरोक्त उत्तर पर टिप्पणी नहीं कर सकता, लेकिन @Pavel Chuchuva के उत्तर से सावधान रहना चाहिए। यदि दोनों निर्देशांक समान हैं, तो वह सूत्र परिणाम नहीं लौटाएगा। उस स्थिति में, दूरी शून्य है, और इसलिए उस पंक्ति को उस सूत्र के साथ वापस नहीं किया जाएगा जैसा कि है।

मैं एक MySQL विशेषज्ञ नहीं हूँ, लेकिन यह मेरे लिए काम कर रहा है:

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;

2
यदि पद समान हैं, तो इसे NULL से बाहर नहीं आना चाहिए , लेकिन शून्य के रूप ACOS(1)में (जैसा कि 0 है)। आप xaxis के साथ गोल मुद्दों को देख सकते हैं
रोलैंड शॉ

3
 SELECT *, (  
    6371 * acos(cos(radians(search_lat)) * cos(radians(lat) ) *   
cos(radians(lng) - radians(search_lng)) + sin(radians(search_lat)) *         sin(radians(lat)))  
) AS distance  
FROM table  
WHERE lat != search_lat AND lng != search_lng AND distance < 25  
 ORDER BY distance  
FETCH 10 ONLY 

25 किमी की दूरी के लिए


अंतिम (रेडियन (लाट) पाप होना चाहिए (रेडियंस (लाट))
KGs

im एक त्रुटि "अज्ञात कॉलम दूरी" प्राप्त कर रहा है यह क्यों है?
जिल जॉन

@JillJohn यदि आप केवल दूरी चाहते हैं तो आप पूरी तरह से दूरी द्वारा आदेश निकाल सकते हैं। यदि आप परिणामों को क्रमबद्ध करना चाहते हैं, तो आप इसका उपयोग कर सकते हैं - ORDER BY (6371 * acos (cos (radians (search_lat)) * cos (radians (lat)) * cos (radians (lng) - radians (search_lng)) + sin (radians) (search_lat)) * पाप (रेडियन (lat)))।
हरीश लालवानी

2

मुझे लगा कि मेरी जावास्क्रिप्ट कार्यान्वयन एक अच्छा संदर्भ होगा:

/*
 * Check to see if the second coord is within the precision ( meters )
 * of the first coord and return accordingly
 */
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);
}

0

मैसूरल में दूरी की गणना करें

 SELECT (6371 * acos(cos(radians(lat2)) * cos(radians(lat1) ) * cos(radians(long1) -radians(long2)) + sin(radians(lat2)) * sin(radians(lat1)))) AS distance

इस प्रकार दूरी मूल्य की गणना की जाएगी और आवश्यकतानुसार कोई भी आवेदन कर सकता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.