Voglio essere in grado di eseguire uno SQL SELECT (sqlite) su un tavolo e ORDER BY ciascuna distanza righe da un punto arbitrario

StackOverflow https://stackoverflow.com/questions/4231177

Domanda

Ho un database SQLite con un tavolo pieno di località geografiche, ciascuna memorizzato come un valore di latitudine e longitudine in gradi. Volevo essere in grado di eseguire un SELECT SQL in questa tabella e ORDER BY distanza di ogni riga da un punto arbitrario. Ho raggiunto questo utilizzando la funzione definita sqlite personalizzato (distanceFunc) qui di seguito.

Ecco la funzione, insieme ad una macro convenienza convertire da gradi in radianti. Questa funzione si basa su un calcolatore a distanza online, che si avvale della legge sferica di coseni.

http://en.wikipedia.org/wiki/Spherical_law_of_cosines

"In trigonometria sferica, il teorema del coseno (chiamato anche la regola del coseno per lati) è un teorema relativo lati e angoli di triangoli sferici, analogo al diritto comune dei coseni dalla trigonometria piana."

#define DEG2RAD(degrees) (degrees * 0.01745327) // degrees * pi over 180

Il "distanceFunc" in (Location.m)

static void distanceFunc(sqlite3_context *context, int argc, sqlite3_value **argv) {
 // check that we have four arguments (lat1, lon1, lat2, lon2)
 assert(argc == 4);

 // check that all four arguments are non-null
 if (sqlite3_value_type(argv[0]) == SQLITE_NULL || sqlite3_value_type(argv[1]) == SQLITE_NULL || sqlite3_value_type(argv[2]) == SQLITE_NULL || sqlite3_value_type(argv[3]) == SQLITE_NULL) {
  sqlite3_result_null(context);
  return;
 }

 // get the four argument values
 double lat1 = sqlite3_value_double(argv[0]);
 double lon1 = sqlite3_value_double(argv[1]);
 double lat2 = sqlite3_value_double(argv[2]);
 double lon2 = sqlite3_value_double(argv[3]);

 // convert lat1 and lat2 into radians now, to avoid doing it twice below
 double lat1rad = DEG2RAD(lat1);
 double lat2rad = DEG2RAD(lat2);

 // apply the spherical law of cosines to our latitudes and longitudes, and set the result appropriately
 // 6378.1 is the approximate radius of the earth in kilometres
 sqlite3_result_double(context, acos(sin(lat1rad) * sin(lat2rad) + cos(lat1rad) * cos(lat2rad) * cos(DEG2RAD(lon2) - DEG2RAD(lon1))) * 6378.1);
}

Io lo chiamo il mio metodo "getDistanceBetweenLongLat", che è anche in "Location.m" come questo:

Utilizzato nel mio (AppDelegate.m):

Location *location = [[Location alloc] init];
location.latitude = -37.1134; //double property
location.longitude = 145.4254; //double property
[location getDistanceBetweenLongLat:[self dbPath]];

Il mio metodo "getDistanceBetweenLongLat" in (Location.m):

- (void) getDistanceBetweenLongLat:(NSString *)dbPath {

    AppDelegate *_ad = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK) {

        sqlite3_create_function(database, "distance", 4, SQLITE_UTF8, NULL, &distanceFunc, NULL, NULL);

        const char *sql = "select * from locations order by distance(latitude, longitude, ?, ?)";
        sqlite3_stmt *selectstmt;

        sqlite3_bind_double(selectStmt, 1, latitude);
        sqlite3_bind_double(selectStmt, 2, longitude);

        if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK) {

            while(sqlite3_step(selectstmt) == SQLITE_ROW) {

                NSInteger primaryKey = sqlite3_column_int(selectstmt, 0);
                Location *location = [[Location alloc] initWithPrimaryKey:primaryKey];
                location.latitude = sqlite3_column_double(selectstmt, 1);
                location.longitude = sqlite3_column_double(selectstmt, 2);
                location.title = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 3)];
                location.street1 = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 4)];
                location.street2 = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 5)];
                location.suburb = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 6)];
                NSInteger postCodeItem = sqlite3_column_int(selectstmt, 7);
                location.postcode = postCodeItem;

                location.isDirty = NO;

                [_ad.locations addObject:location];
                [location release];

            }
        }
    } else {
        //Even though the open call failed, close the database connection to release all the memory.
        sqlite3_close(database);
    }
}

Questa è chiamata la "distanceFunc" bene. Tuttavia, quando si arriva alla dichiarazione in cui esso controlla che tutti e quattro gli argomenti sono non nulli sono tutti tornando come null.

Tuttavia, quando cambio la mia dichiarazione di sql sopra al seguente:

const char *sql = "select * from locations order by distance(latitude, longitude, '?', '?')"; //single quotes added

I quattro argomenti non tornare come null. Tuttavia, gli ultimi due valori di argomento sono 0 quando dovrebbero essere i seguenti, giusto?

location.latitude = -37.1134; //double property
location.longitude = 145.4254; //double property:

Da "distanceFunc" in (Location.m):

double lat1 = sqlite3_value_double(argv[0]); // -17.7699 from 1st result in Locations table
double lon1 = sqlite3_value_double(argv[1]); // 245.1103 from 1st result in Locations table
double lat2 = sqlite3_value_double(argv[2]); // 0
double lon2 = sqlite3_value_double(argv[3]); // 0

Io uso più o meno lo stesso metodo per ottenere i dati iniziali per la visualizzazione e che particolari popola di matrice più che bene. Questa volta voglio solo il mio allineamento posizioni (in _ad.locations) per contenere i risultati in ordine di distanza, invece modo che io possa visualizzare in un UITableView.

Nota: il "distanceFunc" usato si possono trovare anche al seguente link: http://www.thismuchiknow.co.uk/?p=71&cpage=1#comment-30834

È stato utile?

Soluzione

Io uso una variante, in cui latVal e Longval sono mia posizione attuale:.

NSString *sqlString = @"SELECT * FROM stores WHERE status = 1 AND distance(latitude, longitude, %f, %f, id) < 800";
sqlString = [NSString stringWithFormat:sqlString, latVal, longVal];

Inoltre uso una variabile per leggere il valore di distanza all'interno del distanceFunc, cambiando le ultime due righe al seguente:

float val = acos(sin(lat1rad) * sin(lat2rad) + cos(lat1rad) * cos(lat2rad) * cos(DEG2RAD(lon2) - DEG2RAD(lon1))) * 6378.1;
distanceVal = (float)val;

sqlite3_result_double(context, val);

I quindi salvare il distanceVal nei miei oggetti che si adattano alla partita, applicando una sorta agli elementi restituiti:

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"distance" ascending:YES];
[ locations sortUsingDescriptors: [ NSArray arrayWithObject: sortDescriptor ] ];
[sortDescriptor release];
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top