timezone_finder 1.5.5 → 1.5.6
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.
- 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
|