SQlite Getting nearest locations (with latitude and longitude)

后端 未结 5 586
無奈伤痛
無奈伤痛 2020-11-22 05:54

I have data with latitude and longitude stored in my SQLite database, and I want to get the nearest locations to the parameters I put in (ex. My current location - lat/lng,

相关标签:
5条回答
  • 2020-11-22 06:34

    1) At first filter your SQLite data with a good approximation and decrease amount of data that you need to evaluate in your java code. Use the following procedure for this purpose:

    To have a deterministic threshold and more accurate filter on data, It is better to calculate 4 locations that are in radius meter of the north, west, east and south of your central point in your java code and then check easily by less than and more than SQL operators (>, <) to determine if your points in database are in that rectangle or not.

    The method calculateDerivedPosition(...) calculates those points for you (p1, p2, p3, p4 in picture).

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

    And now create your query:

    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 is the name of the column in the database that stores latitude values and COL_Y is for longitude.

    So you have some data that are near your central point with a good approximation.

    2) Now you can loop on these filtered data and determine if they are really near your point (in the circle) or not using the following methods:

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

    Enjoy!

    I used and customized this reference and completed it.

    0 讨论(0)
  • 2020-11-22 06:38

    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.

    0 讨论(0)
  • 2020-11-22 06:39

    Try something like this:

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

    After this, id contains the item you want from the database so you can fetch it:

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

    Hope that helps!

    0 讨论(0)
  • 2020-11-22 06:43

    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
    

    And you should also store LAT_LON_SQ_SUM = LAT_COL^2 + LON_COL^2 as additional column in database. Populate it inserting your entities into database. This slightly improves performance while extracting large amount of data.

    0 讨论(0)
  • 2020-11-22 06:51

    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.

    0 讨论(0)
提交回复
热议问题