Repository: parse-community/Parse-Swift (swift)

Issue:
GeoQuerie's withinPolygon are not working properly
### Issue Description
GeoQueries running with withinPolygon constraint do not work and return error -1 (Unknown server Error) or 1 (Internal Server Error), either when used with a Polygon such as:
```
let query = City.query(withinPolygon(key: "location", polygon: myPolygon))
```

or with an array of GeoPoints such as:
```
let query = City.query(withinPolygon(key: "location", points: [geoPoint1, geoPoint2, geoPoint3, geoPoint4]))
```

### Steps to reproduce
Parse 4.5.0 running at Back4App
Parse-Swift in the latest version added to the project using SPM
Class with GeoPoint property contained in the area of Polygon or array of GeoPoints

### Actual Outcome
Parse Error either Error -1 (Unknown Server Error) or 1 (Internal Server Error)
Message from Debug Print:

```
Error querying: ParseError code=1 error=Internal server error.
```

### Expected Outcome
A list of objects contained in the current Polygon or GeoPoint Array.
Same code with same coordinates when used with the JS SDK return objects correctly.

JS SDK output:
```
{"location":{"__type":"GeoPoint","latitude":-15.79485821477289,"longitude":-47.88391074690196},"name":"Brasília","createdAt":"2022-06-20T01:53:03.809Z","updatedAt":"2022-06-20T01:53:03.809Z","objectId":"7gd45OXwIi"}
```

### Environment
Client
- Parse Swift SDK version: `4.7.0 installed via SPM`
- Xcode version: `XCode 13.3`
- Operating system (iOS, macOS, watchOS, etc.): `Mac OSX Monterey 12.3.1`
- Operating system version: `12.3.1`
- Emulator: `iOS 14.0 image`

Server
- Parse Server version: `4.5.0`
- Operating system: `Linux`
- Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): `Back4App`

Database
- System (MongoDB or Postgres): `MongoDB`
- Database version: `3.6`
- Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): `Back4App`

### Logs
Error querying: ParseError code=1 error=Internal server error.

### Code to Reproduce
Assuming you have a Class called "City" which contains a property named "location" that has a valid GeoPoint contained in the Polygon or array of GeoPoints.
The code below uses an array of GeoPoints, but the behavior is the same with a Polygon.

```
private func withinPolygon_query() {
        do {
            let geoPoint1 = try ParseGeoPoint(latitude: 90.0, longitude: -180.0)
            let geoPoint2 = try ParseGeoPoint(latitude: -90.0, longitude: -180.0)
            let geoPoint3 = try ParseGeoPoint(latitude: 90.0, longitude: 180.0)
            let geoPoint4 = try ParseGeoPoint(latitude: -90.0, longitude: 180.0)
            
            let query = City.query(withinPolygon(key: "location", points: [geoPoint1, geoPoint2, geoPoint3, geoPoint4]))
            
            query.find { results in
                switch results {
                    case .success(let cities):
                        cities.forEach { (city) in
                            print("Found City with objectId \"\(city.objectId!)\"")
                        }
                    case .failure(let error):
                        print("Error querying: \(error)")
                }
            }
        } catch let error as ParseError {
            print("Failed to instantiate vertices: \(error.message)")
        } catch {
            print("Failed to instantiate vertices: \(error.localizedDescription)")
        }
    }
```


Same implementation in Node.JS using the JS SDK, which responds correctly with all objects:

```
async function run(){
    let geoPoint1 = new Parse.GeoPoint({latitude: 90.0, longitude: -180.0});
    let geoPoint2 = new Parse.GeoPoint({latitude: -90.0, longitude: -180.0});
    let geoPoint3 = new Parse.GeoPoint({latitude: 90.0, longitude: 180.0});
    let geoPoint4 = new Parse.GeoPoint({latitude: -90.0, longitude: 180.0});

    const query = new Parse.Query("City");
    query.withinPolygon("location", [geoPoint1, geoPoint2, geoPoint3, geoPoint4]);
    const results = await query.find();
    results.forEach((result) => {
        console.log(JSON.stringify(result));
    });
}
```

Pull request description:
### New Pull Request Checklist
<!--
    Please check the following boxes [x] before submitting your issue.
    Click the "Preview" tab for better readability.
    Thanks for contributing to Parse Platform!
-->

- [x] I am not disclosing a [vulnerability](https://github.com/parse-community/Parse-Swift/security/policy).
- [x] I am creating this PR in reference to an [issue](https://github.com/parse-community/Parse-Swift/issues?q=is%3Aissue).

### Issue Description
<!-- Add a brief description of the issue this PR solves. -->
The `withinPolygon` `QueryConstraint` currently does not encode `ParseGeoPoint`'s or `ParsePolygon` correctly.

Related issue: #380 

### Approach
<!-- Add a description of the approach in this PR. -->
- [x] fix `withinPolygon` encoding for both `ParseGeoPoint` and `ParsePolygon` types
- [x] Swap the latitude and longitude when encoding/decoding `ParsePolygon` types 

### TODOs before merging
<!--
    Add TODOs that need to be completed before merging this PR.
    Delete TODOs that do not apply to this PR.
-->

- [x] Add tests
- [x] Add entry to changelog
- [x] Add changes to documentation (guides, repository pages, in-code descriptions)

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 59a8a0d8a..67660383c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,13 +2,14 @@
 
 ### main
 
-[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.7.0...main)
+[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.8.0...main)
 * _Contributing to this repo? Add info about your change here to be included in the next release_
 
 __New features__
 - Add ParseSpotify authentication ([#375](https://github.com/parse-community/Parse-Swift/pull/375)), thanks to [Ulaş Sancak](https://github.com/rocxteady).
 
 __Fixes__
+- Encode withinPolygon Queryconstraint correctly ([#381](https://github.com/parse-community/Parse-Swift/pull/381)), thanks to [Corey Baker](https://github.com/cbaker6).
 - Use select for ParseLiveQuery when fields are not present ([#376](https://github.com/parse-community/Parse-Swift/pull/376)), thanks to [Corey Baker](https://github.com/cbaker6).
 
 ### 4.7.0
diff --git a/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift
index b8dab79cf..9c04c2332 100644
--- a/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift
+++ b/ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift
@@ -251,6 +251,58 @@ 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)
+    ]
+    let query9 = GameScore.query(withinPolygon(key: "location", points: points))
+    query9.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 \(String(describing: score.location)) within the
+                    polygon using points: \(points)
+                """)
+            }
+        case .failure(let error):
+            assertionFailure("Error querying: \(error)")
+        }
+    }
+} catch {
+    print("Could not create geopoints: \(error)")
+}
+
+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)
+    ]
+    let polygon = try ParsePolygon(points)
+    let query10 = GameScore.query(withinPolygon(key: "location", polygon: polygon))
+    query10.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 \(String(describing: score.location)) within the
+                    polygon: \(polygon)
+                """)
+            }
+        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
diff --git a/Sources/ParseSwift/ParseConstants.swift b/Sources/ParseSwift/ParseConstants.swift
index 722f0e774..67caa34e0 100644
--- a/Sources/ParseSwift/ParseConstants.swift
+++ b/Sources/ParseSwift/ParseConstants.swift
@@ -10,7 +10,7 @@ import Foundation
 
 enum ParseConstants {
     static let sdk = "swift"
-    static let version = "4.7.0"
+    static let version = "4.8.0"
     static let fileManagementDirectory = "parse/"
     static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
     static let fileManagementLibraryDirectory = "Library/"
diff --git a/Sources/ParseSwift/Types/ParsePolygon.swift b/Sources/ParseSwift/Types/ParsePolygon.swift
index 256a8a275..f1195963f 100644
--- a/Sources/ParseSwift/Types/ParsePolygon.swift
+++ b/Sources/ParseSwift/Types/ParsePolygon.swift
@@ -105,8 +105,8 @@ extension ParsePolygon {
         let points = try values.decode([[Double]].self, forKey: .coordinates)
         try points.forEach {
             if $0.count == 2 {
-                guard let latitude = $0.first,
-                      let longitude = $0.last else {
+                guard let latitude = $0.last,
+                      let longitude = $0.first else {
                     throw ParseError(code: .unknownError, message: "Could not decode ParsePolygon: \(points)")
                 }
                 decodedCoordinates.append(try ParseGeoPoint(latitude: latitude,
@@ -125,7 +125,7 @@ extension ParsePolygon {
         try container.encode(__type, forKey: .__type)
         var nestedUnkeyedContainer = container.nestedUnkeyedContainer(forKey: .coordinates)
         try coordinates.forEach {
-            try nestedUnkeyedContainer.encode([$0.latitude, $0.longitude])
+            try nestedUnkeyedContainer.encode([$0.longitude, $0.latitude])
         }
     }
 }
diff --git a/Sources/ParseSwift/Types/QueryConstraint.swift b/Sources/ParseSwift/Types/QueryConstraint.swift
index 91f9c659d..23dfa275b 100644
--- a/Sources/ParseSwift/Types/QueryConstraint.swift
+++ b/Sources/ParseSwift/Types/QueryConstraint.swift
@@ -588,8 +588,7 @@ public func withinGeoBox(key: String, fromSouthWest southwest: ParseGeoPoint,
  - returns: The same instance of `QueryConstraint` as the receiver.
  */
 public func withinPolygon(key: String, points: [ParseGeoPoint]) -> QueryConstraint {
-    let polygon = points.flatMap { [[$0.latitude, $0.longitude]]}
-    let dictionary = [QueryConstraint.Comparator.polygon.rawValue: polygon]
+    let dictionary = [QueryConstraint.Comparator.polygon.rawValue: points]
     return .init(key: key, value: dictionary, comparator: .geoWithin)
 }
 
@@ -604,7 +603,6 @@ public func withinPolygon(key: String, points: [ParseGeoPoint]) -> QueryConstrai
  - returns: The same instance of `QueryConstraint` as the receiver.
  */
 public func withinPolygon(key: String, polygon: ParsePolygon) -> QueryConstraint {
-    let polygon = polygon.coordinates.flatMap { [[$0.latitude, $0.longitude]]}
     let dictionary = [QueryConstraint.Comparator.polygon.rawValue: polygon]
     return .init(key: key, value: dictionary, comparator: .geoWithin)
 }


Test patch:
diff --git a/Tests/ParseSwiftTests/ParsePolygonTests.swift b/Tests/ParseSwiftTests/ParsePolygonTests.swift
index 6eca412c1..3c04ebe20 100644
--- a/Tests/ParseSwiftTests/ParsePolygonTests.swift
+++ b/Tests/ParseSwiftTests/ParsePolygonTests.swift
@@ -117,13 +117,13 @@ class ParsePolygonTests: XCTestCase {
 
     func testDebugString() throws {
         let polygon = try ParsePolygon(points)
-        let expected = "{\"__type\":\"Polygon\",\"coordinates\":[[0,0],[0,1],[1,1],[1,0],[0,0]]}"
+        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],[0,1],[1,1],[1,0],[0,0]]}"
+        let expected = "{\"__type\":\"Polygon\",\"coordinates\":[[0,0],[1,0],[1,1],[0,1],[0,0]]}"
         XCTAssertEqual(polygon.description, expected)
     }
 }
diff --git a/Tests/ParseSwiftTests/ParseQueryTests.swift b/Tests/ParseSwiftTests/ParseQueryTests.swift
index 31e28601a..29b2d5691 100644
--- a/Tests/ParseSwiftTests/ParseQueryTests.swift
+++ b/Tests/ParseSwiftTests/ParseQueryTests.swift
@@ -2924,89 +2924,33 @@ class ParseQueryTests: XCTestCase { // swiftlint:disable:this type_body_length
         }
     }
 
+    #if !os(Linux) && !os(Android) && !os(Windows)
     // swiftlint:disable:next function_body_length
     func testWhereKeyWithinPolygonPoints() throws {
-        let expected: [String: AnyCodable] = [
-            "yolo": ["$geoWithin": ["$polygon": [
-                                        [10.1, 20.1],
-                                        [20.1, 30.1],
-                                        [30.1, 40.1]]
-                                ]
-            ]
-        ]
+        // swiftlint:disable:next line_length
+        let expected = "{\"yolo\":{\"$geoWithin\":{\"$polygon\":[{\"__type\":\"GeoPoint\",\"latitude\":10.1,\"longitude\":20.100000000000001},{\"__type\":\"GeoPoint\",\"latitude\":20.100000000000001,\"longitude\":30.100000000000001},{\"__type\":\"GeoPoint\",\"latitude\":30.100000000000001,\"longitude\":40.100000000000001}]}}}"
         let geoPoint1 = try ParseGeoPoint(latitude: 10.1, longitude: 20.1)
         let geoPoint2 = try ParseGeoPoint(latitude: 20.1, longitude: 30.1)
         let geoPoint3 = try ParseGeoPoint(latitude: 30.1, longitude: 40.1)
         let polygon = [geoPoint1, geoPoint2, geoPoint3]
         let constraint = withinPolygon(key: "yolo", points: polygon)
         let query = GameScore.query(constraint)
-        let queryWhere = query.`where`
-
-        do {
-            let encoded = try ParseCoding.jsonEncoder().encode(queryWhere)
-            let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded)
-            XCTAssertEqual(expected.keys, decodedDictionary.keys)
-
-            guard let expectedValues = expected.values.first?.value as? [String: [String: [[Double]]]],
-                  let expectedBox = expectedValues["$geoWithin"]?["$polygon"] else {
-                XCTFail("Should have casted")
-                return
-            }
-
-            guard let decodedValues = decodedDictionary.values.first?.value as? [String: [String: [[Double]]]],
-                  let decodedBox = decodedValues["$geoWithin"]?["$polygon"] else {
-                XCTFail("Should have casted")
-                return
-            }
-            XCTAssertEqual(expectedBox, decodedBox)
-
-        } catch {
-            XCTFail(error.localizedDescription)
-            return
-        }
+        XCTAssertEqual(query.where.description, expected)
     }
 
     // swiftlint:disable:next function_body_length
     func testWhereKeyWithinPolygon() throws {
-        let expected: [String: AnyCodable] = [
-            "yolo": ["$geoWithin": ["$polygon": [
-                                        [10.1, 20.1],
-                                        [20.1, 30.1],
-                                        [30.1, 40.1]]
-                                ]
-            ]
-        ]
+        // swiftlint:disable:next line_length
+        let expected = "{\"yolo\":{\"$geoWithin\":{\"$polygon\":{\"__type\":\"Polygon\",\"coordinates\":[[20.100000000000001,10.1],[30.100000000000001,20.100000000000001],[40.100000000000001,30.100000000000001]]}}}}"
         let geoPoint1 = try ParseGeoPoint(latitude: 10.1, longitude: 20.1)
         let geoPoint2 = try ParseGeoPoint(latitude: 20.1, longitude: 30.1)
         let geoPoint3 = try ParseGeoPoint(latitude: 30.1, longitude: 40.1)
         let polygon = try ParsePolygon(geoPoint1, geoPoint2, geoPoint3)
         let constraint = withinPolygon(key: "yolo", polygon: polygon)
         let query = GameScore.query(constraint)
-        let queryWhere = query.`where`
-
-        do {
-            let encoded = try ParseCoding.jsonEncoder().encode(queryWhere)
-            let decodedDictionary = try JSONDecoder().decode([String: AnyCodable].self, from: encoded)
-            XCTAssertEqual(expected.keys, decodedDictionary.keys)
-
-            guard let expectedValues = expected.values.first?.value as? [String: [String: [[Double]]]],
-                  let expectedBox = expectedValues["$geoWithin"]?["$polygon"] else {
-                XCTFail("Should have casted")
-                return
-            }
-
-            guard let decodedValues = decodedDictionary.values.first?.value as? [String: [String: [[Double]]]],
-                  let decodedBox = decodedValues["$geoWithin"]?["$polygon"] else {
-                XCTFail("Should have casted")
-                return
-            }
-            XCTAssertEqual(expectedBox, decodedBox)
-
-        } catch {
-            XCTFail(error.localizedDescription)
-            return
-        }
+        XCTAssertEqual(query.where.description, expected)
     }
+    #endif
 
     func testWhereKeyPolygonContains() throws {
         let expected: [String: AnyCodable] = [