Skip to content

Commit 447287a

Browse files
dplewisflovilmart
authored andcommitted
Add Polygon Type and PolygonContain to Query (#1157)
* Add Polygon Type and PolygonContain to query * added encoder test
1 parent ba0afd1 commit 447287a

15 files changed

+490
-0
lines changed

Parse.xcodeproj/project.pbxproj

Lines changed: 54 additions & 0 deletions
Large diffs are not rendered by default.

Parse/Internal/PFPolygonPrivate.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright (c) 2015-present, Parse, LLC.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import <Foundation/Foundation.h>
11+
12+
#import <Parse/PFPolygon.h>
13+
14+
@class PFPolygon;
15+
16+
@interface PFPolygon (Private)
17+
18+
// Internal commands
19+
20+
/*
21+
Gets the encoded format for a Polygon.
22+
*/
23+
- (NSDictionary *)encodeIntoDictionary;
24+
25+
/**
26+
Creates a Polygon from its encoded format.
27+
*/
28+
+ (instancetype)polygonWithDictionary:(NSDictionary *)dictionary;
29+
30+
@end

Parse/Internal/ParseInternal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#import "PFEventuallyQueue.h"
1717
#import "PFFieldOperation.h"
1818
#import "PFGeoPointPrivate.h"
19+
#import "PFPolygonPrivate.h"
1920
#import "PFInternalUtils.h"
2021
#import "PFKeyValueCache.h"
2122
#import "PFObjectPrivate.h"

Parse/Internal/Query/PFQueryConstants.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ extern NSString *const PFQueryKeyContainsAll;
2222
extern NSString *const PFQueryKeyNearSphere;
2323
extern NSString *const PFQueryKeyWithin;
2424
extern NSString *const PFQueryKeyGeoWithin;
25+
extern NSString *const PFQueryKeyGeoIntersects;
2526
extern NSString *const PFQueryKeyRegex;
2627
extern NSString *const PFQueryKeyExists;
2728
extern NSString *const PFQueryKeyInQuery;
@@ -37,6 +38,7 @@ extern NSString *const PFQueryKeyObject;
3738
extern NSString *const PFQueryOptionKeyMaxDistance;
3839
extern NSString *const PFQueryOptionKeyBox;
3940
extern NSString *const PFQueryOptionKeyPolygon;
41+
extern NSString *const PFQueryOptionKeyPoint;
4042
extern NSString *const PFQueryOptionKeyRegexOptions;
4143

4244
NS_ASSUME_NONNULL_END

Parse/Internal/Query/PFQueryConstants.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
NSString *const PFQueryKeyNearSphere = @"$nearSphere";
2121
NSString *const PFQueryKeyWithin = @"$within";
2222
NSString *const PFQueryKeyGeoWithin = @"$geoWithin";
23+
NSString *const PFQueryKeyGeoIntersects = @"$geoIntersects";
2324
NSString *const PFQueryKeyRegex = @"$regex";
2425
NSString *const PFQueryKeyExists = @"$exists";
2526
NSString *const PFQueryKeyInQuery = @"$inQuery";
@@ -35,4 +36,5 @@
3536
NSString *const PFQueryOptionKeyMaxDistance = @"$maxDistance";
3637
NSString *const PFQueryOptionKeyBox = @"$box";
3738
NSString *const PFQueryOptionKeyPolygon = @"$polygon";
39+
NSString *const PFQueryOptionKeyPoint = @"$point";
3840
NSString *const PFQueryOptionKeyRegexOptions = @"$options";

Parse/PFDecoder.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#import "PFFieldOperationDecoder.h"
1616
#import "PFFile_Private.h"
1717
#import "PFGeoPointPrivate.h"
18+
#import "PFPolygonPrivate.h"
1819
#import "PFInternalUtils.h"
1920
#import "PFMacros.h"
2021
#import "PFObjectPrivate.h"
@@ -56,6 +57,9 @@ - (id)decodeDictionary:(NSDictionary *)dictionary {
5657
} else if ([type isEqualToString:@"GeoPoint"]) {
5758
return [PFGeoPoint geoPointWithDictionary:dictionary];
5859

60+
} else if ([type isEqualToString:@"Polygon"]) {
61+
return [PFPolygon polygonWithDictionary:dictionary];
62+
5963
} else if ([type isEqualToString:@"Relation"]) {
6064
return [PFRelation relationFromDictionary:dictionary withDecoder:self];
6165

Parse/PFEncoder.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#import "PFFieldOperation.h"
1717
#import "PFFile_Private.h"
1818
#import "PFGeoPointPrivate.h"
19+
#import "PFPolygonPrivate.h"
1920
#import "PFObjectPrivate.h"
2021
#import "PFOfflineStore.h"
2122
#import "PFRelationPrivate.h"
@@ -75,6 +76,10 @@ - (id)encodeObject:(id)object {
7576
// TODO (hallucinogen): pass object encoder here
7677
return [object encodeIntoDictionary];
7778

79+
} else if ([object isKindOfClass:[PFPolygon class]]) {
80+
// TODO (hallucinogen): pass object encoder here
81+
return [object encodeIntoDictionary];
82+
7883
} else if ([object isKindOfClass:[PFRelation class]]) {
7984
// TODO (hallucinogen): pass object encoder here
8085
return [object encodeIntoDictionary];

Parse/PFPolygon.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Copyright (c) 2015-present, Parse, LLC.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import <CoreLocation/CoreLocation.h>
11+
#import <Foundation/Foundation.h>
12+
#import "PFGeoPoint.h"
13+
14+
NS_ASSUME_NONNULL_BEGIN
15+
16+
@class PFPolygon;
17+
18+
/**
19+
`PFPolygon` may be used to embed a latitude / longitude points as the value for a key in a `PFObject`.
20+
It could be used to perform queries in a geospatial manner using `PFQuery.-whereKey:polygonContains:`.
21+
*/
22+
@interface PFPolygon : NSObject <NSCopying, NSCoding>
23+
24+
///--------------------------------------
25+
#pragma mark - Creating a Polygon
26+
///--------------------------------------
27+
28+
/**
29+
Creates a new `PFPolygon` object for the given `CLLocation`, set to the location's coordinates.
30+
31+
@param coordinates Array of `CLLocation`, `PFGeoPoint` or `(lat,lng)`
32+
@return Returns a new PFPolygon at specified location.
33+
*/
34+
+ (instancetype)polygonWithCoordinates:(NSArray *)coordinates;
35+
36+
/**
37+
Test if this polygon contains a point
38+
39+
@param point `PFGeoPoint` to test
40+
@return Returns a boolean.
41+
*/
42+
- (BOOL)containsPoint:(PFGeoPoint *)point;
43+
44+
///--------------------------------------
45+
#pragma mark - Controlling Position
46+
///--------------------------------------
47+
48+
/**
49+
Array of `PFGeoPoints` or CLLocations
50+
*/
51+
@property (nonatomic, strong) NSArray* coordinates;
52+
53+
@end
54+
55+
NS_ASSUME_NONNULL_END

Parse/PFPolygon.m

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Copyright (c) 2015-present, Parse, LLC.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import "PFPolygon.h"
11+
#import "PFPolygonPrivate.h"
12+
13+
#import <math.h>
14+
15+
#import "PFAssert.h"
16+
#import "PFCoreManager.h"
17+
#import "PFHash.h"
18+
#import "PFLocationManager.h"
19+
#import "Parse_Private.h"
20+
21+
@implementation PFPolygon
22+
23+
///--------------------------------------
24+
#pragma mark - Init
25+
///--------------------------------------
26+
27+
+ (instancetype)polygonWithCoordinates:(NSArray *)coordinates {
28+
PFPolygon *polygon = [[self alloc] init];
29+
polygon.coordinates = coordinates;
30+
return polygon;
31+
}
32+
33+
34+
///--------------------------------------
35+
#pragma mark - Accessors
36+
///--------------------------------------
37+
38+
- (void)setCoordinates:(NSArray *)coordinates {
39+
PFParameterAssert([coordinates isKindOfClass:[NSArray class]],
40+
@"`coordinates` must be a NSArray: %@", coordinates);
41+
42+
PFParameterAssert(coordinates.count > 3,
43+
@"`Polygon` must have at least 3 GeoPoints or Points %@", coordinates);
44+
45+
NSMutableArray* points = [[NSMutableArray alloc] init];
46+
PFGeoPoint *geoPoint = [PFGeoPoint geoPoint];
47+
48+
for (int i = 0; i < coordinates.count; i += 1) {
49+
id coord = coordinates[i];
50+
if ([coord isKindOfClass:[PFGeoPoint class]]) {
51+
geoPoint = coord;
52+
} else if ([coord isKindOfClass:[NSArray class]] && ((NSArray*)coord).count == 2) {
53+
NSArray* arr = (NSArray*)coord;
54+
double latitude = [arr[0] doubleValue];
55+
double longitude = [arr[1] doubleValue];
56+
geoPoint = [PFGeoPoint geoPointWithLatitude:latitude longitude:longitude];
57+
} else if ([coord isKindOfClass:[CLLocation class]]) {
58+
geoPoint = [PFGeoPoint geoPointWithLocation:coord];
59+
} else {
60+
PFParameterAssertionFailure(@"Coordinates must be an Array of GeoPoints or Points: %@", coord);
61+
}
62+
[points addObject:@[@(geoPoint.latitude), @(geoPoint.longitude)]];
63+
}
64+
65+
_coordinates = points;
66+
}
67+
68+
- (BOOL)containsPoint:(PFGeoPoint *)point {
69+
double minX = [_coordinates[0][0] doubleValue];
70+
double maxX = [_coordinates[0][0] doubleValue];
71+
double minY = [_coordinates[0][1] doubleValue];
72+
double maxY = [_coordinates[0][1] doubleValue];
73+
for ( int i = 1; i < _coordinates.count; i += 1) {
74+
NSArray *p = _coordinates[i];
75+
minX = fmin( [p[0] doubleValue], minX );
76+
maxX = fmax( [p[0] doubleValue], maxX );
77+
minY = fmin( [p[1] doubleValue], minY );
78+
maxY = fmax( [p[1] doubleValue], maxY );
79+
}
80+
81+
if (point.latitude < minX || point.latitude > maxX || point.longitude < minY || point.longitude > maxY) {
82+
return false;
83+
}
84+
85+
bool inside = false;
86+
for ( int i = 0, j = (int)_coordinates.count - 1 ; i < _coordinates.count; j = i++) {
87+
double startX = [_coordinates[i][0] doubleValue];
88+
double startY = [_coordinates[i][1] doubleValue];
89+
double endX = [_coordinates[j][0] doubleValue];
90+
double endY = [_coordinates[j][1] doubleValue];
91+
if ( ( startY > point.longitude ) != ( endY > point.longitude ) &&
92+
point.latitude < ( endX - startX ) * ( point.longitude - startY ) / ( endY - startY ) + startX ) {
93+
inside = !inside;
94+
}
95+
}
96+
97+
return inside;
98+
}
99+
100+
///--------------------------------------
101+
#pragma mark - Encoding
102+
///--------------------------------------
103+
104+
static NSString *const PFPolygonCodingTypeKey = @"__type";
105+
static NSString *const PFPolygonCodingCoordinatesKey = @"coordinates";
106+
107+
- (NSDictionary *)encodeIntoDictionary {
108+
return @{
109+
PFPolygonCodingTypeKey : @"Polygon",
110+
PFPolygonCodingCoordinatesKey : self.coordinates
111+
};
112+
}
113+
114+
+ (instancetype)polygonWithDictionary:(NSDictionary *)dictionary {
115+
return [[self alloc] initWithEncodedDictionary:dictionary];
116+
}
117+
118+
- (instancetype)initWithEncodedDictionary:(NSDictionary *)dictionary {
119+
self = [self init];
120+
if (!self) return nil;
121+
122+
id coordObj = dictionary[PFPolygonCodingCoordinatesKey];
123+
PFParameterAssert([coordObj isKindOfClass:[NSArray class]], @"Invalid coordinates type passed: %@", coordObj);
124+
125+
_coordinates = coordObj;
126+
127+
return self;
128+
}
129+
130+
///--------------------------------------
131+
#pragma mark - NSObject
132+
///--------------------------------------
133+
134+
- (BOOL)isEqual:(id)object {
135+
if (self == object) {
136+
return YES;
137+
}
138+
139+
if (![object isKindOfClass:[PFPolygon class]]) {
140+
return NO;
141+
}
142+
143+
PFPolygon *polygon = object;
144+
145+
return ([_coordinates isEqualToArray:polygon.coordinates]);
146+
}
147+
148+
- (NSString *)description {
149+
return [NSString stringWithFormat:@"<%@: %p, coordinates: %@>",
150+
[self class],
151+
self,
152+
_coordinates];
153+
}
154+
155+
///--------------------------------------
156+
#pragma mark - NSCopying
157+
///--------------------------------------
158+
159+
- (instancetype)copyWithZone:(NSZone *)zone {
160+
PFPolygon *polygon = [[self class] polygonWithCoordinates:self.coordinates];
161+
return polygon;
162+
}
163+
164+
///--------------------------------------
165+
#pragma mark - NSCoding
166+
///--------------------------------------
167+
168+
- (instancetype)initWithCoder:(NSCoder *)coder {
169+
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
170+
dictionary[PFPolygonCodingTypeKey] = [coder decodeObjectForKey:PFPolygonCodingTypeKey];
171+
dictionary[PFPolygonCodingCoordinatesKey] = [coder decodeObjectForKey:PFPolygonCodingCoordinatesKey];
172+
return [self initWithEncodedDictionary:dictionary];
173+
}
174+
175+
- (void)encodeWithCoder:(NSCoder *)coder {
176+
NSDictionary *dictionary = [self encodeIntoDictionary];
177+
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
178+
[coder encodeObject:obj forKey:key];
179+
}];
180+
}
181+
182+
@end

Parse/PFQuery.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,18 @@ typedef void (^PFQueryArrayResultBlock)(NSArray<PFGenericObject> *_Nullable obje
323323
*/
324324
- (instancetype)whereKey:(NSString *)key withinPolygon:(NSArray<PFGeoPoint *> *)points;
325325

326+
/**
327+
* Add a constraint to the query that requires a particular key's
328+
* coordinates that contains a `PFGeoPoint`
329+
* (Requires [email protected])
330+
*
331+
* @param key The key to be constrained.
332+
* @param point `PFGeoPoint`.
333+
*
334+
* @return The same instance of `PFQuery` as the receiver. This allows method chaining.
335+
*/
336+
- (instancetype)whereKey:(NSString *)key polygonContains:(PFGeoPoint *)point;
337+
326338
///--------------------------------------
327339
#pragma mark - Adding String Constraints
328340
///--------------------------------------

Parse/PFQuery.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,11 @@ - (instancetype)whereKey:(NSString *)key withinPolygon:(NSArray<PFGeoPoint *> *)
306306
return [self whereKey:key condition:PFQueryKeyGeoWithin object:dictionary];
307307
}
308308

309+
- (instancetype)whereKey:(NSString *)key polygonContains:(PFGeoPoint *)point {
310+
NSDictionary *dictionary = @{ PFQueryOptionKeyPoint : point };
311+
return [self whereKey:key condition:PFQueryKeyGeoIntersects object:dictionary];
312+
}
313+
309314
- (instancetype)whereKey:(NSString *)key matchesRegex:(NSString *)regex {
310315
return [self whereKey:key condition:PFQueryKeyRegex object:regex];
311316
}

Parse/Parse.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#import <Parse/PFFile+Deprecated.h>
2727
#import <Parse/PFFile+Synchronous.h>
2828
#import <Parse/PFGeoPoint.h>
29+
#import <Parse/PFPolygon.h>
2930
#import <Parse/PFObject.h>
3031
#import <Parse/PFObject+Subclass.h>
3132
#import <Parse/PFObject+Synchronous.h>

0 commit comments

Comments
 (0)