মাইএসকিউএল গ্রেট সার্কেল দূরত্ব (হ্যাভারসাইন সূত্র)


184

আমি একটি কার্যকরী পিএইচপি স্ক্রিপ্ট পেয়েছি যা দ্রাঘিমাংশ এবং অক্ষাংশ মানের হয় এবং তারপরে তাদের একটি মাইএসকিউএল কোয়েরিতে প্রবেশ করে। আমি এটি পুরোপুরি মাইএসকিউএল তৈরি করতে চাই। এখানে আমার বর্তমান পিএইচপি কোড রয়েছে:

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

কীভাবে কেউ পুরোপুরি মাইএসকিউএল তৈরি করতে জানেন? আমি ইন্টারনেটটি কিছুটা ব্রাউজ করেছি তবে এর বেশিরভাগ সাহিত্যই বেশ বিভ্রান্তিকর।


4
নীচের সমস্ত দুর্দান্ত উত্তরের উপর ভিত্তি করে, এখানে কাজ করা
হ্যাভারসাইন


stackoverflow.com/a/40272394/1281385 কীভাবে নিশ্চিত করতে হবে সূচকটি কীভাবে নিশ্চিত করা যায়
এক্সসাম

উত্তর:


357

থেকে Google কোড প্রায়শই জিজ্ঞাসিত প্রশ্নাবলী - পিএইচপি, মাইএসকিউএল ও Google মানচিত্রের সঙ্গে কোনো দোকানের স্থান তৈরি করা হচ্ছে :

এখানে এসকিউএল বিবৃতিটি নিকটস্থ 20 টি অবস্থানের সন্ধান করবে যা 37, -122 স্থানাঙ্কের 25 মাইল ব্যাসার্ধের মধ্যে রয়েছে। এটি সেই সারির অক্ষাংশ / দ্রাঘিমাংশ এবং লক্ষ্য অক্ষাংশ / দ্রাঘিমাংশের ভিত্তিতে দূরত্ব গণনা করে এবং তারপরে কেবল সারিগুলির জন্য জিজ্ঞাসা করে যেখানে দূরত্বের মান 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
বর্গক্ষেত্র বিবৃতি সত্যিই ভাল। তবে আমি এই বিবৃতিতে আমার স্থানাঙ্কগুলি কোথায় পাস করতে পারি? আমি কোথাও স্থানাঙ্কগুলি পেরিয়ে দেখতে পারি না
মান

32
37 এবং -122 আপনার স্থানাঙ্কের সাথে প্রতিস্থাপন করুন।
পাভেল চুচুভা

5
আমি লক্ষ লক্ষ জায়গা (+ কয়েক হাজার দর্শক) থাকলে এর পারফরম্যান্সের প্রভাবগুলি সম্পর্কে অবাক হয়েছি ...
হালিল Özgür


2
@ ফস অ্যাভান্স হ্যাঁ, আপনার markersআইডি, ল্যান এবং এলএনজি ক্ষেত্রের টেবিল থাকলে এই ক্যোয়ারীটি কাজ করবে ।
পাভেল চুচুভা

32

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

রেডিয়ানে অক্ষাংশ এবং দ্রাঘিমাংশ সহ with

সুতরাং

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

আপনার এসকিউএল কোয়েরি হয়

কিমি বা মাইল এ আপনার ফলাফল পেতে, ফলাফলকে পৃথিবীর গড় ব্যাসার্ধের সাথে ( 3959মাইল, 6371কিমি বা 3440নটিক্যাল মাইল) গুন করুন

আপনার উদাহরণে আপনি যে জিনিসটি গণনা করছেন তা হ'ল একটি বাউন্ডিং বাক্স। আপনি যদি স্থানাঙ্কিত সক্ষম মাইএসকিউএল কলামে আপনার স্থানাঙ্কের ডেটা রাখেন , তবে ডেটা অনুসন্ধান করতে আপনি মাইএসকিউএল এর বিল্ড কার্যকারিতা ব্যবহার করতে পারেন ।

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

আপনি যদি টোকুডিবি ব্যবহার করে থাকেন তবে আপনি পূর্বাভাসগুলির মধ্যে দুটিতে ক্লাস্টারিং সূচক যুক্ত করলে আপনি আরও ভাল পারফরম্যান্স পাবেন, উদাহরণস্বরূপ:

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

আপনার ডিগ্রিগুলিতে বেসিক ল্যাট এবং লম্বা পাশাপাশি রেডিয়ানগুলিতে পাপ (ল্যাট), রেডিয়ানে কোস (ল্যাট) * কোস (লোন) এবং প্রতি পয়েন্টের জন্য রেডিয়ানে কোস (ল্যাট) * পাপ (লোন) প্রয়োজন হবে। তারপরে আপনি একটি মাইএসকিএল ফাংশন তৈরি করেন, এটি স্মার্ট:

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

এটি আপনাকে দূরত্ব দেয়।

ল্যাট / লম্বায় একটি সূচি যুক্ত করতে ভুলবেন না যাতে বাউন্ডিং বক্সিং অনুসন্ধানকে ধীর করার পরিবর্তে অনুসন্ধানে সহায়তা করতে পারে (উপরের ক্রিয়েট টেবিল কোয়েরিতে সূচিটি ইতিমধ্যে যুক্ত করা হয়েছে)।

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

শুধুমাত্র ল্যাট / দীর্ঘ স্থানাঙ্ক সহ একটি পুরানো সারণী দেওয়া, আপনি এটির আপডেট করার জন্য একটি স্ক্রিপ্ট সেট আপ করতে পারেন: (পিএইচপি মেকরোডব ব্যবহার করে)

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

এই প্রস্তুতিগুলি দেওয়া, ক্যোয়ারী এই জাতীয় কিছু হয়েছে (পিএইচপি):

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

উপরের ক্যোয়ারির ব্যাখ্যা দিয়ে বলতে পারেন যে এটির সূচক ব্যবহার করা হচ্ছে না যদি না এর মধ্যে ট্রিগার করার যথেষ্ট ফলাফল থাকে। স্থানাঙ্ক টেবিলের পর্যাপ্ত ডেটা থাকলে সূচকটি ব্যবহার করা হবে। আপনি টেবিলের আকারের সাথে কোনও সূচক না রেখে সূচকটি ব্যবহার করতে SELECT এ ফোর্স ইন্ডেক্স (ল্যাট_লোন_আইডিএক্স) যুক্ত করতে পারেন, যাতে আপনি এটি সঠিকভাবে কাজ করছে তা এক্সপ্লেইন দিয়ে যাচাই করতে পারেন।

উপরের কোডের নমুনাগুলির সাথে আপনার ন্যূনতম ত্রুটি সহ দূরত্বে অবজেক্ট অনুসন্ধানের একটি কার্যকরী এবং স্কেলযোগ্য বাস্তবায়ন হওয়া উচিত।


10

আমাকে কিছুটা বিশদভাবে কাজ করতে হয়েছিল, তাই আমি আমার ফলাফলটি ভাগ করব। এই ব্যবহার zipটেবিল latitudeএবং longitudeটেবিল। এটি গুগল ম্যাপের উপর নির্ভর করে না; বরং আপনি এটিকে ল্যাট / লম্বা যে কোনও টেবিলের সাথে মানিয়ে নিতে পারেন।

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ল্যাব / দীর্ঘ বিন্দু 42.81 / -70.81 এর 50.0 মাইলের মধ্যে সারণীতে 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

আমি উপরের উত্তরে মন্তব্য করতে পারি না, তবে @ পাভেল চুচুয়ার উত্তর সম্পর্কে সাবধানতা অবলম্বন করব। যদি উভয় স্থানাঙ্ক একই থাকে তবে সেই সূত্র কোনও ফলাফল ফেরত দেবে না। সেক্ষেত্রে দূরত্ব শূন্য, এবং সুতরাং যে সূত্রটি সেই সূত্রের সাথে ফেরত দেওয়া হবে না।

আমি কোনও মাইএসকিউএল বিশেষজ্ঞ নই, তবে এটি আমার পক্ষে কাজ করছে বলে মনে হচ্ছে:

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
অবস্থানগুলি অভিন্ন হলে, এটি শূন্য থেকে বের হওয়া উচিত নয় , তবে শূন্য হিসাবে ( ACOS(1)0 হিসাবে )। আপনি পারে xaxis সমস্যা rounding দেখতে * xaxis + + yaxis * yaxis + + zaxis * ACOS জন্য সীমার বাইরে যাচ্ছে zaxis কিন্তু আপনি বিরুদ্ধে পাহারা বলে মনে হচ্ছে না?
রোভল্যান্ড শ

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 কিমি দূরত্বে


গত (রেডিয়ানে (Lat) পাপ (রেডিয়ানে হতে হবে (Lat))
কিলোগ্রাম

আমি একটি ত্রুটি পেয়েছি "অজানা কলামের দূরত্ব" কেন এটি?
জিল জন

@ জিলজাহান যদি আপনি কেবল দূরত্ব চান তবে আপনি দূরত্বে অর্ডারটি পুরোপুরি সরিয়ে ফেলতে পারেন। আপনি যদি ফলাফলগুলি বাছাই করতে চান তবে আপনি এটি ব্যবহার করতে পারেন - অর্ডার বাই (71৩71১ * আকোস (কোস্ট (রেডিয়ানস (সার্চ_ল্যাট)) * কোস (রেডিয়ানস (ল্যাট)) * কোস (রেডিয়ানস (এলএনজি) - রেডিয়ানস (সার্চ_লং)) পাপ (রেডিয়ানস (সার্চ_ল্যাট)) * পাপ (রেডিয়ানস (ল্যাট)))।
হরিশ লালওয়ানি

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

সুতরাং দূরত্ব মান গণনা করা হবে এবং যে কেউ প্রয়োজন হিসাবে আবেদন করতে পারেন।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.