Skip to content

Add Polygon Type and PolygonContain to Query #1157

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

Merged
merged 2 commits into from
Jul 20, 2017
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
54 changes: 54 additions & 0 deletions Parse.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions Parse/Internal/PFPolygonPrivate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* 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.
*/

#import <Foundation/Foundation.h>

#import <Parse/PFPolygon.h>

@class PFPolygon;

@interface PFPolygon (Private)

// Internal commands

/*
Gets the encoded format for a Polygon.
*/
- (NSDictionary *)encodeIntoDictionary;

/**
Creates a Polygon from its encoded format.
*/
+ (instancetype)polygonWithDictionary:(NSDictionary *)dictionary;

@end
1 change: 1 addition & 0 deletions Parse/Internal/ParseInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#import "PFEventuallyQueue.h"
#import "PFFieldOperation.h"
#import "PFGeoPointPrivate.h"
#import "PFPolygonPrivate.h"
#import "PFInternalUtils.h"
#import "PFKeyValueCache.h"
#import "PFObjectPrivate.h"
Expand Down
2 changes: 2 additions & 0 deletions Parse/Internal/Query/PFQueryConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extern NSString *const PFQueryKeyContainsAll;
extern NSString *const PFQueryKeyNearSphere;
extern NSString *const PFQueryKeyWithin;
extern NSString *const PFQueryKeyGeoWithin;
extern NSString *const PFQueryKeyGeoIntersects;
extern NSString *const PFQueryKeyRegex;
extern NSString *const PFQueryKeyExists;
extern NSString *const PFQueryKeyInQuery;
Expand All @@ -37,6 +38,7 @@ extern NSString *const PFQueryKeyObject;
extern NSString *const PFQueryOptionKeyMaxDistance;
extern NSString *const PFQueryOptionKeyBox;
extern NSString *const PFQueryOptionKeyPolygon;
extern NSString *const PFQueryOptionKeyPoint;
extern NSString *const PFQueryOptionKeyRegexOptions;

NS_ASSUME_NONNULL_END
2 changes: 2 additions & 0 deletions Parse/Internal/Query/PFQueryConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
NSString *const PFQueryKeyNearSphere = @"$nearSphere";
NSString *const PFQueryKeyWithin = @"$within";
NSString *const PFQueryKeyGeoWithin = @"$geoWithin";
NSString *const PFQueryKeyGeoIntersects = @"$geoIntersects";
NSString *const PFQueryKeyRegex = @"$regex";
NSString *const PFQueryKeyExists = @"$exists";
NSString *const PFQueryKeyInQuery = @"$inQuery";
Expand All @@ -35,4 +36,5 @@
NSString *const PFQueryOptionKeyMaxDistance = @"$maxDistance";
NSString *const PFQueryOptionKeyBox = @"$box";
NSString *const PFQueryOptionKeyPolygon = @"$polygon";
NSString *const PFQueryOptionKeyPoint = @"$point";
NSString *const PFQueryOptionKeyRegexOptions = @"$options";
4 changes: 4 additions & 0 deletions Parse/PFDecoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import "PFFieldOperationDecoder.h"
#import "PFFile_Private.h"
#import "PFGeoPointPrivate.h"
#import "PFPolygonPrivate.h"
#import "PFInternalUtils.h"
#import "PFMacros.h"
#import "PFObjectPrivate.h"
Expand Down Expand Up @@ -56,6 +57,9 @@ - (id)decodeDictionary:(NSDictionary *)dictionary {
} else if ([type isEqualToString:@"GeoPoint"]) {
return [PFGeoPoint geoPointWithDictionary:dictionary];

} else if ([type isEqualToString:@"Polygon"]) {
return [PFPolygon polygonWithDictionary:dictionary];

} else if ([type isEqualToString:@"Relation"]) {
return [PFRelation relationFromDictionary:dictionary withDecoder:self];

Expand Down
5 changes: 5 additions & 0 deletions Parse/PFEncoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#import "PFFieldOperation.h"
#import "PFFile_Private.h"
#import "PFGeoPointPrivate.h"
#import "PFPolygonPrivate.h"
#import "PFObjectPrivate.h"
#import "PFOfflineStore.h"
#import "PFRelationPrivate.h"
Expand Down Expand Up @@ -75,6 +76,10 @@ - (id)encodeObject:(id)object {
// TODO (hallucinogen): pass object encoder here
return [object encodeIntoDictionary];

} else if ([object isKindOfClass:[PFPolygon class]]) {
// TODO (hallucinogen): pass object encoder here
return [object encodeIntoDictionary];

} else if ([object isKindOfClass:[PFRelation class]]) {
// TODO (hallucinogen): pass object encoder here
return [object encodeIntoDictionary];
Expand Down
55 changes: 55 additions & 0 deletions Parse/PFPolygon.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* 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.
*/

#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#import "PFGeoPoint.h"

NS_ASSUME_NONNULL_BEGIN

@class PFPolygon;

/**
`PFPolygon` may be used to embed a latitude / longitude points as the value for a key in a `PFObject`.
It could be used to perform queries in a geospatial manner using `PFQuery.-whereKey:polygonContains:`.
*/
@interface PFPolygon : NSObject <NSCopying, NSCoding>

///--------------------------------------
#pragma mark - Creating a Polygon
///--------------------------------------

/**
Creates a new `PFPolygon` object for the given `CLLocation`, set to the location's coordinates.

@param coordinates Array of `CLLocation`, `PFGeoPoint` or `(lat,lng)`
@return Returns a new PFPolygon at specified location.
*/
+ (instancetype)polygonWithCoordinates:(NSArray *)coordinates;

/**
Test if this polygon contains a point

@param point `PFGeoPoint` to test
@return Returns a boolean.
*/
- (BOOL)containsPoint:(PFGeoPoint *)point;

///--------------------------------------
#pragma mark - Controlling Position
///--------------------------------------

/**
Array of `PFGeoPoints` or CLLocations
*/
@property (nonatomic, strong) NSArray* coordinates;

@end

NS_ASSUME_NONNULL_END
182 changes: 182 additions & 0 deletions Parse/PFPolygon.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/**
* 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.
*/

#import "PFPolygon.h"
#import "PFPolygonPrivate.h"

#import <math.h>

#import "PFAssert.h"
#import "PFCoreManager.h"
#import "PFHash.h"
#import "PFLocationManager.h"
#import "Parse_Private.h"

@implementation PFPolygon

///--------------------------------------
#pragma mark - Init
///--------------------------------------

+ (instancetype)polygonWithCoordinates:(NSArray *)coordinates {
PFPolygon *polygon = [[self alloc] init];
polygon.coordinates = coordinates;
return polygon;
}


///--------------------------------------
#pragma mark - Accessors
///--------------------------------------

- (void)setCoordinates:(NSArray *)coordinates {
PFParameterAssert([coordinates isKindOfClass:[NSArray class]],
@"`coordinates` must be a NSArray: %@", coordinates);

PFParameterAssert(coordinates.count > 3,
@"`Polygon` must have at least 3 GeoPoints or Points %@", coordinates);

NSMutableArray* points = [[NSMutableArray alloc] init];
PFGeoPoint *geoPoint = [PFGeoPoint geoPoint];

for (int i = 0; i < coordinates.count; i += 1) {
id coord = coordinates[i];
if ([coord isKindOfClass:[PFGeoPoint class]]) {
geoPoint = coord;
} else if ([coord isKindOfClass:[NSArray class]] && ((NSArray*)coord).count == 2) {
NSArray* arr = (NSArray*)coord;
double latitude = [arr[0] doubleValue];
double longitude = [arr[1] doubleValue];
geoPoint = [PFGeoPoint geoPointWithLatitude:latitude longitude:longitude];
} else if ([coord isKindOfClass:[CLLocation class]]) {
geoPoint = [PFGeoPoint geoPointWithLocation:coord];
} else {
PFParameterAssertionFailure(@"Coordinates must be an Array of GeoPoints or Points: %@", coord);
}
[points addObject:@[@(geoPoint.latitude), @(geoPoint.longitude)]];
}

_coordinates = points;
}

- (BOOL)containsPoint:(PFGeoPoint *)point {
double minX = [_coordinates[0][0] doubleValue];
double maxX = [_coordinates[0][0] doubleValue];
double minY = [_coordinates[0][1] doubleValue];
double maxY = [_coordinates[0][1] doubleValue];
for ( int i = 1; i < _coordinates.count; i += 1) {
NSArray *p = _coordinates[i];
minX = fmin( [p[0] doubleValue], minX );
maxX = fmax( [p[0] doubleValue], maxX );
minY = fmin( [p[1] doubleValue], minY );
maxY = fmax( [p[1] doubleValue], maxY );
}

if (point.latitude < minX || point.latitude > maxX || point.longitude < minY || point.longitude > maxY) {
return false;
}

bool inside = false;
for ( int i = 0, j = (int)_coordinates.count - 1 ; i < _coordinates.count; j = i++) {
double startX = [_coordinates[i][0] doubleValue];
double startY = [_coordinates[i][1] doubleValue];
double endX = [_coordinates[j][0] doubleValue];
double endY = [_coordinates[j][1] doubleValue];
if ( ( startY > point.longitude ) != ( endY > point.longitude ) &&
point.latitude < ( endX - startX ) * ( point.longitude - startY ) / ( endY - startY ) + startX ) {
inside = !inside;
}
}

return inside;
}

///--------------------------------------
#pragma mark - Encoding
///--------------------------------------

static NSString *const PFPolygonCodingTypeKey = @"__type";
static NSString *const PFPolygonCodingCoordinatesKey = @"coordinates";

- (NSDictionary *)encodeIntoDictionary {
return @{
PFPolygonCodingTypeKey : @"Polygon",
PFPolygonCodingCoordinatesKey : self.coordinates
};
}

+ (instancetype)polygonWithDictionary:(NSDictionary *)dictionary {
return [[self alloc] initWithEncodedDictionary:dictionary];
}

- (instancetype)initWithEncodedDictionary:(NSDictionary *)dictionary {
self = [self init];
if (!self) return nil;

id coordObj = dictionary[PFPolygonCodingCoordinatesKey];
PFParameterAssert([coordObj isKindOfClass:[NSArray class]], @"Invalid coordinates type passed: %@", coordObj);

_coordinates = coordObj;

return self;
}

///--------------------------------------
#pragma mark - NSObject
///--------------------------------------

- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}

if (![object isKindOfClass:[PFPolygon class]]) {
return NO;
}

PFPolygon *polygon = object;

return ([_coordinates isEqualToArray:polygon.coordinates]);
}

- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, coordinates: %@>",
[self class],
self,
_coordinates];
}

///--------------------------------------
#pragma mark - NSCopying
///--------------------------------------

- (instancetype)copyWithZone:(NSZone *)zone {
PFPolygon *polygon = [[self class] polygonWithCoordinates:self.coordinates];
return polygon;
}

///--------------------------------------
#pragma mark - NSCoding
///--------------------------------------

- (instancetype)initWithCoder:(NSCoder *)coder {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
dictionary[PFPolygonCodingTypeKey] = [coder decodeObjectForKey:PFPolygonCodingTypeKey];
dictionary[PFPolygonCodingCoordinatesKey] = [coder decodeObjectForKey:PFPolygonCodingCoordinatesKey];
return [self initWithEncodedDictionary:dictionary];
}

- (void)encodeWithCoder:(NSCoder *)coder {
NSDictionary *dictionary = [self encodeIntoDictionary];
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
[coder encodeObject:obj forKey:key];
}];
}

@end
12 changes: 12 additions & 0 deletions Parse/PFQuery.h
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,18 @@ typedef void (^PFQueryArrayResultBlock)(NSArray<PFGenericObject> *_Nullable obje
*/
- (instancetype)whereKey:(NSString *)key withinPolygon:(NSArray<PFGeoPoint *> *)points;

/**
* Add a constraint to the query that requires a particular key's
* coordinates that contains a `PFGeoPoint`
* (Requires [email protected])
*
* @param key The key to be constrained.
* @param point `PFGeoPoint`.
*
* @return The same instance of `PFQuery` as the receiver. This allows method chaining.
*/
- (instancetype)whereKey:(NSString *)key polygonContains:(PFGeoPoint *)point;

///--------------------------------------
#pragma mark - Adding String Constraints
///--------------------------------------
Expand Down
5 changes: 5 additions & 0 deletions Parse/PFQuery.m
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,11 @@ - (instancetype)whereKey:(NSString *)key withinPolygon:(NSArray<PFGeoPoint *> *)
return [self whereKey:key condition:PFQueryKeyGeoWithin object:dictionary];
}

- (instancetype)whereKey:(NSString *)key polygonContains:(PFGeoPoint *)point {
NSDictionary *dictionary = @{ PFQueryOptionKeyPoint : point };
return [self whereKey:key condition:PFQueryKeyGeoIntersects object:dictionary];
}

- (instancetype)whereKey:(NSString *)key matchesRegex:(NSString *)regex {
return [self whereKey:key condition:PFQueryKeyRegex object:regex];
}
Expand Down
1 change: 1 addition & 0 deletions Parse/Parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#import <Parse/PFFile+Deprecated.h>
#import <Parse/PFFile+Synchronous.h>
#import <Parse/PFGeoPoint.h>
#import <Parse/PFPolygon.h>
#import <Parse/PFObject.h>
#import <Parse/PFObject+Subclass.h>
#import <Parse/PFObject+Synchronous.h>
Expand Down
Loading