1) 首先,在SQLite中使用较好的近似值过滤数据,并减少您需要在Java代码中评估的数据量。为此,请使用以下步骤:
为了在数据上有一个确定性的阈值和更精确的过滤器,最好在Java代码中计算4个位置,这四个位置分别在中心点半径
米以北、西、东、南的位置,然后使用比较运算符(<, >)轻松地判断数据库中的点是否在该矩形内。
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的回答非常有用(谢谢!),但仅适用于使用直角坐标系(例如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>)
这仍然只是一个近似值,但比第一个更好,因此排序顺序的不准确性会更加罕见。
((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN) * <fudge>)
是否小于 distance
或 distance^2
? - Bobs虽然这个问题已经被回答并且已经得到认可,但是我想分享一下我的经验和解决方法。
虽然我很乐意在设备上执行haversine函数来计算用户当前位置与任何特定目标位置之间的精确距离,但有时需要按照距离排序和限制查询结果。
一个不太令人满意的解决方案是返回所有结果,并在此之后进行排序和过滤,但这将导致第二个光标和许多不必要的结果被返回和丢弃。
我的首选解决方案是传入long和lats的平方差值的排序顺序:
((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) +
(<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN))
为了排序而进行完整的haversine计算是没有必要的,也没有必要对结果进行平方根,因此SQLite可以处理这个计算。
编辑:
这个答案仍然受到关注。在大多数情况下它都能正常工作,但如果你需要更高的精度,请查看@Teasel的回答,他添加了一个“调整系数”,可以修复随着纬度接近90度而增加的不准确性。
cos(latitude)
来缩放经度距离,以使纬度和经度大致相等。请参阅http://en.wikipedia.org/wiki/Geographical_distance#Spherical_Earth_projected_to_a_plane。 - noisecapellaORDER 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
作为额外的列。将实体插入数据库时进行填充。这样做可以稍微提高在提取大量数据时的性能。
//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));
希望这能帮到你!