Files
iOS/Tests/Shared/CLLocation+Extensions.test.swift
Zac West 90ed5be5fc Fixes zone enter with multiple overlapping zones where core ignores accuracy (#1641)
Fixes #1627.

## Summary
When a GPS coordinate is outside the zone, even if accuracy places it inside, the logic in core will prefer a zone which contains the exact coordinates, regardless of accuracy. This moves the coordinate over to be inside the zone.

## Any other notes
- Allows fuzzers to change coordinate, even though we really do not want to do this. This is effectively fudging the real location data, which is something I've avoided as much as possible to this point.
- Adds a helper method to determine bearing to another location so that we know how to adjust the position towards the center of the zone.
2021-05-15 12:30:43 -07:00

145 lines
5.8 KiB
Swift

import CoreLocation
import Foundation
@testable import Shared
import XCTest
class CLLocationExtensionsTests: XCTestCase {
private var coordinate: CLLocationCoordinate2D!
override func setUp() {
super.setUp()
coordinate = .init(latitude: 37.7660435, longitude: -122.3952834)
}
func testArrayRepresentation() {
XCTAssertEqual(coordinate.toArray(), [coordinate.latitude, coordinate.longitude])
}
func testMovingSmallAmount() {
for angle: Double in stride(from: 0, to: 360, by: 20) {
let moved = coordinate.moving(
distance: .init(value: 30, unit: .meters),
direction: .init(value: angle, unit: .degrees)
)
let regionContain = CLCircularRegion(center: coordinate, radius: 40, identifier: "")
let regionInside = CLCircularRegion(center: coordinate, radius: 20, identifier: "")
XCTAssertTrue(regionContain.contains(moved))
XCTAssertFalse(regionInside.contains(moved))
}
}
func testMovingMediumAmount() {
for angle: Double in stride(from: 0, to: 360, by: 20) {
let moved = coordinate.moving(
distance: .init(value: 3100, unit: .meters),
direction: .init(value: angle, unit: .degrees)
)
let regionContain = CLCircularRegion(center: coordinate, radius: 3200, identifier: "")
let regionInside = CLCircularRegion(center: coordinate, radius: 3000, identifier: "")
XCTAssertTrue(regionContain.contains(moved))
XCTAssertFalse(regionInside.contains(moved))
}
}
func testMovingLargeAmount() {
for angle: Double in stride(from: 0, to: 360, by: 20) {
let moved = coordinate.moving(
distance: .init(value: 1_000_000, unit: .meters),
direction: .init(value: angle, unit: .degrees)
)
let regionContain = CLCircularRegion(center: coordinate, radius: 1_100_000, identifier: "")
let regionInside = CLCircularRegion(center: coordinate, radius: 900_000, identifier: "")
XCTAssertTrue(regionContain.contains(moved))
XCTAssertFalse(regionInside.contains(moved))
}
}
func testDistanceWithAccuracy() {
let region = CLCircularRegion(center: coordinate, radius: 20, identifier: "")
let offsetCoordinate = coordinate.moving(
distance: .init(value: 50, unit: .meters),
direction: .init(value: 0, unit: .degrees)
)
let locationNoAccuracy = CLLocation(
latitude: offsetCoordinate.latitude,
longitude: offsetCoordinate.longitude
)
let locationWithAccuracy = CLLocation(
coordinate: offsetCoordinate,
altitude: 0,
horizontalAccuracy: 10,
verticalAccuracy: 0,
timestamp: Date()
)
XCTAssertEqual(region.distanceWithAccuracy(from: locationNoAccuracy), 30, accuracy: 0.1)
XCTAssertEqual(region.distanceWithAccuracy(from: locationWithAccuracy), 20, accuracy: 0.1)
}
func testContainsWithAccuracy() {
let region = CLCircularRegion(center: coordinate, radius: 20, identifier: "")
let offsetCoordinate = coordinate.moving(
distance: .init(value: 25, unit: .meters),
direction: .init(value: 0, unit: .degrees)
)
let locationNoAccuracy = CLLocation(
latitude: offsetCoordinate.latitude,
longitude: offsetCoordinate.longitude
)
let locationWithAccuracy = CLLocation(
coordinate: offsetCoordinate,
altitude: 0,
horizontalAccuracy: 10,
verticalAccuracy: 0,
timestamp: Date()
)
XCTAssertFalse(region.containsWithAccuracy(locationNoAccuracy))
XCTAssertTrue(region.containsWithAccuracy(locationWithAccuracy))
}
func testBearing() {
// old mint is our starting point
let start = CLLocation(latitude: 37.78319463773435, longitude: -122.40664036682519)
XCTAssertEqual(start.coordinate.bearing(to: start.coordinate).value, 0)
for (name, destination, roughBearing) in [
// basically north
("mister jius", CLLocation(latitude: 37.793855824513756, longitude: -122.40657738553836), 0.0),
// basically east
("21st amendment", CLLocation(latitude: 37.783090295994604, longitude: -122.39266797412634), 90.0),
// basically south
("deli board", CLLocation(latitude: 37.77781029169787, longitude: -122.4070094340342), 180.0),
// basically west
("brendas", CLLocation(latitude: 37.78313519107828, longitude: -122.41904411931317), 270.0),
// hotel right nearby
("hotel zetta", CLLocation(latitude: 37.78345931149641, longitude: -122.4070579074126), 309),
] {
let bearing = start.coordinate.bearing(to: destination.coordinate)
let distance = start.distance(from: destination)
let recomputedCoordinate = start.coordinate.moving(
distance: .init(value: distance, unit: .meters),
direction: bearing
)
let recomputedLocation = CLLocation(
latitude: recomputedCoordinate.latitude,
longitude: recomputedCoordinate.longitude
)
// the locations i picked aren't exactly cardinal directions, so there's a small fuzz, but they are close
XCTAssertEqual(bearing.converted(to: .degrees).value, roughBearing, accuracy: 4, name)
// it should be good enough to get us back to very close accuracy-wise to the location
XCTAssertEqual(recomputedLocation.distance(from: destination), 0, accuracy: 0.005 * distance, name)
}
}
}