我希望能够在表上执行 SQL SELECT (sqlite) 并按每行距任意点的距离进行排序

发布于 2024-10-03 15:52:53 字数 5074 浏览 1 评论 0原文

我有一个 sqlite 数据库,其中包含一个充满地理位置的表,每个位置都存储为以度为单位的纬度和经度值。我希望能够在此表上执行 SQL SELECT 并按每行距任意点的距离进行排序。我通过使用下面定义的自定义 sqlite 函数 (distanceFunc) 实现了这一点。

这是该函数以及一个将度数转换为弧度的便捷宏。该函数基于在线距离计算器,该计算器利用余弦球面定律。

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

“在球面三角学中,余弦(也称为边的余弦规则)是有关球面三角形的边和角的定理,类似于平面三角学中的普通余弦定律。”

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

(Location.m)中的“distanceFunc”

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

我将我的方法称为“getDistanceBetweenLongLat”,该方法也在“Location.m”中,如下所示:

在我的(AppDelegate.m)中使用:

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

(Location.m)中的我的“getDistanceBetweenLongLat”方法:

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

这调用“distanceFunc”就可以了。然而,当它到达检查所有四个参数是否为非空的语句时,它们都将返回为空。

但是,当我将上面的 sql 语句更改为以下内容时:

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

四个参数不会返回为 null。但是,最后两个参数值应该为 0,对吗?

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

来自(Location.m)中的“distanceFunc”:

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

我使用几乎相同的方法来获取要显示的初始数据,并且该特定数组填充得很好。这次我只希望我的位置数组(在 _ad.locations 中)包含按距离排序的结果,这样我就可以在 UITableView 中显示它们。

注意:使用的“distanceFunc”也可以在以下链接中找到:http://www.thismuchiknow.co.uk/?p=71&cpage=1#comment-30834

I have a sqlite database with a table full of geographic locations, each stored as a latitude and longitude value in degrees. I wanted to be able to perform a SQL SELECT on this table and ORDER BY each row’s distance from an arbitrary point. I’ve achieved this by using the defined custom sqlite function (distanceFunc) below.

Here’s the function, together with a convenience macro to convert from degrees to radians. This function is based on an online distance calculator, which makes use of the spherical law of cosines.

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

"In spherical trigonometry, the law of cosines (also called the cosine rule for sides) is a theorem relating the sides and angles of spherical triangles, analogous to the ordinary law of cosines from plane trigonometry."

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

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

I call my method "getDistanceBetweenLongLat" which is also in "Location.m" like this:

Used in my (AppDelegate.m):

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

My "getDistanceBetweenLongLat" method 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);
    }
}

This is calling the "distanceFunc" just fine. However when it gets to the statement where it checks that all four arguments are non-null they are all coming back as null.

However, when I change my sql statement above to the following:

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

The four arguments don't come back as null. However, the last two argument values are 0 when they should be the following, right?

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

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

I use pretty much the same method to get initial data to display and that particular array populates just fine. This time I just want my locations array (in _ad.locations) to contain the results ordered by distance instead so I can display them in a UITableView.

NOTE: The "distanceFunc" used can also be found at the following link: http://www.thismuchiknow.co.uk/?p=71&cpage=1#comment-30834

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

卷耳 2024-10-10 15:52:53

我使用一个变体,其中 latVal 和 longVal 是我当前的位置。:

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

我还使用一个变量来读取 distanceFunc 内的距离值,将最后两行更改为以下内容:

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

然后将 distanceVal 保存到适合匹配的对象中,对返回的项目进行排序:

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

I use a variation, where latVal and longVal are my current location.:

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

i also use a variable to read the distance value within the distanceFunc, changing the last two lines to the following:

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 then save the distanceVal into my objects that fit the match, applying a sort to the items returned:

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"distance" ascending:YES];
[ locations sortUsingDescriptors: [ NSArray arrayWithObject: sortDescriptor ] ];
[sortDescriptor release];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文