Ich möchte in der Lage, eine SQL SELECT (sqlite) auf einem Tisch und ORDER BY jeder Zeilen Abstand von einem beliebigen Punkt durchzuführen,

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

Frage

Ich habe eine SQLite-Datenbank mit einem Tisch voller geografischer Standorte, gespeichert jeweils als Breiten- und Längenwert in Grad. Ich wollte BY Abstand jede Zeile von einem beliebigen Punkt eine SQL SELECT auf dieser Tabelle und durchzuführen, um können. Ich habe erreicht dies durch die definierte benutzerdefinierten SQLite-Funktion (distanceFunc) unten.

Hier ist die Funktion, zusammen mit einem Convenience-Makro von Grad in Radiant zu konvertieren. Diese Funktion basiert auf einer Online-Entfernungsrechner, die Verwendung des sphärischen Kosinussatz macht.

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

„in sphärischer Trigonometrie, das Gesetz der Cosinus (auch die Cosinus-Regel für Seiten genannt) ist ein Satz beziehen, die Seiten und Winkel der sphärischen Dreiecken, die analog zu dem gewöhnlichen Kosinussatz von der Ebene der Trigonometrie.“

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

Die "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);
}

Ich nenne meine Methode "getDistanceBetweenLongLat", die auch in "Location.m" wie folgt aus:

Wird in meinem (AppDelegate.m):

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

Meine "getDistanceBetweenLongLat" -Methode 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);
    }
}

Das ist das sogenannte "distanceFunc" just fine. Allerdings, wenn es kommt zu der Aussage, wo es überprüft, dass alle vier Argumente sind nicht-null sie alle kommen zurück als null sind.

Allerdings, wenn ich meine SQL-Anweisung oben auf die folgende Änderung:

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

Die vier Argumente kommen nicht als null zurück. Allerdings sind die beiden letzten Argument Werte 0, wenn sie die folgenden sein sollte, nicht wahr?

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

Von "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

Ich verwende so ziemlich die gleiche Methode Anfangsdatum Anzeige zu erhalten und dass bestimmte Array gefüllt werden just fine. Dieses Mal habe ich meine Positionen Array nur wollen (in _ad.locations) bestellt, die Ergebnisse enthalten nach Entfernung statt, damit ich sie in einem UITableView angezeigt werden kann.

Hinweis: Die "distanceFunc" verwendet wird, kann auch unter folgendem Link zu finden: http://www.thismuchiknow.co.uk/?p=71&cpage=1#comment-30834

War es hilfreich?

Lösung

Ich verwende eine Variation, wo latVal und Longval meine aktuelle Position ist.

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

i auch eine Variable verwenden, um den Entfernungswert innerhalb des distanceFunc zu lesen, die letzten beiden Zeilen des folgenden Wechsel:

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

ich dann speichern Sie die distanceVal in meine Objekte, die das Spiel passen, eine Art, um die Elemente der Anwendung zurückgegeben:

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"distance" ascending:YES];
[ locations sortUsingDescriptors: [ NSArray arrayWithObject: sortDescriptor ] ];
[sortDescriptor release];
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top