timezone_finder 1.5.5 → 1.5.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog +8 -0
- data/README.md +34 -14
- data/lib/timezone_finder/file_converter.rb +43 -41
- data/lib/timezone_finder/gem_version.rb +2 -2
- data/lib/timezone_finder/helpers.rb +78 -65
- data/lib/timezone_finder/timezone_data.bin +0 -0
- data/lib/timezone_finder/timezone_finder.rb +93 -44
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e74c3bfc9784467f66b1c270bc2473168e8b8838
|
4
|
+
data.tar.gz: c5bce138f2d33d467b46a3cdf8bfd214e3d01971
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 205cb31ffe251b8c2104fbe97f4535462d94a1a71625f7e090ecdb05937fd9c79861e8c912858d344ff6cf6b8aa8e00ab720c5d48f62ac2fa82010b0b3e1d4b9
|
7
|
+
data.tar.gz: c183ace8223052a82150e8627e91779647be2ca83ee83b33ca456c0033c56eecb2e40e315d70734794232cca6f6db355db4e015b4fdf7ebbcd4e283e5c39dad3
|
data/ChangeLog
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
== 2016-06-19 version 1.5.6
|
2
|
+
|
3
|
+
* using little endian encoding now
|
4
|
+
* introduced test for checking the proper functionality of the helper functions
|
5
|
+
* wrote tests for proximity algorithms
|
6
|
+
* improved proximity algorithms: introduced exact_computation, return_distances and force_evaluation functionality (s. Readme or documentation for more info)
|
7
|
+
|
8
|
+
|
1
9
|
== 2016-06-03 version 1.5.5
|
2
10
|
|
3
11
|
* using the newest version (2016d, May 2016) of the tz_world data from http://efele.net/maps/tz/world/
|
data/README.md
CHANGED
@@ -34,10 +34,13 @@ require 'timezone_finder'
|
|
34
34
|
tf = TimezoneFinder.create
|
35
35
|
```
|
36
36
|
|
37
|
-
####
|
37
|
+
#### timezone\_at():
|
38
38
|
|
39
|
-
This
|
40
|
-
|
39
|
+
This is the default function to check which timezone a point lies in.
|
40
|
+
If no timezone has been found, `nil` is being returned.
|
41
|
+
**NOTE:** This approach is optimized for speed and the common case to only query points actually within a timezone.
|
42
|
+
This might not be what you are looking for however: When there is only one possible timezone in proximity, this timezone would be returned
|
43
|
+
(without checking if the point is included first).
|
41
44
|
|
42
45
|
```ruby
|
43
46
|
# point = (longitude, latitude)
|
@@ -46,36 +49,53 @@ puts tf.timezone_at(*point)
|
|
46
49
|
# = Europe/Berlin
|
47
50
|
```
|
48
51
|
|
49
|
-
####
|
52
|
+
#### certain\_timezone\_at()
|
53
|
+
|
54
|
+
This function is for making sure a point is really inside a timezone. It is slower, because all polygons (with shortcuts in that area)
|
55
|
+
are checked until one polygon is matched.
|
50
56
|
|
51
57
|
```ruby
|
52
58
|
puts tf.certain_timezone_at(*point)
|
53
59
|
# = Europe/Berlin
|
54
60
|
```
|
55
61
|
|
56
|
-
####
|
62
|
+
#### Proximity algorithm
|
63
|
+
|
64
|
+
Only use this when the point is not inside a polygon, because the approach otherwise makes no sense.
|
65
|
+
This returns the closest timezone of all polygons within +-1 degree lng and +-1 degree lat (or None).
|
57
66
|
|
58
67
|
```ruby
|
59
|
-
# only use this when the point is not inside a polygon!
|
60
|
-
# this checks all the polygons within +-1 degree lng and +-1 degree lat
|
61
68
|
point = (12.773955, 55.578595)
|
62
69
|
puts tf.closest_timezone_at(*point)
|
63
70
|
# = Europe/Copenhagens
|
64
71
|
```
|
65
72
|
|
66
|
-
####
|
73
|
+
#### Other options:
|
74
|
+
|
75
|
+
To increase search radius even more, use the `delta_degree`-option:
|
67
76
|
|
68
77
|
```ruby
|
69
|
-
# this checks all the polygons within +-3 degree lng and +-3 degree lat
|
70
|
-
# I recommend only slowly increasing the search radius
|
71
|
-
# keep in mind that x degrees lat are not the same distance apart than x degree lng!
|
72
78
|
puts tf.closest_timezone_at(*point, 3)
|
73
79
|
# = Europe/Copenhagens
|
74
80
|
```
|
75
81
|
|
76
|
-
|
77
|
-
|
78
|
-
|
82
|
+
This checks all the polygons within +-3 degree lng and +-3 degree lat.
|
83
|
+
I recommend only slowly increasing the search radius, since computation time increases quite quickly
|
84
|
+
(with the amount of polygons which need to be evaluated) and there might be many polygons within a couple degrees.
|
85
|
+
|
86
|
+
Also keep in mind that x degrees lat are not the same distance apart than x degree lng (earth is a sphere)!
|
87
|
+
So to really make sure you got the closest timezone increase the search radius until you get a result,
|
88
|
+
then increase the radius once more and take this result. (this should only make a difference in really rare cases)
|
89
|
+
|
90
|
+
With `exact_computation=true` the distance to every polygon edge is computed (way more complicated)
|
91
|
+
, instead of just evaluating the distances to all the vertices. This only makes a real difference when polygons are very close.
|
92
|
+
|
93
|
+
With `return_distances=true` the output looks like this:
|
94
|
+
|
95
|
+
[ 'tz_name_of_the_closest_polygon',[ distances to every polygon in km], [tz_names of every polygon]]
|
96
|
+
|
97
|
+
Note that some polygons might not be tested (for example when a zone is found to be the closest already).
|
98
|
+
To prevent this use `force_evaluation=true`.
|
79
99
|
|
80
100
|
## Developer
|
81
101
|
|
@@ -102,6 +102,8 @@ module TimezoneFinder
|
|
102
102
|
def parse_polygons_from_json(path = 'tz_world.json')
|
103
103
|
f = open(path, 'r')
|
104
104
|
puts 'Parsing data from .json'
|
105
|
+
puts 'encountered holes at: '
|
106
|
+
|
105
107
|
# file_line is the current line in the .json file being parsed. This is not the id of the Polygon!
|
106
108
|
file_line = 0
|
107
109
|
f.each_line do |row|
|
@@ -671,7 +673,7 @@ EOT
|
|
671
673
|
|
672
674
|
puts("number of filled shortcut zones are: #{amount_filled_shortcuts} (=#{(amount_filled_shortcuts.fdiv(amount_of_shortcuts) * 100).round(2)}% of all shortcuts)")
|
673
675
|
|
674
|
-
# for every shortcut S
|
676
|
+
# for every shortcut S< and L< is written (nr of entries and address)
|
675
677
|
shortcut_space = 360 * NR_SHORTCUTS_PER_LNG * 180 * NR_SHORTCUTS_PER_LAT * 6
|
676
678
|
nr_of_entries_in_shortcut.each do |nr|
|
677
679
|
# every line in every shortcut takes up 2bytes
|
@@ -685,28 +687,28 @@ EOT
|
|
685
687
|
puts("now writing file \"#{path}\"")
|
686
688
|
output_file = open(path, 'wb')
|
687
689
|
# write nr_of_lines
|
688
|
-
output_file.write([@nr_of_lines].pack('S
|
690
|
+
output_file.write([@nr_of_lines].pack('S<'))
|
689
691
|
# write start address of shortcut_data:
|
690
|
-
output_file.write([shortcut_start_address].pack('L
|
692
|
+
output_file.write([shortcut_start_address].pack('L<'))
|
691
693
|
|
692
|
-
# S
|
693
|
-
output_file.write([@amount_of_holes].pack('S
|
694
|
+
# S< amount of holes
|
695
|
+
output_file.write([@amount_of_holes].pack('S<'))
|
694
696
|
|
695
|
-
# L
|
696
|
-
output_file.write([hole_start_address].pack('L
|
697
|
+
# L< Address of Hole area (end of shortcut area +1) @ 8
|
698
|
+
output_file.write([hole_start_address].pack('L<'))
|
697
699
|
|
698
700
|
# write zone_ids
|
699
701
|
zone_ids.each do |zone_id|
|
700
|
-
output_file.write([zone_id].pack('S
|
702
|
+
output_file.write([zone_id].pack('S<'))
|
701
703
|
end
|
702
704
|
# write number of values
|
703
705
|
@all_lengths.each do |length|
|
704
|
-
output_file.write([length].pack('S
|
706
|
+
output_file.write([length].pack('S<'))
|
705
707
|
end
|
706
708
|
|
707
709
|
# write polygon_addresses
|
708
710
|
@all_lengths.each do |length|
|
709
|
-
output_file.write([polygon_address].pack('L
|
711
|
+
output_file.write([polygon_address].pack('L<'))
|
710
712
|
# data of the next polygon is at the address after all the space the points take
|
711
713
|
# nr of points stored * 2 ints per point * 4 bytes per int
|
712
714
|
polygon_address += 8 * length
|
@@ -719,16 +721,16 @@ EOT
|
|
719
721
|
|
720
722
|
# write boundary_data
|
721
723
|
@boundaries.each do |b|
|
722
|
-
output_file.write(b.map { |c| Helpers.coord2int(c) }.pack('l
|
724
|
+
output_file.write(b.map { |c| Helpers.coord2int(c) }.pack('l<l<l<l<'))
|
723
725
|
end
|
724
726
|
|
725
727
|
# write polygon_data
|
726
728
|
@all_coords.each do |x_coords, y_coords|
|
727
729
|
x_coords.each do |x|
|
728
|
-
output_file.write([Helpers.coord2int(x)].pack('l
|
730
|
+
output_file.write([Helpers.coord2int(x)].pack('l<'))
|
729
731
|
end
|
730
732
|
y_coords.each do |y|
|
731
|
-
output_file.write([Helpers.coord2int(y)].pack('l
|
733
|
+
output_file.write([Helpers.coord2int(y)].pack('l<'))
|
732
734
|
end
|
733
735
|
end
|
734
736
|
|
@@ -738,7 +740,7 @@ EOT
|
|
738
740
|
# write all nr of entries
|
739
741
|
nr_of_entries_in_shortcut.each do |nr|
|
740
742
|
fail "There are too many polygons in this shortcuts: #{nr}" if nr > 300
|
741
|
-
output_file.write([nr].pack('S
|
743
|
+
output_file.write([nr].pack('S<'))
|
742
744
|
end
|
743
745
|
|
744
746
|
# write Address of first Polygon_nr in shortcut field (x,y)
|
@@ -746,9 +748,9 @@ EOT
|
|
746
748
|
shortcut_address = output_file.tell + 259_200 * NR_SHORTCUTS_PER_LNG * NR_SHORTCUTS_PER_LAT
|
747
749
|
nr_of_entries_in_shortcut.each do |nr|
|
748
750
|
if nr == 0
|
749
|
-
output_file.write([0].pack('L
|
751
|
+
output_file.write([0].pack('L<'))
|
750
752
|
else
|
751
|
-
output_file.write([shortcut_address].pack('L
|
753
|
+
output_file.write([shortcut_address].pack('L<'))
|
752
754
|
# each line_nr takes up 2 bytes of space
|
753
755
|
shortcut_address += 2 * nr
|
754
756
|
end
|
@@ -758,17 +760,17 @@ EOT
|
|
758
760
|
shortcut_entries.each do |entries|
|
759
761
|
entries.each do |entry|
|
760
762
|
fail entry if entry > @nr_of_lines
|
761
|
-
output_file.write([entry].pack('S
|
763
|
+
output_file.write([entry].pack('S<'))
|
762
764
|
end
|
763
765
|
end
|
764
766
|
|
765
767
|
# [HOLE AREA, Y = number of holes (very few: around 22)]
|
766
768
|
|
767
|
-
# '
|
769
|
+
# 'S<' for every hole store the related line
|
768
770
|
i = 0
|
769
771
|
@related_line.each do |line|
|
770
772
|
fail ArgumentError, line if line > @nr_of_lines
|
771
|
-
output_file.write([line].pack('S
|
773
|
+
output_file.write([line].pack('S<'))
|
772
774
|
i += 1
|
773
775
|
end
|
774
776
|
|
@@ -776,15 +778,15 @@ EOT
|
|
776
778
|
fail ArgumentError, 'There are more related lines than holes.'
|
777
779
|
end
|
778
780
|
|
779
|
-
# 'S
|
781
|
+
# 'S<' Y times [H unsigned short: nr of values (coordinate PAIRS! x,y in int32 int32) in this hole]
|
780
782
|
@all_hole_lengths.each do |length|
|
781
|
-
output_file.write([length].pack('S
|
783
|
+
output_file.write([length].pack('S<'))
|
782
784
|
end
|
783
785
|
|
784
|
-
# '
|
786
|
+
# 'L<' Y times [ I unsigned int: absolute address of the byte where the data of that hole starts]
|
785
787
|
hole_address = output_file.tell + @amount_of_holes * 4
|
786
788
|
@all_hole_lengths.each do |length|
|
787
|
-
output_file.write([hole_address].pack('L
|
789
|
+
output_file.write([hole_address].pack('L<'))
|
788
790
|
# each pair of points takes up 8 bytes of space
|
789
791
|
hole_address += 8 * length
|
790
792
|
end
|
@@ -793,10 +795,10 @@ EOT
|
|
793
795
|
# write hole polygon_data
|
794
796
|
@all_holes.each do |x_coords, y_coords|
|
795
797
|
x_coords.each do |x|
|
796
|
-
output_file.write([Helpers.coord2int(x)].pack('l
|
798
|
+
output_file.write([Helpers.coord2int(x)].pack('l<'))
|
797
799
|
end
|
798
800
|
y_coords.each do |y|
|
799
|
-
output_file.write([Helpers.coord2int(y)].pack('l
|
801
|
+
output_file.write([Helpers.coord2int(y)].pack('l<'))
|
800
802
|
end
|
801
803
|
end
|
802
804
|
|
@@ -821,56 +823,56 @@ and it takes lot less space, without loosing too much accuracy (min accuracy is
|
|
821
823
|
|
822
824
|
no of rows (= no of polygons = no of boundaries)
|
823
825
|
approx. 28k -> use 2byte unsigned short (has range until 65k)
|
824
|
-
'S
|
826
|
+
'S<' = n
|
825
827
|
|
826
|
-
L
|
828
|
+
L< Address of Shortcut area (end of polygons+1) @ 2
|
827
829
|
|
828
|
-
S
|
830
|
+
S< amount of holes @6
|
829
831
|
|
830
|
-
L
|
832
|
+
L< Address of Hole area (end of shortcut area +1) @ 8
|
831
833
|
|
832
|
-
'S
|
834
|
+
'S<' n times [H unsigned short: zone number=ID in this line, @ 12 + 2* lineNr]
|
833
835
|
|
834
|
-
'S
|
836
|
+
'S<' n times [H unsigned short: nr of values (coordinate PAIRS! x,y in long long) in this line, @ 12 + 2n + 2* lineNr]
|
835
837
|
|
836
|
-
'L
|
838
|
+
'L<'n times [ I unsigned int: absolute address of the byte where the polygon-data of that line starts,
|
837
839
|
@ 12 + 4 * n + 4*lineNr]
|
838
840
|
|
839
841
|
|
840
842
|
|
841
843
|
n times 4 int32 (take up 4*4 per line): xmax, xmin, ymax, ymin @ 12 + 8n + 16* lineNr
|
842
|
-
'l
|
844
|
+
'l<l<l<l<'
|
843
845
|
|
844
846
|
|
845
847
|
[starting @ 12+ 24*n = polygon data start address]
|
846
848
|
(for every line: x coords, y coords:) stored @ Address section (see above)
|
847
|
-
'l
|
849
|
+
'l<' * amount of points
|
848
850
|
|
849
851
|
360 * NR_SHORTCUTS_PER_LNG * 180 * NR_SHORTCUTS_PER_LAT:
|
850
852
|
[atm: 360* 1 * 180 * 2 = 129,600]
|
851
|
-
129,600 times S
|
853
|
+
129,600 times S< number of entries in shortcut field (x,y) @ Pointer see above
|
852
854
|
|
853
855
|
|
854
856
|
[SHORTCUT AREA]
|
855
857
|
360 * NR_SHORTCUTS_PER_LNG * 180 * NR_SHORTCUTS_PER_LAT:
|
856
858
|
[atm: 360* 1 * 180 * 2 = 129,600]
|
857
|
-
129,600 times S
|
859
|
+
129,600 times S< number of entries in shortcut field (x,y) @ Pointer see above
|
858
860
|
|
859
861
|
|
860
862
|
Address of first Polygon_nr in shortcut field (x,y) [0 if there is no entry] @ Pointer see above + 129,600
|
861
|
-
129,600 times L
|
863
|
+
129,600 times L<
|
862
864
|
|
863
865
|
[X = number of filled shortcuts]
|
864
|
-
X times S
|
866
|
+
X times S< * amount Polygon_Nr @ address stored in previous section
|
865
867
|
|
866
868
|
|
867
869
|
[HOLE AREA, Y = number of holes (very few: around 22)]
|
868
870
|
|
869
|
-
'S
|
871
|
+
'S<' for every hole store the related line
|
870
872
|
|
871
|
-
'S
|
873
|
+
'S<' Y times [S unsigned short: nr of values (coordinate PAIRS! x,y in int32 int32) in this hole]
|
872
874
|
|
873
|
-
'L
|
875
|
+
'L<' Y times [ L unsigned int: absolute address of the byte where the data of that hole starts]
|
874
876
|
|
875
877
|
Y times [ 2x i signed ints for every hole: x coords, y coords ]
|
876
878
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module TimezoneFinder
|
2
|
-
VERSION = '1.5.
|
2
|
+
VERSION = '1.5.6' unless defined? TimezoneFinder::Version
|
3
3
|
# https://github.com/MrMinimal64/timezonefinder
|
4
|
-
BASED_SHA1_OF_PYTHON = '
|
4
|
+
BASED_SHA1_OF_PYTHON = 'c948967a2f9c8bd03535b181fa6faa13168955ca'
|
5
5
|
end
|
@@ -5,9 +5,9 @@
|
|
5
5
|
module TimezoneFinder
|
6
6
|
class Helpers
|
7
7
|
# tests if a point pX(x,y) is Left|On|Right of an infinite line from p1 to p2
|
8
|
-
# Return:
|
9
|
-
#
|
10
|
-
#
|
8
|
+
# Return: -1 for pX left of the line from! p1 to! p2
|
9
|
+
# 0 for pX on the line [is not needed]
|
10
|
+
# 1 for pX right of the line
|
11
11
|
# this approach is only valid because we already know that y lies within ]y1;y2]
|
12
12
|
def self.position_to_line(x, y, x1, x2, y1, y2)
|
13
13
|
if x1 < x2
|
@@ -15,9 +15,9 @@ module TimezoneFinder
|
|
15
15
|
if x > x2
|
16
16
|
# pX is further right than p2,
|
17
17
|
if y1 > y2
|
18
|
-
return
|
18
|
+
return -1
|
19
19
|
else
|
20
|
-
return
|
20
|
+
return 1
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -25,9 +25,9 @@ module TimezoneFinder
|
|
25
25
|
# pX is further left than p1
|
26
26
|
if y1 > y2
|
27
27
|
# so it has to be right of the line p1-p2
|
28
|
-
return
|
28
|
+
return 1
|
29
29
|
else
|
30
|
-
return
|
30
|
+
return -1
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
@@ -39,9 +39,9 @@ module TimezoneFinder
|
|
39
39
|
# pX is further right than p1,
|
40
40
|
if y1 > y2
|
41
41
|
# so it has to be left of the line p1-p2
|
42
|
-
return
|
42
|
+
return -1
|
43
43
|
else
|
44
|
-
return
|
44
|
+
return 1
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -49,16 +49,16 @@ module TimezoneFinder
|
|
49
49
|
# pX is further left than p2,
|
50
50
|
if y1 > y2
|
51
51
|
# so it has to be right of the line p1-p2
|
52
|
-
return
|
52
|
+
return 1
|
53
53
|
else
|
54
|
-
return
|
54
|
+
return -1
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
58
|
# TODO: is not return also accepted
|
59
59
|
if x1 == x2 && x == x1
|
60
60
|
# could also be equal
|
61
|
-
return
|
61
|
+
return 0
|
62
62
|
end
|
63
63
|
|
64
64
|
# x1 greater than x2
|
@@ -74,35 +74,35 @@ module TimezoneFinder
|
|
74
74
|
if delta_x > 0
|
75
75
|
if x1gtx2
|
76
76
|
if y1 > y2
|
77
|
-
return
|
77
|
+
return 1
|
78
78
|
else
|
79
|
-
return
|
79
|
+
return -1
|
80
80
|
end
|
81
81
|
|
82
82
|
else
|
83
83
|
if y1 > y2
|
84
|
-
return
|
84
|
+
return 1
|
85
85
|
else
|
86
|
-
return
|
86
|
+
return -1
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
90
|
elsif delta_x == 0
|
91
|
-
return
|
91
|
+
return 0
|
92
92
|
|
93
93
|
else
|
94
94
|
if x1gtx2
|
95
95
|
if y1 > y2
|
96
|
-
return
|
96
|
+
return -1
|
97
97
|
else
|
98
|
-
return
|
98
|
+
return 1
|
99
99
|
end
|
100
100
|
|
101
101
|
else
|
102
102
|
if y1 > y2
|
103
|
-
return
|
103
|
+
return -1
|
104
104
|
else
|
105
|
-
return
|
105
|
+
return 1
|
106
106
|
end
|
107
107
|
end
|
108
108
|
end
|
@@ -120,7 +120,7 @@ module TimezoneFinder
|
|
120
120
|
x2 = coords[0][i]
|
121
121
|
# print(long2coord(x), long2coord(y), long2coord(x1), long2coord(x2), long2coord(y1), long2coord(y2),
|
122
122
|
# position_to_line(x, y, x1, x2, y1, y2))
|
123
|
-
if position_to_line(x, y, x1, x2, y1, y2) ==
|
123
|
+
if position_to_line(x, y, x1, x2, y1, y2) == -1
|
124
124
|
# point is left of line
|
125
125
|
# return true when its on the line?! this is very unlikely to happen!
|
126
126
|
# and would need to be checked every time!
|
@@ -131,7 +131,7 @@ module TimezoneFinder
|
|
131
131
|
if y2 < y
|
132
132
|
x1 = coords[0][i - 1]
|
133
133
|
x2 = coords[0][i]
|
134
|
-
if position_to_line(x, y, x1, x2, y1, y2) ==
|
134
|
+
if position_to_line(x, y, x1, x2, y1, y2) == 1
|
135
135
|
# point is right of line
|
136
136
|
wn -= 1
|
137
137
|
end
|
@@ -148,7 +148,7 @@ module TimezoneFinder
|
|
148
148
|
if y2 >= y
|
149
149
|
x1 = coords[0][-1]
|
150
150
|
x2 = coords[0][0]
|
151
|
-
if position_to_line(x, y, x1, x2, y1, y2) ==
|
151
|
+
if position_to_line(x, y, x1, x2, y1, y2) == -1
|
152
152
|
# point is left of line
|
153
153
|
wn += 1
|
154
154
|
end
|
@@ -157,7 +157,7 @@ module TimezoneFinder
|
|
157
157
|
if y2 < y
|
158
158
|
x1 = coords[0][-1]
|
159
159
|
x2 = coords[0][0]
|
160
|
-
if position_to_line(x, y, x1, x2, y1, y2) ==
|
160
|
+
if position_to_line(x, y, x1, x2, y1, y2) == 1
|
161
161
|
# point is right of line
|
162
162
|
wn -= 1
|
163
163
|
end
|
@@ -205,52 +205,54 @@ module TimezoneFinder
|
|
205
205
|
[point[0], point[1] * cos_rad + point[2] * sin_rad, point[2] * cos_rad - point[1] * sin_rad]
|
206
206
|
end
|
207
207
|
|
208
|
-
def self.y_rotate(
|
208
|
+
def self.y_rotate(rad, point)
|
209
209
|
# y stays the same
|
210
|
-
|
211
|
-
sin_rad = Math.sin(
|
212
|
-
cos_rad = Math.cos(
|
213
|
-
[point[0] * cos_rad - point[2] * sin_rad, point[1], point[
|
210
|
+
# this is actually a rotation with -rad (use symmetry of sin/cos)
|
211
|
+
sin_rad = Math.sin(rad)
|
212
|
+
cos_rad = Math.cos(rad)
|
213
|
+
[point[0] * cos_rad - point[2] * sin_rad, point[1], point[2] * cos_rad - point[0] * sin_rad]
|
214
214
|
end
|
215
215
|
|
216
|
-
def self.coords2cartesian(
|
217
|
-
|
218
|
-
lat = radians(lat)
|
219
|
-
[Math.cos(lng) * Math.cos(lat), Math.sin(lng) * Math.cos(lat), Math.sin(lat)]
|
216
|
+
def self.coords2cartesian(lng_rad, lat_rad)
|
217
|
+
[Math.cos(lng_rad) * Math.cos(lat_rad), Math.sin(lng_rad) * Math.cos(lat_rad), Math.sin(lat_rad)]
|
220
218
|
end
|
221
219
|
|
222
|
-
# uses the simplified haversine formula for this special case
|
220
|
+
# uses the simplified haversine formula for this special case (lat_p1 = 0)
|
223
221
|
# :param lng_rad: the longitude of the point in radians
|
224
222
|
# :param lat_rad: the latitude of the point
|
225
223
|
# :param lng_rad_p1: the latitude of the point1 on the equator (lat=0)
|
226
|
-
# :return: distance between the point and p1 (lng_rad_p1,0) in
|
224
|
+
# :return: distance between the point and p1 (lng_rad_p1,0) in km
|
225
|
+
# this is only an approximation since the earth is not a real sphere
|
227
226
|
def self.distance_to_point_on_equator(lng_rad, lat_rad, lng_rad_p1)
|
228
|
-
2
|
227
|
+
# 2* for the distance in rad and * 12742 (mean diameter of earth) for the distance in km
|
228
|
+
12742 * Math.asin(Math.sqrt((Math.sin(lat_rad / 2.0)) ** 2 + Math.cos(lat_rad) * Math.sin((lng_rad - lng_rad_p1) / 2.0) ** 2))
|
229
229
|
end
|
230
230
|
|
231
231
|
# :param lng_p1: the longitude of point 1 in radians
|
232
232
|
# :param lat_p1: the latitude of point 1 in radians
|
233
233
|
# :param lng_p2: the longitude of point 1 in radians
|
234
234
|
# :param lat_p2: the latitude of point 1 in radians
|
235
|
-
# :return: distance between p1 and p2 in
|
235
|
+
# :return: distance between p1 and p2 in km
|
236
|
+
# this is only an approximation since the earth is not a real sphere
|
236
237
|
def self.haversine(lng_p1, lat_p1, lng_p2, lat_p2)
|
237
|
-
2
|
238
|
+
# 2* for the distance in rad and * 12742(mean diameter of earth) for the distance in km
|
239
|
+
12742 * Math.asin(Math.sqrt(Math.sin((lat_p1 - lat_p2) / 2.0) ** 2 + Math.cos(lat_p2) * Math.cos(lat_p1) * Math.sin((lng_p1 - lng_p2) / 2.0) ** 2))
|
238
240
|
end
|
239
241
|
|
240
|
-
# :param
|
241
|
-
# :param
|
242
|
-
# :param p0_lng: lng of p0 in
|
243
|
-
# :param p0_lat: lat of p0 in
|
244
|
-
# :param pm1_lng: lng of pm1 in
|
245
|
-
# :param pm1_lat: lat of pm1 in
|
246
|
-
# :param p1_lng: lng of p1 in
|
247
|
-
# :param p1_lat: lat of p1 in
|
242
|
+
# :param lng_rad: lng of px in radians
|
243
|
+
# :param lat_rad: lat of px in radians
|
244
|
+
# :param p0_lng: lng of p0 in radians
|
245
|
+
# :param p0_lat: lat of p0 in radians
|
246
|
+
# :param pm1_lng: lng of pm1 in radians
|
247
|
+
# :param pm1_lat: lat of pm1 in radians
|
248
|
+
# :param p1_lng: lng of p1 in radians
|
249
|
+
# :param p1_lat: lat of p1 in radians
|
248
250
|
# :return: shortest distance between pX and the polygon section (pm1---p0---p1) in radians
|
249
|
-
def self.compute_min_distance(
|
250
|
-
# rotate coordinate system (= all the points) so that p0 would have
|
251
|
-
# z rotation is simply substracting the
|
251
|
+
def self.compute_min_distance(lng_rad, lat_rad, p0_lng, p0_lat, pm1_lng, pm1_lat, p1_lng, p1_lat)
|
252
|
+
# rotate coordinate system (= all the points) so that p0 would have lat_rad=lng_rad=0 (=origin)
|
253
|
+
# z rotation is simply substracting the lng_rad
|
252
254
|
# convert the points to the cartesian coorinate system
|
253
|
-
px_cartesian = coords2cartesian(
|
255
|
+
px_cartesian = coords2cartesian(lng_rad - p0_lng, lat_rad)
|
254
256
|
p1_cartesian = coords2cartesian(p1_lng - p0_lng, p1_lat)
|
255
257
|
pm1_cartesian = coords2cartesian(pm1_lng - p0_lng, pm1_lat)
|
256
258
|
|
@@ -260,16 +262,16 @@ module TimezoneFinder
|
|
260
262
|
|
261
263
|
# for both p1 and pm1 separately do:
|
262
264
|
|
263
|
-
# rotate coordinate system so that this point also has
|
265
|
+
# rotate coordinate system so that this point also has lat_p1_rad=0 and lng_p1_rad>0 (p0 does not change!)
|
264
266
|
rotation_rad = Math.atan2(p1_cartesian[2], p1_cartesian[1])
|
265
267
|
p1_cartesian = x_rotate(rotation_rad, p1_cartesian)
|
266
268
|
lng_p1_rad = Math.atan2(p1_cartesian[1], p1_cartesian[0])
|
267
269
|
px_retrans_rad = cartesian2rad(*x_rotate(rotation_rad, px_cartesian))
|
268
270
|
|
269
|
-
# if
|
271
|
+
# if lng_rad of px is between 0 (<-point1) and lng_rad of point 2:
|
270
272
|
# the distance between point x and the 'equator' is the shortest
|
271
273
|
# if the point is not between p0 and p1 the distance to the closest of the two points should be used
|
272
|
-
# so clamp/clip the
|
274
|
+
# so clamp/clip the lng_rad of px to the interval of [0; lng_rad p1] and compute the distance with it
|
273
275
|
temp_distance = distance_to_point_on_equator(px_retrans_rad[0], px_retrans_rad[1],
|
274
276
|
[[px_retrans_rad[0], lng_p1_rad].min, 0].max)
|
275
277
|
|
@@ -294,11 +296,11 @@ module TimezoneFinder
|
|
294
296
|
(double * 10**7).to_i
|
295
297
|
end
|
296
298
|
|
297
|
-
def self.
|
299
|
+
def self.distance_to_polygon_exact(lng_rad, lat_rad, nr_points, points, trans_points)
|
298
300
|
# transform all points (long long) to coords
|
299
301
|
(0...nr_points).each do |i|
|
300
|
-
trans_points[0][i] = int2coord(points[0][i])
|
301
|
-
trans_points[1][i] = int2coord(points[1][i])
|
302
|
+
trans_points[0][i] = radians(int2coord(points[0][i]))
|
303
|
+
trans_points[1][i] = radians(int2coord(points[1][i]))
|
302
304
|
end
|
303
305
|
|
304
306
|
# check points -2, -1, 0 first
|
@@ -307,8 +309,8 @@ module TimezoneFinder
|
|
307
309
|
|
308
310
|
p1_lng = trans_points[0][-2]
|
309
311
|
p1_lat = trans_points[1][-2]
|
310
|
-
min_distance = compute_min_distance(
|
311
|
-
p1_lat)
|
312
|
+
min_distance = compute_min_distance(lng_rad, lat_rad, trans_points[0][-1], trans_points[1][-1], pm1_lng, pm1_lat,
|
313
|
+
p1_lng, p1_lat)
|
312
314
|
|
313
315
|
index_p0 = 1
|
314
316
|
index_p1 = 2
|
@@ -316,9 +318,9 @@ module TimezoneFinder
|
|
316
318
|
p1_lng = trans_points[0][index_p1]
|
317
319
|
p1_lat = trans_points[1][index_p1]
|
318
320
|
|
319
|
-
|
320
|
-
|
321
|
-
|
321
|
+
min_distance = [min_distance,
|
322
|
+
compute_min_distance(lng_rad, lat_rad, trans_points[0][index_p0], trans_points[1][index_p0],
|
323
|
+
pm1_lng, pm1_lat, p1_lng, p1_lat)].min
|
322
324
|
|
323
325
|
index_p0 += 2
|
324
326
|
index_p1 += 2
|
@@ -329,20 +331,31 @@ module TimezoneFinder
|
|
329
331
|
min_distance
|
330
332
|
end
|
331
333
|
|
334
|
+
def self.distance_to_polygon(lng_rad, lat_rad, nr_points, points)
|
335
|
+
min_distance = 40100000
|
336
|
+
|
337
|
+
(0...nr_points).each do |i|
|
338
|
+
min_distance = [min_distance, haversine(lng_rad, lat_rad, radians(int2coord(points[0][i])),
|
339
|
+
radians(int2coord(points[1][i])))].min
|
340
|
+
end
|
341
|
+
|
342
|
+
min_distance
|
343
|
+
end
|
344
|
+
|
332
345
|
# Ruby original
|
333
346
|
# like numpy.fromfile
|
334
347
|
def self.fromfile(file, unsigned, byte_width, count)
|
335
348
|
if unsigned
|
336
349
|
case byte_width
|
337
350
|
when 2
|
338
|
-
unpack_format = 'S
|
351
|
+
unpack_format = 'S<*'
|
339
352
|
end
|
340
353
|
else
|
341
354
|
case byte_width
|
342
355
|
when 4
|
343
|
-
unpack_format = 'l
|
356
|
+
unpack_format = 'l<*'
|
344
357
|
when 8
|
345
|
-
unpack_format = 'q
|
358
|
+
unpack_format = 'q<*'
|
346
359
|
end
|
347
360
|
end
|
348
361
|
|
Binary file
|
@@ -17,15 +17,15 @@ module TimezoneFinder
|
|
17
17
|
|
18
18
|
# for more info on what is stored how in the .bin please read the comments in file_converter
|
19
19
|
# read the first 2byte int (= number of polygons stored in the .bin)
|
20
|
-
@nr_of_entries = @binary_file.read(2).unpack('S
|
20
|
+
@nr_of_entries = @binary_file.read(2).unpack('S<')[0]
|
21
21
|
|
22
22
|
# set addresses
|
23
23
|
# the address where the shortcut section starts (after all the polygons) this is 34 433 054
|
24
|
-
@shortcuts_start = @binary_file.read(4).unpack('L
|
24
|
+
@shortcuts_start = @binary_file.read(4).unpack('L<')[0]
|
25
25
|
|
26
|
-
@amount_of_holes = @binary_file.read(2).unpack('S
|
26
|
+
@amount_of_holes = @binary_file.read(2).unpack('S<')[0]
|
27
27
|
|
28
|
-
@hole_area_start = @binary_file.read(4).unpack('L
|
28
|
+
@hole_area_start = @binary_file.read(4).unpack('L<')[0]
|
29
29
|
|
30
30
|
@nr_val_start_address = 2 * @nr_of_entries + 12
|
31
31
|
@adr_start_address = 4 * @nr_of_entries + 12
|
@@ -44,7 +44,7 @@ module TimezoneFinder
|
|
44
44
|
amount_of_holes = 0
|
45
45
|
@binary_file.seek(@hole_area_start)
|
46
46
|
(0...@amount_of_holes).each do |i|
|
47
|
-
related_line = @binary_file.read(2).unpack('S
|
47
|
+
related_line = @binary_file.read(2).unpack('S<')[0]
|
48
48
|
# puts(related_line)
|
49
49
|
if related_line == last_encountered_line_nr
|
50
50
|
amount_of_holes += 1
|
@@ -74,7 +74,7 @@ module TimezoneFinder
|
|
74
74
|
def id_of(line = 0)
|
75
75
|
# ids start at address 6. per line one unsigned 2byte int is used
|
76
76
|
@binary_file.seek((12 + 2 * line))
|
77
|
-
@binary_file.read(2).unpack('S
|
77
|
+
@binary_file.read(2).unpack('S<')[0]
|
78
78
|
end
|
79
79
|
|
80
80
|
def ids_of(iterable)
|
@@ -83,7 +83,7 @@ module TimezoneFinder
|
|
83
83
|
i = 0
|
84
84
|
iterable.each do |line_nr|
|
85
85
|
@binary_file.seek((12 + 2 * line_nr))
|
86
|
-
id_array[i] = @binary_file.read(2).unpack('S
|
86
|
+
id_array[i] = @binary_file.read(2).unpack('S<')[0]
|
87
87
|
i += 1
|
88
88
|
end
|
89
89
|
|
@@ -100,10 +100,10 @@ module TimezoneFinder
|
|
100
100
|
# shortcuts are stored: (0,0) (0,1) (0,2)... (1,0)...
|
101
101
|
@binary_file.seek(@shortcuts_start + 720 * x + 2 * y)
|
102
102
|
|
103
|
-
nr_of_polygons = @binary_file.read(2).unpack('S
|
103
|
+
nr_of_polygons = @binary_file.read(2).unpack('S<')[0]
|
104
104
|
|
105
105
|
@binary_file.seek(@first_shortcut_address + 1440 * x + 4 * y)
|
106
|
-
@binary_file.seek(@binary_file.read(4).unpack('L
|
106
|
+
@binary_file.seek(@binary_file.read(4).unpack('L<')[0])
|
107
107
|
Helpers.fromfile(@binary_file, true, 2, nr_of_polygons)
|
108
108
|
end
|
109
109
|
|
@@ -113,19 +113,19 @@ module TimezoneFinder
|
|
113
113
|
# shortcuts are stored: (0,0) (0,1) (0,2)... (1,0)...
|
114
114
|
@binary_file.seek(@shortcuts_start + 720 * x + 2 * y)
|
115
115
|
|
116
|
-
nr_of_polygons = @binary_file.read(2).unpack('S
|
116
|
+
nr_of_polygons = @binary_file.read(2).unpack('S<')[0]
|
117
117
|
|
118
118
|
@binary_file.seek(@first_shortcut_address + 1440 * x + 4 * y)
|
119
|
-
@binary_file.seek(@binary_file.read(4).unpack('L
|
119
|
+
@binary_file.seek(@binary_file.read(4).unpack('L<')[0])
|
120
120
|
Helpers.fromfile(@binary_file, true, 2, nr_of_polygons)
|
121
121
|
end
|
122
122
|
|
123
123
|
def coords_of(line = 0)
|
124
124
|
@binary_file.seek((@nr_val_start_address + 2 * line))
|
125
|
-
nr_of_values = @binary_file.read(2).unpack('S
|
125
|
+
nr_of_values = @binary_file.read(2).unpack('S<')[0]
|
126
126
|
|
127
127
|
@binary_file.seek(@adr_start_address + 4 * line)
|
128
|
-
@binary_file.seek(@binary_file.read(4).unpack('L
|
128
|
+
@binary_file.seek(@binary_file.read(4).unpack('L<')[0])
|
129
129
|
|
130
130
|
# return [Helpers.fromfile(@binary_file, false, 8, nr_of_values),
|
131
131
|
# Helpers.fromfile(@binary_file, false, 8, nr_of_values)]
|
@@ -139,10 +139,10 @@ module TimezoneFinder
|
|
139
139
|
|
140
140
|
(0...amount_of_holes).each do |_i|
|
141
141
|
@binary_file.seek(@nr_val_hole_address + 2 * hole_id)
|
142
|
-
nr_of_values = @binary_file.read(2).unpack('S
|
142
|
+
nr_of_values = @binary_file.read(2).unpack('S<')[0]
|
143
143
|
|
144
144
|
@binary_file.seek(@adr_hole_address + 4 * hole_id)
|
145
|
-
@binary_file.seek(@binary_file.read(4).unpack('L
|
145
|
+
@binary_file.seek(@binary_file.read(4).unpack('L<')[0])
|
146
146
|
|
147
147
|
yield [Helpers.fromfile(@binary_file, false, 4, nr_of_values),
|
148
148
|
Helpers.fromfile(@binary_file, false, 4, nr_of_values)]
|
@@ -158,22 +158,54 @@ module TimezoneFinder
|
|
158
158
|
# (it can't search beyond the 180 deg lng border yet)
|
159
159
|
# this checks all the polygons within [delta_degree] degree lng and lat
|
160
160
|
# Keep in mind that x degrees lat are not the same distance apart than x degree lng!
|
161
|
+
# This is also the reason why there could still be a closer polygon even though you got a result already.
|
162
|
+
# order to make sure to get the closest polygon, you should increase the search radius
|
163
|
+
# until you get a result and then increase it once more (and take that result).
|
164
|
+
# This should only make a difference in really rare cases however.
|
161
165
|
# :param lng: longitude of the point in degree
|
162
166
|
# :param lat: latitude in degree
|
163
167
|
# :param delta_degree: the 'search radius' in degree
|
164
|
-
# :
|
165
|
-
|
168
|
+
# :param exact_computation: when enabled the distance to every polygon edge is computed (way more complicated),
|
169
|
+
# instead of only evaluating the distances to all the vertices (=default).
|
170
|
+
# This only makes a real difference when polygons are very close.
|
171
|
+
# :param return_distances: when enabled the output looks like this:
|
172
|
+
# ( 'tz_name_of_the_closest_polygon',[ distances to all polygons in km], [tz_names of all polygons])
|
173
|
+
# :param force_evaluation:
|
174
|
+
# :return: the timezone name of the closest found polygon, the list of distances or None
|
175
|
+
def closest_timezone_at(lng, lat, delta_degree = 1, exact_computation = false, return_distances = false, force_evaluation = false)
|
176
|
+
exact_routine = lambda do |polygon_nr|
|
177
|
+
coords = coords_of(polygon_nr)
|
178
|
+
nr_points = coords[0].length
|
179
|
+
empty_array = [[0.0] * nr_points, [0.0] * nr_points]
|
180
|
+
Helpers.distance_to_polygon_exact(lng, lat, nr_points, coords, empty_array)
|
181
|
+
end
|
182
|
+
|
183
|
+
normal_routine = lambda do |polygon_nr|
|
184
|
+
coords = coords_of(polygon_nr)
|
185
|
+
nr_points = coords[0].length
|
186
|
+
Helpers.distance_to_polygon(lng, lat, nr_points, coords)
|
187
|
+
end
|
188
|
+
|
166
189
|
if lng > 180.0 or lng < -180.0 or lat > 90.0 or lat < -90.0
|
167
190
|
fail "The coordinates are out ouf bounds: (#{lng}, #{lat})"
|
168
191
|
end
|
169
192
|
|
170
|
-
|
171
|
-
|
193
|
+
if exact_computation
|
194
|
+
routine = exact_routine
|
195
|
+
else
|
196
|
+
routine = normal_routine
|
197
|
+
end
|
198
|
+
|
199
|
+
# the maximum possible distance is half the perimeter of earth pi * 12743km = 40,054.xxx km
|
200
|
+
min_distance = 40100
|
172
201
|
# transform point X into cartesian coordinates
|
173
202
|
current_closest_id = nil
|
174
203
|
central_x_shortcut = (lng + 180).floor.to_i
|
175
204
|
central_y_shortcut = ((90 - lat) * 2).floor.to_i
|
176
205
|
|
206
|
+
lng = Helpers.radians(lng)
|
207
|
+
lat = Helpers.radians(lat)
|
208
|
+
|
177
209
|
polygon_nrs = []
|
178
210
|
|
179
211
|
# there are 2 shortcuts per 1 degree lat, so to cover 1 degree two shortcuts (rows) have to be checked
|
@@ -205,39 +237,57 @@ module TimezoneFinder
|
|
205
237
|
|
206
238
|
# if all the polygons in this shortcut belong to the same zone return it
|
207
239
|
first_entry = ids[0]
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
240
|
+
if ids.count(first_entry) == polygons_in_list
|
241
|
+
unless return_distances || force_evaluation
|
242
|
+
return TIMEZONE_NAMES[first_entry]
|
243
|
+
# TODO: sort from least to most occurrences
|
244
|
+
end
|
245
|
+
end
|
212
246
|
|
247
|
+
distances = [nil] * polygons_in_list
|
213
248
|
pointer = 0
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
if already_checked[pointer] or ids[pointer] == current_closest_id
|
219
|
-
# go to the next polygon
|
220
|
-
polygons_checked += 1
|
221
|
-
|
222
|
-
else
|
223
|
-
# this polygon has to be checked
|
224
|
-
coords = coords_of(polygon_nrs[pointer])
|
225
|
-
nr_points = coords[0].length
|
226
|
-
empty_array = [[0.0] * nr_points, [0.0] * nr_points]
|
227
|
-
distance = Helpers.distance_to_polygon(lng, lat, nr_points, coords, empty_array)
|
228
|
-
|
229
|
-
already_checked[pointer] = true
|
249
|
+
if force_evaluation
|
250
|
+
polygon_nrs.each do |polygon_nr|
|
251
|
+
distance = routine.call(polygon_nr)
|
252
|
+
distances[pointer] = distance
|
230
253
|
if distance < min_distance
|
231
254
|
min_distance = distance
|
232
255
|
current_closest_id = ids[pointer]
|
233
|
-
# whole list has to be searched again!
|
234
|
-
polygons_checked = 1
|
235
256
|
end
|
257
|
+
pointer += 1
|
236
258
|
end
|
237
|
-
|
259
|
+
else
|
260
|
+
# stores which polygons have been checked yet
|
261
|
+
already_checked = [false] * polygons_in_list
|
262
|
+
polygons_checked = 0
|
263
|
+
|
264
|
+
while polygons_checked < polygons_in_list
|
265
|
+
# only check a polygon when its id is not the closest a the moment!
|
266
|
+
if already_checked[pointer] or ids[pointer] == current_closest_id
|
267
|
+
# go to the next polygon
|
268
|
+
polygons_checked += 1
|
269
|
+
|
270
|
+
else
|
271
|
+
# this polygon has to be checked
|
272
|
+
distance = routine.call(polygon_nrs[pointer])
|
273
|
+
distances[pointer] = distance
|
274
|
+
|
275
|
+
already_checked[pointer] = true
|
276
|
+
if distance < min_distance
|
277
|
+
min_distance = distance
|
278
|
+
current_closest_id = ids[pointer]
|
279
|
+
# whole list has to be searched again!
|
280
|
+
polygons_checked = 1
|
281
|
+
end
|
282
|
+
end
|
283
|
+
pointer = (pointer + 1) % polygons_in_list
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
if return_distances
|
288
|
+
return TIMEZONE_NAMES[current_closest_id], distances, ids.map { |x| TIMEZONE_NAMES[x] }
|
238
289
|
end
|
239
290
|
|
240
|
-
# the the whole list has been searched
|
241
291
|
TIMEZONE_NAMES[current_closest_id]
|
242
292
|
end
|
243
293
|
|
@@ -279,7 +329,6 @@ module TimezoneFinder
|
|
279
329
|
return TIMEZONE_NAMES[same_element] if same_element != -1
|
280
330
|
|
281
331
|
# get the boundaries of the polygon = (lng_max, lng_min, lat_max, lat_min)
|
282
|
-
# self.binary_file.seek((@bound_start_address + 32 * polygon_nr), )
|
283
332
|
@binary_file.seek((@bound_start_address + 16 * polygon_nr))
|
284
333
|
boundaries = Helpers.fromfile(@binary_file, false, 4, 4)
|
285
334
|
# only run the algorithm if it the point is withing the boundaries
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timezone_finder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tasuku SUENAGA a.k.a. gunyarakun
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-06-
|
11
|
+
date: 2016-06-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|