SQlite最寄りの場所の取得(緯度と経度)


86

SQLiteデータベースに緯度と経度のデータが保存されており、入力したパラメーターに最も近い場所を取得したいと考えています(例:現在の場所-lat / lngなど)。

これはMySQLで可能であり、SQLiteにはHaversine式(球上の距離の計算)のカスタム外部関数が必要であるというかなりの調査を行いましたが、Javaで記述されて機能するものは見つかりませんでした。

また、カスタム関数を追加する場合は、org.sqlite.jar(for org.sqlite.Function)が必要です。これにより、アプリに不要なサイズが追加されます。

これの反対側は、距離だけを表示することはそれほど問題ではないため、SQLのOrder by関数が必要です-カスタムSimpleCursorAdapterですでに実行しましたが、データを並べ替えることができません。データベースに距離列がありません。これは、場所が変わるたびにデータベースを更新することを意味し、バッテリーとパフォーマンスの浪費になります。したがって、データベースにない列でカーソルを並べ替えるアイデアがあれば、私も感謝します。

この機能を使用するAndroidアプリがたくさんあることは知っていますが、誰かがその魔法について説明してくれませんか。

ちなみに、私はこの代替案を見つけました:SQLiteのRadiusに基づいてレコードを取得するためのクエリ?

latとlngのcosとsinの値に対して4つの新しい列を作成することを提案していますが、他にそれほど冗長ではない方法はありますか?


org.sqlite.Functionが機能するかどうかを確認しましたか(式が正しくない場合でも)?
トーマスミュラー

いいえ、アプリに2,6MBの.jarを追加するよりも優れた(冗長な)代替(編集済みの投稿)を見つけました。しかし、私はまだより良い解決策を探しています。ありがとう!
ジュレ2010

戻り距離の単位タイプは何ですか?

これは、現在地とオブジェクトの場所の間の距離に基づいてAndroidでSQliteクエリを構築するための完全な実装です。
EricLarch 2012年

回答:


112

1)最初に、SQLiteデータを適切な近似値でフィルタリングし、Javaコードで評価する必要のあるデータの量を減らします。この目的には、次の手順を使用します。

決定論持つようにしきい値データに、より正確なフィルターを、これは計算に優れている4箇所にあるradius北のメートル、西、東とあなたの中心点の南側をJavaコードで、その後未満と以上で簡単に確認してくださいデータベース内のポイントがその長方形内にあるかどうかを判別するためのSQL演算子(>、<)

このメソッドcalculateDerivedPosition(...)は、これらのポイントを計算します(図のp1、p2、p3、p4)。

ここに画像の説明を入力してください

/**
* Calculates the end-point from a given source at a given range (meters)
* and bearing (degrees). This methods uses simple geometry equations to
* calculate the end-point.
* 
* @param point
*           Point of origin
* @param range
*           Range in meters
* @param bearing
*           Bearing in degrees
* @return End-point from the source given the desired range and bearing.
*/
public static PointF calculateDerivedPosition(PointF point,
            double range, double bearing)
    {
        double EarthRadius = 6371000; // m

        double latA = Math.toRadians(point.x);
        double lonA = Math.toRadians(point.y);
        double angularDistance = range / EarthRadius;
        double trueCourse = Math.toRadians(bearing);

        double lat = Math.asin(
                Math.sin(latA) * Math.cos(angularDistance) +
                        Math.cos(latA) * Math.sin(angularDistance)
                        * Math.cos(trueCourse));

        double dlon = Math.atan2(
                Math.sin(trueCourse) * Math.sin(angularDistance)
                        * Math.cos(latA),
                Math.cos(angularDistance) - Math.sin(latA) * Math.sin(lat));

        double lon = ((lonA + dlon + Math.PI) % (Math.PI * 2)) - Math.PI;

        lat = Math.toDegrees(lat);
        lon = Math.toDegrees(lon);

        PointF newPoint = new PointF((float) lat, (float) lon);

        return newPoint;

    }

そして今、あなたのクエリを作成します:

PointF center = new PointF(x, y);
final double mult = 1; // mult = 1.1; is more reliable
PointF p1 = calculateDerivedPosition(center, mult * radius, 0);
PointF p2 = calculateDerivedPosition(center, mult * radius, 90);
PointF p3 = calculateDerivedPosition(center, mult * radius, 180);
PointF p4 = calculateDerivedPosition(center, mult * radius, 270);

strWhere =  " WHERE "
        + COL_X + " > " + String.valueOf(p3.x) + " AND "
        + COL_X + " < " + String.valueOf(p1.x) + " AND "
        + COL_Y + " < " + String.valueOf(p2.y) + " AND "
        + COL_Y + " > " + String.valueOf(p4.y);

COL_Xは、緯度の値を格納するデータベース内の列の名前であり、COL_Y経度を表します。

したがって、中心点に近いデータがいくつかあり、適切に近似されています。

2)これで、これらのフィルタリングされたデータをループして、次の方法を使用して、それらが実際にポイント(円内)に近いかどうかを判断できます。

public static boolean pointIsInCircle(PointF pointForCheck, PointF center,
            double radius) {
        if (getDistanceBetweenTwoPoints(pointForCheck, center) <= radius)
            return true;
        else
            return false;
    }

public static double getDistanceBetweenTwoPoints(PointF p1, PointF p2) {
        double R = 6371000; // m
        double dLat = Math.toRadians(p2.x - p1.x);
        double dLon = Math.toRadians(p2.y - p1.y);
        double lat1 = Math.toRadians(p1.x);
        double lat2 = Math.toRadians(p2.x);

        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2)
                * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        double d = R * c;

        return d;
    }

楽しい!

このリファレンスを使用してカスタマイズ、完成させました。


上記のこの概念のJavascript実装を探している場合は、ChrisVenessのすばらしいWebページを確認してください。可動タイプ.co.uk / scripts
latlong.html–

@Menma xは緯度、yは経度です。半径:画像に表示されている円の半径。
ボブス2015

上記の解決策は正しく、機能します。試してみてください... :)
YS

1
これはおおよその解決策です!高速でインデックスに適したSQLクエリを介して、非常に近似的な結果が得られます。極端な状況では、間違った結果になります。数キロメートルの範囲内で少数の近似結果が得られたら、より低速でより正確な方法を使用してそれらの結果をフィルタリングします。半径が非常に大きいフィルタリングや、アプリが赤道で頻繁に使用される場合は、使用しないでください。
user1643723 2018年

1
しかし、私はそれを理解していません。CalculateDerivedPositionは、lat、lng座標をデカルト座標に変換してから、SQLクエリで、これらのデカルト値をlat、long値と比較しています。2つの異なる幾何学的座標?これはどのように作動しますか?ありがとう。
Misgevolution

70

クリスの答えは本当に役に立ちます(ありがとう!)が、直線座標(UTMやOSグリッド参照など)を使用している場合にのみ機能します。緯度/経度に度を使用する場合(WGS84など)、上記は赤道でのみ機能します。他の緯度では、ソート順に対する経度の影響を減らす必要があります。(北極に近いと想像してください...緯度の程度は他の場所と同じですが、経度の程度は数フィートしかない場合があります。これは、並べ替え順序が正しくないことを意味します)。

赤道にいない場合は、現在の緯度に基づいてファッジファクターを事前に計算します。

<fudge> = Math.pow(Math.cos(Math.toRadians(<lat>)),2);

次に、次の順序で注文します。

((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN) * <fudge>)

これはまだ概算ですが、最初のものよりもはるかに優れているため、並べ替え順序の不正確さははるかにまれです。


3
これは、極に収束し、結果をより近くに歪める縦線についての非常に興味深い点です。いい修正。
クリスシンプソン

1
これは機能しているようですcursor = db.getReadableDatabase()。rawQuery( "Select nome、id as _id、" + "(" + Latitude + "--lat)*(" + Latitude + "-lat)+(" +経度+ "-lon)*(" +経度+ "-lon)*" + fudge + "as distanza" + "from cliente" + "order by distanza asc"、null);
max4ever 2012

すべきである((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN) * <fudge>)よりも小さいですdistancedistance^2
ボブズ2012年

ラジアンのファッジファクターと度の列ではありませんか?それらを同じ単位に変換するべきではありませんか?
Rangel Reale

1
いいえ、ファッジファクターはスケーリングファクターであり、極では0、赤道では1です。度でもラジアンでもありません。単なる無次元数です。Java Math.cos関数にはラジアン単位の引数が必要であり、<lat>は度単位であると想定したため、Math.toRadians関数になります。しかし、結果のコサインには単位がありません。
ティーゼル2013年

68

私はこれが答えられ、受け入れられたことを知っていますが、私の経験と解決策を追加したいと思いました。

ユーザーの現在の位置と特定のターゲット位置との間の正確な距離を計算するためにデバイスでhaversine関数を実行することはできましたが、クエリ結果を距離順に並べ替えて制限する必要がありました。

満足のいく解決策ではありませんが、事後にロットを返し、並べ替えとフィルタリングを行うことですが、これにより2番目のカーソルが生成され、多くの不要な結果が返され、破棄されます。

私の好ましい解決策は、longとlatsのデルタ値の2乗のソート順で渡すことでした。

((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) +
 (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN))

並べ替え順序のためだけに完全なハバーシンを実行する必要はなく、結果を平方根にする必要もないため、SQLiteで計算を処理できます。

編集:

この答えはまだ愛を受けています。ほとんどの場合は正常に機能しますが、もう少し精度が必要な場合は、緯度が90に近づくにつれて増加する不正確さを修正する「ファッジ」係数を追加する以下の@Teaselによる回答を確認してください。


素晴らしい答え。それがどのように機能し、このアルゴリズムは何と呼ばれているのか説明していただけますか?
iMatoria 2011年

3
@ iMatoria-これはピタゴラスの有名な定理の縮小版です。2セットの座標が与えられた場合、2つのX値の差は直角三角形の一方の辺を表し、Y値の差はもう一方の辺を表します。斜辺(したがって、ポイント間の距離)を取得するには、これら2つの値の二乗を合計してから、結果を平方根します。私たちの場合、最後のビット(平方根)は実行できないため実行しません。幸い、これはソート順には必要ありません。
クリスシンプソン

6
私のアプリBostonBusMapでは、このソリューションを使用して、現在の場所に最も近い停車地を表示しました。ただしcos(latitude)、緯度と経度をほぼ等しくするには、経度の距離をスケーリングする必要があります。参照してくださいen.wikipedia.org/wiki/...
noisecapella

0

パフォーマンスを可能な限り向上させるために、次のORDER BY句を使用して@ChrisSimpsonのアイデアを改善することをお勧めします。

ORDER BY (<L> - <A> * LAT_COL - <B> * LON_COL + LAT_LON_SQ_SUM)

この場合、コードから次の値を渡す必要があります。

<L> = center_lat^2 + center_lon^2
<A> = 2 * center_lat
<B> = 2 * center_lon

またLAT_LON_SQ_SUM = LAT_COL^2 + LON_COL^2、追加の列としてデータベースに保存する必要があります。エンティティをデータベースに挿入してデータを入力します。これにより、大量のデータを抽出する際のパフォーマンスがわずかに向上します。


-3

次のようなものを試してください。

    //locations to calculate difference with 
    Location me   = new Location(""); 
    Location dest = new Location(""); 

    //set lat and long of comparison obj 
    me.setLatitude(_mLat); 
    me.setLongitude(_mLong); 

    //init to circumference of the Earth 
    float smallest = 40008000.0f; //m 

    //var to hold id of db element we want 
    Integer id = 0; 

    //step through results 
    while(_myCursor.moveToNext()){ 

        //set lat and long of destination obj 
        dest.setLatitude(_myCursor.getFloat(_myCursor.getColumnIndexOrThrow(DataBaseHelper._FIELD_LATITUDE))); 
        dest.setLongitude(_myCursor.getFloat(_myCursor.getColumnIndexOrThrow(DataBaseHelper._FIELD_LONGITUDE))); 

        //grab distance between me and the destination 
        float dist = me.distanceTo(dest); 

        //if this is the smallest dist so far 
        if(dist < smallest){ 
            //store it 
            smallest = dist; 

            //grab it's id 
            id = _myCursor.getInt(_myCursor.getColumnIndexOrThrow(DataBaseHelper._FIELD_ID)); 
        } 
    } 

この後、idにはデータベースから必要なアイテムが含まれるため、次のようにフェッチできます。

    //now we have traversed all the data, fetch the id of the closest event to us 
    _myCursor = _myDBHelper.fetchID(id); 
    _myCursor.moveToFirst(); 

    //get lat and long of nearest location to user, used to push out to map view 
    _mLatNearest  = _myCursor.getFloat(_myCursor.getColumnIndexOrThrow(DataBaseHelper._FIELD_LATITUDE)); 
    _mLongNearest = _myCursor.getFloat(_myCursor.getColumnIndexOrThrow(DataBaseHelper._FIELD_LONGITUDE)); 

お役に立てば幸いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.