SQLite coraz najbliższe lokalizacje (z szerokości i długości geograficznej)

Mam dane o szerokości i długości geograficznej zapisane w mojej bazie danych SQLite i chcę uzyskać najbliższe lokalizacje do parametrów, które umieściłem (np. Moja obecna lokalizacja-lat / lng itp.).

Wiem, że jest to możliwe w MySQL, i zrobiłem trochę badań, że SQLite potrzebuje niestandardowej funkcji zewnętrznej dla Formuły Haversine (Obliczanie odległości na kuli), ale nie znalazłem nic, co jest napisane w Javie i działa.

Również, jeśli chcę dodać funkcje niestandardowe, potrzebuję org.sqlite.jar (for org.sqlite.Function), A to dodaje niepotrzebny rozmiar do aplikacji.

Druga strona jest taka, że potrzebuję kolejności według funkcji z SQL, ponieważ wyświetlanie samej odległości nie stanowi większego problemu - zrobiłem to już w moim custom SimpleCursorAdapter, ale nie mogę sortować danych, ponieważ nie mam kolumny odległość w mojej bazie danych. Oznacza to aktualizowanie bazy danych za każdym razem, gdy zmienia się Lokalizacja, a to strata baterii i wydajności. Więc jeśli ktoś ma jakiś pomysł na sortowanie kursora kolumną, której nie ma w bazie danych, też byłbym wdzięczny!

Wiem, że istnieje mnóstwo aplikacji na Androida, które używają tej funkcji, ale czy ktoś może wyjaśnić magię.

Przy okazji, znalazłem alternatywę: zapytanie, aby uzyskać rekordy oparte na Radius w SQLite?

Sugeruje, aby zrobić 4 nowe kolumny dla wartości cos i sin lat i lng, ale czy jest jakiś inny, nie tak zbędny sposób?

Author: Community, 2010-09-12

5 answers

1) na początku filtruj dane SQLite z dobrym przybliżeniem i zmniejsz ilość danych, które musisz ocenić w kodzie java. W tym celu należy zastosować następującą procedurę:

Aby mieć deterministyczny próg i dokładniejszy filtr danych, lepiej obliczyć 4 lokalizacje, które znajdują się w radius na północ, zachód, wschód i południe od twojego centralnego punktu w kodzie java, a następnie łatwo sprawdzić o mniej i więcej niż Operatory SQL(>, aby określić, czy twoje punkty w bazie danych są w tym prostokącie, czy nie.

Metoda calculateDerivedPosition(...) oblicza te punkty dla Ciebie (p1, p2, P3, P4 Na zdjęciu).

Tutaj wpisz opis obrazka

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

    }

A teraz Utwórz swoje zapytanie:

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 jest nazwą kolumny w bazie danych, która przechowuje wartości szerokości geograficznej i {[6] } jest dla długości geograficznej.

Więc masz pewne dane, które są blisko Twojego punktu centralnego z dobrym przybliżeniem.

2) Teraz możesz zapętlić te przefiltrowane Dane i określić, czy są one naprawdę blisko Twojego punktu (w okręgu), czy nie, używając następujących metod:

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;
    }
Smacznego! Wykorzystałem i dostosowałem ten odnośnik i go uzupełniłem.
 113
Author: Bobs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-15 00:15:53

Odpowiedź Chrisa jest naprawdę przydatna (dzięki!), ale będzie działać tylko wtedy, gdy używasz współrzędnych prostoliniowych (np. Jeśli używasz stopni dla lat/lng (NP WGS84) to powyższe działa tylko na równiku. Na innych szerokościach geograficznych należy zmniejszyć wpływ długości geograficznej na kolejność sortowania. (Wyobraź sobie, że jesteś blisko bieguna północnego... stopień szerokości geograficznej jest nadal taki sam jak wszędzie, ale stopień długości geograficznej może być tylko kilka stóp. Będzie to oznaczać, że kolejność sortowania jest niepoprawne).

Jeśli nie jesteś na równiku, wstępnie Oblicz współczynnik krówki, na podstawie bieżącej szerokości geograficznej:

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

Następnie zamów przez:

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

To wciąż tylko przybliżenie, ale znacznie lepsze niż pierwsze, więc nieścisłości w kolejności sortowania będą znacznie rzadsze.

 71
Author: Teasel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-21 12:51:14

Wiem, że to zostało odebrane i zaakceptowane, ale pomyślałem, że dodam moje doświadczenia i rozwiązanie.

Podczas gdy byłem szczęśliwy wykonując funkcję haversine na urządzeniu, aby obliczyć dokładną odległość między aktualną pozycją użytkownika a konkretną lokalizacją docelową, konieczne było sortowanie i ograniczanie wyników zapytań w kolejności odległości.

Mniej niż zadowalającym rozwiązaniem jest zwrócenie partii i sortowanie i filtrowanie po fakcie, ale spowodowałoby to drugi kursor i wiele niepotrzebnych rezultatów jest zwracanych i odrzucanych.

Moim preferowanym rozwiązaniem było przekazanie w porządku sortowania kwadratowych wartości delta długości i lat:

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

Nie ma potrzeby wykonywania pełnego haversine tylko dla kolejności sortowania i nie ma potrzeby pierwiastkowania kwadratowego wyników, dlatego SQLite może obsłużyć obliczenia.

EDIT:

[1]}Ta odpowiedź wciąż otrzymuje miłość. W większości przypadków działa dobrze, ale jeśli potrzebujesz trochę większej dokładności, proszę sprawdź odpowiedź @Teasel poniżej, która dodaje czynnik "krówka", który naprawia nieścisłości, które zwiększają się w miarę zbliżania szerokości geograficznej 90.
 69
Author: Chris Simpson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-19 00:17:57

W celu jak największego zwiększenia wydajności proponuję ulepszyć pomysł @ Chrisa Simpsona następującą klauzulą ORDER BY:

ORDER BY (<L> - <A> * LAT_COL - <B> * LON_COL + LAT_LON_SQ_SUM)

W tym przypadku należy przekazać następujące wartości z kodu:

<L> = center_lat^2 + center_lon^2
<A> = 2 * center_lat
<B> = 2 * center_lon

I należy również przechowywać LAT_LON_SQ_SUM = LAT_COL^2 + LON_COL^2 jako dodatkową kolumnę w bazie danych. Wypełnij go wstawiając swoje podmioty do bazy danych. To nieznacznie poprawia wydajność przy wydobywaniu dużej ilości danych.

 0
Author: Sergey Metlov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-12 11:37:03

Spróbuj czegoś takiego:

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

Następnie id zawiera element, który chcesz pobrać z bazy danych, więc możesz go pobrać:

    //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)); 
Mam nadzieję, że to pomoże!
 -3
Author: Scott Helme,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-13 13:03:34