Skip to content

Commit c28fda5

Browse files
committed
Support for withinPolygon query. Closes #31
1 parent 679b3c5 commit c28fda5

File tree

6 files changed

+126
-4
lines changed

6 files changed

+126
-4
lines changed

README.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1761,7 +1761,7 @@ The `where` clause is based on utilizing a set of constraints on the defined col
17611761
{ :column.constraint => value }
17621762
```
17631763

1764-
## Query Constraints
1764+
## [Query Constraints](https://www.modernistik.com/gems/parse-stack/Parse/Constraint.html)
17651765
Most of the constraints supported by Parse are available to `Parse::Query`. Assuming you have a column named `field`, here are some examples. For an explanation of the constraints, please see [Parse Query Constraints documentation](http://docs.parseplatform.org/rest/guide/#queries). You can build your own custom query constraints by creating a `Parse::Constraint` subclass. For all these `where` clauses assume `q` is a `Parse::Query` object.
17661766

17671767
#### Equals
@@ -1974,7 +1974,7 @@ Song.all :artist => Artist.pointer(artist_id)
19741974
Song.all :artist => Parse::Pointer.new("Artist", artist_id)
19751975
```
19761976

1977-
### Geo Queries
1977+
### [Geo Queries](https://www.modernistik.com/gems/parse-stack/Parse/Constraint/NearSphereQueryConstraint.html)
19781978
Equivalent to the `$nearSphere` Parse query operation. This is only applicable if the field is of type `GeoPoint`. This will query Parse and return a list of results ordered by distance with the nearest object being first.
19791979

19801980
```ruby
@@ -2000,7 +2000,7 @@ PlaceObject.all :location.near => geopoint.max_miles(10)
20002000

20012001
We will support `$maxDistanceInKilometers` (for kms) and `$maxDistanceInRadians` (for radian angle) in the future.
20022002

2003-
#### Bounding Box Constraint
2003+
#### [Bounding Box Constraint](https://www.modernistik.com/gems/parse-stack/Parse/Constraint/WithinGeoBoxQueryConstraint.html)
20042004
Equivalent to the `$within` Parse query operation and `$box` geopoint constraint. The rectangular bounding box is defined by a southwest point as the first parameter, followed by the a northeast point. Please note that Geo box queries that cross the international date lines are not currently supported by Parse.
20052005

20062006
```ruby
@@ -2015,6 +2015,22 @@ ne = Parse::GeoPoint.new 36.12, -115.31 # Las Vegas
20152015
PlaceObject.all :location.within_box => [sw,ne]
20162016
```
20172017

2018+
#### [Polygon Area Constraint](https://www.modernistik.com/gems/parse-stack/Parse/Constraint/WithinPolygonQueryConstraint.html)
2019+
Equivalent to the `$geoWithin` Parse query operation and `$polygon` geopoint constraint. The polygon area is described by a list of `Parse::GeoPoint` objects and should contain 3 or more points. This feature is only available in Parse-Server version 2.4.2 and later.
2020+
2021+
```ruby
2022+
# As many points as you want, minimum 3
2023+
q.where :field.within_polygon => [geopoint1, geopoint2, geopoint3]
2024+
2025+
# Polygon for the Bermuda Triangle
2026+
bermuda = Parse::GeoPoint.new 32.3078000,-64.7504999 # Bermuda
2027+
miami = Parse::GeoPoint.new 25.7823198,-80.2660226 # Miami, FL
2028+
san_juan = Parse::GeoPoint.new 18.3848232,-66.0933608 # San Juan, PR
2029+
2030+
# get all sunken ships inside the Bermuda Triangle
2031+
SunkenShip.all :location.within_polygon => [bermuda, san_juan, miami]
2032+
```
2033+
20182034
### Relational Queries
20192035
Equivalent to the `$relatedTo` Parse query operation. If you want to retrieve objects that are members of a `Relation` field in your Parse class.
20202036

bin/console

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ def setup
2020

2121
end
2222
puts "Type 'setup' to connect to Parse-Server"
23+
24+
# Create shortnames
25+
Parse.use_shortnames!
26+
2327
# You can add fixtures and/or initialization code here to make experimenting
2428
# with your gem easier. You can also use a different console, if you like.
2529

lib/parse/query/constraint.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def formatted_value(value)
107107
d = { __type: Parse::Model::TYPE_DATE, iso: d.iso8601(3) } if d.respond_to?(:iso8601)
108108
d = d.pointer if d.respond_to?(:pointer) #simplified query object
109109
d = d.to_s if d.is_a?(Regexp)
110-
#d = d.pointer if d.is_a?(Parse::Object) #simplified query object
110+
# d = d.pointer if d.is_a?(Parse::Object) #simplified query object
111111
# d = d.compile
112112
if d.is_a?(Parse::Query)
113113
compiled = d.compile(encode: false, includeClassName: true)

lib/parse/query/constraints.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,50 @@ def build
663663
end
664664
end
665665

666+
# Equivalent to the `$geoWithin` Parse query operation and `$polygon` geopoints
667+
# constraint. The polygon area is defined by a list of {Parse::GeoPoint}
668+
# objects that make up the enclosed area. A polygon query should have 3 or more geopoints.
669+
# Please note that some Geo queries that cross the international date lines are not currently
670+
# supported by Parse.
671+
#
672+
# # As many points as you want, minimum 3
673+
# q.where :field.within_polygon => [geopoint1, geopoint2, geopoint3]
674+
#
675+
# # Polygon for the Bermuda Triangle
676+
# bermuda = Parse::GeoPoint.new 32.3078000,-64.7504999 # Bermuda
677+
# miami = Parse::GeoPoint.new 25.7823198,-80.2660226 # Miami, FL
678+
# san_juan = Parse::GeoPoint.new 18.3848232,-66.0933608 # San Juan, PR
679+
#
680+
# # get all sunken ships inside the Bermuda Triangle
681+
# SunkenShip.all :location.within_polygon => [bermuda, san_juan, miami]
682+
#
683+
class WithinPolygonQueryConstraint < Constraint
684+
# @!method within_polygon
685+
# A registered method on a symbol to create the constraint. Maps to Parse
686+
# operator "$geoWithin" with "$polygon" subconstraint. Takes an array of {Parse::GeoPoint} objects.
687+
# @example
688+
# # As many points as you want
689+
# q.where :field.within_polygon => [geopoint1, geopoint2, geopoint3]
690+
# @return [WithinPolygonQueryConstraint]
691+
# @version 1.7.0 (requires Server v2.4.2 or later)
692+
contraint_keyword :$geoWithin
693+
register :within_polygon
694+
695+
# @return [Hash] the compiled constraint.
696+
def build
697+
geopoint_values = formatted_value
698+
unless geopoint_values.is_a?(Array) &&
699+
geopoint_values.all? {|point| point.is_a?(Parse::GeoPoint) } &&
700+
geopoint_values.count > 2
701+
raise ArgumentError, '[Parse::Query] Invalid query value parameter passed to'\
702+
' `within_polygon` constraint: Value must be an array with 3'\
703+
' or more `Parse::GeoPoint` objects'
704+
end
705+
706+
{ @operation.operand => { :$geoWithin => { :$polygon => geopoint_values } } }
707+
end
708+
end
709+
666710
end
667711

668712
end
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
require_relative '../../../../test_helper'
2+
3+
class TestWithinPolygonQueryConstraint < Minitest::Test
4+
extend Minitest::Spec::DSL
5+
include ConstraintTests
6+
7+
def setup
8+
@klass = Parse::Constraint::WithinPolygonQueryConstraint
9+
@key = :$geoWithin
10+
@operand = :within_polygon
11+
@keys = [:within_polygon]
12+
@skip_scalar_values_test = true
13+
14+
@bermuda = Parse::GeoPoint.new 32.3078000,-64.7504999 # Bermuda
15+
@miami = Parse::GeoPoint.new 25.7823198,-80.2660226 # Miami, FL
16+
@san_juan = Parse::GeoPoint.new 18.3848232,-66.0933608 # San Juan, PR
17+
@san_diego = Parse::GeoPoint.new 32.9201332,-117.1088263
18+
end
19+
20+
def build(value)
21+
{"field" => { @key => { :$polygon => value } } }
22+
end
23+
24+
def test_argument_error
25+
triangle = [@bermuda, @miami] # missing one
26+
assert_raises(ArgumentError) { User.query(:location.within_polygon => nil).compile }
27+
assert_raises(ArgumentError) { User.query(:location.within_polygon => []).compile }
28+
assert_raises(ArgumentError) { User.query(:location.within_polygon => [@bermuda, 2343]).compile }
29+
assert_raises(ArgumentError) { User.query(:location.within_polygon => triangle).compile }
30+
triangle.push @san_juan
31+
refute_raises(ArgumentError) { User.query(:location.within_polygon => triangle).compile }
32+
quad = triangle + [@san_diego]
33+
refute_raises(ArgumentError) { User.query(:location.within_polygon => quad).compile }
34+
end
35+
36+
def test_compiled_query
37+
triangle = [@bermuda, @miami, @san_juan]
38+
compiled_query = {"location"=>{"$geoWithin"=>{"$polygon"=>[
39+
{:__type=>"GeoPoint", :latitude=>32.3078, :longitude=>-64.7504999},
40+
{:__type=>"GeoPoint", :latitude=>25.7823198, :longitude=>-80.2660226},
41+
{:__type=>"GeoPoint", :latitude=>18.3848232, :longitude=>-66.0933608}
42+
]}}}
43+
query = User.query(:location.within_polygon => [@bermuda, @miami, @san_juan])
44+
assert_equal query.compile_where.as_json, compiled_query
45+
46+
compiled_query = {"location"=>{"$geoWithin"=>{"$polygon"=>[
47+
{:__type=>"GeoPoint", :latitude=>32.9201332, :longitude=>-117.1088263},
48+
{:__type=>"GeoPoint", :latitude=>25.7823198, :longitude=>-80.2660226},
49+
{:__type=>"GeoPoint", :latitude=>18.3848232, :longitude=>-66.0933608},
50+
{:__type=>"GeoPoint", :latitude=>32.3078, :longitude=>-64.7504999}
51+
]}}}
52+
query = User.query(:location.within_polygon => [@san_diego, @miami, @san_juan, @bermuda])
53+
assert_equal query.compile_where.as_json, compiled_query
54+
end
55+
56+
57+
end

test/test_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'byebug'
44
require_relative '../lib/parse/stack.rb'
55

6+
Parse.use_shortnames!
67

78
module ConstraintTests
89

0 commit comments

Comments
 (0)