SQlite获取最近的地点(有经度和纬度)

我有存储在我的SQLite数据库的经度和纬度的数据,我想获得最近的位置,我放入的参数(例如,我的当前位置 – 纬度/经度等)。

我知道在MySQL中这是可能的,我已经做了相当多的研究,SQLite需要一个自定义的Haversine公式的外部函数(计算球体上的距离),但我还没有发现任何用Java编写的东西, 。

另外,如果我想添加自定义函数,我需要org.sqlite .jar(用于org.sqlite.Function ),并将不必要的大小添加到应用程序。

另一方面,我需要SQL函数中的Order by函数,因为单独显示距离并不是什么大问题 – 我已经在自定义的SimpleCursorAdapter中完成了,但我无法对数据进行sorting,因为我在我的数据库中没有距离列。 这意味着每次更改数据库时都会更新数据库,这样会浪费电池和性能。 所以如果有人有任何想法sorting光标与不在数据库中的列,我会很感激!

我知道有很多Android应用程序使用这个function,但有人可以解释这个魔术。

顺便说一句,我发现这个替代方法: 查询获取基于Radius的SQLite中的logging?

它build议为经纬度和纬度的cos和sin值创build4个新的列,但还有其他的,不是多余的方式吗?

1)首先用一个很好的近似值过滤你的SQLite数据,并减less你在java代码中需要评估的数据量。 为此目的使用以下过程:

要有一个确定性的阈值和更准确的数据过滤,最好在你的java代码中计算你的中心点的北,西,东,南radius计中的4个位置 ,然后通过小于等于比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; } 

现在创build你的查询:

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

请享用!

我使用和定制这个参考,并完成它。

克里斯的答案是非常有用的(谢谢!),但只有在使用直线坐标(例如UTM或OS网格引用)时才有效。 如果使用纬度/经度(如WGS84),那么上述只适用于赤道。 在其他纬度,您需要减less经度对sorting顺序的影响。 (想象一下,你接近北极……一定的纬度仍然和其他地方一样,但经度只有几英尺,这意味着sorting顺序是不正确的)。

如果您不在赤道,请根据您当前的纬度预先计算fudge因子:

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

然后按顺序排列:

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

这仍然只是一个近似值,但比第一个好得多,所以sorting不准确将更为罕见。

我知道这已经被回答和接受,但我想我会增加我的经验和解决scheme。

虽然我很高兴在设备上执行半横向function来计算用户当前位置和任何特定目标位置之间的准确距离,但仍需要按照距离的顺序对查询结果进行sorting和限制。

不太令人满意的解决方法是在事实之后返回批次并进行sorting和过滤,但这会导致第二个游标,并且返回并丢弃许多不必要的结果。

我最喜欢的解决scheme是按照长与宽的平方δ值的排列顺序:

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

没有必要为了一个sorting顺序做完整的hasrsine,并且不需要对结果进行平方根,因此SQLite可以处理计算。

编辑:

这个答案还在接受爱情。 它在大多数情况下工作正常,但如果您需要更多的准确性,请查看下面的@Teasel的答案,它增加了一个“fudge”因子,可以修正随着纬度接近90而增加的不准确度。

你有没有考虑过你的项目的Geohash标签/索引,以减less你的结果集的大小,然后应用适当的function。

在类似的区域中的另一个stackoverflow问题: find最接近点的一个给定点

为了尽可能提高性能,我build议用下面的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作为数据库中的附加列存储。 填充它将您的实体插入到数据库中。 这在提取大量数据的同时略微提高了性能。

看看这个post:

距离函数为sqlite

它似乎允许你添加一个自定义的距离()函数到SQLite,这可能会让你避免跳过所有的其他答案。

尝试这样的事情:

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

希望有所帮助!