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이 작동하는지 확인 했습니까 (수식이 올바르지 않더라도)?
Thomas Mueller

아니요, 앱에 2,6MB .jar을 추가하는 것보다 더 좋은 (중복) 대안 (편집 된 게시물)을 찾았습니다. 그러나 나는 여전히 더 나은 해결책을 찾고 있습니다. 감사!
Jure

반환 거리 단위 유형은 무엇입니까?

다음은 사용자 위치와 객체 위치 사이의 거리를 기반으로 Android에서 SQlite 쿼리를 작성 하기 위한 전체 구현 입니다.
EricLarch

답변:


113

1) 처음에는 좋은 근사치로 SQLite 데이터를 필터링하고 Java 코드에서 평가해야하는 데이터 양을 줄입니다. 이를 위해 다음 절차를 사용하십시오.

결정적하도록하려면 임계 값 데이터에 대한보다 정확한 필터를, 계산하는 것이 좋습니다 4 개 위치radius동쪽과 남쪽 중앙 지점의 북쪽의 미터, 서쪽 자바 코드를 다음 쉽게 확인할 것보다 더 많은 이하 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;
    }

즐겨!

이 참조를 사용하고 사용자 정의 하여 완성했습니다.


위의 개념에 대한 자바 스크립트 구현을 찾고 있다면 Chris Veness의 훌륭한 웹 페이지를 확인하십시오. movable-type.co.uk/scripts/latlong.html
barneymc

@Menma x는 위도이고 y는 경도입니다. 반지름 : 그림에 표시된 원의 반지름입니다.
Bobs

위에 주어진 해결책은 정확하고 작동합니다. 그것을 시도 ... :)
YS

1
이것은 대략적인 해결책입니다! 빠르고 색인 친화적 인 SQL 쿼리 를 통해 매우 근사한 결과를 제공합니다 . 극단적 인 상황에서는 잘못된 결과를 제공합니다. 몇 킬로미터의 영역 내에서 대략적인 결과가 적 으면 더 느리고 정확한 방법을 사용하여 해당 결과를 필터링합니다 . 반경이 매우 큰 필터링이나 앱이 적도에서 자주 사용되는 경우에는 사용하지 마십시오!
user1643723

1
그러나 나는 그것을 이해하지 못한다. CalculateDerivedPosition은 위도, 경도 좌표를 데카르트 좌표로 변환하고 SQL 쿼리에서 이러한 데카르트 값을 위도, 경도 값과 비교합니다. 두 개의 다른 기하학적 좌표? 어떻게 작동합니까? 감사.
Misgevolution

70

Chris의 대답은 정말 유용하지만 (감사합니다!), 직선 좌표 (예 : 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) + ("+ longitude + "-lon) * ("+ 경도 + "-lon) *"+ 퍼지 + "as distanza"+ "from cliente"+ "order by distanza asc", null);
max4ever

해야 ((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN) * <fudge>)미만 distance또는 distance^2?
Bobs

퍼지 계수는 라디안이고 열은 도가 아닌가요? 같은 단위로 변환하면 안되나요?
Rangel Reale

1
아니요, 퍼지 계수는 극점에서 0이고 적도에서 1 인 스케일링 계수입니다. 도도 라디안도 아니고 단위가없는 숫자입니다. Java Math.cos 함수에는 라디안으로 된 인수가 필요하며 <lat>이도 단위라고 가정 했으므로 Math.toRadians 함수입니다. 그러나 결과 코사인에는 단위가 없습니다.
Teasel

68

나는 이것이 대답되고 받아 들여 졌다는 것을 알고 있지만 내 경험과 해결책을 추가 할 것이라고 생각했습니다.

사용자의 현재 위치와 특정 대상 위치 사이의 정확한 거리를 계산하기 위해 장치에서 haversine 기능을 수행하는 것이 기뻤지 만 쿼리 결과를 거리 순서대로 정렬하고 제한해야했습니다.

만족스럽지 못한 해결책은 로트를 반환하고 사실 후에 정렬 및 필터링하는 것이지만 이로 인해 두 번째 커서가 발생하고 많은 불필요한 결과가 반환되고 삭제됩니다.

내가 선호하는 솔루션은 long 및 lats의 제곱 델타 값의 정렬 순서로 전달하는 것입니다.

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

정렬 순서를 위해 전체 haversine을 수행 할 필요가 없으며 결과를 제곱근 할 필요가 없으므로 SQLite가 계산을 처리 할 수 ​​있습니다.

편집하다:

이 대답은 여전히 ​​사랑을 받고 있습니다. 대부분의 경우 잘 작동하지만 좀 더 정확도가 필요한 경우 위도가 90에 가까워짐에 따라 증가하는 부정확성을 수정하는 "퍼지"요소를 추가하는 @Teasel의 답변을 확인하십시오.


좋은 대답입니다. 작동 원리와이 알고리즘이 무엇인지 설명해 주시겠습니까?
iMatoria

3
@iMatoria-이것은 피타고라스의 유명한 정리를 잘라낸 것입니다. 두 세트의 좌표가 주어지면 두 X 값의 차이는 직각 삼각형의 한쪽을 나타내고 Y 값의 차이는 다른 쪽을 나타냅니다. 빗변 (및 따라서 점 사이의 거리)을 얻으려면이 두 값의 제곱을 더한 다음 결과를 제곱근하십시오. 우리의 경우에는 할 수 없기 때문에 마지막 비트 (제곱근)를하지 않습니다. 다행히 이것은 정렬 순서에 필요하지 않습니다.
크리스 심슨

6
내 앱 BostonBusMap에서이 솔루션을 사용하여 현재 위치에서 가장 가까운 정류장을 표시했습니다. 그러나 cos(latitude)위도와 경도가 대략 동일 하도록 경도 거리를 조정해야합니다 . 참조 en.wikipedia.org/wiki/...
noisecapella

0

가능한 한 성능을 높이기 위해 다음 ORDER BY절을 사용하여 @Chris Simpson의 아이디어를 개선하는 것이 좋습니다 .

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.