SQlite Nhận các vị trí gần nhất (với vĩ độ và kinh độ)


86

Tôi có dữ liệu với vĩ độ và kinh độ được lưu trữ trong cơ sở dữ liệu SQLite của mình và tôi muốn lấy các vị trí gần nhất với các thông số mà tôi đã đưa vào (ví dụ: Vị trí hiện tại của tôi - lat / lng, v.v.).

Tôi biết rằng điều này có thể xảy ra trong MySQL và tôi đã thực hiện một số nghiên cứu rằng SQLite cần một hàm bên ngoài tùy chỉnh cho công thức Haversine (tính toán khoảng cách trên hình cầu), nhưng tôi chưa tìm thấy bất kỳ thứ gì được viết bằng Java và hoạt động .

Ngoài ra, nếu tôi muốn thêm các chức năng tùy chỉnh, tôi cần org.sqlite.jar (for org.sqlite.Function) và điều đó sẽ thêm kích thước không cần thiết vào ứng dụng.

Mặt khác của điều này là, tôi cần Thứ tự theo hàm từ SQL, bởi vì chỉ hiển thị khoảng cách không phải là vấn đề nhiều - Tôi đã làm điều đó trong SimpleCursorAdapter tùy chỉnh của mình, nhưng tôi không thể sắp xếp dữ liệu, bởi vì tôi không có cột khoảng cách trong cơ sở dữ liệu của tôi. Điều đó có nghĩa là cập nhật cơ sở dữ liệu mỗi khi vị trí thay đổi và điều đó gây lãng phí pin và hiệu suất. Vì vậy, nếu ai đó có bất kỳ ý tưởng nào về việc sắp xếp con trỏ với một cột không có trong cơ sở dữ liệu, tôi cũng rất biết ơn!

Tôi biết có rất nhiều ứng dụng Android sử dụng chức năng này, nhưng ai đó có thể giải thích điều kỳ diệu không.

Nhân tiện, tôi đã tìm thấy giải pháp thay thế này: Truy vấn để lấy bản ghi dựa trên Bán kính trong SQLite?

Đó là gợi ý để tạo 4 cột mới cho các giá trị cos và sin của lat và lng, nhưng có cách nào khác, không quá thừa không?


Bạn đã kiểm tra xem org.sqlite.Function có phù hợp với bạn không (ngay cả khi công thức không đúng)?
Thomas Mueller

Không, tôi đã tìm thấy một giải pháp thay thế (dư thừa) (bài đăng đã chỉnh sửa) có vẻ tốt hơn là thêm .jar 2,6 MB trong ứng dụng. Nhưng tôi vẫn đang tìm kiếm một giải pháp tốt hơn. Cảm ơn!
Jure

Loại đơn vị khoảng cách trở lại là gì?

Đây là cách triển khai đầy đủ để xây dựng truy vấn SQlite trên Android dựa trên khoảng cách giữa vị trí của bạn và vị trí của đối tượng.
EricLarch

Câu trả lời:


112

1) Lúc đầu, hãy lọc dữ liệu SQLite của bạn với lượng dữ liệu gần đúng và giảm dần mà bạn cần đánh giá trong mã java của mình. Sử dụng quy trình sau cho mục đích này:

Để có ngưỡng xác định và bộ lọc dữ liệu chính xác hơn, tốt hơn là bạn nên tính toán 4 vị trí theo radiusmét là bắc, tây, đông và nam của điểm trung tâm trong mã java của bạn và sau đó kiểm tra dễ dàng bằng ít hơn và hơn Toán tử SQL (>, <) để xác định xem các điểm của bạn trong cơ sở dữ liệu có nằm trong hình chữ nhật đó hay không.

Phương pháp calculateDerivedPosition(...)tính các điểm đó cho bạn (p1, p2, p3, p4 trong hình).

nhập mô tả hình ảnh ở đây

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

    }

Và bây giờ hãy tạo truy vấn của bạn:

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_Xlà tên của cột trong cơ sở dữ liệu lưu trữ các giá trị vĩ độ và COL_Ylà kinh độ.

Vì vậy, bạn có một số dữ liệu gần điểm trung tâm của bạn với một giá trị gần đúng.

2) Bây giờ bạn có thể lặp lại các dữ liệu đã lọc này và xác định xem chúng có thực sự gần điểm của bạn (trong vòng kết nối) hay không bằng cách sử dụng các phương pháp sau:

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

Thưởng thức!

Tôi đã sử dụng và tùy chỉnh tài liệu tham khảo này và hoàn thành nó.


Hãy xem trang web tuyệt vời của Chris Veness nếu bạn đang tìm kiếm một triển khai Javascript của khái niệm này ở trên. movable-type.co.uk/scripts/latlong.html
barneymc

@Menma x là vĩ độ và y là kinh độ. radius: bán kính của hình tròn được hiển thị trong hình.
Bobs

giải pháp đưa ra ở trên là đúng và hoạt động. Hãy thử nó ... :)
YS

1
Đây là một giải pháp gần đúng! Nó cung cấp cho bạn kết quả gần đúng thông qua truy vấn SQL nhanh , thân thiện với chỉ mục. Nó sẽ cho kết quả sai trong một số trường hợp khắc nghiệt. Khi bạn đã nhận được một số lượng nhỏ kết quả gần đúng trong phạm vi vài km, hãy sử dụng các phương pháp chậm hơn, chính xác hơn để lọc các kết quả đó . Không sử dụng nó để lọc với bán kính quá lớn hoặc nếu ứng dụng của bạn thường xuyên được sử dụng ở xích đạo!
user1643723 Ngày

1
Nhưng tôi không hiểu. Tính toánDerivedPosition đang chuyển đổi tọa độ vĩ độ, lng sang tọa độ cacte và sau đó bạn trong truy vấn SQL, bạn đang so sánh các giá trị cacte này với các giá trị vĩ độ, dài. Hai tọa độ hình học khác nhau? Cái này hoạt động ra sao? Cảm ơn.
Misgevolution

70

Câu trả lời của Chris thực sự hữu ích (cảm ơn!), Nhưng sẽ chỉ hoạt động nếu bạn đang sử dụng tọa độ tuyến tính (ví dụ: tham chiếu lưới UTM hoặc OS). Nếu sử dụng độ cho lat / lng (ví dụ WGS84) thì ở trên chỉ hoạt động ở đường xích đạo. Ở các vĩ độ khác, bạn cần giảm tác động của kinh độ lên thứ tự sắp xếp. (Hãy tưởng tượng bạn đang ở gần cực bắc ... vĩ độ vẫn giống như ở bất kỳ đâu, nhưng kinh độ có thể chỉ là vài feet. Điều này có nghĩa là thứ tự sắp xếp không chính xác).

Nếu bạn không ở đường xích đạo, hãy tính toán trước hệ số fudge, dựa trên vĩ độ hiện tại của bạn:

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

Sau đó đặt hàng theo:

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

Nó vẫn chỉ là một con số gần đúng, nhưng tốt hơn nhiều so với cái đầu tiên, vì vậy sự thiếu chính xác của thứ tự sắp xếp sẽ hiếm hơn nhiều.


3
Đó là một điểm thực sự thú vị về các đường dọc hội tụ ở các cực và làm lệch kết quả càng gần bạn càng tốt. Sửa chữa tốt.
Chris Simpson

1
điều này dường như hoạt động con trỏ = db.getReadableDatabase (). rawQuery ("Chọn nome, id là _id," + "(" + vĩ độ + "- vĩ độ) * (" + vĩ độ + "- vĩ độ) + (" + kinh độ + "- lon) * (" + longitude + "- lon) *" + fudge + "as distanza" + "from cliente" + "order by distanza asc", null);
max4ever

nên ((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN) * <fudge>)nhỏ hơn distancehoặc distance^2?
Bobs

Không phải hệ số fudge tính bằng radian và cột tính bằng độ? Không nên chuyển đổi chúng sang cùng một đơn vị?
Rangel Reale

1
Không, hệ số fudge là một hệ số tỷ lệ là 0 ở hai cực và 1 ở xích đạo. Nó không phải là độ, cũng không phải radian, nó chỉ là một số không có đơn vị. Hàm Java Math.cos yêu cầu một đối số tính bằng radian và tôi giả sử <lat> là độ, do đó hàm Math.toRadians. Nhưng cosine kết quả không có đơn vị.
Teasel

68

Tôi biết điều này đã được trả lời và chấp nhận nhưng tôi nghĩ rằng tôi sẽ thêm kinh nghiệm và giải pháp của mình.

Trong khi tôi rất vui khi thực hiện một hàm hasrsine trên thiết bị để tính toán khoảng cách chính xác giữa vị trí hiện tại của người dùng và bất kỳ vị trí mục tiêu cụ thể nào, nhưng tôi cần phải sắp xếp và giới hạn kết quả truy vấn theo thứ tự khoảng cách.

Giải pháp kém khả quan hơn là trả về lô và sắp xếp và lọc sau thực tế nhưng điều này sẽ dẫn đến con trỏ thứ hai và nhiều kết quả không cần thiết được trả về và loại bỏ.

Giải pháp ưa thích của tôi là chuyển theo thứ tự sắp xếp của các giá trị delta bình phương của long và lats:

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

Không cần phải thực hiện hasrsine đầy đủ chỉ cho một thứ tự sắp xếp và không cần căn bậc hai kết quả do đó SQLite có thể xử lý phép tính.

BIÊN TẬP:

Câu trả lời này vẫn đang nhận được sự yêu thích. Nó hoạt động tốt trong hầu hết các trường hợp nhưng nếu bạn cần độ chính xác hơn một chút, vui lòng xem câu trả lời của @Teasel bên dưới, nó sẽ thêm yếu tố "fudge" để sửa các điểm không chính xác tăng lên khi vĩ độ tiếp cận 90.


Câu trả lời chính xác. Bạn có thể giải thích cách nó hoạt động và thuật toán này được gọi là gì không?
iMatoria

3
@iMatoria - Đây chỉ là phiên bản rút gọn của định lý nổi tiếng pythagoras. Cho hai bộ tọa độ, hiệu giữa hai giá trị X biểu thị một cạnh của tam giác vuông và chênh lệch giữa các giá trị Y là cạnh kia. Để có cạnh huyền (và do đó là khoảng cách giữa các điểm), bạn cộng bình phương của hai giá trị này với nhau và sau đó căn bậc hai cho kết quả. Trong trường hợp của chúng tôi, chúng tôi không thực hiện bước cuối cùng (tạo rễ hình vuông) bởi vì chúng tôi không thể. May mắn thay, điều này không cần thiết cho một thứ tự sắp xếp.
Chris Simpson

6
Trong ứng dụng BostonBusMap của tôi, tôi đã sử dụng giải pháp này để hiển thị các điểm dừng gần vị trí hiện tại nhất. Tuy nhiên, bạn cần chia tỷ lệ khoảng cách kinh độ cos(latitude)để có vĩ độ và kinh độ gần bằng nhau. Xem en.wikipedia.org/wiki/…
noisecapella

0

Để tăng hiệu suất nhiều nhất có thể, tôi khuyên bạn nên cải thiện ý tưởng của @Chris Simpson với ORDER BYđiều khoản sau :

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

Trong trường hợp này, bạn nên chuyển các giá trị sau từ mã:

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

Và bạn cũng nên lưu trữ LAT_LON_SQ_SUM = LAT_COL^2 + LON_COL^2dưới dạng cột bổ sung trong cơ sở dữ liệu. Điền vào nó bằng cách chèn các thực thể của bạn vào cơ sở dữ liệu. Điều này cải thiện một chút hiệu suất trong khi trích xuất một lượng lớn dữ liệu.


-3

Hãy thử một cái gì đó như sau:

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

Sau đó, id chứa mục bạn muốn từ cơ sở dữ liệu để bạn có thể tìm nạp nó:

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

Hy vọng rằng sẽ giúp!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.