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: Save and decode ParsePolygon correctly #118

Merged
merged 3 commits into from
Jun 13, 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
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@
# Parse-Swift Changelog

### main
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.7.1...main), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/main/documentation/parseswift)
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.7.2...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.2
[Full Changelog](https://github.com/netreconlab/Parse-Swift/compare/5.7.1...5.7.2), [Documentation](https://swiftpackageindex.com/netreconlab/Parse-Swift/5.7.2/documentation/parseswift)

__Fixes__
* ParsePolygon encoding during a save and decoding resulted in (longitude, latitude) when it should be
(latitude, longitude). If a developer used ParseSwift <= 5.7.1
to save/update ParsePolygon's, they will need to update the respective ParseObjects by swapping the latitude
and longitude manually. Deprecated withinPolygon() query constraint for geoPoint() and polygonContains() for
polygon() ([#118](https://github.com/netreconlab/Parse-Swift/pull/118)), thanks to
[Corey Baker](https://github.com/cbaker6).

### 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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ struct GameData: ParseObject {
var originalData: Data?

//: Your own properties.
var polygon: ParsePolygon?
var fence: ParsePolygon?
//: `ParseBytes` needs to be a part of the original schema
//: or else you will need your primaryKey to force an upgrade.
var bytes: ParseBytes?
Expand All @@ -92,9 +92,9 @@ struct GameData: ParseObject {
*/
func merge(with object: Self) throws -> Self {
var updated = try mergeParse(with: object)
if shouldRestoreKey(\.polygon,
if shouldRestoreKey(\.fence,
original: object) {
updated.polygon = object.polygon
updated.fence = object.fence
}
if shouldRestoreKey(\.bytes,
original: object) {
Expand All @@ -108,9 +108,9 @@ struct GameData: ParseObject {
//: to preserve the memberwise initializer.
extension GameData {

init (bytes: ParseBytes?, polygon: ParsePolygon) {
init (bytes: ParseBytes?, fence: ParsePolygon) {
self.bytes = bytes
self.polygon = polygon
self.fence = fence
}
}

Expand Down Expand Up @@ -396,18 +396,18 @@ Task {

Task {
//: How to add `ParseBytes` and `ParsePolygon` to objects.
let points = [
try ParseGeoPoint(latitude: 0, longitude: 0),
try ParseGeoPoint(latitude: 0, longitude: 1),
try ParseGeoPoint(latitude: 1, longitude: 1),
try ParseGeoPoint(latitude: 1, longitude: 0),
try ParseGeoPoint(latitude: 0, longitude: 0)
let detroitPoints = [
try ParseGeoPoint(latitude: 42.631655189280224, longitude: -83.78406753121705),
try ParseGeoPoint(latitude: 42.633047793854814, longitude: -83.75333640366955),
try ParseGeoPoint(latitude: 42.61625254348911, longitude: -83.75149921669944),
try ParseGeoPoint(latitude: 42.61526926650296, longitude: -83.78161794858735),
try ParseGeoPoint(latitude: 42.631655189280224, longitude: -83.78406753121705)
]

do {
let polygon = try ParsePolygon(points)
let detroit = try ParsePolygon(detroitPoints)
let bytes = ParseBytes(data: "hello world".data(using: .utf8)!)
var gameData = GameData(bytes: bytes, polygon: polygon)
var gameData = GameData(bytes: bytes, fence: detroit)
gameData = try await gameData.save()
print("Successfully saved: \(gameData)")
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct GameScore: ParseObject {
//: Your own properties
var points: Int?
var location: ParseGeoPoint?
var polygon: ParsePolygon?
var fence: ParsePolygon?

/*:
Optional - implement your own version of merge
Expand All @@ -48,9 +48,9 @@ struct GameScore: ParseObject {
original: object) {
updated.location = object.location
}
if updated.shouldRestoreKey(\.polygon,
if updated.shouldRestoreKey(\.fence,
original: object) {
updated.polygon = object.polygon
updated.fence = object.fence
}
return updated
}
Expand All @@ -74,7 +74,7 @@ do {
try .init(latitude: 42.0, longitude: -35.0),
try .init(latitude: 42.0, longitude: -20.0)
]
score.polygon = try ParsePolygon(points)
score.fence = try ParsePolygon(points)
}

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

guard let location = savedScore.location else {
print("Something went wrong")
Expand All @@ -99,13 +99,13 @@ score.save { result in

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

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

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

case .failure(let error):
assertionFailure("Error saving: \(error)")
Expand Down Expand Up @@ -293,7 +293,7 @@ do {
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))
let query9 = GameScore.query(geoPoint("location", within: points))
query9.find { results in
switch results {
case .success(let scores):
Expand All @@ -320,7 +320,7 @@ do {
try .init(latitude: 42.0, longitude: -20.0)
]
let polygon = try ParsePolygon(points)
let query10 = GameScore.query(withinPolygon(key: "location", polygon: polygon))
let query10 = GameScore.query(geoPoint("location", within: polygon))
query10.find { results in
switch results {
case .success(let scores):
Expand All @@ -339,6 +339,27 @@ do {
print("Could not create geopoints: \(error)")
}

do {
let location = try ParseGeoPoint(latitude: 40.0, longitude: -30.0)
let query = GameScore.query(polygon("fence", contains: location))
query.find { results in
switch results {
case .success(let scores):
scores.forEach { (score) in
print("""
Someone has a points value of \"\(String(describing: score.points))\"
with a geolocation \(location) within the
polygon: \(String(describing: score.location))
""")
}
case .failure(let error):
assertionFailure("Error querying: \(error)")
}
}
} catch {
print("Could not create geopoints: \(error)")
}

//: Hint of the previous query (asynchronous)
query2 = query2.hint("_id_")
query2.find { result in
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.1"
static let version = "5.7.2"
static let fileManagementDirectory = "parse/"
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
static let fileManagementLibraryDirectory = "Library/"
Expand Down
9 changes: 7 additions & 2 deletions Sources/ParseSwift/Types/ParsePolygon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
public struct ParsePolygon: ParseTypeable, Hashable {
private let __type: String = "Polygon" // swiftlint:disable:this identifier_name
public let coordinates: [ParseGeoPoint]
var isSwappingCoordinates = false

enum CodingKeys: String, CodingKey {
case __type // swiftlint:disable:this identifier_name
Expand Down Expand Up @@ -108,6 +109,10 @@ extension ParsePolygon {
try container.encode(__type, forKey: .__type)
var nestedUnkeyedContainer = container.nestedUnkeyedContainer(forKey: .coordinates)
try coordinates.forEach {
guard isSwappingCoordinates else {
try nestedUnkeyedContainer.encode([$0.latitude, $0.longitude])
return
}
try nestedUnkeyedContainer.encode([$0.longitude, $0.latitude])
}
}
Expand All @@ -124,8 +129,8 @@ extension ParsePolygon {
let points = try values.decode([[Double]].self, forKey: .coordinates)
try points.forEach {
if $0.count == 2 {
guard let latitude = $0.last,
let longitude = $0.first else {
guard let latitude = $0.first,
let longitude = $0.last else {
throw ParseError(code: .otherCause, message: "Could not decode ParsePolygon: \(points)")
}
decodedCoordinates.append(try ParseGeoPoint(latitude: latitude,
Expand Down
54 changes: 51 additions & 3 deletions Sources/ParseSwift/Types/QueryConstraint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -587,11 +587,28 @@
- warning: Requires Parse Server 2.5.0+.
- returns: The same instance of `QueryConstraint` as the receiver.
*/
public func withinPolygon(key: String, points: [ParseGeoPoint]) -> QueryConstraint {
public func geoPoint(_ key: String, within points: [ParseGeoPoint]) -> QueryConstraint {

Check warning on line 590 in Sources/ParseSwift/Types/QueryConstraint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/ParseSwift/Types/QueryConstraint.swift#L590

Added line #L590 was not covered by tests
let dictionary = [QueryConstraint.Comparator.polygon.rawValue: points]
return .init(key: key, value: dictionary, comparator: .geoWithin)
}

/**
Add a constraint to the query that requires a particular key's
coordinates be contained within and on the bounds of a given polygon
Supports closed and open (last point is connected to first) paths.

Polygon must have at least 3 points.

- parameter key: The key to be constrained.
- parameter points: The polygon points as an Array of `ParseGeoPoint`'s.
- warning: Requires Parse Server 2.5.0+.
- returns: The same instance of `QueryConstraint` as the receiver.
*/
@available(*, deprecated, renamed: "geoPoint")
public func withinPolygon(key: String, points: [ParseGeoPoint]) -> QueryConstraint {
geoPoint(key, within: points)
}

Check warning on line 610 in Sources/ParseSwift/Types/QueryConstraint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/ParseSwift/Types/QueryConstraint.swift#L608-L610

Added lines #L608 - L610 were not covered by tests

/**
Add a constraint to the query that requires a particular key's
coordinates be contained within and on the bounds of a given polygon
Expand All @@ -602,11 +619,28 @@
- warning: Requires Parse Server 2.5.0+.
- returns: The same instance of `QueryConstraint` as the receiver.
*/
public func withinPolygon(key: String, polygon: ParsePolygon) -> QueryConstraint {
public func geoPoint(_ key: String, within polygon: ParsePolygon) -> QueryConstraint {
var polygon = polygon
polygon.isSwappingCoordinates = true

Check warning on line 624 in Sources/ParseSwift/Types/QueryConstraint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/ParseSwift/Types/QueryConstraint.swift#L622-L624

Added lines #L622 - L624 were not covered by tests
let dictionary = [QueryConstraint.Comparator.polygon.rawValue: polygon]
return .init(key: key, value: dictionary, comparator: .geoWithin)
}

/**
Add a constraint to the query that requires a particular key's
coordinates be contained within and on the bounds of a given polygon
Supports closed and open (last point is connected to first) paths.

- parameter key: The key to be constrained.
- parameter polygon: The `ParsePolygon`.
- warning: Requires Parse Server 2.5.0+.
- returns: The same instance of `QueryConstraint` as the receiver.
*/
@available(*, deprecated, renamed: "geoPoint")
public func withinPolygon(key: String, polygon: ParsePolygon) -> QueryConstraint {
geoPoint(key, within: polygon)
}

Check warning on line 642 in Sources/ParseSwift/Types/QueryConstraint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/ParseSwift/Types/QueryConstraint.swift#L640-L642

Added lines #L640 - L642 were not covered by tests

/**
Add a constraint to the query that requires a particular key's
coordinates contains a `ParseGeoPoint`.
Expand All @@ -616,11 +650,25 @@
- warning: Requires Parse Server 2.6.0+.
- returns: The same instance of `QueryConstraint` as the receiver.
*/
public func polygonContains(key: String, point: ParseGeoPoint) -> QueryConstraint {
public func polygon(_ key: String, contains point: ParseGeoPoint) -> QueryConstraint {
let dictionary = [QueryConstraint.Comparator.point.rawValue: point]
return .init(key: key, value: dictionary, comparator: .geoIntersects)
}

/**
Add a constraint to the query that requires a particular key's
coordinates contains a `ParseGeoPoint`.

- parameter key: The key of the `ParsePolygon`.
- parameter point: The `ParseGeoPoint` to check for containment.
- warning: Requires Parse Server 2.6.0+.
- returns: The same instance of `QueryConstraint` as the receiver.
*/
@available(*, deprecated, renamed: "polygon")
public func polygonContains(key: String, point: ParseGeoPoint) -> QueryConstraint {
polygon(key, contains: point)
}

/**
Add a constraint for finding string values that contain a provided
string using Full Text Search.
Expand Down
7 changes: 4 additions & 3 deletions Tests/ParseSwiftTests/ParsePolygonTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class ParsePolygonTests: XCTestCase {

func testEncode() throws {
let polygon = try ParsePolygon(points)
let expected = "{\"__type\":\"Polygon\",\"coordinates\":[[0,0],[1,0],[1,1],[0,1],[0,0]]}"
let expected = "{\"__type\":\"Polygon\",\"coordinates\":[[0,0],[0,1],[1,1],[1,0],[0,0]]}"
XCTAssertEqual(polygon.debugDescription, expected)
guard polygon.coordinates.count == points.count else {
XCTAssertEqual(polygon.coordinates.count, points.count)
Expand All @@ -88,7 +88,8 @@ class ParsePolygonTests: XCTestCase {
}

func testDecode() throws {
let polygon = try ParsePolygon(points)
var polygon = try ParsePolygon(points)
polygon.isSwappingCoordinates = false
let encoded = try ParseCoding.jsonEncoder().encode(polygon)
let decoded = try ParseCoding.jsonDecoder().decode(ParsePolygon.self, from: encoded)
XCTAssertEqual(decoded, polygon)
Expand Down Expand Up @@ -141,7 +142,7 @@ class ParsePolygonTests: XCTestCase {

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