2010-11-20 26 views
2

J'ai une base de données sqlite avec une table pleine de lieux géographiques, chacun stocké comme une valeur de latitude et de longitude en degrés. Je voulais être capable d'exécuter un SELECT SQL sur cette table et d'ORDER BY la distance de chaque rangée d'un point arbitraire. J'ai réalisé cela en utilisant la fonction sqlite personnalisée définie (distanceFunc) ci-dessous.Je veux être en mesure d'effectuer un SQL SELECT (sqlite) sur une table et ORDER BY chaque distance de lignes à partir d'un point arbitraire

Voici la fonction, avec une macro de commodité 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 des 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); 
    } 
} 

Ceci appelle le "distanceFunc" très bien. Cependant, quand il arrive à l'instruction où il vérifie que tous les quatre arguments sont non-nul, ils reviennent tous à zéro.

Cependant, quand je change ma déclaration sql ci-dessus pour les éléments suivants:

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'argument sont 0 quand elles devraient être les suivantes, non?

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 pour afficher et ce tableau particulier Remplit très bien. Cette fois, je veux juste que mon tableau d'emplacements (dans _ad.locations) contienne les résultats ordonnés par la distance à la place afin que je puisse les afficher dans un UITableView.

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

+0

Encore bloqué sur celui-ci. Il a déjà eu 30 points de vue et personne n'est même intéressé à prendre une fissure à ce sujet? – fuzz

+1

Utilisez [FMDB] (http://github.com/ccgus/fmdb). –

+0

Je vais essayer FMDB, mais j'aimerais quand même que mon code fonctionne correctement. – fuzz

Répondre

4

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 utilise également une variable pour lire la valeur de distance dans le distanceFunc, en changeant les deux dernières lignes à 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 le match , en appliquant un tri aux éléments retournés:

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"distance" ascending:YES]; 
[ locations sortUsingDescriptors: [ NSArray arrayWithObject: sortDescriptor ] ]; 
[sortDescriptor release]; 
+0

Merci mon pote, je l'ai implémenté de cette façon et ça fonctionne comme prévu. – fuzz