SQlite निकटतम स्थान प्राप्त करना (अक्षांश और देशांतर के साथ)


86

मेरे पास अपने SQLite डेटाबेस में संग्रहीत अक्षांश और देशांतर के साथ डेटा है, और मैं उन मापदंडों को निकटतम स्थान प्राप्त करना चाहता हूं जो मैं (उदाहरण के लिए। मेरा वर्तमान स्थान - lat / lng, आदि)।

मुझे पता है कि यह MySQL में संभव है, और मैंने काफी कुछ शोध किया है कि SQLite को हेवेरसाइन फॉर्मूला (एक गोले पर दूरी की गणना) के लिए एक कस्टम बाहरी फ़ंक्शन की आवश्यकता है, लेकिन मुझे ऐसा कुछ भी नहीं मिला है जो जावा में लिखा गया हो और काम करता हो ।

इसके अलावा, अगर मैं कस्टम फ़ंक्शंस जोड़ना चाहता हूं, तो मुझे org.sqlite.jar (for org.sqlite.Function) की आवश्यकता है, और यह ऐप में अनावश्यक आकार जोड़ता है।

इसका दूसरा पक्ष यह है, मुझे SQL से फ़ंक्शन द्वारा ऑर्डर की आवश्यकता है, क्योंकि अकेले दूरी प्रदर्शित करना एक समस्या नहीं है - मैंने इसे पहले ही अपने कस्टम SimpleCursorAdapter में किया था, लेकिन मैं डेटा को सॉर्ट नहीं कर सकता, क्योंकि मैं मेरे डेटाबेस में दूरी कॉलम नहीं है। इसका मतलब है कि हर बार स्थान बदलने के बाद डेटाबेस को अपडेट करना और यह बैटरी और प्रदर्शन की बर्बादी है। तो अगर किसी के पास डेटाबेस में नहीं है एक कॉलम के साथ कर्सर को छाँटने पर कोई विचार है, तो मैं बहुत आभारी रहूँगा!

मुझे पता है कि वहाँ कई Android एप्लिकेशन हैं जो इस फ़ंक्शन का उपयोग करते हैं, लेकिन क्या कोई जादू को समझा सकता है।

वैसे, मुझे यह विकल्प मिल गया: SQLite में रेडियस के आधार पर रिकॉर्ड प्राप्त करने के लिए क्वेरी?

यह लेट और लैंग के कॉस और पाप मूल्यों के लिए 4 नए कॉलम बनाने का सुझाव दे रहा है, लेकिन क्या कोई दूसरा तरीका नहीं है, इतना बेमानी तरीका है?


क्या आपने जांच की कि क्या org.sqlite.Function आपके लिए काम करता है (भले ही फॉर्मूला सही न हो)?
थॉमस मुलर

नहीं, मुझे एक (निरर्थक) विकल्प (संपादित पोस्ट) मिला जो ऐप में 2,6MB .jar जोड़ने से बेहतर लगता है। लेकिन मैं अब भी एक बेहतर समाधान खोज रहा हूं। धन्यवाद!
Jure

वापसी दूरी इकाई प्रकार क्या है?

आपके स्थान और ऑब्जेक्ट के स्थान के बीच की दूरी के आधार पर Android पर SQlite क्वेरी बनाने के लिए यहां एक पूर्ण कार्यान्वयन है
एरिकलार्क

जवाबों:


112

1) पहले अपने SQLite डेटा को एक अच्छे सन्निकटन के साथ फ़िल्टर करें और उस डेटा की मात्रा कम करें जिसे आपको अपने जावा कोड में मूल्यांकन करने की आवश्यकता है। इस उद्देश्य के लिए निम्नलिखित प्रक्रिया का उपयोग करें:

एक नियतकालिक सीमा है और डेटा पर अधिक सटीक फिल्टर, यह गणना करने के लिए बेहतर है 4 स्थानों है कि में हैं radiusउत्तर की मीटर, पश्चिम, पूर्व और अपने केंद्रीय बिंदु के दक्षिण अपने जावा कोड में और उसके बाद से आसानी से जांच की तुलना में और अधिक से अधिक कम SQL ऑपरेटर (>, <) यह निर्धारित करने के लिए कि डेटाबेस में आपके अंक उस आयत में हैं या नहीं।

विधि 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;
    }

का आनंद लें!

मैंने इस संदर्भ का उपयोग और अनुकूलित किया और इसे पूरा किया।


यदि आप ऊपर इस अवधारणा के एक जावास्क्रिप्ट कार्यान्वयन के लिए देख रहे हैं तो क्रिस वेनेस महान वेबपेज देखें। movable-type.co.uk/scripts/latlong.html
barneymc

@ मेनमा एक्स अक्षांश है और y देशांतर है। त्रिज्या: वृत्त का त्रिज्या जो चित्र में दिखाया गया है।
बोब्स

ऊपर दिया गया समाधान सही है और काम करता है। इसे आज़माएं ... :)
YS

1
यह एक अनुमानित समाधान है! यह आपको एक तेज़ , अनुक्रमणिका-अनुकूल SQL क्वेरी के माध्यम से लगभग अनुमानित परिणाम देता है । यह कुछ विषम परिस्थितियों में गलत परिणाम देगा। एक बार जब आप कई किलोमीटर के क्षेत्र के भीतर अनुमानित परिणाम की छोटी संख्या प्राप्त कर लेते हैं, तो उन परिणामों को फ़िल्टर करने के लिए धीमी, अधिक सटीक विधियों का उपयोग करें । बहुत बड़े दायरे के साथ फ़िल्टर करने के लिए इसका उपयोग न करें या यदि आपका ऐप भूमध्य रेखा पर अक्सर उपयोग किया जा रहा है!
user1643723

1
लेकिन मुझे नहीं मिला। कैलक्लेटडेटिवपिशन लाट को रूपांतरित कर रहा है, एक कार्टेसियन में लैंग कोऑर्डिनेट करता है और फिर आप एसक्यूएल क्वेरी में, आप इन कारटेशियन मानों की लैट, लॉन्ग वैल्यू से तुलना कर रहे हैं। दो अलग-अलग ज्यामितीय निर्देशांक? यह कैसे काम करता है? धन्यवाद।
दुष्कर्म

70

क्रिस का जवाब वास्तव में उपयोगी है (धन्यवाद!), लेकिन केवल तभी काम करेगा जब आप रेक्टिलाइनियर निर्देशांक (जैसे 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>)

यह अभी भी केवल एक सन्निकटन है, लेकिन पहले वाले की तुलना में बहुत बेहतर है, इसलिए सॉर्ट क्रम गलतियाँ बहुत दुर्लभ होंगी।


3
यह ध्रुवों पर परिवर्तित होने वाली अनुदैर्ध्य रेखाओं के बारे में एक बहुत ही दिलचस्प बिंदु है और परिणाम आपको प्राप्त होने वाले निकट को तिरछा करना है। अच्छा तय किया।
क्रिस सिम्पसन 3

1
यह कर्सर काम करने लगता है "- लोन) * (" + देशांतर + "- लोन) *" + फ्यूज + "के रूप में डिस्टेंज़ा" + "क्लाइंट से" + "डिस्टेंजा द्वारा ऑर्डर" आरएससी ", नल);
अधिकतम 4

((<lat> - LAT_COLUMN) * (<lat> - LAT_COLUMN) + (<lng> - LNG_COLUMN) * (<lng> - LNG_COLUMN) * <fudge>)से कम होना चाहिए distanceया distance^2?
बोब्स

रेडियन और स्तंभों में डिग्री में फ्रॉड कारक नहीं है? क्या उन्हें एक ही इकाई में परिवर्तित नहीं किया जाना चाहिए?
रंगेल रीले

1
नहीं, ठगना कारक एक स्केलिंग कारक है जो ध्रुवों पर 0 और भूमध्य रेखा पर 1 है। यह न तो डिग्री में है, न ही रेडियन, यह सिर्फ एक इकाई रहित संख्या है। जावा Math.cos फ़ंक्शन को रेडियन में एक तर्क की आवश्यकता होती है, और मैंने माना कि <lat> डिग्री में था, इसलिए Math.toReians फ़ंक्शन। लेकिन परिणामस्वरूप कोसाइन की कोई इकाई नहीं है।
तेज

68

मुझे पता है कि यह उत्तर दिया गया है और स्वीकार किया गया है, लेकिन मैंने सोचा कि मैं अपने अनुभव और समाधान जोड़ूंगा।

जब भी मैं उपयोगकर्ता की वर्तमान स्थिति और किसी विशेष लक्ष्य स्थान के बीच की दूरी की गणना करने के लिए डिवाइस पर एक हावरसिन फ़ंक्शन करने के लिए खुश था, तो दूरी के क्रम में क्वेरी परिणामों को सॉर्ट करने और सीमित करने की आवश्यकता थी।

संतोषजनक समाधान की तुलना में कम लौटना और सॉर्ट करना है और इस तथ्य के बाद फ़िल्टर करना है, लेकिन इसके परिणामस्वरूप एक दूसरा कर्सर होगा और कई अनावश्यक परिणाम वापस आ जाएंगे और छोड़ दिए जाएंगे।

मेरा पसंदीदा समाधान लंबे और अक्षांशों के चुकता डेल्टा मूल्यों के एक क्रम में पारित करना था:

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

किसी प्रकार के आदेश के लिए पूर्ण हाईवेरिन करने की आवश्यकता नहीं है और परिणामों को वर्गमूल करने की कोई आवश्यकता नहीं है इसलिए SQLite गणना को संभाल सकता है।

संपादित करें:

इस जवाब को अभी भी प्यार मिल रहा है। यह ज्यादातर मामलों में ठीक काम करता है, लेकिन अगर आपको थोड़ी और सटीकता की आवश्यकता है, तो कृपया नीचे दिए गए @Teasel द्वारा उत्तर की जांच करें जिसमें एक "ठगना" कारक जोड़ा गया है जो अक्षांशों को बढ़ाने वाले अशुद्धियों को ठीक करता है जो कि 90 के करीब पहुंचता है।


बहुत बढ़िया जवाब। क्या आप बता सकते हैं कि यह कैसे काम करता है और इस एल्गोरिथम को क्या कहा जाता है?
iMatoria

3
@ आईमैटोरिया - यह पाइथागोरस प्रसिद्ध प्रमेय का सिर्फ एक कट डाउन संस्करण है। निर्देशांक के दो सेटों को देखते हुए, दो एक्स मानों के बीच का अंतर एक समकोण त्रिभुज के एक तरफ का प्रतिनिधित्व करता है और वाई मूल्यों के बीच का अंतर दूसरा है। कर्ण को प्राप्त करने के लिए (और इसलिए अंकों के बीच की दूरी) आप इन दो मूल्यों के वर्गों को एक साथ जोड़ते हैं और फिर परिणाम को वर्गमूल करते हैं। हमारे मामले में हम अंतिम बिट (वर्गमूलक) नहीं करते हैं क्योंकि हम नहीं कर सकते हैं। सौभाग्य से यह क्रमबद्ध क्रम के लिए आवश्यक नहीं है।
क्रिस सिम्पसन 3

6
मेरे ऐप में बोस्टनबसमैप मैंने वर्तमान स्थान के सबसे निकट स्टॉप दिखाने के लिए इस समाधान का उपयोग किया। हालाँकि आपको cos(latitude)अक्षांश और देशांतर लगभग बराबर होने के लिए देशांतर दूरी को मापना होगा। देखें en.wikipedia.org/wiki/...
noisecapella

0

जितना संभव हो उतना प्रदर्शन बढ़ाने के लिए मैं निम्नलिखित ORDER BYखंड के साथ @ क्रिस सिम्पसन के विचार को बेहतर बनाने का सुझाव देता हूं :

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डेटाबेस में अतिरिक्त कॉलम के रूप में भी स्टोर करना चाहिए । डेटाबेस में अपनी संस्थाओं को सम्मिलित करते हुए इसे आबाद करें। यह बड़ी मात्रा में डेटा निकालते समय प्रदर्शन में थोड़ा सुधार करता है।


-3

कुछ इस तरह की कोशिश करो:

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

इसके बाद, आईडी में वह आइटम होता है जिसे आप डेटाबेस से चाहते हैं ताकि आप उसे प्राप्त कर सकें:

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

उम्मीद है की वो मदद करदे!

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.