1
1
import * as chai from 'chai' ;
2
2
3
+ import {
4
+ boundingBoxBits , degreesToRadians , distanceBetween , geohashForLocation , geohashQuery ,
5
+ geohashQueryBounds , GEOHASH_PRECISION , metersToLongitudeDegrees , validateGeohash , validateKey ,
6
+ validateLocation , wrapLongitude , Geopoint
7
+ } from '../src/index' ;
8
+ import {
9
+ invalidGeohashes , invalidKeys , invalidLocations , invalidQueryCriterias ,
10
+ validGeohashes , validKeys , validLocations , validQueryCriterias
11
+ } from './common' ;
12
+
3
13
const expect = chai . expect ;
4
14
5
- // TODO: move relevant tests from the `geofire` package to `geofire-common`.
6
- describe ( 'Dummy test to ensure the run succeeds:' , ( ) => {
7
- it ( "hello world" , ( ) => {
8
- expect ( true ) . to . be . true ;
15
+ describe ( 'geoFireUtils Tests:' , ( ) => {
16
+ describe ( 'Parameter validation:' , ( ) => {
17
+ it ( 'validateKey() does not throw errors given valid keys' , ( ) => {
18
+ validKeys . forEach ( ( validKey ) => {
19
+ expect ( ( ) => validateKey ( validKey ) ) . not . to . throw ( ) ;
20
+ } ) ;
21
+ } ) ;
22
+
23
+ it ( 'validateKey() throws errors given invalid keys' , ( ) => {
24
+ invalidKeys . forEach ( ( invalidKey ) => {
25
+ // @ts -ignore
26
+ expect ( ( ) => validateKey ( invalidKey ) ) . to . throw ( ) ;
27
+ } ) ;
28
+ } ) ;
29
+
30
+ it ( 'validateLocation() does not throw errors given valid locations' , ( ) => {
31
+ validLocations . forEach ( ( validLocation , i ) => {
32
+ expect ( ( ) => validateLocation ( validLocation as Geopoint ) ) . not . to . throw ( ) ;
33
+ } ) ;
34
+ } ) ;
35
+
36
+ it ( 'validateLocation() throws errors given invalid locations' , ( ) => {
37
+ invalidLocations . forEach ( ( invalidLocation , i ) => {
38
+ // @ts -ignore
39
+ expect ( ( ) => validateLocation ( invalidLocation ) ) . to . throw ( ) ;
40
+ } ) ;
41
+ } ) ;
42
+
43
+ it ( 'validateGeohash() does not throw errors given valid geohashes' , ( ) => {
44
+ validGeohashes . forEach ( ( validGeohash , i ) => {
45
+ expect ( ( ) => validateGeohash ( validGeohash ) ) . not . to . throw ( ) ;
46
+ } ) ;
47
+ } ) ;
48
+
49
+ it ( 'validateGeohash() throws errors given invalid geohashes' , ( ) => {
50
+ invalidGeohashes . forEach ( ( invalidGeohash , i ) => {
51
+ // @ts -ignore
52
+ expect ( ( ) => validateGeohash ( invalidGeohash ) ) . to . throw ( ) ;
53
+ } ) ;
54
+ } ) ;
55
+ } ) ;
56
+
57
+ describe ( 'Distance calculations:' , ( ) => {
58
+ it ( 'degreesToRadians() converts degrees to radians' , ( ) => {
59
+ expect ( degreesToRadians ( 0 ) ) . to . be . closeTo ( 0 , 0 ) ;
60
+ expect ( degreesToRadians ( 45 ) ) . to . be . closeTo ( 0.7854 , 4 ) ;
61
+ expect ( degreesToRadians ( 90 ) ) . to . be . closeTo ( 1.5708 , 4 ) ;
62
+ expect ( degreesToRadians ( 135 ) ) . to . be . closeTo ( 2.3562 , 4 ) ;
63
+ expect ( degreesToRadians ( 180 ) ) . to . be . closeTo ( 3.1416 , 4 ) ;
64
+ expect ( degreesToRadians ( 225 ) ) . to . be . closeTo ( 3.9270 , 4 ) ;
65
+ expect ( degreesToRadians ( 270 ) ) . to . be . closeTo ( 4.7124 , 4 ) ;
66
+ expect ( degreesToRadians ( 315 ) ) . to . be . closeTo ( 5.4978 , 4 ) ;
67
+ expect ( degreesToRadians ( 360 ) ) . to . be . closeTo ( 6.2832 , 4 ) ;
68
+ expect ( degreesToRadians ( - 45 ) ) . to . be . closeTo ( - 0.7854 , 4 ) ;
69
+ expect ( degreesToRadians ( - 90 ) ) . to . be . closeTo ( - 1.5708 , 4 ) ;
70
+ } ) ;
71
+
72
+ it ( 'degreesToRadians() throws errors given invalid inputs' , ( ) => {
73
+ // @ts -ignore
74
+ expect ( ( ) => degreesToRadians ( '' ) ) . to . throw ( ) ;
75
+ // @ts -ignore
76
+ expect ( ( ) => degreesToRadians ( 'a' ) ) . to . throw ( ) ;
77
+ // @ts -ignore
78
+ expect ( ( ) => degreesToRadians ( true ) ) . to . throw ( ) ;
79
+ // @ts -ignore
80
+ expect ( ( ) => degreesToRadians ( false ) ) . to . throw ( ) ;
81
+ // @ts -ignore
82
+ expect ( ( ) => degreesToRadians ( [ 1 ] ) ) . to . throw ( ) ;
83
+ // @ts -ignore
84
+ expect ( ( ) => degreesToRadians ( { } ) ) . to . throw ( ) ;
85
+ expect ( ( ) => degreesToRadians ( null ) ) . to . throw ( ) ;
86
+ expect ( ( ) => degreesToRadians ( undefined ) ) . to . throw ( ) ;
87
+ } ) ;
88
+
89
+ it ( 'dist() calculates the distance between locations' , ( ) => {
90
+ expect ( distanceBetween ( [ 90 , 180 ] , [ 90 , 180 ] ) ) . to . be . closeTo ( 0 , 0 ) ;
91
+ expect ( distanceBetween ( [ - 90 , - 180 ] , [ 90 , 180 ] ) ) . to . be . closeTo ( 20015 , 1 ) ;
92
+ expect ( distanceBetween ( [ - 90 , - 180 ] , [ - 90 , 180 ] ) ) . to . be . closeTo ( 0 , 1 ) ;
93
+ expect ( distanceBetween ( [ - 90 , - 180 ] , [ 90 , - 180 ] ) ) . to . be . closeTo ( 20015 , 1 ) ;
94
+ expect ( distanceBetween ( [ 37.7853074 , - 122.4054274 ] , [ 78.216667 , 15.55 ] ) ) . to . be . closeTo ( 6818 , 1 ) ;
95
+ expect ( distanceBetween ( [ 38.98719 , - 77.250783 ] , [ 29.3760648 , 47.9818853 ] ) ) . to . be . closeTo ( 10531 , 1 ) ;
96
+ expect ( distanceBetween ( [ 38.98719 , - 77.250783 ] , [ - 54.933333 , - 67.616667 ] ) ) . to . be . closeTo ( 10484 , 1 ) ;
97
+ expect ( distanceBetween ( [ 29.3760648 , 47.9818853 ] , [ - 54.933333 , - 67.616667 ] ) ) . to . be . closeTo ( 14250 , 1 ) ;
98
+ expect ( distanceBetween ( [ - 54.933333 , - 67.616667 ] , [ - 54 , - 67 ] ) ) . to . be . closeTo ( 111 , 1 ) ;
99
+ } ) ;
100
+
101
+ it ( 'dist() does not throw errors given valid locations' , ( ) => {
102
+ validLocations . forEach ( ( validLocation , i ) => {
103
+ expect ( ( ) => distanceBetween ( validLocation as Geopoint , [ 0 , 0 ] ) ) . not . to . throw ( ) ;
104
+ expect ( ( ) => distanceBetween ( [ 0 , 0 ] , validLocation as Geopoint ) ) . not . to . throw ( ) ;
105
+ } ) ;
106
+ } ) ;
107
+
108
+ it ( 'dist() throws errors given invalid locations' , ( ) => {
109
+ invalidLocations . forEach ( ( invalidLocation , i ) => {
110
+ // @ts -ignore
111
+ expect ( ( ) => distanceBetween ( invalidLocation , [ 0 , 0 ] ) ) . to . throw ( ) ;
112
+ // @ts -ignore
113
+ expect ( ( ) => distanceBetween ( [ 0 , 0 ] , invalidLocation ) ) . to . throw ( ) ;
114
+ } ) ;
115
+ } ) ;
116
+ } ) ;
117
+
118
+ describe ( 'Geohashing:' , ( ) => {
119
+ it ( 'geohashForLocation() encodes locations to geohashes given no precision' , ( ) => {
120
+ expect ( geohashForLocation ( [ - 90 , - 180 ] ) ) . to . be . equal ( '000000000000' . slice ( 0 , GEOHASH_PRECISION ) ) ;
121
+ expect ( geohashForLocation ( [ 90 , 180 ] ) ) . to . be . equal ( 'zzzzzzzzzzzz' . slice ( 0 , GEOHASH_PRECISION ) ) ;
122
+ expect ( geohashForLocation ( [ - 90 , 180 ] ) ) . to . be . equal ( 'pbpbpbpbpbpb' . slice ( 0 , GEOHASH_PRECISION ) ) ;
123
+ expect ( geohashForLocation ( [ 90 , - 180 ] ) ) . to . be . equal ( 'bpbpbpbpbpbp' . slice ( 0 , GEOHASH_PRECISION ) ) ;
124
+ expect ( geohashForLocation ( [ 37.7853074 , - 122.4054274 ] ) ) . to . be . equal ( '9q8yywe56gcf' . slice ( 0 , GEOHASH_PRECISION ) ) ;
125
+ expect ( geohashForLocation ( [ 38.98719 , - 77.250783 ] ) ) . to . be . equal ( 'dqcjf17sy6cp' . slice ( 0 , GEOHASH_PRECISION ) ) ;
126
+ expect ( geohashForLocation ( [ 29.3760648 , 47.9818853 ] ) ) . to . be . equal ( 'tj4p5gerfzqu' . slice ( 0 , GEOHASH_PRECISION ) ) ;
127
+ expect ( geohashForLocation ( [ 78.216667 , 15.55 ] ) ) . to . be . equal ( 'umghcygjj782' . slice ( 0 , GEOHASH_PRECISION ) ) ;
128
+ expect ( geohashForLocation ( [ - 54.933333 , - 67.616667 ] ) ) . to . be . equal ( '4qpzmren1kwb' . slice ( 0 , GEOHASH_PRECISION ) ) ;
129
+ expect ( geohashForLocation ( [ - 54 , - 67 ] ) ) . to . be . equal ( '4w2kg3s54y7h' . slice ( 0 , GEOHASH_PRECISION ) ) ;
130
+ } ) ;
131
+
132
+ it ( 'geohashForLocation() encodes locations to geohashes given a custom precision' , ( ) => {
133
+ expect ( geohashForLocation ( [ - 90 , - 180 ] , 6 ) ) . to . be . equal ( '000000' ) ;
134
+ expect ( geohashForLocation ( [ 90 , 180 ] , 20 ) ) . to . be . equal ( 'zzzzzzzzzzzzzzzzzzzz' ) ;
135
+ expect ( geohashForLocation ( [ - 90 , 180 ] , 1 ) ) . to . be . equal ( 'p' ) ;
136
+ expect ( geohashForLocation ( [ 90 , - 180 ] , 5 ) ) . to . be . equal ( 'bpbpb' ) ;
137
+ expect ( geohashForLocation ( [ 37.7853074 , - 122.4054274 ] , 8 ) ) . to . be . equal ( '9q8yywe5' ) ;
138
+ expect ( geohashForLocation ( [ 38.98719 , - 77.250783 ] , 18 ) ) . to . be . equal ( 'dqcjf17sy6cppp8vfn' ) ;
139
+ expect ( geohashForLocation ( [ 29.3760648 , 47.9818853 ] , 12 ) ) . to . be . equal ( 'tj4p5gerfzqu' ) ;
140
+ expect ( geohashForLocation ( [ 78.216667 , 15.55 ] , 1 ) ) . to . be . equal ( 'u' ) ;
141
+ expect ( geohashForLocation ( [ - 54.933333 , - 67.616667 ] , 7 ) ) . to . be . equal ( '4qpzmre' ) ;
142
+ expect ( geohashForLocation ( [ - 54 , - 67 ] , 9 ) ) . to . be . equal ( '4w2kg3s54' ) ;
143
+ } ) ;
144
+
145
+ it ( 'geohashForLocation() does not throw errors given valid locations' , ( ) => {
146
+ validLocations . forEach ( ( validLocation , i ) => {
147
+ expect ( ( ) => geohashForLocation ( validLocation as Geopoint ) ) . not . to . throw ( ) ;
148
+ } ) ;
149
+ } ) ;
150
+
151
+ it ( 'geohashForLocation() throws errors given invalid locations' , ( ) => {
152
+ invalidLocations . forEach ( ( invalidLocation , i ) => {
153
+ // @ts -ignore
154
+ expect ( ( ) => geohashForLocation ( invalidLocation ) ) . to . throw ( ) ;
155
+ } ) ;
156
+ } ) ;
157
+
158
+ it ( 'geohashForLocation() does not throw errors given valid precision' , ( ) => {
159
+ const validPrecisions = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , undefined ] ;
160
+
161
+ validPrecisions . forEach ( ( validPrecision , i ) => {
162
+ expect ( ( ) => geohashForLocation ( [ 0 , 0 ] , validPrecision ) ) . not . to . throw ( ) ;
163
+ } ) ;
164
+ } ) ;
165
+
166
+ it ( 'geohashForLocation() throws errors given invalid precision' , ( ) => {
167
+ const invalidPrecisions = [ 0 , - 1 , 1.5 , 23 , '' , 'a' , true , false , [ ] , { } , [ 1 ] , { a : 1 } , null ] ;
168
+
169
+ invalidPrecisions . forEach ( ( invalidPrecision , i ) => {
170
+ // @ts -ignore
171
+ expect ( ( ) => geohashForLocation ( [ 0 , 0 ] , invalidPrecision ) ) . to . throw ( ) ;
172
+ } ) ;
173
+ } ) ;
174
+ } ) ;
175
+
176
+ describe ( 'Coordinate calculations:' , ( ) => {
177
+ it ( 'metersToLongtitudeDegrees calculates correctly' , ( ) => {
178
+ expect ( metersToLongitudeDegrees ( 1000 , 0 ) ) . to . be . closeTo ( 0.008983 , 5 ) ;
179
+ expect ( metersToLongitudeDegrees ( 111320 , 0 ) ) . to . be . closeTo ( 1 , 5 ) ;
180
+ expect ( metersToLongitudeDegrees ( 107550 , 15 ) ) . to . be . closeTo ( 1 , 5 ) ;
181
+ expect ( metersToLongitudeDegrees ( 96486 , 30 ) ) . to . be . closeTo ( 1 , 5 ) ;
182
+ expect ( metersToLongitudeDegrees ( 78847 , 45 ) ) . to . be . closeTo ( 1 , 5 ) ;
183
+ expect ( metersToLongitudeDegrees ( 55800 , 60 ) ) . to . be . closeTo ( 1 , 5 ) ;
184
+ expect ( metersToLongitudeDegrees ( 28902 , 75 ) ) . to . be . closeTo ( 1 , 5 ) ;
185
+ expect ( metersToLongitudeDegrees ( 0 , 90 ) ) . to . be . closeTo ( 0 , 5 ) ;
186
+ expect ( metersToLongitudeDegrees ( 1000 , 90 ) ) . to . be . closeTo ( 360 , 5 ) ;
187
+ expect ( metersToLongitudeDegrees ( 1000 , 89.9999 ) ) . to . be . closeTo ( 360 , 5 ) ;
188
+ expect ( metersToLongitudeDegrees ( 1000 , 89.995 ) ) . to . be . closeTo ( 102.594208 , 5 ) ;
189
+ } ) ;
190
+
191
+ it ( 'wrapLongitude wraps correctly' , ( ) => {
192
+ expect ( wrapLongitude ( 0 ) ) . to . be . closeTo ( 0 , 6 ) ;
193
+ expect ( wrapLongitude ( 180 ) ) . to . be . closeTo ( 180 , 6 ) ;
194
+ expect ( wrapLongitude ( - 180 ) ) . to . be . closeTo ( - 180 , 6 ) ;
195
+ expect ( wrapLongitude ( 182 ) ) . to . be . closeTo ( - 178 , 6 ) ;
196
+ expect ( wrapLongitude ( 270 ) ) . to . be . closeTo ( - 90 , 6 ) ;
197
+ expect ( wrapLongitude ( 360 ) ) . to . be . closeTo ( 0 , 6 ) ;
198
+ expect ( wrapLongitude ( 540 ) ) . to . be . closeTo ( - 180 , 6 ) ;
199
+ expect ( wrapLongitude ( 630 ) ) . to . be . closeTo ( - 90 , 6 ) ;
200
+ expect ( wrapLongitude ( 720 ) ) . to . be . closeTo ( 0 , 6 ) ;
201
+ expect ( wrapLongitude ( 810 ) ) . to . be . closeTo ( 90 , 6 ) ;
202
+ expect ( wrapLongitude ( - 360 ) ) . to . be . closeTo ( 0 , 6 ) ;
203
+ expect ( wrapLongitude ( - 182 ) ) . to . be . closeTo ( 178 , 6 ) ;
204
+ expect ( wrapLongitude ( - 270 ) ) . to . be . closeTo ( 90 , 6 ) ;
205
+ expect ( wrapLongitude ( - 360 ) ) . to . be . closeTo ( 0 , 6 ) ;
206
+ expect ( wrapLongitude ( - 450 ) ) . to . be . closeTo ( - 90 , 6 ) ;
207
+ expect ( wrapLongitude ( - 540 ) ) . to . be . closeTo ( 180 , 6 ) ;
208
+ expect ( wrapLongitude ( - 630 ) ) . to . be . closeTo ( 90 , 6 ) ;
209
+ expect ( wrapLongitude ( 1080 ) ) . to . be . closeTo ( 0 , 6 ) ;
210
+ expect ( wrapLongitude ( - 1080 ) ) . to . be . closeTo ( 0 , 6 ) ;
211
+ } ) ;
212
+ } ) ;
213
+
214
+ describe ( 'Bounding box bits:' , ( ) => {
215
+ it ( 'boundingBoxBits must return correct number of bits' , ( ) => {
216
+ expect ( boundingBoxBits ( [ 35 , 0 ] , 1000 ) ) . to . be . equal ( 28 ) ;
217
+ expect ( boundingBoxBits ( [ 35.645 , 0 ] , 1000 ) ) . to . be . equal ( 27 ) ;
218
+ expect ( boundingBoxBits ( [ 36 , 0 ] , 1000 ) ) . to . be . equal ( 27 ) ;
219
+ expect ( boundingBoxBits ( [ 0 , 0 ] , 1000 ) ) . to . be . equal ( 28 ) ;
220
+ expect ( boundingBoxBits ( [ 0 , - 180 ] , 1000 ) ) . to . be . equal ( 28 ) ;
221
+ expect ( boundingBoxBits ( [ 0 , 180 ] , 1000 ) ) . to . be . equal ( 28 ) ;
222
+ expect ( boundingBoxBits ( [ 0 , 0 ] , 8000 ) ) . to . be . equal ( 22 ) ;
223
+ expect ( boundingBoxBits ( [ 45 , 0 ] , 1000 ) ) . to . be . equal ( 27 ) ;
224
+ expect ( boundingBoxBits ( [ 75 , 0 ] , 1000 ) ) . to . be . equal ( 25 ) ;
225
+ expect ( boundingBoxBits ( [ 75 , 0 ] , 2000 ) ) . to . be . equal ( 23 ) ;
226
+ expect ( boundingBoxBits ( [ 90 , 0 ] , 1000 ) ) . to . be . equal ( 1 ) ;
227
+ expect ( boundingBoxBits ( [ 90 , 0 ] , 2000 ) ) . to . be . equal ( 1 ) ;
228
+ } ) ;
9
229
} ) ;
10
230
} ) ;
231
+
232
+ describe ( 'Geohash queries:' , ( ) => {
233
+ it ( 'Geohash queries must be of the right size' , ( ) => {
234
+ expect ( geohashQuery ( '64m9yn96mx' , 6 ) ) . to . be . deep . equal ( [ '60' , '6h' ] ) ;
235
+ expect ( geohashQuery ( '64m9yn96mx' , 1 ) ) . to . be . deep . equal ( [ '0' , 'h' ] ) ;
236
+ expect ( geohashQuery ( '64m9yn96mx' , 10 ) ) . to . be . deep . equal ( [ '64' , '65' ] ) ;
237
+ expect ( geohashQuery ( '6409yn96mx' , 11 ) ) . to . be . deep . equal ( [ '640' , '64h' ] ) ;
238
+ expect ( geohashQuery ( '64m9yn96mx' , 11 ) ) . to . be . deep . equal ( [ '64h' , '64~' ] ) ;
239
+ expect ( geohashQuery ( '6' , 10 ) ) . to . be . deep . equal ( [ '6' , '6~' ] ) ;
240
+ expect ( geohashQuery ( '64z178' , 12 ) ) . to . be . deep . equal ( [ '64s' , '64~' ] ) ;
241
+ expect ( geohashQuery ( '64z178' , 15 ) ) . to . be . deep . equal ( [ '64z' , '64~' ] ) ;
242
+ } ) ;
243
+
244
+ it ( 'Query bounds from geohashQueryBounds must contain points in circle' , ( ) => {
245
+ function inQuery ( queries , hash ) {
246
+ for ( let i = 0 ; i < queries . length ; i ++ ) {
247
+ if ( hash >= queries [ i ] [ 0 ] && hash < queries [ i ] [ 1 ] ) {
248
+ return true ;
249
+ }
250
+ }
251
+ return false ;
252
+ }
253
+ for ( let i = 0 ; i < 200 ; i ++ ) {
254
+ const centerLat = Math . pow ( Math . random ( ) , 5 ) * 160 - 80 ;
255
+ const centerLong = Math . pow ( Math . random ( ) , 5 ) * 360 - 180 ;
256
+ const radius = Math . random ( ) * Math . random ( ) * 100000 ;
257
+ const degreeRadius = metersToLongitudeDegrees ( radius , centerLat ) ;
258
+ const queries = geohashQueryBounds ( [ centerLat , centerLong ] , radius ) ;
259
+ for ( let j = 0 ; j < 1000 ; j ++ ) {
260
+ const pointLat = Math . max ( - 89.9 , Math . min ( 89.9 , centerLat + Math . random ( ) * degreeRadius ) ) ;
261
+ const pointLong = wrapLongitude ( centerLong + Math . random ( ) * degreeRadius ) ;
262
+ if ( distanceBetween ( [ centerLat , centerLong ] , [ pointLat , pointLong ] ) < radius / 1000 ) {
263
+ expect ( inQuery ( queries , geohashForLocation ( [ pointLat , pointLong ] ) ) ) . to . be . equal ( true ) ;
264
+ }
265
+ }
266
+ }
267
+ } ) ;
268
+ } ) ;
0 commit comments