SQlite 获取最近的位置(带有纬度和经度)

2022-08-31 11:34:17

我的 SQLite 数据库中存储了带有纬度和经度的数据,并且我想获取与我输入的参数最近的位置(例如我目前的位置 - 纬度/液化天然气等)。

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

此外,如果我想添加自定义函数,我需要.jar(for),这会给应用程序增加不必要的大小。org.sqliteorg.sqlite.Function

另一方面,我需要来自SQL的Order by函数,因为单独显示距离并不是一个大问题 - 我已经在我的自定义SimpleCursorAdapter中做到了这一点,但我无法对数据进行排序,因为我的数据库中没有距离列。这意味着每次位置更改时都会更新数据库,这是浪费电池和性能。因此,如果有人对使用不在数据库中的列对游标进行排序有任何想法,我也将不胜感激!

我知道有大量的Android应用程序使用此功能,但有人可以解释一下它的魔力吗?

顺便说一句,我找到了这个替代方案:查询以获取基于SQLite中的Radius的记录?

它建议为纬度和lng的cos和sin值制作4个新列,但是还有其他不是那么多余的方法吗?


答案 1

1)首先使用良好的近似值过滤SQLite数据,并减少需要在Java代码中评估的数据量。为此,请按下列步骤操作:

要对数据进行确定性阈值和更准确的筛选,最好在 Java 代码中计算中心点的北部、西部、东部和南部的 4 个位置,然后通过小于和大于 SQL 运算符(>、<)轻松检查,以确定数据库中的点是否在该矩形中。radius

该方法为您计算这些点(图片中的p1,p2,p3,p4)。calculateDerivedPosition(...)

enter image description here

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

享受!

我使用并自定义了此引用并完成了它。


答案 2

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

它仍然只是一个近似值,但比第一个好得多,因此排序顺序不准确的情况要少得多。


推荐