Je veux être en mesure d'effectuer une requête SQL SELECT (sqlite) sur une table et ORDER BY chaque distance de lignes d'un point arbitraire

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

Question

J'ai une base de données SQLite avec une table des emplacements géographiques, chacun enregistré une valeur de latitude et de longitude en degrés. Je voulais être en mesure d'effectuer un SQL SELECT sur cette table et ORDER BY distance de chaque rangée d'un point arbitraire. J'ai réalisé cela en utilisant la fonction définie sur mesure SQLite (distanceFunc) ci-dessous.

Voici la fonction, avec une macro pratique pour convertir des degrés en radians. Cette fonction est basée sur un calculateur de distance en ligne, qui utilise la loi sphérique de cosinus.

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

« Pour trigonométrie sphérique, la loi des cosinus (également appelée la règle de cosinus pour les côtés) est un théorème reliant les côtés et les angles de triangles sphériques, analogues à la loi ordinaire du cosinus de trigonométrie plane. »

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

Le "distanceFunc" dans (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);
}

J'appelle ma méthode « getDistanceBetweenLongLat » qui est aussi dans « Location.m » comme ceci:

Utilisé dans mon (AppDelegate.m):

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

Ma méthode "getDistanceBetweenLongLat" dans (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);
    }
}

appelle la « distanceFunc » très bien. Cependant, quand il arrive à la déclaration où il vérifie que les quatre arguments ne sont pas nuls, ils sont tous reviennent comme nulle.

Cependant, quand je change ma déclaration sql ci-dessus pour ce qui suit:

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

Les quatre arguments ne reviennent pas comme nulle. Cependant, les deux dernières valeurs d'arguments sont 0 quand ils devraient être les suivants, à droite?

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

De "distanceFunc" dans (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

J'utilise à peu près la même méthode pour obtenir des données initiales à l'affichage et ce tableau particulier Remplit très bien. Cette fois, je veux juste mon tableau des emplacements (en _ad.locations) pour contenir les résultats ordonnés par la distance au lieu que je puisse les afficher dans un UITableView.

NOTE: Le "distanceFunc" utilisé peut également être trouvée à l'adresse suivante: http://www.thismuchiknow.co.uk/?p=71&cpage=1#comment-30834

Était-ce utile?

La solution

J'utilise une variante, où latVal et Longval sont mon emplacement actuel.

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

i utilisent également une variable à lire la valeur de distance au sein de la distanceFunc, en changeant les deux dernières lignes de ce qui suit:

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

Je puis enregistrez le distanceVal dans mes objets qui correspondent à la rencontre, en appliquant une sorte aux articles retournés:

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"distance" ascending:YES];
[ locations sortUsingDescriptors: [ NSArray arrayWithObject: sortDescriptor ] ];
[sortDescriptor release];
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top