【问题标题】:SQLite query with location fields inside of region带有区域内位置字段的 SQLite 查询
【发布时间】:2015-06-12 12:02:32
【问题描述】:

我有一个包含两列的数据库,纬度和经度。 我想知道是否可以在 Transact-SQL 命令中仅检索作为参数传递的位置的定义区域中的记录;

例如,假设您有以下记录,它们都来自同一个位置,但我们将它们用于测试目的。

现在我有了 SQL 查询:

[self.fmQueue inDatabase:^(FMDatabase *db)
    {
    CLLocationCoordinate2D center = CLLocationCoordinate2DMake(latitude, longitude);
    CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:400.0f identifier:@"identifier"];

    NSString* queryCommand = [NSString stringWithFormat:@"SELECT * FROM %@ WHERE ?EXPRESSION_ABOUT_REGION?", readingsTable];
    FMResultSet* result = [db executeQuery:queryCommand];
    if (completionblock)
        {
        completionblock([self readingsArrayFromResultSet:result]);
        }
    }];

我可以只检索记录,然后将每个记录与构造的CLLocationCoordinate2D[region containsCoordinate:CLLocationCoordinate2D] 进行比较,但这感觉性能太差了。

我正在寻找最高效和最合适的解决方案,以在 T-SQL 查询中检索所需的区域相关位置记录。

【问题讨论】:

  • SQLite 不支持 T-SQL;为什么不使用 SQL?你知道计算两点距离的公式吗?
  • 好吧,正如你所说,我使用 SQL,“SELECT * FROM %@ WHERE ?”。所以我想创建一个最大半径为 400 米的区域,并将其用作命令查询中的表达式。我认为距离不会达到我的目的。
  • SQLite 没有几何对象。 (实心)圆是距离中心小于一定距离的所有点的集合。
  • 我想最好的解决方案是在返回的结果中使用 ObjC。

标签: ios objective-c sqlite geolocation fmdb


【解决方案1】:

先前近似的问题

这里的上一个答案将地球建模为笛卡尔平面,经纬度分别为x和y,然后围绕点绘制圆形或矩形---实际形状无关紧要,两种方法都有相同的属性。

如果您的约束真的很松散,这可能很好,并且它们可能在您的特定实例中出现。为了让这个答案对每个人都更有用,我已经回答了这个问题,假设你的约束没有那么松散。对于移动地理定位应用程序,这种近似通常会导致问题。也许您已经看到了一个错误的应用程序。由此产生的距离没有任何意义。以前的方法有几个缺陷:

  1. 错误的近似值:地球的规模很大,所以简单的近似值可能会偏离数英里。如果用户在车里,这很糟糕,如果用户步行,这真的很糟糕。带有度数的简单技巧并不能改善圆形或正方形:纬度和经度分布不均匀,您需要像 Haversine 这样的完整近似值才能获得准确的数据。
  2. 排除好点:除非您严重扩展形状,否则您的样本将排除有效点。
  3. 包括坏点:扩展形状以捕获排除的有效点越多,捕获的无效点就越多。
  4. 损坏的排序顺序:如果没有真正的计算,您将无法正确按距离对输出进行排序。这意味着您生成的任何有序列表都将被取消。通常,您希望这些点按接近的顺序排列。
  5. 两次计算:如果您返回并修复它,您将进行两次计算。

C/Objective-C 中的单点半正弦

选择代码已经在另一个答案中。这是您引用的重新计算,您必须在 Objective-C 或 C 之后为每个点执行:

/////////////////////////////////////
//Fill these in or turn them into function arguments
float lat1=center_point.latitude;
float lon1=center_point.longitude;

float lat2=argument_point.latitude;
float lon2=argument_point.longitude;
/////////////////////////////////////

//Conversion factor, degrees to radians
float PI=3.14159265358979;
float f=PI/180;
lat1*=f; lon1*=f; lat2*=f; lon2*=f;

//Haversine Formula (from R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159):
float dlon = lon2 - lon1;
float dlat = lat2 - lat1;
float a = pow((sin(dlat/2)),2) + cos(lat1) * cos(lat2) * pow((sin(dlon/2)),2);
float c = 2 * asin(MIN(1,sqrt(a)));
float R = 6378.137;//km Radius of Earth
float d = R * c;

d *= 0.621371192; //optional: km to miles [statute] conversion factor

//NSString conversion?
//if (d >= .23) return [NSString stringWithFormat:@"%0.1f m",d]; //.23m ~ 400 yds
//d *= 5280; //miles to feet conversion factor
//d /= 3; //feet to yards
//int y=(int)d;
//return [NSString stringWithFormat:@"%d yds",y];

return d;

这应该是完成所讨论的任务所需的所有工具。

SQLite 中的Haversine

我想向您展示一个直接的仅限 SQLite 的解决方案,但我始终无法让 Haversine 直接在 SQLite 中令人满意地运行。您在 SQLite 中没有平方根。你没有得到 pow() 函数,尽管你可以重复一个参数本身。你不会得到 sin、cos 或 sinh。有一些扩展可以添加其中一些功能。我不知道与基本 SQLite 相比,它们的支持程度如何。即使有它们,它也会太慢。

人们似乎建议使用预先计算的正弦值更新列。只要您不需要对整列求正弦值就可以了,在这种情况下,每次需要进行新计算时,您都在向表中写入一个新列,这很糟糕。无论如何,我想向您展示一下 SQLite 在计算 Haversine 时的速度有多慢,但我根本无法让它计算它。 (我认为我对 SQLite 的记忆很慢实际上是服务器上的 MySQL 的记忆很慢。)

Kerf中的全点解决方案

我希望前面的讨论是对标准工具可以做什么的近乎详尽的了解。

好消息是,如果您操作正确,此计算在手机上会很快。我为您构建了一个东西,我认为它可以更好地解决您的问题。如果你愿意使用其他工具,在Kerf 这个问题很简单。我回去并致力于三角函数的回购矢量化操作,以便计算速度更快。我的 iPhone 5 将在 20 毫秒内完成 10,000 个点,在 150 毫秒内完成 100,000 个点。您可以在 1.5 秒内完成 100 万分,但此时您需要一个 throbber。按照规则披露:我建立了它。

来自 Kerf REPL:

//ARTIFICIAL POINT GENERATION ///////////////////
n: 10**4
point_lat: 80 + rand(40.0)
point_lon: 80 + rand(40.0)

mytable: {{lats: 60 + rand(n, 60.0), lons: 60 + rand(n, 60.0)}}

lats : mytable.lats
lons : mytable.lons

/////////////////////////////////////
//COMPUTATION////////////////////////

dlon: lons - point_lon
dlat: lats - point_lat

distances_km: (6378.137 * 2) * asin(mins(1,sqrt(pow(sin(dlat/2),2) + cos(point_lat) * cos(lats) * pow(sin(dlon/2) ,2))))


//distances_miles: 0.621371192  * distances_km //km to miles [statute] conversion
//sort_order: ascend distances_km

或通过 Kerf iOS SDK。删除语句末尾的分号将允许您将其作为 JSON 记录到终端。

KSKerfSDK *kerf = [KSKerfSDK new];

kerf.showTimingEnabled = YES;

//Sample Data Generation
[kerf jsonObjectFromCall:@"n: 10**4;"];
[kerf jsonObjectFromCall:@"point_lat: 80 + rand(40.0);"];
[kerf jsonObjectFromCall:@"point_lon: 80 + rand(40.0);"];
[kerf jsonObjectFromCall:@"mytable: {{lats: 60 + rand(n, 60.0), lons: 60 + rand(n, 60.0)}};"];
[kerf jsonObjectFromCall:@"lats : mytable.lats;"];
[kerf jsonObjectFromCall:@"lons : mytable.lons;"];

//Computation
[kerf jsonObjectFromCall:@"dlon: lons - point_lon;"];
[kerf jsonObjectFromCall:@"dlat: lats - point_lat;"];
NSLog(@"%@", [kerf jsonObjectFromCall:@"distances_km: (6378.137 * 2) * asin(mins(1,sqrt(pow(sin(dlat/2),2) + cos(point_lat) * cos(lats) * pow(sin(dlon/2) ,2)))); "]);

【讨论】:

  • 哇!我将在周末研究和申请,我需要填补一些空白和测试。感谢您的出色表现,我会尽快回复我的实施结果。
【解决方案2】:

要测试点 (x,y) 是否在圆心 (cx,cy) 和半径 r 的圆内,请使用公式 (x-cx)² + (y-cy)² <= r²。 这与圆形不对应,因为经度和纬度值在地球表面的长度不同,但足够接近。 在 SQL 中:

... WHERE (longitude - :lon) * (longitude - :lon) +
          (latitude  - :lat) * (latitude  - :lat) <= :r * :r

如果您改用矩形,则可以使用更简单的表达式,这些表达式有可能通过索引进行优化:

... WHERE longitude BETWEEN :XMin AND :XMax
      AND latitude  BETWEEN :YMin AND :YMax

【讨论】:

  • 我必须彻底检查,但它似乎使用该等式返回到目前为止所需的所有数据。我有一个问题,如果在方程式中计算地球距离时以米为单位考虑半径,或者它是否与单位无关;
  • 所以,度数。我必须将距离转换为度数,具体取决于位置,我认为它会很漂亮。感谢您的帮助。
猜你喜欢
  • 2017-04-22
  • 1970-01-01
  • 1970-01-01
  • 2019-06-11
  • 1970-01-01
  • 2011-06-23
  • 2018-10-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多