diff --git a/Parse/src/main/java/com/parse/OfflineQueryLogic.java b/Parse/src/main/java/com/parse/OfflineQueryLogic.java index 3a95f1186..dd626cb7c 100644 --- a/Parse/src/main/java/com/parse/OfflineQueryLogic.java +++ b/Parse/src/main/java/com/parse/OfflineQueryLogic.java @@ -231,6 +231,12 @@ private static boolean matchesEqualConstraint(Object constraint, Object value) { && lhs.getLongitude() == rhs.getLongitude(); } + if (constraint instanceof ParsePolygon && value instanceof ParsePolygon) { + ParsePolygon lhs = (ParsePolygon) constraint; + ParsePolygon rhs = (ParsePolygon) value; + return lhs.equals(rhs); + } + return compare(constraint, value, new Decider() { @Override public boolean decide(Object constraint, Object value) { @@ -457,6 +463,23 @@ private static boolean matchesWithinConstraint(Object constraint, Object value) && target.getLongitude() <= northeast.getLongitude()); } + /** + * Matches $geoIntersects constraints. + */ + private static boolean matchesGeoIntersectsConstraint(Object constraint, Object value) + throws ParseException { + if (value == null || value == JSONObject.NULL) { + return false; + } + + @SuppressWarnings("unchecked") + HashMap constraintMap = + (HashMap) constraint; + ParseGeoPoint point = constraintMap.get("$point"); + ParsePolygon target = (ParsePolygon) value; + return target.containsPoint(point); + } + /** * Returns true iff the given value matches the given operator and constraint. * @@ -512,6 +535,9 @@ private static boolean matchesStatelessConstraint(String operator, Object constr case "$within": return matchesWithinConstraint(constraint, value); + case "$geoIntersects": + return matchesGeoIntersectsConstraint(constraint, value); + default: throw new UnsupportedOperationException(String.format( "The offline store does not yet support the %s operator.", operator)); diff --git a/Parse/src/main/java/com/parse/ParseDecoder.java b/Parse/src/main/java/com/parse/ParseDecoder.java index bf7b0c6b0..9493e3c1b 100644 --- a/Parse/src/main/java/com/parse/ParseDecoder.java +++ b/Parse/src/main/java/com/parse/ParseDecoder.java @@ -57,24 +57,24 @@ protected ParseDecoder() { } return outputMap; } - + /** * Gets the ParseObject another object points to. By default a new * object will be created. */ protected ParseObject decodePointer(String className, String objectId) { - return ParseObject.createWithoutData(className, objectId); + return ParseObject.createWithoutData(className, objectId); } public Object decode(Object object) { if (object instanceof JSONArray) { return convertJSONArrayToList((JSONArray) object); } - + if (!(object instanceof JSONObject)) { return object; } - + JSONObject jsonObject = (JSONObject) object; String opString = jsonObject.optString("__op", null); @@ -121,6 +121,20 @@ public Object decode(Object object) { return new ParseGeoPoint(latitude, longitude); } + if (typeString.equals("Polygon")) { + List coordinates = new ArrayList(); + try { + JSONArray array = jsonObject.getJSONArray("coordinates"); + for (int i = 0; i < array.length(); ++i) { + JSONArray point = array.getJSONArray(i); + coordinates.add(new ParseGeoPoint(point.getDouble(0), point.getDouble(1))); + } + } catch (JSONException e) { + throw new RuntimeException(e); + } + return new ParsePolygon(coordinates); + } + if (typeString.equals("Object")) { return ParseObject.fromJSON(jsonObject, null, this); } @@ -132,7 +146,7 @@ public Object decode(Object object) { if (typeString.equals("OfflineObject")) { throw new RuntimeException("An unexpected offline pointer was encountered."); } - + return null; } } diff --git a/Parse/src/main/java/com/parse/ParseEncoder.java b/Parse/src/main/java/com/parse/ParseEncoder.java index 096092f57..05588de46 100644 --- a/Parse/src/main/java/com/parse/ParseEncoder.java +++ b/Parse/src/main/java/com/parse/ParseEncoder.java @@ -40,6 +40,7 @@ || value instanceof ParseACL || value instanceof ParseFile || value instanceof ParseGeoPoint + || value instanceof ParsePolygon || value instanceof ParseRelation; } @@ -84,6 +85,14 @@ public Object encode(Object object) { return json; } + if (object instanceof ParsePolygon) { + ParsePolygon polygon = (ParsePolygon) object; + JSONObject json = new JSONObject(); + json.put("__type", "Polygon"); + json.put("coordinates", polygon.coordinatesToJSONArray()); + return json; + } + if (object instanceof ParseACL) { ParseACL acl = (ParseACL) object; return acl.toJSONObject(this); diff --git a/Parse/src/main/java/com/parse/ParseGeoPoint.java b/Parse/src/main/java/com/parse/ParseGeoPoint.java index 4a785fd50..4f901d592 100644 --- a/Parse/src/main/java/com/parse/ParseGeoPoint.java +++ b/Parse/src/main/java/com/parse/ParseGeoPoint.java @@ -49,7 +49,7 @@ public ParseGeoPoint() { /** * Creates a new point with the specified latitude and longitude. - * + * * @param latitude * The point's latitude. * @param longitude @@ -96,7 +96,7 @@ protected ParseGeoPoint(Parcel source) { /** * Set latitude. Valid range is (-90.0, 90.0). Extremes should not be used. - * + * * @param latitude * The point's latitude. */ @@ -116,7 +116,7 @@ public double getLatitude() { /** * Set longitude. Valid range is (-180.0, 180.0). Extremes should not be used. - * + * * @param longitude * The point's longitude. */ @@ -137,7 +137,7 @@ public double getLongitude() { /** * Get distance in radians between this point and another {@code ParseGeoPoint}. This is the * smallest angular distance between the two points. - * + * * @param point * {@code ParseGeoPoint} describing the other point being measured against. */ @@ -162,7 +162,7 @@ public double distanceInRadiansTo(ParseGeoPoint point) { /** * Get distance between this point and another {@code ParseGeoPoint} in kilometers. - * + * * @param point * {@code ParseGeoPoint} describing the other point being measured against. */ @@ -172,7 +172,7 @@ public double distanceInKilometersTo(ParseGeoPoint point) { /** * Get distance between this point and another {@code ParseGeoPoint} in kilometers. - * + * * @param point * {@code ParseGeoPoint} describing the other point being measured against. */ @@ -274,7 +274,7 @@ public ParseGeoPoint then(Task task) throws Exception { * times for a fix. * * For better battery efficiency and faster location fixes, you can set * {@link Criteria#setPowerRequirement(int)}, however, this will result in lower accuracy. - * + * * @param timeout * The number of milliseconds to allow before timing out. * @param criteria @@ -290,6 +290,18 @@ public static void getCurrentLocationInBackground(long timeout, Criteria criteri ParseTaskUtils.callbackOnMainThreadAsync(getCurrentLocationInBackground(timeout, criteria), callback); } + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ParseGeoPoint)) { + return false; + } + if (obj == this) { + return true; + } + return ((ParseGeoPoint) obj).getLatitude() == latitude && + ((ParseGeoPoint) obj).getLongitude() == longitude; + } + @Override public String toString() { return String.format(Locale.US, "ParseGeoPoint[%.6f,%.6f]", latitude, longitude); diff --git a/Parse/src/main/java/com/parse/ParseObject.java b/Parse/src/main/java/com/parse/ParseObject.java index 6be0ced25..1847956d2 100644 --- a/Parse/src/main/java/com/parse/ParseObject.java +++ b/Parse/src/main/java/com/parse/ParseObject.java @@ -3457,6 +3457,24 @@ public ParseGeoPoint getParseGeoPoint(String key) { } } + /** + * Access a {@link ParsePolygon} value. + * + * @param key + * The key to access the value for + * @return {@code null} if there is no such key or if it is not a {@link ParsePolygon}. + */ + public ParsePolygon getParsePolygon(String key) { + synchronized (mutex) { + checkGetAccess(key); + Object value = estimatedData.get(key); + if (!(value instanceof ParsePolygon)) { + return null; + } + return (ParsePolygon) value; + } + } + /** * Access the {@link ParseACL} governing this object. */ diff --git a/Parse/src/main/java/com/parse/ParseParcelDecoder.java b/Parse/src/main/java/com/parse/ParseParcelDecoder.java index eab948eb2..9f434e740 100644 --- a/Parse/src/main/java/com/parse/ParseParcelDecoder.java +++ b/Parse/src/main/java/com/parse/ParseParcelDecoder.java @@ -65,6 +65,9 @@ public Object decode(Parcel source) { case ParseParcelEncoder.TYPE_GEOPOINT: return new ParseGeoPoint(source, this); + case ParseParcelEncoder.TYPE_POLYGON: + return new ParsePolygon(source, this); + case ParseParcelEncoder.TYPE_ACL: return new ParseACL(source, this); diff --git a/Parse/src/main/java/com/parse/ParseParcelEncoder.java b/Parse/src/main/java/com/parse/ParseParcelEncoder.java index e82262dd2..2cf905458 100644 --- a/Parse/src/main/java/com/parse/ParseParcelEncoder.java +++ b/Parse/src/main/java/com/parse/ParseParcelEncoder.java @@ -55,6 +55,7 @@ private static boolean isValidType(Object value) { /* package */ final static String TYPE_OP = "Operation"; /* package */ final static String TYPE_FILE = "File"; /* package */ final static String TYPE_GEOPOINT = "GeoPoint"; + /* package */ final static String TYPE_POLYGON = "Polygon"; public void encode(Object object, Parcel dest) { try { @@ -84,6 +85,10 @@ public void encode(Object object, Parcel dest) { dest.writeString(TYPE_GEOPOINT); ((ParseGeoPoint) object).writeToParcel(dest, this); + } else if (object instanceof ParsePolygon) { + dest.writeString(TYPE_POLYGON); + ((ParsePolygon) object).writeToParcel(dest, this); + } else if (object instanceof ParseACL) { dest.writeString(TYPE_ACL); ((ParseACL) object).writeToParcel(dest, this); diff --git a/Parse/src/main/java/com/parse/ParsePolygon.java b/Parse/src/main/java/com/parse/ParsePolygon.java new file mode 100644 index 000000000..0a07f9675 --- /dev/null +++ b/Parse/src/main/java/com/parse/ParsePolygon.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.parse; + +import android.location.Criteria; +import android.location.Location; +import android.os.Parcel; +import android.os.Parcelable; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.util.Locale; +import java.util.List; + +import bolts.Continuation; +import bolts.Task; + +/** + * {@code ParsePolygon} represents a set of coordinates that may be associated with a key + * in a {@link ParseObject} or used as a reference point for geo queries. This allows proximity + * based queries on the key. + * + * Example: + *
+ * List points = new ArrayList();
+ * points.add(new ParseGeoPoint(0,0));
+ * points.add(new ParseGeoPoint(0,1));
+ * points.add(new ParseGeoPoint(1,1));
+ * points.add(new ParseGeoPoint(1,0));
+ * ParsePolygon polygon = new ParsePolygon(points);
+ * ParseObject object = new ParseObject("PlaceObject");
+ * object.put("area", polygon);
+ * object.save();
+ * 
+ */ + +public class ParsePolygon implements Parcelable { + + private List coordinates; + + /** + * Creates a new polygon with the specified {@link ParseGeoPoint}. + * + * @param coords + * The polygon's coordinates. + */ + public ParsePolygon(List coords) { + setCoordinates(coords); + } + + /** + * Creates a copy of {@code polygon}; + * + * @param polygon + * The polygon to copy. + */ + public ParsePolygon(ParsePolygon polygon) { + this(polygon.getCoordinates()); + } + + /** + * Creates a new point instance from a {@link Parcel} source. This is used when unparceling a + * ParsePolygon. Subclasses that need Parcelable behavior should provide their own + * {@link android.os.Parcelable.Creator} and override this constructor. + * + * @param source The recovered parcel. + */ + protected ParsePolygon(Parcel source) { + this(source, ParseParcelDecoder.get()); + } + + /** + * Creates a new point instance from a {@link Parcel} using the given {@link ParseParcelDecoder}. + * The decoder is currently unused, but it might be in the future, plus this is the pattern we + * are using in parcelable classes. + * + * @param source the parcel + * @param decoder the decoder + */ + ParsePolygon(Parcel source, ParseParcelDecoder decoder) { + setCoordinates(source.readArrayList(null)); + } + + + /** + * Set coordinates. Valid are Array of GeoPoint, ParseGeoPoint or Location + * at least 3 points + * + * @param coords + * The polygon's coordinates. + */ + public void setCoordinates(List coords) { + this.coordinates = ParsePolygon.validate(coords); + } + + /** + * Get coordinates. + */ + public List getCoordinates() { + return coordinates; + } + + /** + * Get converts coordinate to JSONArray. + */ + protected JSONArray coordinatesToJSONArray() throws JSONException{ + JSONArray points = new JSONArray(); + for (ParseGeoPoint coordinate : coordinates) { + JSONArray point = new JSONArray(); + point.put(coordinate.getLatitude()); + point.put(coordinate.getLongitude()); + points.put(point); + } + return points; + } + + /** + * Checks if this {@code ParsePolygon}; contains {@link ParseGeoPoint}. + */ + public boolean containsPoint(ParseGeoPoint point) { + double minX = coordinates.get(0).getLatitude(); + double maxX = coordinates.get(0).getLatitude(); + double minY = coordinates.get(0).getLongitude(); + double maxY = coordinates.get(0).getLongitude(); + + for ( int i = 1; i < coordinates.size(); i += 1) { + ParseGeoPoint geoPoint = coordinates.get(i); + minX = Math.min(geoPoint.getLatitude(), minX); + maxX = Math.max(geoPoint.getLatitude(), maxX); + minY = Math.min(geoPoint.getLongitude(), minY); + maxY = Math.max(geoPoint.getLongitude(), maxY); + } + + boolean outside = point.getLatitude() < minX || point.getLatitude() > maxX || point.getLongitude() < minY || point.getLongitude() > maxY; + if (outside) { + return false; + } + + boolean inside = false; + for (int i = 0, j = coordinates.size() - 1 ; i < coordinates.size(); j = i++) { + double startX = coordinates.get(i).getLatitude(); + double startY = coordinates.get(i).getLongitude(); + double endX = coordinates.get(j).getLatitude(); + double endY = coordinates.get(j).getLongitude(); + + boolean intersect = (( startY > point.getLongitude() ) != ( endY > point.getLongitude() ) && + point.getLatitude() < ( endX - startX ) * ( point.getLongitude() - startY ) / ( endY - startY ) + startX); + + if (intersect) { + inside = !inside; + } + } + return inside; + } + + /** + * Throws exception for invalid coordinates. + */ + static List validate(List coords) { + if (coords.size() < 3) { + throw new IllegalArgumentException("Polygon must have at least 3 GeoPoints"); + } + return coords; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ParsePolygon)) { + return false; + } + if (obj == this) { + return true; + } + ParsePolygon other = (ParsePolygon) obj; + + if (coordinates.size() != other.getCoordinates().size()) { + return false; + } + + boolean isEqual = true; + for (int i = 0; i < coordinates.size(); i += 1) { + if (coordinates.get(i).getLatitude() != other.getCoordinates().get(i).getLatitude() || + coordinates.get(i).getLongitude() != other.getCoordinates().get(i).getLongitude()) { + isEqual = false; + break; + } + } + return isEqual; + } + + @Override + public String toString() { + return String.format(Locale.US, "ParsePolygon: %s", coordinates); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + writeToParcel(dest, ParseParcelEncoder.get()); + } + + void writeToParcel(Parcel dest, ParseParcelEncoder encoder) { + dest.writeList(coordinates); + } + + public final static Creator CREATOR = new Creator() { + @Override + public ParsePolygon createFromParcel(Parcel source) { + return new ParsePolygon(source, ParseParcelDecoder.get()); + } + + @Override + public ParsePolygon[] newArray(int size) { + return new ParsePolygon[size]; + } + }; +} diff --git a/Parse/src/main/java/com/parse/ParseQuery.java b/Parse/src/main/java/com/parse/ParseQuery.java index 9542d6e1c..652cb3363 100644 --- a/Parse/src/main/java/com/parse/ParseQuery.java +++ b/Parse/src/main/java/com/parse/ParseQuery.java @@ -468,6 +468,12 @@ public Builder whereWithin(String key, ParseGeoPoint southwest, ParseGeoPoint return addCondition(key, "$within", dictionary); } + public Builder whereGeoIntersects(String key, ParseGeoPoint point) { + Map dictionary = new HashMap<>(); + dictionary.put("$point", point); + return addCondition(key, "$geoIntersects", dictionary); + } + public Builder addCondition(String key, String condition, Collection value) { return addConditionInternal(key, condition, Collections.unmodifiableCollection(value)); @@ -1842,6 +1848,23 @@ public ParseQuery whereWithinGeoBox( return this; } + /** + * Add a constraint to the query that requires a particular key's + * coordinates that contains a {@link ParseGeoPoint}s + * + * (Requires parse-server@2.6.0) + * + * @param key + * The key to be constrained. + * @param point + * ParseGeoPoint + * @return this, so you can chain this call. + */ + public ParseQuery wherePolygonContains(String key, ParseGeoPoint point) { + builder.whereGeoIntersects(key, point); + return this; + } + /** * Add a regular expression constraint for finding string values that match the provided regular * expression. diff --git a/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java b/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java index 3d7a79e05..7a77cdad4 100644 --- a/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java +++ b/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java @@ -280,6 +280,44 @@ public void testMatchesEqualsWithGeoPoint() throws Exception { assertFalse(matches(logic, query, object)); } + @Test + public void testMatchesEqualsWithPolygon() throws Exception { + List points = new ArrayList(); + points.add(new ParseGeoPoint(0,0)); + points.add(new ParseGeoPoint(0,1)); + points.add(new ParseGeoPoint(1,1)); + points.add(new ParseGeoPoint(1,0)); + + ParsePolygon polygon = new ParsePolygon(points); + ParseObject object = new ParseObject("TestObject"); + object.put("polygon", polygon); + + ParseQuery.State query; + OfflineQueryLogic logic = new OfflineQueryLogic(null); + + query = new ParseQuery.State.Builder<>("TestObject") + .whereEqualTo("polygon", polygon) + .build(); + assertTrue(matches(logic, query, object)); + + List diff = new ArrayList(); + diff.add(new ParseGeoPoint(0,0)); + diff.add(new ParseGeoPoint(0,10)); + diff.add(new ParseGeoPoint(10,10)); + diff.add(new ParseGeoPoint(10,0)); + diff.add(new ParseGeoPoint(0,0)); + + query = new ParseQuery.State.Builder<>("TestObject") + .whereEqualTo("polygon", new ParsePolygon(diff)) + .build(); + assertFalse(matches(logic, query, object)); + + // Not Polygon + object = new ParseObject("TestObject"); + object.put("polygon", "A"); + assertFalse(matches(logic, query, object)); + } + @Test public void testMatchesEqualsWithNumbers() throws ParseException { OfflineQueryLogic logic = new OfflineQueryLogic(null); @@ -508,6 +546,39 @@ public void testMatchesWithin() throws ParseException { assertFalse(matches(logic, query, object)); } + @Test + public void testMatchesGeoIntersects() throws ParseException { + List points = new ArrayList(); + points.add(new ParseGeoPoint(0,0)); + points.add(new ParseGeoPoint(0,1)); + points.add(new ParseGeoPoint(1,1)); + points.add(new ParseGeoPoint(1,0)); + + ParseGeoPoint inside = new ParseGeoPoint(0.5,0.5); + ParseGeoPoint outside = new ParseGeoPoint(10,10); + + ParsePolygon polygon = new ParsePolygon(points); + + ParseObject object = new ParseObject("TestObject"); + object.put("polygon", polygon); + + ParseQuery.State query; + OfflineQueryLogic logic = new OfflineQueryLogic(null); + query = new ParseQuery.State.Builder<>("TestObject") + .whereGeoIntersects("polygon", inside) + .build(); + assertTrue(matches(logic, query, object)); + + query = new ParseQuery.State.Builder<>("TestObject") + .whereGeoIntersects("polygon", outside) + .build(); + assertFalse(matches(logic, query, object)); + + // Non-existant key + object = new ParseObject("TestObject"); + assertFalse(matches(logic, query, object)); + } + //endregion //region compare diff --git a/Parse/src/test/java/com/parse/ParseDecoderTest.java b/Parse/src/test/java/com/parse/ParseDecoderTest.java index f7f64abb3..6a24d0880 100644 --- a/Parse/src/test/java/com/parse/ParseDecoderTest.java +++ b/Parse/src/test/java/com/parse/ParseDecoderTest.java @@ -18,6 +18,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; @@ -187,6 +188,34 @@ public void testGeoPoint() throws JSONException { assertEquals(30, parseGeoPoint.getLatitude(), DELTA); } + @Test + public void testPolygonWithoutCoordinates() throws JSONException { + JSONObject json = new JSONObject(); + json.put("__type", "Polygon"); + + thrown.expect(RuntimeException.class); + ParseDecoder.get().decode(json); + } + + @Test + public void testPolygon() throws JSONException { + List points = new ArrayList(); + points.add(new ParseGeoPoint(0,0)); + points.add(new ParseGeoPoint(0,1)); + points.add(new ParseGeoPoint(1,1)); + points.add(new ParseGeoPoint(1,0)); + + ParsePolygon polygon = new ParsePolygon(points); + + JSONObject json = new JSONObject(); + json.put("__type", "Polygon"); + json.put("coordinates", polygon.coordinatesToJSONArray()); + + ParsePolygon parsePolygon = (ParsePolygon) ParseDecoder.get().decode(json); + assertNotNull(parsePolygon); + assertEquals(polygon.getCoordinates(), parsePolygon.getCoordinates()); + } + @Test public void testParseObject() throws JSONException { JSONObject json = new JSONObject(); diff --git a/Parse/src/test/java/com/parse/ParseEncoderTest.java b/Parse/src/test/java/com/parse/ParseEncoderTest.java index 23cdd4f09..dafee8af9 100644 --- a/Parse/src/test/java/com/parse/ParseEncoderTest.java +++ b/Parse/src/test/java/com/parse/ParseEncoderTest.java @@ -20,6 +20,7 @@ import org.robolectric.annotation.Config; import java.util.ArrayList; +import java.util.List; import java.util.Date; import java.util.HashMap; @@ -102,6 +103,21 @@ public void testParseGeoPoint() throws JSONException { assertEquals(-20, geoPointJSON.getDouble("longitude"), DELTA); } + @Test + public void testParsePolygon() throws JSONException { + List points = new ArrayList(); + points.add(new ParseGeoPoint(0,0)); + points.add(new ParseGeoPoint(0,1)); + points.add(new ParseGeoPoint(1,1)); + points.add(new ParseGeoPoint(1,0)); + + ParsePolygon parsePolygon = new ParsePolygon(points); + JSONObject polygonJSON = (JSONObject) testClassObject.encode(parsePolygon); + assertNotNull(polygonJSON); + assertEquals("Polygon", polygonJSON.getString("__type")); + assertEquals(parsePolygon.coordinatesToJSONArray(), polygonJSON.getJSONArray("coordinates")); + } + @Test public void testParseACL() throws JSONException { ParseACL parseACL = new ParseACL(); diff --git a/Parse/src/test/java/com/parse/ParseGeoPointTest.java b/Parse/src/test/java/com/parse/ParseGeoPointTest.java index 435c1843e..dea3a37b4 100644 --- a/Parse/src/test/java/com/parse/ParseGeoPointTest.java +++ b/Parse/src/test/java/com/parse/ParseGeoPointTest.java @@ -38,6 +38,21 @@ public void testConstructors() { assertEquals(lng, copy.getLongitude(), 0); } + @Test + public void testEquals() { + ParseGeoPoint pointA = new ParseGeoPoint(30d, 50d); + ParseGeoPoint pointB = new ParseGeoPoint(30d, 50d); + ParseGeoPoint pointC = new ParseGeoPoint(45d, 45d); + + assertTrue(pointA.equals(pointB)); + assertTrue(pointA.equals(pointA)); + assertTrue(pointB.equals(pointA)); + + assertFalse(pointA.equals(null)); + assertFalse(pointA.equals(true)); + assertFalse(pointA.equals(pointC)); + } + @Test public void testParcelable() { ParseGeoPoint point = new ParseGeoPoint(30d, 50d); diff --git a/Parse/src/test/java/com/parse/ParseObjectTest.java b/Parse/src/test/java/com/parse/ParseObjectTest.java index 9cab0107c..bb11aefa7 100644 --- a/Parse/src/test/java/com/parse/ParseObjectTest.java +++ b/Parse/src/test/java/com/parse/ParseObjectTest.java @@ -364,6 +364,29 @@ public void testGetParseGeoPointWithWrongValue() throws Exception { assertNull(object.getParseGeoPoint("key")); } + @Test + public void testGetParsePolygon() throws Exception { + ParseObject object = new ParseObject("Test"); + List points = new ArrayList(); + points.add(new ParseGeoPoint(0,0)); + points.add(new ParseGeoPoint(0,1)); + points.add(new ParseGeoPoint(1,1)); + points.add(new ParseGeoPoint(1,0)); + + ParsePolygon polygon = new ParsePolygon(points); + object.put("key", polygon); + + assertEquals(polygon, object.getParsePolygon("key")); + } + + @Test + public void testGetParsePolygonWithWrongValue() throws Exception { + ParseObject object = new ParseObject("Test"); + object.put("key", 1); + + assertNull(object.getParsePolygon("key")); + } + @Test public void testGetACL() throws Exception { ParseObject object = new ParseObject("Test"); @@ -491,7 +514,7 @@ public void testGetLongWithWrongValue() throws Exception { assertEquals(0, object.getLong("key")); } - + //endregion //region testParcelable diff --git a/Parse/src/test/java/com/parse/ParsePolygonTest.java b/Parse/src/test/java/com/parse/ParsePolygonTest.java new file mode 100644 index 000000000..181e2ffe6 --- /dev/null +++ b/Parse/src/test/java/com/parse/ParsePolygonTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.parse; + +import android.os.Parcel; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +@RunWith(RobolectricTestRunner.class) +@Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) +public class ParsePolygonTest { + + @Test + public void testConstructors() { + List arrayPoints = Arrays.asList( + new ParseGeoPoint(0,0), + new ParseGeoPoint(0,1), + new ParseGeoPoint(1,1), + new ParseGeoPoint(1,0) + ); + + List listPoints = new ArrayList(); + listPoints.add(new ParseGeoPoint(0,0)); + listPoints.add(new ParseGeoPoint(0,1)); + listPoints.add(new ParseGeoPoint(1,1)); + listPoints.add(new ParseGeoPoint(1,0)); + + ParsePolygon polygonList = new ParsePolygon(listPoints); + assertEquals(listPoints, polygonList.getCoordinates()); + + ParsePolygon polygonArray = new ParsePolygon(arrayPoints); + assertEquals(arrayPoints, polygonArray.getCoordinates()); + + ParsePolygon copyList = new ParsePolygon(polygonList); + assertEquals(polygonList.getCoordinates(), copyList.getCoordinates()); + + ParsePolygon copyArray = new ParsePolygon(polygonArray); + assertEquals(polygonArray.getCoordinates(), copyArray.getCoordinates()); + } + + @Test(expected = IllegalArgumentException.class) + public void testThreePointMinimum() { + ParseGeoPoint p1 = new ParseGeoPoint(0,0); + ParseGeoPoint p2 = new ParseGeoPoint(0,1); + List points = Arrays.asList(p1,p2); + ParsePolygon polygon = new ParsePolygon(points); + } + + @Test + public void testEquality() { + List points = new ArrayList(); + points.add(new ParseGeoPoint(0,0)); + points.add(new ParseGeoPoint(0,1)); + points.add(new ParseGeoPoint(1,1)); + points.add(new ParseGeoPoint(1,0)); + + List diff = new ArrayList(); + diff.add(new ParseGeoPoint(0,0)); + diff.add(new ParseGeoPoint(0,10)); + diff.add(new ParseGeoPoint(10,10)); + diff.add(new ParseGeoPoint(10,0)); + diff.add(new ParseGeoPoint(0,0)); + + ParsePolygon polygonA = new ParsePolygon(points); + ParsePolygon polygonB = new ParsePolygon(points); + ParsePolygon polygonC = new ParsePolygon(diff); + + assertTrue(polygonA.equals(polygonB)); + assertTrue(polygonA.equals(polygonA)); + assertTrue(polygonB.equals(polygonA)); + + assertFalse(polygonA.equals(null)); + assertFalse(polygonA.equals(true)); + assertFalse(polygonA.equals(polygonC)); + } + + @Test + public void testContainsPoint() { + List points = new ArrayList(); + points.add(new ParseGeoPoint(0,0)); + points.add(new ParseGeoPoint(0,1)); + points.add(new ParseGeoPoint(1,1)); + points.add(new ParseGeoPoint(1,0)); + + ParseGeoPoint inside = new ParseGeoPoint(0.5,0.5); + ParseGeoPoint outside = new ParseGeoPoint(10,10); + + ParsePolygon polygon = new ParsePolygon(points); + + assertTrue(polygon.containsPoint(inside)); + assertFalse(polygon.containsPoint(outside)); + } + + @Test + public void testParcelable() { + List points = new ArrayList(); + points.add(new ParseGeoPoint(0,0)); + points.add(new ParseGeoPoint(0,1)); + points.add(new ParseGeoPoint(1,1)); + points.add(new ParseGeoPoint(1,0)); + ParsePolygon polygon = new ParsePolygon(points); + Parcel parcel = Parcel.obtain(); + polygon.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + polygon = ParsePolygon.CREATOR.createFromParcel(parcel); + assertEquals(polygon.getCoordinates(), points); + } +} diff --git a/Parse/src/test/java/com/parse/ParseQueryTest.java b/Parse/src/test/java/com/parse/ParseQueryTest.java index 74c63b6c6..7177543fb 100644 --- a/Parse/src/test/java/com/parse/ParseQueryTest.java +++ b/Parse/src/test/java/com/parse/ParseQueryTest.java @@ -530,6 +530,23 @@ public void testWhereWithinGeoBox() throws Exception { assertTrue(list.contains(pointAgain)); } + @Test + public void testWherePolygonContains() throws Exception { + ParseQuery query = new ParseQuery<>("Test"); + ParseGeoPoint point = new ParseGeoPoint(10, 10); + + query.wherePolygonContains("key", point); + + // We generate a state to verify the content of the builder + ParseQuery.State state = query.getBuilder().build(); + ParseQuery.QueryConstraints queryConstraints = state.constraints(); + ParseQuery.KeyConstraints keyConstraints = + (ParseQuery.KeyConstraints) queryConstraints.get("key"); + Map map = (Map) keyConstraints.get("$geoIntersects"); + ParseGeoPoint geoPoint = (ParseGeoPoint) map.get("$point"); + assertEquals(geoPoint, point); + } + @Test public void testWhereWithinRadians() throws Exception { ParseQuery query = new ParseQuery<>("Test"); @@ -741,7 +758,7 @@ private static void verifyCondition( assertEquals(map.get(constraintKey), values.get(constraintKey)); } } - + //endregion /**