Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ParsePolygon.containsPoint() correctly uses longitude and latitude #116

Merged
merged 4 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@
# Parse-Swift Changelog

### main
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.7.0...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift)
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.7.1...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift)
* _Contributing to this repo? Add info about your change here to be included in the next release_

### 5.7.1
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.7.0...5.7.1), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.7.1/documentation/parseswift)

__Fixes__
* ParsePolygon.containsPoint() correctly uses longitude and latitude ([#116](https://github.com/netreconlab/Parse-Swift/pull/116)), thanks to
[Corey Baker](https://github.com/cbaker6).

### 5.7.0
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.6.0...5.7.0), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.7.0/documentation/parseswift)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ struct GameScore: ParseObject {
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var location: ParseGeoPoint?
var originalData: Data?

//: Your own properties
var points: Int?
var location: ParseGeoPoint?
var polygon: ParsePolygon?

/*:
Optional - implement your own version of merge
Expand All @@ -43,6 +44,14 @@ struct GameScore: ParseObject {
original: object) {
updated.points = object.points
}
if updated.shouldRestoreKey(\.location,
original: object) {
updated.location = object.location
}
if updated.shouldRestoreKey(\.polygon,
original: object) {
updated.polygon = object.polygon
}
return updated
}
}
Expand All @@ -60,6 +69,12 @@ extension GameScore {
var score = GameScore(points: 10)
do {
try score.location = ParseGeoPoint(latitude: 40.0, longitude: -30.0)
let points: [ParseGeoPoint] = [
try .init(latitude: 35.0, longitude: -30.0),
try .init(latitude: 42.0, longitude: -35.0),
try .init(latitude: 42.0, longitude: -20.0)
]
score.polygon = try ParsePolygon(points)
}

/*:
Expand All @@ -75,13 +90,22 @@ score.save { result in
assert(savedScore.updatedAt != nil)
assert(savedScore.points == 10)
assert(savedScore.location != nil)
assert(savedScore.polygon != nil)

guard let location = savedScore.location else {
print("Something went wrong")
return
}

print(location)
print("Saved location: \(location)")

guard let polygon = savedScore.polygon else {
print("Something went wrong")
return
}

print("Saved polygon: \(polygon)")
print("Saved polygon geopoints: \(polygon.coordinates)")

case .failure(let error):
assertionFailure("Error saving: \(error)")
Expand Down Expand Up @@ -265,9 +289,9 @@ query8.findAll { result in

do {
let points: [ParseGeoPoint] = [
try .init(latitude: 35.0, longitude: -28.0),
try .init(latitude: 45.0, longitude: -28.0),
try .init(latitude: 39.0, longitude: -35.0)
try .init(latitude: 35.0, longitude: -30.0),
try .init(latitude: 42.0, longitude: -35.0),
try .init(latitude: 42.0, longitude: -20.0)
]
let query9 = GameScore.query(withinPolygon(key: "location", points: points))
query9.find { results in
Expand All @@ -291,9 +315,9 @@ do {

do {
let points: [ParseGeoPoint] = [
try .init(latitude: 35.0, longitude: -28.0),
try .init(latitude: 45.0, longitude: -28.0),
try .init(latitude: 39.0, longitude: -35.0)
try .init(latitude: 35.0, longitude: -30.0),
try .init(latitude: 42.0, longitude: -35.0),
try .init(latitude: 42.0, longitude: -20.0)
]
let polygon = try ParsePolygon(points)
let query10 = GameScore.query(withinPolygon(key: "location", polygon: polygon))
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/ParseConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

enum ParseConstants {
static let sdk = "swift"
static let version = "5.7.0"
static let version = "5.7.1"
static let fileManagementDirectory = "parse/"
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
static let fileManagementLibraryDirectory = "Library/"
Expand Down
70 changes: 40 additions & 30 deletions Sources/ParseSwift/Types/ParsePolygon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

/**
`ParsePolygon` is used to create a polygon that represents the coordinates
that may be associated with a key in a ParseObject or used as a reference point
that may be associated with a key in a `ParseObject` or used as a reference point
for geo queries. This allows proximity-based queries on the key.
*/
public struct ParsePolygon: ParseTypeable, Hashable {
Expand Down Expand Up @@ -51,25 +51,25 @@ public struct ParsePolygon: ParseTypeable, Hashable {
- parameter point: The point to check.
*/
public func containsPoint(_ point: ParseGeoPoint) -> Bool {
var minX = coordinates[0].latitude
var maxX = coordinates[0].latitude
var minY = coordinates[0].longitude
var maxY = coordinates[0].longitude
var minX = coordinates[0].longitude
var maxX = coordinates[0].longitude
var minY = coordinates[0].latitude
var maxY = coordinates[0].latitude

var modifiedCoordinates = coordinates
modifiedCoordinates.removeFirst()
for coordinate in modifiedCoordinates {
minX = Swift.min(coordinate.latitude, minX)
maxX = Swift.max(coordinate.latitude, maxX)
minY = Swift.min(coordinate.longitude, minY)
maxY = Swift.max(coordinate.longitude, maxY)
minX = Swift.min(coordinate.longitude, minX)
maxX = Swift.max(coordinate.longitude, maxX)
minY = Swift.min(coordinate.latitude, minY)
maxY = Swift.max(coordinate.latitude, maxY)
}

// Check if outside of the polygon
if point.latitude < minX ||
point.latitude > maxX ||
point.longitude < minY ||
point.longitude > maxY {
if point.longitude < minX ||
point.longitude > maxX ||
point.latitude < minY ||
point.latitude > maxY {
return false
}

Expand All @@ -78,14 +78,14 @@ public struct ParsePolygon: ParseTypeable, Hashable {
// Check if intersects polygon
var otherIndex = coordinates.count - 1
for (index, coordinate) in coordinates.enumerated() {
let startX = coordinate.latitude
let startY = coordinate.longitude
let endX = coordinates[otherIndex].latitude
let endY = coordinates[otherIndex].longitude
let startYComparison = startY > point.longitude
let endYComparison = endY > point.longitude
let startX = coordinate.longitude
let startY = coordinate.latitude
let endX = coordinates[otherIndex].longitude
let endY = coordinates[otherIndex].latitude
let startYComparison = startY > point.latitude
let endYComparison = endY > point.latitude
if startYComparison != endYComparison &&
point.latitude < ((endX - startX) * (point.longitude - startY)) / (endY - startY) + startX {
point.longitude < ((endX - startX) * (point.latitude - startY)) / (endY - startY) + startX {
return true
}
if index == 0 {
Expand All @@ -98,7 +98,26 @@ public struct ParsePolygon: ParseTypeable, Hashable {
}
}

// MARK: Encodable

extension ParsePolygon {

public func encode(to encoder: Encoder) throws {
try validate()
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(__type, forKey: .__type)
var nestedUnkeyedContainer = container.nestedUnkeyedContainer(forKey: .coordinates)
try coordinates.forEach {
try nestedUnkeyedContainer.encode([$0.longitude, $0.latitude])
}
}

}

// MARK: Decodable

extension ParsePolygon {

public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
var decodedCoordinates = [ParseGeoPoint]()
Expand All @@ -110,7 +129,7 @@ extension ParsePolygon {
throw ParseError(code: .otherCause, message: "Could not decode ParsePolygon: \(points)")
}
decodedCoordinates.append(try ParseGeoPoint(latitude: latitude,
longitude: longitude))
longitude: longitude))
} else {
throw ParseError(code: .otherCause, message: "Could not decode ParsePolygon: \(points)")
}
Expand All @@ -119,13 +138,4 @@ extension ParsePolygon {
try validate()
}

public func encode(to encoder: Encoder) throws {
try validate()
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(__type, forKey: .__type)
var nestedUnkeyedContainer = container.nestedUnkeyedContainer(forKey: .coordinates)
try coordinates.forEach {
try nestedUnkeyedContainer.encode([$0.longitude, $0.latitude])
}
}
}
34 changes: 26 additions & 8 deletions Tests/ParseSwiftTests/ParsePolygonTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class ParsePolygonTests: XCTestCase {
public let coordinates: [[Double]]
}

var points = [ParseGeoPoint]()

override func setUp() async throws {
try await super.setUp()
guard let url = URL(string: "http://localhost:1337/parse") else {
Expand Down Expand Up @@ -45,8 +47,6 @@ class ParsePolygonTests: XCTestCase {
try await ParseStorage.shared.deleteAll()
}

var points = [ParseGeoPoint]()

func testContainsPoint() throws {
let polygon = try ParsePolygon(points)
let inside = try ParseGeoPoint(latitude: 0.5, longitude: 0.5)
Expand All @@ -55,6 +55,17 @@ class ParsePolygonTests: XCTestCase {
XCTAssertFalse(polygon.containsPoint(outside))
}

func testContainsPoint2() throws {
let point = try ParseGeoPoint(latitude: 40, longitude: -30)
let points: [ParseGeoPoint] = [
try .init(latitude: 35.0, longitude: -30.0),
try .init(latitude: 42.0, longitude: -35.0),
try .init(latitude: 42.0, longitude: -20.0)
]
let polygon = try ParsePolygon(points)
XCTAssertTrue(polygon.containsPoint(point))
}

func testCheckInitializerRequiresMinPoints() throws {
let point = try ParseGeoPoint(latitude: 0, longitude: 0)
XCTAssertNoThrow(try ParsePolygon([point, point, point]))
Expand All @@ -63,6 +74,19 @@ class ParsePolygonTests: XCTestCase {
XCTAssertThrowsError(try ParsePolygon(point, point))
}

func testEncode() throws {
let polygon = try ParsePolygon(points)
let expected = "{\"__type\":\"Polygon\",\"coordinates\":[[0,0],[1,0],[1,1],[0,1],[0,0]]}"
XCTAssertEqual(polygon.debugDescription, expected)
guard polygon.coordinates.count == points.count else {
XCTAssertEqual(polygon.coordinates.count, points.count)
return
}
for (index, coordinates) in polygon.coordinates.enumerated() {
XCTAssertEqual(coordinates, points[index])
}
}

func testDecode() throws {
let polygon = try ParsePolygon(points)
let encoded = try ParseCoding.jsonEncoder().encode(polygon)
Expand Down Expand Up @@ -115,12 +139,6 @@ class ParsePolygonTests: XCTestCase {
}
}

func testDebugString() throws {
let polygon = try ParsePolygon(points)
let expected = "{\"__type\":\"Polygon\",\"coordinates\":[[0,0],[1,0],[1,1],[0,1],[0,0]]}"
XCTAssertEqual(polygon.debugDescription, expected)
}

func testDescription() throws {
let polygon = try ParsePolygon(points)
let expected = "{\"__type\":\"Polygon\",\"coordinates\":[[0,0],[1,0],[1,1],[0,1],[0,0]]}"
Expand Down