按动态位置的距离过滤核心数据实体

时间:2017-02-10 18:24:10

标签: ios swift core-data nspredicate

我有一个NSManagedObject派生类(实体),其实例持久存储在本地SQL-lite存储中。该类还具有经度和纬度属性,我需要根据特定坐标的距离来获取实体。我尝试将NSPredicate与自定义函数一起使用,但是我找不到关于如何实现该函数的文档(......如果它支持的话)。有没有人知道如何在核心数据实体上执行这种动态过滤?我尝试使用NSPredicate withBlock,但它不适用于SQL-Lite数据库上持久化的对象。请帮忙。

4 个答案:

答案 0 :(得分:3)

您无法使用Core Data执行此操作。在获取实体后,您可以使用“瞬态”属性来模拟动态位置的距离,甚至可以根据“瞬态”属性对项目进行排序。但是,如果它们是持久属性,则只能从持久性存储中获取属性。

实际上,如果坐标被索引,我发现在矩形窗口中查询点非常快。通过获取动态位置的纬度/经度,加/减搜索半径来构建窗口,如果是未经投影的数据(纯lat / lon,不是UTM或类似网格),则为经度窗口投入余弦(纬度)调整。

如果真的不够快,你可以在你的实体上存储一个geohash,它会给你一个字符串你可以进行前缀搜索。有关讨论,请参阅https://en.wikipedia.org/wiki/Geohash。或者您可以实现真正的空间索引。但我从未遇到过使用这些方法的核心数据性能问题。

答案 1 :(得分:0)

我将我的位置作为纬度/经度坐标存储在数据模型中。然后我写了一些辅助扩展来找到lat / lon坐标的半矩形区域并通过它进行查询。这极大地限制了结果集,因此如果您需要按位置排序,则可以使用CLLocation距离计算器对生成的对象进行排序。

我创建了一个CLCircularRegion,你可以看到我创建查询的buildPredicate()函数。

这是我正在使用的代码:

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

答案 2 :(得分:0)

这里是答案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)];

答案 3 :(得分:0)

这是上述答案的 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)