7

OK-私はこれに約3か月間オンとオフで取り組んできましたが、出くわしたすべての地理的近接式を使い果たしたので、正しい結果を得るのに近づいていません。助けを求めてください。

狙い

私は店舗検索のかなり基本的な実装を設定しています。ユーザーは郵便番号を入力し、事前定義された検索範囲のリストから選択します。gmaps APIは、このアドレスの緯度/経度座標を生成し、それらをphpスクリプトに渡します。このスクリプトでは、ユーザー座標がmysqlデータベーステーブル(以下の構造)に対して照会されます。

post_id int(11)                             
post_type varchar(20)                                
lat   float(10,6)                               
lng   float(10,6)

このクエリの結果(投稿ID)は、マップマーカーデータを含むXMLを生成するワードプレスクエリに入力されます。(wordpressクエリはpost__inとposts_per_page -1を使用して、クエリによって生成されたすべてのIDの情報を表示します

問題

一言で言えば、私が遭遇したHaversine式のすべての実装では、マーカーが欠落しているようです。具体的には、ユーザーが入力した座標に非常に近いマーカーです(正確にはわかりませんが、約500m以内だと思います)。これは、ユーザーが郵便番号を入力し、その場所のすぐ近くに店舗が表示されないかのように、大きな問題です。

さまざまなチュートリアルから掘り下げたフォーラムラの約8つの異なる順列を試しましたが、同じ結果が得られました。以下は、ユーザーが入力した位置に非常に近いものを除くすべてのマーカーを提供するサイトで現在使用している式です。

$center_lat = $_GET["lat"];
$center_lng = $_GET["lng"];
$radius = $_GET["radius"];

// Calculate square radius search

$lat1 = (float) $center_lat - ( (int) $radius / 69 );
$lat2 = (float) $center_lat + ( (int) $radius / 69 );
$lng1 = (float) $center_lng - (int) $radius / abs( cos( deg2rad( (float) $center_lat ) ) * 69 );
$lng2 = (float) $center_lng + (int) $radius / abs( cos( deg2rad( (float) $center_lat ) ) * 69 );

$sqlsquareradius = "
SELECT 
post_id, lat, lng
FROM
wp_geodatastore
WHERE
lat BETWEEN ".$lat1." AND ".$lat2."
AND
lng BETWEEN ".$lng1." AND ".$lng2."
"; // End $sqlsquareradius

// Create sql for circle radius check
$sqlcircleradius = "
SELECT
t.post_id,
3956 * 2 * ASIN(
    SQRT(
        POWER(
            SIN(
                ( ".(float) $center_lat." - abs(t.lat) ) * pi() / 180 / 2
            ), 2
        ) + COS(
            ".(float) $center_lat." * pi() / 180
        ) * COS(
            abs(t.lat) * pi() / 180
        ) * POWER(
            SIN(
                ( ".(float) $center_lng." - t.lng ) * pi() / 180 / 2
            ), 2
        )
    )
) AS distance
FROM
(".$sqlsquareradius.") AS t
HAVING
distance <= ".(int) $radius."
ORDER BY distance
"; // End $sqlcircleradius


$result = mysql_query($sqlcircleradius);

$row = mysql_fetch_array( $result );

while($row = mysql_fetch_array( $result )) {
// the contents of each row
$post_ids[] = $row['post_id'];
}

ここでMikePelleyによって提案された1つの式がありました:ジオロケーションSQLクエリが正確な場所を見つけられません

この数式は、ユーザーが入力した場所に非常に近いマーカーを示しているように見えましたが、指定された半径内に表示されるべき他のマーカーを見逃していました。混乱を解消するために、これは私が使用したコードです。

$center_lat = $_GET["lat"];
$center_lng = $_GET["lng"];
$radius = $_GET["radius"];

$sql = "
SELECT post_id, lat, lng, 
truncate((degrees(acos( sin(radians(lat)) 
* sin(radians(".$center_lat.")) 
+ cos(radians(lat)) 
* cos(radians(".$center_lat.")) 
* cos(radians(".$center_lng." - lng) ) ) ) 
* 69.09*1.6),1) as distance 
FROM wp_geodatastore HAVING distance <= ".$radius." ORDER BY distance desc
"; // End $sqlcircleradius


$result = mysql_query($sql);

$row = mysql_fetch_array( $result );

while($row = mysql_fetch_array( $result )) {
// Print out the contents of each row
$post_ids[] = $row['post_id'];
}

リクエスト

基本的に、これらのコードブロックのどちらも正しいマーカーを表示していない理由を知りたいと思います。誰かがコードの改善を提案したり、私が見逃したかもしれないいくつかのリソースに私を向けることができれば、それは素晴らしいことです

編集

私の疑似回答は機能していると思いましたが、それでも問題が解決していませんでした。私は今、非常に異なる方法を採用することになりました。ここで見つけることができる非常に優れたjqueryストアロケーターを使用しています:http ://www.bjornblog.com/web/jquery-store-locator-plugin

そこにあるすべてのプロジェクトで機能するわけではありませんが、私のニーズには完璧です(そして機能します!)

4

5 に答える 5

2

編集 このロケーション ファインダーは頻繁に登場するので、記事を書きました。

http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

元の投稿

まず、harsine 式を一度だけ処理して、ストアド関数に入れて、その厄介な詳細を忘れることができるようにしましょう。注: このソリューション全体は法定マイル単位です。

DELIMITER $$

CREATE
    FUNCTION distance(lat1 FLOAT, long1 FLOAT, lat2 FLOAT, long2 FLOAT)
    RETURNS FLOAT
    DETERMINISTIC NO SQL
    BEGIN
    RETURN (3959 * ACOS(COS(RADIANS(lat1)) 
                 * COS(RADIANS(lat2)) 
                 * COS(RADIANS(long1) - RADIANS(long2)) 
                 + SIN(RADIANS(lat1)) 
                 * SIN(RADIANS(lat2)) 
                )); 
    END$$

DELIMITER ;

では、バウンディング ボックスを検索し、距離関数と距離順で検索を絞り込むクエリをまとめてみましょう。

あなたの質問のPHPコードに基づいて:

$radiusが半径であると仮定します$center_lat。は$center_lng基準点です。

$sqlsquareradius = "
SELECT post_id, lat, lng
  FROM
(
    SELECT post_id, lat, lng,
           distance(lat, lng, " . $center_lat . "," . $center_lng . ") AS distance
      FROM wp_geodatastore
     WHERE lat >=  " . $center_lat . " -(" . $radius . "/69)
       AND lat <=  " . $center_lat . " +(" . $radius . "/69)
       AND lng >=  " . $center_lng . " -(" . $radius . "/69)
       AND lng <=  " . $center_lng . " +(" . $radius . "/69)
)a
WHERE distance <= " . $radius . "
ORDER BY distance
";

これについていくつか注意してください。

まず、バウンディング ボックスの計算を PHP ではなく SQL で行います。すべての計算を 1 つの環境に保持することを除いて、これには正当な理由はありません。 法定マイル(radius / 69)での度数です。radius

第二に、緯度に基づいて縦方向のバウンディング ボックスのサイズを調整しません。代わりに、単純ではあるが少し大きすぎるバウンディング ボックスを使用します。この境界ボックスは、いくつかの余分なレコードをキャッチしますが、距離の測定によってそれらは取り除かれます。一般的な郵便番号/店舗検索アプリの場合、パフォーマンスの違いはごくわずかです。さらに多くのレコード (すべての電柱のデータベースなど) を検索する場合、それほど簡単ではないかもしれません。

第 3 に、ネストされたクエリを使用して距離の除去を行い、アイテムごとに距離関数を複数回実行する必要がないようにします。

4 番目に、距離 ASCENDING で並べ替えます。これは、ゼロ距離の結果が結果セットの最初に表示されることを意味します。通常、最も近いものを最初にリストするのが理にかなっています。

第5に、全体ではFLOATなく使用DOUBLEします。それには十分な理由があります。地球が完全な球であるという近似を行うため、harsine 距離式は完全ではありません。その近似は、数値のイプシロンとほぼ同じレベルの精度で崩壊しFLOATます。この問題に対する欺瞞DOUBLE的な数値のやり過ぎも同様です。(駐車場の排水などの土木工事にこのハバーシン公式を使用しないでください。そうしないと、数イプシロン、数インチの深さの大きな水たまりができることを約束します。) 店舗検索アプリケーションには問題ありません。

lat6 番目に、列のインデックスを作成したいと思うことは間違いありません。場所のテーブルが頻繁に変更されない場合は、lng列のインデックスも作成すると役立ちます。ただし、latインデックスを使用すると、クエリのパフォーマンスが大幅に向上します。

最後に、ストアド プロシージャと SQL をテストしましたが、PHP はテストしませんでした。

参照: http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL また、医療施設向けの一連の近接ファインダーに関する私の経験。

- - - - - - - - 編集 - - - - - - - - - -

ストアド プロシージャを定義できるユーザー インターフェイスがない場合は、面倒です。とにかく、PHP では sprintf 呼び出しで番号付きパラメーターを使用できるため、このようにネストされたステートメント全体を生成できます。注: %$1f などが必要になる場合があります。これを試す必要があります。

$sql_stmt = sprintf ("
  SELECT post_id, lat, lng
    FROM
  (
    SELECT post_id, lat, lng,
           (3959 * ACOS(COS(RADIANS(lat)) 
                 * COS(RADIANS(%$1s)) 
                 * COS(RADIANS(lng) - RADIANS(%$2s)) 
                 + SIN(RADIANS(lat)) 
                 * SIN(RADIANS(%$1s)) 
            ))
           AS distance
      FROM wp_geodatastore
     WHERE lat >=  %$1s -(%$3s/69)
       AND lat <=  %$1s +(%$3s/69)
       AND lng >=  %$2s -(%$3s/69)
       AND lng <=  %$2s +(%$3s/69)
  )a
   WHERE distance <= %$3s
   ORDER BY distance
",$center_lat,$center_lng, $radius);
于 2012-01-07T02:40:36.010 に答える
0

これは、稼働中の本番システムからのコードです。

6371.04 * acos(cos(pi()/2-radians(90-wgs84_lat)) * cos(pi()/2-radians(90-$lat)) * cos(radians(wgs84_long)-radians($lon)) + sin(pi()/2-radians(90-wgs84_lat)) * sin(pi()/2-radians(90-$lat))) as distance

別の距離フォーミュラを使用しますが、店舗検索の場合、違いは最小限です。

于 2012-01-08T18:27:58.857 に答える
0

これが、私自身の地理的近接度の計算でしばらくの間成功裏に使用した解決策です。

/**
 * This portion of the routine  calculates the minimum and maximum lat and
 * long within a given range.  This portion of the code was written
 * by Jeff Bearer (http:return true;//www.jeffbearer.com).
 */

$lat = somevalue;      // The latitude of our search origin
$lon = someothervalue; // The longitude of our search origin
$range = 50;   // The range of our search, in miles, of your zip

// Find Max - Min Lat / Long for Radius and zero point and query only zips in that range.
$lat_range = $range / 69.172;
$lon_range = abs($range / (cos($lon) * 69.172));
$min_lat = number_format($lat - $lat_range, '4', '.', '');
$max_lat = number_format($lat + $lat_range, '4', '.', '');
$min_lon = number_format($lon - $lon_range, '4', '.', '');
$max_lon = number_format($lon + $lon_range, '4', '.', '');

/* Query for matching zips:

    SELECT post_id, lat, lng
    FROM wp_geodatastore
    WHERE
    lat BETWEEN $min_lat AND $max_lat
    AND lng BETWEEN $min_lon AND $max_lon
*/
于 2012-01-06T20:57:51.710 に答える
0

http://www.phpclasses.org/package/6202-PHP-Generate-points-of-an-Hilbert-curve.htmlで私のクラスを試すことができます。harvesine 式とヒルベルト曲線を使用して quadkey を計算します。その後、quadkey を左から右に検索できます。キーのすべての位置は、モンスター カーブ上のポイントです。曲線のより良い説明は、Nick の空間インデックス quadtree hilbert 曲線ブログで見つけることができます。mysql の空間インデックス拡張機能を使用するのと似ていますが、より詳細に制御できます。z カーブまたはムーア カーブを使用したり、外観を変更したりできます。

于 2012-01-07T22:27:34.620 に答える
0

少し横に考えて、マーカーが見つからないという問題に対する「一種の」解決策を思いつきました。私が最初に投稿した2つの方程式は正しい結果をもたらしましたが、それぞれがターゲットに近いマーカーまたは検索半径の端にあるマーカーを見逃していました

あまりエレガントではありませんが、両方の方程式を実行して2つの配列を作成し、それらを組み合わせて(重複を削除して)、探しているすべてのマーカーが得られると考えました。これは機能します (明らかにパフォーマンスが低下しますが、トラフィックの多いアプリケーションではありません) ので、当面はこれを使用しますが、より実用的な解決策があればまだ探しています!

于 2012-01-08T22:35:39.287 に答える