@@ -9,80 +9,119 @@ var assert = require('assert');
9
9
10
10
/*!
11
11
* Get a near filter from a given where object. For connector use only.
12
+ * NB: This only supports one near parameter per where object. eg, this:
13
+ * {where: {or: [{location: {near: "29,-90"}},{location: {near: "50,-72"}}] }}
14
+ * would throw an error.
12
15
*/
13
16
14
17
exports . nearFilter = function nearFilter ( where ) {
15
- var result = false ;
16
-
17
- if ( where && typeof where === 'object' ) {
18
- Object . keys ( where ) . forEach ( function ( key ) {
19
- var ex = where [ key ] ;
20
-
21
- if ( ex && ex . near ) {
22
- result = {
23
- near : ex . near ,
24
- maxDistance : ex . maxDistance ,
25
- unit : ex . unit ,
26
- key : key ,
27
- } ;
18
+ function nearSearch ( clause , parentKeys ) {
19
+ if ( typeof clause !== 'object' ) {
20
+ return false ;
21
+ }
22
+ if ( ! parentKeys ) {
23
+ parentKeys = [ ] ;
24
+ }
25
+
26
+ Object . keys ( clause ) . forEach ( function ( clauseKey ) {
27
+ if ( Array . isArray ( clause [ clauseKey ] ) ) {
28
+ clause [ clauseKey ] . forEach ( function ( el ) {
29
+ var ret = nearSearch ( el , parentKeys . concat ( clauseKey ) ) ;
30
+ if ( ret ) return ret ;
31
+ } ) ;
32
+ } else {
33
+ if ( clause [ clauseKey ] . hasOwnProperty ( 'near' ) ) {
34
+ var result = clause [ clauseKey ] ;
35
+ nearResults . push ( {
36
+ near : result . near ,
37
+ maxDistance : result . maxDistance ,
38
+ unit : result . unit ,
39
+ key : parentKeys . concat ( clauseKey ) ,
40
+ } ) ;
41
+ }
28
42
}
29
43
} ) ;
30
44
}
45
+ var nearResults = [ ] ;
46
+ nearSearch ( where ) ;
31
47
32
- return result ;
48
+ if ( nearResults . length === 0 ) {
49
+ return false ;
50
+ }
51
+ return nearResults ;
33
52
} ;
34
53
35
54
/*!
36
- * Filter a set of objects using the given `nearFilter`.
55
+ * Filter a set of objects using the given `nearFilter`. Can support multiple
56
+ * locations, but will include results from all of them.
57
+ *
58
+ * WARNING: "or" operator with GeoPoint does not work as expected, eg:
59
+ * {where: {or: [{location: {near: (29,-90)}},{name:'Sean'}] }}
60
+ * Will actually work as if you had used "and". This is because geo filtering
61
+ * takes place outside of the SQL query, so the result set of "name = Sean" is
62
+ * returned by the database, and then the location filtering happens in the app
63
+ * logic. So the "near" operator is always an "and" of the SQL filters, and "or"
64
+ * of other GeoPoint filters.
65
+ *
66
+ * Additionally, since this step occurs after the SQL result set is returned,
67
+ * if using GeoPoints with pagination the result set may be smaller than the
68
+ * page size. The page size is enforced at the DB level, and then we may
69
+ * remove results at the Geo-app level. If we "limit: 25", but 4 of those results
70
+ * do not have a matching geopoint field, the request will only return 21 results.
71
+ * This may make it erroneously look like a given page is the end of the result set.
37
72
*/
38
73
39
74
exports . filter = function ( arr , filter ) {
40
- var origin = filter . near ;
41
- var max = filter . maxDistance > 0 ? filter . maxDistance : false ;
42
- var unit = filter . unit ;
43
- var key = filter . key ;
44
-
45
- // create distance index
46
75
var distances = { } ;
47
76
var result = [ ] ;
48
77
49
- arr . forEach ( function ( obj ) {
50
- var loc = obj [ key ] ;
78
+ filters . forEach ( function ( filter ) {
79
+ var origin = filter . near ;
80
+ var max = filter . maxDistance > 0 ? filter . maxDistance : false ;
81
+ var unit = filter . unit ;
82
+ var key = filter . key ;
51
83
52
- // filter out objects without locations
53
- if ( ! loc ) return ;
84
+ // create distance index
85
+ arr . forEach ( function ( obj ) {
86
+ var loc = obj [ key ] ;
54
87
55
- if ( ! ( loc instanceof GeoPoint ) ) {
56
- loc = GeoPoint ( loc ) ;
57
- }
88
+ // filter out objects without locations
89
+ if ( ! loc ) return ;
90
+
91
+ if ( ! ( loc instanceof GeoPoint ) ) {
92
+ loc = GeoPoint ( loc ) ;
93
+ }
58
94
59
- if ( typeof loc . lat !== 'number' ) return ;
60
- if ( typeof loc . lng !== 'number' ) return ;
95
+ if ( typeof loc . lat !== 'number' ) return ;
96
+ if ( typeof loc . lng !== 'number' ) return ;
61
97
62
- var d = GeoPoint . distanceBetween ( origin , loc , { type : unit } ) ;
98
+ var d = GeoPoint . distanceBetween ( origin , loc , { type : unit } ) ;
63
99
64
- if ( max && d > max ) {
65
- // dont add
66
- } else {
67
- distances [ obj . id ] = d ;
68
- result . push ( obj ) ;
69
- }
70
- } ) ;
100
+ if ( max && d > max ) {
101
+ // dont add
102
+ } else {
103
+ distances [ obj . id ] = d ;
104
+ result . push ( obj ) ;
105
+ }
106
+ } ) ;
71
107
72
- return result . sort ( function ( objA , objB ) {
73
- var a = objA [ key ] ;
74
- var b = objB [ key ] ;
108
+ result . sort ( function ( objA , objB ) {
109
+ var a = objA [ key ] ;
110
+ var b = objB [ key ] ;
75
111
76
- if ( a && b ) {
77
- var da = distances [ objA . id ] ;
78
- var db = distances [ objB . id ] ;
112
+ if ( a && b ) {
113
+ var da = distances [ objA . id ] ;
114
+ var db = distances [ objB . id ] ;
79
115
80
- if ( db === da ) return 0 ;
81
- return da > db ? 1 : - 1 ;
82
- } else {
83
- return 0 ;
84
- }
116
+ if ( db === da ) return 0 ;
117
+ return da > db ? 1 : - 1 ;
118
+ } else {
119
+ return 0 ;
120
+ }
121
+ } ) ;
85
122
} ) ;
123
+
124
+ return result ;
86
125
} ;
87
126
88
127
exports . GeoPoint = GeoPoint ;
0 commit comments