【问题标题】: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)
              }
          }
      

      【讨论】:

      • 不正是我需要的... tx
      【解决方案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)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-09
        相关资源
        最近更新 更多