【问题标题】:Filter core data entities by distance from a dynamic position按与动态位置的距离过滤核心数据实体
【发布时间】:2017-02-10 18:24:09
【问题描述】:
我有一个 NSManagedObject 派生类(实体),其实例保存在本地 SQL-lite 存储中。该类还具有经度和纬度属性,我需要根据与特定坐标的距离来获取实体。我尝试将 NSPredicate 与自定义函数一起使用,但我找不到有关如何实现该函数的文档(...如果它确实支持)。有没有人知道如何对核心数据实体执行这种动态过滤?我尝试使用 NSPredicate withBlock,但它不适用于 SQL-Lite 数据库上持久化的对象。请帮忙。
【问题讨论】:
标签:
ios
swift
core-data
nspredicate
【解决方案1】:
Core Data 无法做到这一点。在获取实体后,您可以使用 Transient 属性对与动态位置的距离进行建模,甚至可以根据该 Transient 属性对项目进行排序。但是,如果它们是持久属性,则只能从持久存储中获取属性。
在实践中,如果坐标被索引,我发现在矩形窗口中查询点非常快。通过获取动态位置的纬度/经度,加上/减去搜索半径来构建窗口,如果它是非投影数据(纯纬度/经度,而不是 UTM 或类似网格),则对经度窗口进行余弦(纬度)调整。
如果这真的不够快,您可以在您的实体上存储一个 geohash,它会为您提供一个可以进行前缀搜索的字符串。请参阅https://en.wikipedia.org/wiki/Geohash 进行讨论。或者你可以实现一个真正的空间索引。但我从来没有遇到过值得实施这两种方法的 Core Data 性能问题。
【解决方案2】:
我将我的位置作为纬度/经度坐标存储在数据模型中。然后我写了一些辅助扩展来找到纬度/经度坐标的半矩形区域并通过它进行查询。这极大地限制了结果集,因此如果您需要按位置排序,您可以使用 CLLocation 距离计算器对结果对象进行排序。
我创建了一个 CLCircularRegion,你可以看到我的 buildPredicate() 函数创建了查询。
这是我正在使用的代码:
斯威夫特 3
extension CLLocationDegrees {
static var north: CLLocationDegrees {
return 90.0
}
static var south: CLLocationDegrees {
return -90.0
}
static var east: CLLocationDegrees {
return 180.0
}
static var west: CLLocationDegrees {
return -180.0
}
var radians: Double {
return Double.pi * self / 180.0
}
}
extension CLLocationCoordinate2D {
var metersPerDegreeLatitude: CLLocationDistance {
return 111319.4907932736
}
var metersPerDegreeLongitude: CLLocationDistance {
return max(0.0, cos(self.latitude.radians) * self.metersPerDegreeLatitude)
}
}
extension CLCircularRegion {
var northernmostLatitude: CLLocationDegrees {
let longitude = self.center.latitude + self.radius / self.center.metersPerDegreeLatitude
return min(longitude, .north)
}
var southernmostLatitude: CLLocationDegrees {
let longitude = self.center.latitude - self.radius / self.center.metersPerDegreeLatitude
return max(longitude, .south)
}
var easternmostLongitude: CLLocationDegrees {
guard self.northernmostLatitude <= .north else {
return .east
}
guard self.southernmostLatitude >= .south else {
return .east
}
return min(.east, self.center.longitude + self.radius / (self.center.metersPerDegreeLongitude + 0.0001))
}
var westernmostLongitude: CLLocationDegrees {
guard self.northernmostLatitude <= .north else {
return .west
}
guard self.southernmostLatitude >= .south else {
return .west
}
return max(.west, self.center.longitude - self.radius / (self.center.metersPerDegreeLongitude + 0.0001))
}
func buildPredicate(latitudeName: String = "latitude", longitudeName: String = "longitude") -> NSPredicate {
let args = [self.southernmostLatitude, self.northernmostLatitude, self.westernmostLongitude, self.easternmostLongitude]
return NSPredicate(format: "\(latitudeName) >= %@ && \(latitudeName) <= %@ && \(longitudeName) >= %@ && \(longitudeName) <= %@", argumentArray: args)
}
}
【解决方案3】:
这是答案https://www.objc.io/issues/4-core-data/core-data-fetch-requests/
static double const D = 80. * 1.1;
double const R = 6371009.; // Earth readius in meters
double meanLatitidue = pointOfInterest.latitude * M_PI / 180.;
double deltaLatitude = D / R * 180. / M_PI;
double deltaLongitude = D / (R * cos(meanLatitidue)) * 180. / M_PI;
double minLatitude = pointOfInterest.latitude - deltaLatitude;
double maxLatitude = pointOfInterest.latitude + deltaLatitude;
double minLongitude = pointOfInterest.longitude - deltaLongitude;
double maxLongitude = pointOfInterest.longitude + deltaLongitude;
request.result = [NSPredicate predicateWithFormat:
@"(%@ <= longitude) AND (longitude <= %@)"
@"AND (%@ <= latitude) AND (latitude <= %@)",
@(minLongitude), @(maxLongitude), @(minLatitude), @(maxLatitude)];
【解决方案4】:
这是上述答案的 Swift 版本
let D: Double = 80 * 1.1
let R: Double = 6371009
let meanLatitidue = pointOfInterest.coordinate.latitude * .pi / 180
let deltaLatitude = D / R * 180 / .pi
let deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / .pi
let minLatitude: Double = pointOfInterest.coordinate.latitude - deltaLatitude
let maxLatitude: Double = pointOfInterest.coordinate.latitude + deltaLatitude
let minLongitude: Double = pointOfInterest.coordinate.longitude - deltaLongitude
let maxLongitude: Double = pointOfInterest.coordinate.longitude + deltaLongitude
let predicate = NSPredicate(format: "(%lf <= longitude) AND (longitude <= %lf) AND (%lf <= latitude) AND (latitude <= %lf)", minLongitude, maxLongitude,minLatitude, maxLatitude)