Saya memiliki data dengan lintang dan bujur yang disimpan dalam database SQLite saya, dan saya ingin mendapatkan lokasi terdekat dengan parameter yang saya masukkan (mis. Lokasi saya saat ini - lat / lng, dll.).
Saya tahu bahwa ini dimungkinkan di MySQL, dan saya telah melakukan cukup banyak penelitian bahwa SQLite memerlukan fungsi eksternal khusus untuk rumus Haversine (menghitung jarak pada bola), tetapi saya belum menemukan apa pun yang tertulis di Java dan berfungsi .
Selain itu, jika saya ingin menambahkan fungsi khusus, saya memerlukan org.sqlite
.jar (untuk org.sqlite.Function
), dan itu menambahkan ukuran yang tidak perlu ke aplikasi.
Sisi lain dari ini adalah, saya memerlukan Urutan berdasarkan fungsi dari SQL, karena menampilkan jarak saja tidak terlalu menjadi masalah - Saya sudah melakukannya di SimpleCursorAdapter kustom saya, tetapi saya tidak dapat mengurutkan data, karena saya tidak memiliki kolom jarak di database saya. Itu berarti memperbarui database setiap kali lokasi berubah dan itu hanya membuang-buang baterai dan kinerja. Jadi, jika seseorang memiliki ide untuk mengurutkan kursor dengan kolom yang tidak ada di database, saya juga akan berterima kasih!
Saya tahu ada banyak sekali aplikasi Android di luar sana yang menggunakan fungsi ini, tetapi bisakah seseorang menjelaskan keajaibannya.
Omong-omong, saya menemukan alternatif ini: Kueri untuk mendapatkan catatan berdasarkan Radius di SQLite?
Ini menyarankan untuk membuat 4 kolom baru untuk nilai cos dan sin dari lat dan lng, tetapi adakah cara lain yang tidak terlalu berlebihan?
sumber
Jawaban:
1) Pertama-tama filter data SQLite Anda dengan perkiraan yang baik dan kurangi jumlah data yang perlu Anda evaluasi dalam kode java Anda. Gunakan prosedur berikut untuk tujuan ini:
Untuk memiliki ambang deterministik dan filter yang lebih akurat pada data, lebih baik menghitung 4 lokasi yang berada dalam
radius
meter dari utara, barat, timur dan selatan titik pusat Anda dalam kode java dan kemudian memeriksa dengan mudah dengan kurang dari dan lebih dari Operator SQL (>, <) untuk menentukan apakah poin Anda dalam database berada dalam persegi panjang itu atau tidak.Metode
calculateDerivedPosition(...)
menghitung poin tersebut untuk Anda (p1, p2, p3, p4 dalam gambar)./** * 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; }
Dan sekarang buat kueri Anda:
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
adalah nama kolom dalam database yang menyimpan nilai garis lintang danCOL_Y
untuk bujur.Jadi, Anda memiliki beberapa data yang mendekati titik pusat Anda dengan perkiraan yang baik.
2) Sekarang Anda dapat mengulang data yang difilter ini dan menentukan apakah data tersebut benar-benar dekat dengan titik Anda (dalam lingkaran) atau tidak menggunakan metode berikut:
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; }
Nikmati!
Saya menggunakan dan menyesuaikan referensi ini dan menyelesaikannya.
sumber
Jawaban Chris sangat berguna (terima kasih!), Tetapi hanya akan berfungsi jika Anda menggunakan koordinat bujursangkar (mis. Referensi kisi UTM atau OS). Jika menggunakan derajat untuk lintang / bujur (misalnya WGS84) maka yang di atas hanya berfungsi di ekuator. Di lintang lain, Anda perlu mengurangi pengaruh bujur pada tata urutan. (Bayangkan Anda dekat dengan kutub utara ... derajat garis lintangnya masih sama dengan di mana pun, tetapi derajat garis bujur mungkin hanya beberapa kaki. Ini berarti urutan pengurutannya salah).
Jika Anda tidak berada di ekuator, hitung dahulu faktor fudge, berdasarkan garis lintang Anda saat ini:
<fudge> = Math.pow(Math.cos(Math.toRadians(<lat>)),2);
Kemudian pesan berdasarkan:
((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN) * <fudge>)
Ini masih hanya perkiraan, tetapi jauh lebih baik daripada yang pertama, jadi ketidakakuratan urutan urutan akan jauh lebih jarang.
sumber
((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN) * <fudge>)
kurang daridistance
ataudistance^2
?Saya tahu ini telah dijawab dan diterima tetapi saya pikir saya akan menambahkan pengalaman dan solusi saya.
Meskipun saya senang melakukan fungsi haversine pada perangkat untuk menghitung jarak akurat antara posisi pengguna saat ini dan lokasi target tertentu, ada kebutuhan untuk mengurutkan dan membatasi hasil kueri dalam urutan jarak.
Solusi yang kurang memuaskan adalah mengembalikan lot, mengurutkan, dan memfilter setelah kejadian tetapi ini akan menghasilkan kursor kedua dan banyak hasil yang tidak perlu dikembalikan dan dibuang.
Solusi yang saya sukai adalah meneruskan dengan urutan nilai delta kuadrat dari long dan lats:
((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN))
Tidak perlu melakukan haversine lengkap hanya untuk pengurutan dan tidak perlu mengakar pangkat dua hasil sehingga SQLite dapat menangani penghitungan.
EDIT:
Jawaban ini tetap menerima cinta. Ini berfungsi dengan baik dalam banyak kasus, tetapi jika Anda memerlukan sedikit lebih banyak akurasi, silakan lihat jawaban @Teasel di bawah ini yang menambahkan faktor "fudge" yang memperbaiki ketidakakuratan yang meningkat saat garis lintang mendekati 90.
sumber
cos(latitude)
membuat garis lintang dan garis bujur kira-kira sama. Lihat en.wikipedia.org/wiki/…Untuk meningkatkan performa sebanyak mungkin, saya sarankan untuk meningkatkan ide @Chris Simpson dengan
ORDER BY
klausa berikut :ORDER BY (<L> - <A> * LAT_COL - <B> * LON_COL + LAT_LON_SQ_SUM)
Dalam hal ini Anda harus meneruskan nilai berikut dari kode:
<L> = center_lat^2 + center_lon^2 <A> = 2 * center_lat <B> = 2 * center_lon
Dan Anda juga harus menyimpan
LAT_LON_SQ_SUM = LAT_COL^2 + LON_COL^2
sebagai kolom tambahan di database. Isi itu dengan memasukkan entitas Anda ke dalam database. Ini sedikit meningkatkan kinerja saat mengekstrak data dalam jumlah besar.sumber
Coba sesuatu seperti ini:
//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)); } }
Setelah ini, id berisi item yang Anda inginkan dari database sehingga Anda dapat mengambilnya:
//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));
Semoga membantu!
sumber