SQlite 가장 가까운 위치 얻기 (위도와 경도 포함)
SQLite 데이터베이스에 위도와 경도가있는 데이터가 저장되어 있으며 입력 한 매개 변수 (예 : 내 현재 위치-위도 / 경도 등)에 가장 가까운 위치를 가져오고 싶습니다.
나는 이것이 MySQL에서 가능하다는 것을 알고 있으며 SQLite에 Haversine 공식 (구면의 거리 계산)에 대한 사용자 정의 외부 함수가 필요하다는 꽤 많은 연구를 수행했지만 Java로 작성되어 작동하는 것을 찾지 못했습니다. .
또한 사용자 지정 함수를 추가하려면 org.sqlite
.jar (for org.sqlite.Function
)가 필요하며 앱에 불필요한 크기가 추가됩니다.
다른 측면은 SQL의 Order by 함수가 필요하다는 것입니다. 거리 만 표시하는 것은 그다지 문제가되지 않기 때문입니다. 사용자 지정 SimpleCursorAdapter에서 이미 수행했지만 데이터를 정렬 할 수 없습니다. 내 데이터베이스에 거리 열이 없습니다. 이는 위치가 변경 될 때마다 데이터베이스를 업데이트하는 것을 의미하며 이는 배터리와 성능을 낭비하는 것입니다. 따라서 누군가 데이터베이스에없는 열로 커서를 정렬하는 방법에 대해 알고 있다면 저도 감사하겠습니다!
이 기능을 사용하는 Android 앱이 많이 있다는 것을 알고 있지만 누군가 마술을 설명해 주시겠습니까?
그건 그렇고, 나는이 대안을 찾았습니다 : SQLite의 Radius를 기반으로 레코드를 가져 오는 쿼리?
lat 및 lng의 cos 및 sin 값에 대해 4 개의 새 열을 만들 것을 제안하고 있지만 중복되지 않는 다른 방법이 있습니까?
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's answer is really useful (thanks!), but will only work if you are using rectilinear coordinates (eg UTM or OS grid references). If using degrees for lat/lng (eg WGS84) then the above only works at the equator. At other latitudes, you need to decrease the impact of longitude on the sort order. (Imagine you're close to the north pole... a degree of latitude is still the same as it is anywhere, but a degree of longitude may only be a few feet. This will mean that the sort order is incorrect).
If you are not at the equator, pre-calculate the fudge-factor, based on your current latitude:
<fudge> = Math.pow(Math.cos(Math.toRadians(<lat>)),2);
Then order by:
((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN) * <fudge>)
It's still only an approximation, but much better than the first one, so sort order inaccuracies will be much rarer.
I know this has been answered and accepted but thought I'd add my experiences and solution.
Whilst I was happy to do a haversine function on the device to calculate the accurate distance between the user's current position and any particular target location there was a need to sort and limit the query results in order of distance.
The less than satisfactory solution is to return the lot and sort and filter after the fact but this would result in a second cursor and many unnecessary results being returned and discarded.
My preferred solution was to pass in a sort order of the squared delta values of the long and lats:
((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) +
(<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN))
There's no need to do the full haversine just for a sort order and there's no need to square root the results therefore SQLite can handle the calculation.
EDIT:
This answer is still receiving love. It works fine in most cases but if you need a little more accuracy, please check out the answer by @Teasel below which adds a "fudge" factor that fixes inaccuracies that increase as the latitude approaches 90.
Have you considered a Geohash tag/index for your entries to reduce the size of your result set and then apply the appropriate function.
Another stackoverflow question in a similar area: finding-the-closest-point-to-a-given-point
In order to increase performance as much as possible I suggest improve @Chris Simpson's idea with the following ORDER BY
clause:
ORDER BY (<L> - <A> * LAT_COL - <B> * LON_COL + LAT_LON_SQ_SUM)
In this case you should pass the following values from code:
<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
데이터베이스에 추가 열로 저장해야 합니다. 엔티티를 데이터베이스에 삽입하여 채우십시오. 이렇게하면 많은 양의 데이터를 추출하는 동안 성능이 약간 향상됩니다.
이 게시물을 살펴보십시오.
SQLite에 사용자 정의 Distance () 함수를 추가하여 다른 답변의 모든 후프를 뛰어 넘지 않도록 할 수 있습니다.
다음과 같이 시도하십시오.
//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));
도움이 되었기를 바랍니다.
'developer tip' 카테고리의 다른 글
Android의 다른 레이아웃에서 동일한 ID를 사용할 수 있습니까? (0) | 2020.10.04 |
---|---|
Ruby on Rails의 시작 / 홈 페이지-모범 사례 (0) | 2020.10.04 |
Android : Toast와 함께 onBackPressed ()를 사용하는 적절한 방법 (0) | 2020.10.04 |
SqlConnectionStringBuilder를 사용하여 연결 문자열에서 데이터베이스 이름을 얻는 방법 (0) | 2020.10.04 |
Android 차트 라이브러리 (0) | 2020.10.04 |