Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // JKHeatMapEngine.swift
- // JKTinder
- // Created by Joseph Kalash on 2/10/18.
- // Copyright © 2018 Joseph Kalash. All rights reserved.
- import Alamofire
- import DeepDiff
- // Note: This manager only deals with profile IDs and does not store any other info. Once user selectes a node in the
- // cluster data, the array of profileIDs are passed to the next VC where the profile details are fetched and displayed to the user
- struct DistanceEstimate : Hashable {
- var distance : JKTrilateration.Circle //The central point estimate
- var weight : Double //Estimated error on the distance
- // JKTrilateration.Circle already conforms to Hashable
- var hashValue: Int {
- return distance.hashValue
- }
- // JKTrilateration.Circle already conforms to Hashable thus conforms to Equatable as well
- static func == (lhs: DistanceEstimate, rhs: DistanceEstimate) -> Bool {
- return lhs.distance == rhs.distance && lhs.weight == rhs.weight
- }
- }
- struct LocationEstimate : Hashable {
- var estimatedDistances : Set<DistanceEstimate> //The set of estimated distances
- var estimatedLocation : Vector2?
- // JKTrilateration.Circle already conforms to Hashable
- var hashValue: Int {
- return estimatedDistances.hashValue
- }
- // JKTrilateration.Circle already conforms to Hashable thus conforms to Equatable as well
- static func == (lhs: LocationEstimate, rhs: LocationEstimate) -> Bool {
- return lhs.estimatedDistances == rhs.estimatedDistances && lhs.estimatedLocation == rhs.estimatedLocation
- }
- mutating func locate() {
- var knownPos : [[Double]] = []
- var dist : [Double] = []
- let distances : [JKTrilateration.Circle] = self.estimatedDistances.flatMap({ $0.distance })
- for d in distances {
- knownPos.append([d.center.x, d.center.y])
- dist.append(d.radius)
- }
- //Convert distance to km
- dist = dist.map({ $0 / 1000 })
- let weights = self.estimatedDistances.flatMap({ $0.weight })
- let optimizer = NonLinearLeastSquareOptimizer(knownPos: knownPos, dist: &dist, weights: weights)
- self.estimatedLocation = optimizer.getLocation()
- }
- }
- //Continuously run :
- //1. Fetch random point in the boundary defined by circle at center of mapView / radius zoomlevel
- //2. Request nearby profiles and store all distance estimates in userLocations data structure
- //3. Run Levmarq least square optimization on all profiles that have at least 3 entry points
- //4. Refresh locatedUsers with new/updated estimated locations
- //5. Refresh MapView with the new points
- class JKHeatMapEngine {
- // Stores all points found for given profile IDs. Maps profile IDs to a set of estimates
- // Each estimated point distance has a weight; the error on the computation (used to regularize in ML training)
- // Also holds that have been located, alongside their location. Maps a profile id to a location.
- internal var userLocations : [String : LocationEstimate] = [:]
- public var runner : DataRequest? {
- didSet {
- oldValue?.cancel() //Cancel any existing sequest when being reassigned
- }
- }
- //Repeatedly call the runner until either method called again or runner canceled by the controller
- public func fetchUsers(mapCenter : Vector2, mapZoomRadius : Double, callback : @escaping (_ diff: Array<Change<[String : Vector2]>>) -> Void) {
- let randomPoint = Utils.randomPointInCircle(JKTrilateration.Circle(center: mapCenter, radius: mapZoomRadius))
- runner = APIManager.current.fetchNearbyAt(loc: randomPoint, callback: { (json, error) in
- guard let dict = json, let profiles = dict["profiles"] as? [NSDictionary] else {
- self.fetchUsers(mapCenter: mapCenter, mapZoomRadius: mapZoomRadius, callback: callback)
- return
- }
- //Store to compute DeepDiff
- var profileIdsToLocate : [String] = []
- //A user either has a distance shared --> Append a point
- for i in 0 ..< profiles.count {
- if let profileId = profiles[i]["profileId"] as? String {
- var estimate : DistanceEstimate? = nil
- if let distance = profiles[i]["distance"] as? Double {
- estimate = DistanceEstimate(distance: JKTrilateration.Circle(center: randomPoint, radius: distance), weight: 5.0)
- }
- else {
- //Find the approximate distance
- if let (distance, w) = Utils.estimateDistance(of: i, profiles: profiles) {
- estimate = DistanceEstimate(distance: JKTrilateration.Circle(center: randomPoint, radius: distance), weight: w)
- }
- }
- //If we somehow found no estimate, skip
- if estimate == nil { continue }
- //Check if user location already a record
- if self.userLocations[profileId] != nil {
- self.userLocations[profileId]!.estimatedDistances.insert(estimate!) //Appent to existing set
- //Locates profiles whose distance found is anywhere between 3rd and 6th
- if self.userLocations[profileId]!.estimatedDistances.count == 3 {
- profileIdsToLocate.append(profileId)
- }
- }
- else {
- //Create initial set
- self.userLocations[profileId] = LocationEstimate(estimatedDistances: [estimate!], estimatedLocation: nil)
- }
- }
- }
- //Compute distances
- for id in profileIdsToLocate {
- self.userLocations[id]!.locate()
- }
- //Repeat as long as we were able to locate users
- if profileIdsToLocate.count > 0 {
- self.fetchUsers(mapCenter: mapCenter, mapZoomRadius: mapZoomRadius, callback: callback)
- }
- }, applyFilterParams: false)
- }
- }
Add Comment
Please, Sign In to add comment