geohex_v3 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/geohex.rb +487 -0
  3. metadata +44 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2ca328c7d1bdfd21f37d881bd8e42542d6a855f2
4
+ data.tar.gz: 92d2b24ec9389f72089753368153d682e5e5cfa2
5
+ SHA512:
6
+ metadata.gz: 30660c57b43a1ba22db773e95572ee45110fd8229ea3df321980e709e70f46c6b2ae8ba5b90f1d185d93d91e78826ce84cc25b4137296912e4c2170b4cd2c9f8
7
+ data.tar.gz: b04d15a17db24d83c79ade3c2e5a1b171d0d65fbddb70c8e93891d3f92d2cbb630750b6e0ab422bc4b7b4c29bef0ddb8734dc869ef7bc08113c3db872c7fb295
@@ -0,0 +1,487 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # ported from js libraries http://geohex.net
4
+ # author Haruyuki Seki
5
+ #
6
+ require 'rubygems'
7
+ require 'ostruct'
8
+
9
+ module GeoHex
10
+ VERSION = '3.0.1'
11
+
12
+ H_KEY = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
13
+ H_BASE = 20037508.34
14
+ H_DEG = Math::PI*(30.0/180)
15
+ H_K = Math.tan(H_DEG)
16
+ R = 20037508.34
17
+
18
+ class Zone
19
+ attr_accessor :code, :lat, :lon, :x, :y
20
+ def initialize(*params)
21
+ raise ArgumentError, "latlng or code is needed" if params.size == 0
22
+
23
+ @lat = params[0]
24
+ @lon = params[1]
25
+ @x = params[2]
26
+ @y = params[3]
27
+ @code= params[4]
28
+
29
+ end
30
+
31
+ def self.calcHexSize(level)
32
+ H_BASE/(3**(level+3))
33
+ end
34
+
35
+ def level
36
+ H_KEY.index(code[0,1])
37
+ end
38
+ def hexSize
39
+ self.calcHexSize(self.level)
40
+ end
41
+
42
+ def hexCoords
43
+ h_lat = self.lat
44
+ h_lon = self.lon
45
+ h_xy = GeoHex::Zone.loc2xy(h_lon, h_lat)
46
+ h_x = h_xy.x
47
+ h_y = h_xy.y
48
+ h_deg = Math.tan(Math::PI * (60 / 180))
49
+ h_size = self.hexSize
50
+ h_top = xy2loc(h_x, h_y + h_deg * h_size).lat
51
+ h_btm = xy2loc(h_x, h_y - h_deg * h_size).lat
52
+
53
+ h_l = xy2loc(h_x - 2 * h_size, h_y).lon
54
+ h_r = xy2loc(h_x + 2 * h_size, h_y).lon
55
+ h_cl = xy2loc(h_x - 1 * h_size, h_y).lon
56
+ h_cr = xy2loc(h_x + 1 * h_size, h_y).lon
57
+
58
+ [
59
+ {:lat => h_lat, :lon => h_l},
60
+ {:lat => h_top, :lon => h_cl},
61
+ {:lat => h_top, :lon => h_cr},
62
+ {:lat => h_lat, :lon => h_r},
63
+ {:lat => h_btm, :lon => h_cr},
64
+ {:lat => h_btm, :lon => h_cl}
65
+ ]
66
+ end
67
+
68
+ # latlon to geohex
69
+ def self.encode(lat,lon,level=7)
70
+ raise ArgumentError, "latitude must be double" unless (lat.is_a?(Numeric))
71
+ raise ArgumentError, "latitude must be between -85 and 85" if (lat < -85.1 || lat > 85.1)
72
+ raise ArgumentError, "latitude must be between -85 and 85" if (lat < -85.1 || lat > 85.1)
73
+ raise ArgumentError, "longitude must be between -180 and 180" if (lon < -180 || lon > 180)
74
+ raise ArgumentError, "level must be between 0 and 24" if (level < 0 || level > 24)
75
+
76
+ h_size = self.calcHexSize(level)
77
+ z_xy = loc2xy(lon,lat)
78
+ lon_grid = z_xy.x
79
+ lat_grid = z_xy.y
80
+ unit_x = 6.0* h_size
81
+ unit_y = 6.0* h_size*H_K
82
+ h_pos_x = (lon_grid + lat_grid / H_K) / unit_x
83
+ h_pos_y = (lat_grid - H_K*lon_grid) / unit_y
84
+
85
+ h_x_0 = h_pos_x.floor
86
+ h_y_0 = h_pos_y.floor
87
+ h_x_q = h_pos_x - h_x_0
88
+ h_y_q = h_pos_y - h_y_0
89
+ h_x = h_pos_x.round
90
+ h_y = h_pos_y.round
91
+
92
+ h_max=(H_BASE/unit_x + H_BASE/unit_y).round
93
+
94
+ if ( h_y_q > -h_x_q + 1 )
95
+ if ( h_y_q < (2 * h_x_q ) and h_y_q > (0.5 * h_x_q ) )
96
+ h_x = h_x_0 + 1
97
+ h_y = h_y_0 + 1
98
+ end
99
+ elsif ( h_y_q < -h_x_q + 1 )
100
+ if ( (h_y_q > (2 * h_x_q ) - 1 ) && ( h_y_q < ( 0.5 * h_x_q ) + 0.5 ) )
101
+ h_x = h_x_0
102
+ h_y = h_y_0
103
+ end
104
+ end
105
+
106
+ h_lat = (H_K * h_x * unit_x + h_y * unit_y) / 2
107
+ h_lon = (h_lat - h_y * unit_y)/H_K
108
+
109
+ z_loc = xy2loc(h_lon,h_lat)
110
+ z_loc_x = z_loc.lon
111
+ z_loc_y = z_loc.lat
112
+
113
+ if (H_BASE - h_lon <h_size)
114
+ z_loc_x = 180.0
115
+ h_xy = h_x
116
+ h_x = h_y
117
+ h_y = h_xy
118
+ end
119
+
120
+ h_x_p = (h_x<0) ? 1 : 0
121
+ h_y_p = (h_y<0) ? 1 : 0
122
+ h_x_abs = ((h_x).abs * 2 + h_x_p).to_f
123
+ h_y_abs = ((h_y).abs * 2 + h_y_p).to_f
124
+ h_x_10000 = ((h_x_abs%777600000)/12960000).floor
125
+ h_x_1000 = ((h_x_abs%12960000)/216000).floor
126
+ h_x_100 = ((h_x_abs%216000)/3600).floor
127
+ h_x_10 = ((h_x_abs%3600)/60).floor
128
+ h_x_1 = ((h_x_abs%3600)%60).floor
129
+ h_y_10000 = ((h_y_abs%77600000)/12960000).floor
130
+ h_y_1000 = ((h_y_abs%12960000)/216000).floor
131
+ h_y_100 = ((h_y_abs%216000)/3600).floor
132
+ h_y_10 = ((h_y_abs%3600)/60).floor
133
+ h_y_1 = ((h_y_abs%3600)%60).floor
134
+ h_code = H_KEY[level % 60, 1]
135
+ h_code += H_KEY[h_x_10000, 1]+H_KEY[h_y_10000, 1] if(h_max >=12960000/2)
136
+ h_code += H_KEY[h_x_1000, 1]+H_KEY[h_y_1000, 1] if(h_max >=216000/2)
137
+ h_code += H_KEY[h_x_100, 1]+H_KEY[h_y_100, 1] if(h_max >=3600/2)
138
+ h_code += H_KEY[h_x_10, 1]+H_KEY[h_y_10, 1] if(h_max >=60/2)
139
+ h_code += H_KEY[h_x_1, 1]+H_KEY[h_y_1, 1]
140
+
141
+ return h_code
142
+ end
143
+
144
+ # geohex to latlon
145
+ def self.decode(code)
146
+ c_length = code.length
147
+ level = H_KEY.index(code[0,1])
148
+ scl = level
149
+ h_size = self.calcHexSize(level)
150
+ unit_x = 6.0 * h_size
151
+ unit_y = 6.0 * h_size * H_K
152
+ h_max = (H_BASE / unit_x + H_BASE / unit_y).round
153
+ h_x = 0
154
+ h_y = 0
155
+
156
+ if (h_max >= 12960000 / 2)
157
+ h_x = H_KEY.index(code[1,1]) * 12960000 +
158
+ H_KEY.index(code[3,1]) * 216000 +
159
+ H_KEY.index(code[5,1]) * 3600 +
160
+ H_KEY.index(code[7,1]) * 60 +
161
+ H_KEY.index(code[9,1])
162
+ h_y = H_KEY.index(code[2,1]) * 12960000 +
163
+ H_KEY.index(code[4,1]) * 216000 +
164
+ H_KEY.index(code[6,1]) * 3600 +
165
+ H_KEY.index(code[8,1]) * 60 +
166
+ H_KEY.index(code[10,1])
167
+ elsif (h_max >= 216000 / 2)
168
+ h_x = H_KEY.index(code[1,1]) * 216000 +
169
+ H_KEY.index(code[3,1]) * 3600 +
170
+ H_KEY.index(code[5,1]) * 60 +
171
+ H_KEY.index(code[7,1])
172
+ h_y = H_KEY.index(code[2,1]) * 216000 +
173
+ H_KEY.index(code[4,1]) * 3600 +
174
+ H_KEY.index(code[6,1]) * 60 +
175
+ H_KEY.index(code[8,1])
176
+ elsif (h_max >= 3600 / 2)
177
+ h_x = H_KEY.index(code[1,1]) * 3600 +
178
+ H_KEY.index(code[3,1]) * 60 +
179
+ H_KEY.index(code[5,1])
180
+ h_y = H_KEY.index(code[2,1]) * 3600 +
181
+ H_KEY.index(code[4,1]) * 60 +
182
+ H_KEY.index(code[6,1])
183
+ elsif (h_max >= 60 / 2)
184
+ h_x = H_KEY.index(code[1,1]) * 60 +
185
+ H_KEY.index(code[3,1])
186
+ h_y = H_KEY.index(code[2,1]) * 60 +
187
+ H_KEY.index(code[4,1])
188
+ else
189
+ h_x = H_KEY.index(code[1,1])
190
+ h_y = H_KEY.index(code[2,1])
191
+ end
192
+ h_x = (h_x % 2 == 1) ? -(h_x - 1) / 2 : h_x / 2
193
+ h_y = (h_y % 2 == 1) ? -(h_y - 1) / 2 : h_y / 2
194
+
195
+ h_lat_y = (H_K * h_x * unit_x + h_y * unit_y) / 2
196
+ h_lon_x = (h_lat_y - h_y * unit_y) / H_K
197
+
198
+ h_loc = xy2loc(h_lon_x, h_lat_y)
199
+ return [h_loc.lat, h_loc.lon, level]
200
+ end
201
+
202
+ def self.getZoneByXy(_x,_y, _level)
203
+
204
+ h_size = self.calcHexSize(_level)
205
+
206
+ h_x = _x
207
+ h_y = _y
208
+
209
+ unit_x = 6 * h_size
210
+ unit_y = 6 * h_size * H_K
211
+
212
+ h_lat = (H_K * h_x * unit_x + h_y * unit_y) / 2
213
+ h_lon = (h_lat - h_y * unit_y) / H_K
214
+
215
+ z_loc = self.xy2loc(h_lon, h_lat)
216
+ z_loc_x = z_loc.lon
217
+ z_loc_y = z_loc.lat
218
+
219
+ max_hsteps = 3**(_level + 2)
220
+ hsteps = (h_x - h_y).abs
221
+
222
+ if hsteps == max_hsteps
223
+ if h_x > h_y
224
+ tmp = h_x
225
+ h_x = h_y
226
+ h_y = tmp
227
+ end
228
+ z_loc_x = -180
229
+ end
230
+
231
+ h_code = ""
232
+
233
+ code3_x = Array.new
234
+ code3_y = Array.new
235
+ code3 = ""
236
+ code9 = ""
237
+ mod_x = h_x
238
+ mod_y = h_y
239
+
240
+ (0..(_level + 2)).each do |i|
241
+ h_pow = 3**(_level + 2 - i)
242
+
243
+ if mod_x >= ((h_pow.to_f)/2).ceil
244
+ code3_x[i] =2
245
+ mod_x -= h_pow
246
+ elsif mod_x <= -((h_pow.to_f)/2).ceil
247
+ code3_x[i] =0
248
+ mod_x += h_pow
249
+ else
250
+ code3_x[i] =1
251
+ end
252
+
253
+
254
+
255
+ if mod_y >= ((h_pow.to_f)/2).ceil
256
+ code3_y[i] = 2
257
+ mod_y -= h_pow
258
+ elsif mod_y <= -((h_pow.to_f)/2).ceil
259
+ code3_y[i] = 0
260
+ mod_y += h_pow
261
+ else
262
+ code3_y[i] = 1
263
+ end
264
+
265
+
266
+
267
+ if i==2 && (z_loc_x==-180 || z_loc_x>=0)
268
+ if code3_x[0] == 2 && code3_y[0] == 1 && code3_x[1] == code3_y[1] && code3_x[2] == code3_y[2]
269
+ code3_x[0] = 1
270
+ code3_y[0] = 2
271
+ elsif code3_x[0] == 1 && code3_y[0] == 0 && code3_x[1] == code3_y[1] && code3_x[2] == code3_y[2]
272
+ code3_x[0] = 0
273
+ code3_y[0] = 1
274
+ end
275
+ end
276
+ end
277
+
278
+ code3_x.each_with_index do |x,i|
279
+ code3 += "#{code3_x[i]}#{code3_y[i]}"
280
+ code9 += code3.to_i(3).to_s
281
+ h_code += code9
282
+ code9 = ""
283
+ code3 = ""
284
+ end
285
+
286
+ h_2 = h_code[3..h_code.length]
287
+ h_1 = h_code[0..2]
288
+ h_a1 = (h_1.to_f/30).floor
289
+ h_a2 = h_1.to_i%30
290
+
291
+
292
+ h_code ="#{H_KEY[h_a1]}#{H_KEY[h_a2]}#{h_2}"
293
+
294
+
295
+ return GeoHex::Zone.new(z_loc_y, z_loc_x, _x, _y, h_code)
296
+
297
+ end
298
+
299
+ def self.getXYByLocation(lat, lon, _level)
300
+ h_size = calcHexSize(_level)
301
+ z_xy = loc2xy(lon, lat)
302
+ lon_grid = z_xy.x
303
+ lat_grid = z_xy.y
304
+ unit_x = 6 * h_size
305
+ unit_y = 6 * h_size * H_K
306
+ h_pos_x = (lon_grid + lat_grid / H_K) / unit_x
307
+ h_pos_y = (lat_grid - H_K * lon_grid) / unit_y
308
+ h_x_0 = (h_pos_x).floor
309
+ h_y_0 = (h_pos_y).floor
310
+ h_x_q = h_pos_x - h_x_0;
311
+ h_y_q = h_pos_y - h_y_0
312
+ h_x = (h_pos_x).round
313
+ h_y = (h_pos_y).round
314
+
315
+ if h_y_q > -h_x_q + 1
316
+ if h_y_q < 2 * h_x_q && h_y_q > 0.5 * h_x_q
317
+ h_x = h_x_0 + 1
318
+ h_y = h_y_0 + 1
319
+ end
320
+ elsif h_y_q < -h_x_q + 1
321
+ if h_y_q > (2 * h_x_q) - 1 && h_y_q < 0.5 * h_x_q + 0.5
322
+ h_x = h_x_0
323
+ h_y = h_y_0
324
+ end
325
+ end
326
+
327
+ inner_xy = adjustXY(h_x, h_y, _level)
328
+ h_x = inner_xy[:x]
329
+ h_y = inner_xy[:y]
330
+
331
+ {
332
+ x: h_x,
333
+ y: h_y
334
+ };
335
+ end
336
+
337
+ def self.getZoneByLocation(_lat, _lon, _level)
338
+ xy = self.getXYByLocation(_lat, _lon, _level)
339
+ zone = self.getZoneByXy(xy[:x], xy[:y], _level)
340
+
341
+ zone
342
+ end
343
+
344
+ def self.getXYByCode(code)
345
+ level = code.length - 2
346
+ h_size = calcHexSize(level)
347
+ unit_x = 6 * h_size
348
+ unit_y = 6 * h_size * H_K
349
+ h_x = 0
350
+ h_y = 0
351
+
352
+ h_dec9 = (( H_KEY.index(code[0]) * 30 + H_KEY.index(code[1])).to_s + code[2..-1].to_s)
353
+
354
+ if h_dec9[0].match(/[15]/) && h_dec9[1].match(/[^125]/) && h_dec9[2].match(/[^125]/)
355
+ if h_dec9[0] == 5
356
+ h_dec9 = "7" + h_dec9[1..(h_dec9.length-1)]
357
+ elsif h_dec9[0] == 1
358
+ h_dec9 = "3" + h_dec9[1..h_dec9.length-1]
359
+ end
360
+ end
361
+
362
+ d9xlen = h_dec9.length
363
+ state = (level + 3 - d9xlen)
364
+ if state != 0
365
+ (0..state-1).each do |i|
366
+ h_dec9 = "0" + h_dec9
367
+ d9xlen += 1
368
+ end
369
+ end
370
+
371
+ h_dec3 = String.new()
372
+
373
+ (0..(d9xlen-1)).each do |i|
374
+ h_dec0 = h_dec9[i].to_i.to_s(3)
375
+
376
+ if !h_dec0
377
+ h_dec3 += "00"
378
+ elsif h_dec0.length == 1
379
+ h_dec3 += "0"
380
+ end
381
+ h_dec3 += h_dec0;
382
+ end
383
+
384
+ h_decx =[]
385
+ h_decy =[]
386
+
387
+ (0..((h_dec3.length/2)-1)).each do |i|
388
+ x = i*2
389
+ y = i*2+1
390
+ h_decx[i] = h_dec3[x]
391
+ h_decy[i] = h_dec3[y]
392
+ end
393
+
394
+ (0..level+2).each do |i|
395
+ h_pow = 3**(level+2-i)
396
+
397
+ if h_decx[i].to_i == 0
398
+ h_x -= h_pow;
399
+ elsif h_decx[i].to_i == 2
400
+ h_x += h_pow;
401
+ end
402
+
403
+ if h_decy[i].to_i == 0
404
+ h_y -= h_pow
405
+ elsif h_decy[i].to_i == 2
406
+ h_y += h_pow
407
+ end
408
+
409
+ end
410
+
411
+
412
+
413
+ inner_xy = adjustXY(h_x,h_y,level)
414
+ h_x = inner_xy[:x]
415
+ h_y = inner_xy[:y]
416
+
417
+ {
418
+ x: h_x,
419
+ y: h_y
420
+ }
421
+ end
422
+
423
+ def self.getZoneByCode(code)
424
+ xy = getXYByCode(code)
425
+ level = code.length - 2
426
+ zone = getZoneByXy(xy[:x], xy[:y], level)
427
+
428
+ zone
429
+ end
430
+
431
+ end
432
+
433
+ class << Zone
434
+
435
+ def loc2xy(_lon,_lat)
436
+ x=_lon*H_BASE/180
437
+ y= Math.log(Math.tan((90+_lat)*Math::PI/360)) / (Math::PI / 180 )
438
+ y= y * H_BASE / 180
439
+ return OpenStruct.new("x" => x, "y" => y)
440
+ end
441
+
442
+ def xy2loc(_x,_y)
443
+ lon=(_x/H_BASE)*180
444
+ lat=(_y/H_BASE)*180
445
+ lat=180.0/Math::PI*(2.0*Math.atan(Math.exp(lat*Math::PI/180))-Math::PI/2)
446
+ return OpenStruct.new("lon" => lon,"lat" => lat)
447
+ end
448
+
449
+ def adjustXY(_x, _y, _level)
450
+ x = _x
451
+ y = _y
452
+ rev = 0
453
+ max_hsteps = 3**(_level+2)
454
+ hsteps = (x - y).abs
455
+ if hsteps == max_hsteps && x>y
456
+ tmp = x
457
+ x = y
458
+ y = tmp
459
+ rev = 1
460
+ elsif hsteps > max_hsteps
461
+ dif = hsteps - max_hsteps
462
+ dif_x = (dif/2).floor
463
+ dif_y = dif - dif_x
464
+ if x > y
465
+ edge_x = x - dif_x
466
+ edge_y = y + dif_y
467
+ h_xy = edge_x
468
+ edge_x = edge_y
469
+ edge_y = h_xy
470
+ x = edge_x + dif_x
471
+ y = edge_y - dif_y
472
+ elsif y > x
473
+ edge_x = x + dif_x
474
+ edge_y = y - dif_y
475
+ h_xy = edge_x
476
+ edge_x = edge_y
477
+ edge_y = h_xy
478
+ x = edge_x - dif_x
479
+ y = edge_y + dif_y
480
+ end
481
+ end
482
+
483
+ { x: x, y: y, rev: rev }
484
+ end
485
+
486
+ end
487
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: geohex_v3
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Dave Hines
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-07 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: convert Lat and Long into geohexes for database optimization
14
+ email: dave.hines@vervemobile.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/geohex.rb
20
+ homepage: http://rubygems.org/gems/geohex_v3
21
+ licenses:
22
+ - MIT
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.4.8
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: Geohex gem for geohexing!
44
+ test_files: []