timezone_finder 0.0.1 → 1.5.5
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 +14 -0
- data/README.md +9 -10
- data/lib/timezone_finder/file_converter.rb +244 -76
- data/lib/timezone_finder/gem_version.rb +2 -2
- data/lib/timezone_finder/helpers.rb +41 -42
- data/lib/timezone_finder/timezone_data.bin +0 -0
- data/lib/timezone_finder/timezone_finder.rb +89 -20
- data/lib/timezone_finder/timezone_names.rb +2 -0
- data/timezone_finder.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13d427ec4275e9a9cf091179165cdaee2c427f08
|
4
|
+
data.tar.gz: bd258529875fec7b818f7839c2a116ff6a29e434
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a7e3da84f42cc2b1a55c06cfd0ceb35119be8dce2c549a1872fd7f32f3373685582891d34800d03456111e3b89c3c55bda86902a8ff62aa3699077e3e72c525b
|
7
|
+
data.tar.gz: 48374abaab46b420242964dd2373899b4d602f10605964f7497b7580f24200af8d62e99b6f551ea32b79c446dfc2b6cf266d41cfbdad58328f0722c4e9b2097c
|
data/ChangeLog
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
== 2016-06-03 version 1.5.5
|
2
|
+
|
3
|
+
* using the newest version (2016d, May 2016) of the tz_world data from http://efele.net/maps/tz/world/
|
4
|
+
* holes in the polygons which are stored in the tz_world data are now correctly stored and handled
|
5
|
+
* rewrote the file_converter for storing the holes at the end of the
|
6
|
+
timezone_data.bin
|
7
|
+
* added specific test cases for hole handling
|
8
|
+
* made some optimizations in the algorithms
|
9
|
+
|
10
|
+
|
11
|
+
== 2016-05-26 version 0.0.1
|
12
|
+
|
13
|
+
* First release
|
14
|
+
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
[](https://travis-ci.org/gunyarakun/timezone_finder)
|
4
4
|
[](https://badge.fury.io/rb/timezone_finder)
|
5
5
|
|
6
|
-
This is a fast and lightweight ruby project
|
6
|
+
This is a fast and lightweight ruby project for looking up the corresponding
|
7
7
|
timezone for a given lat/lng on earth entirely offline.
|
8
8
|
|
9
9
|
This project is derived from
|
@@ -36,6 +36,9 @@ tf = TimezoneFinder.create
|
|
36
36
|
|
37
37
|
#### fast algorithm:
|
38
38
|
|
39
|
+
This approach is fast, but might not be what you are looking for:
|
40
|
+
For example when there is only one possible timezone in proximity, this timezone would be returned (without checking if the point is included first).
|
41
|
+
|
39
42
|
```ruby
|
40
43
|
# point = (longitude, latitude)
|
41
44
|
point = (13.358, 52.5061)
|
@@ -101,21 +104,17 @@ this converts the .json into the needed .bin (overwriting the old version!) and
|
|
101
104
|
|
102
105
|
## Known Issues
|
103
106
|
|
104
|
-
The original author MrMinimal64 ran tests for approx. 5M points and
|
105
|
-
|
106
|
-
All points in **Lesotho** are counted to the 'Africa/Johannesburg' timezone instead of 'Africa/Maseru'.
|
107
|
-
I am pretty sure this is because it is completely surrounded by South Africa and in the data the area of Lesotho is not excluded from this timezone.
|
108
|
-
|
109
|
-
Same for the small **usbekish enclaves** in **Kirgisitan** and some points in the **Arizona Dessert** (some weird rules apply here).
|
110
|
-
|
111
|
-
Those are mistakes in the data not my algorithms and in order to fix this he would need check for and then separately handle these special cases.
|
112
|
-
This would not only slow down the algorithms, but also make them ugly.
|
107
|
+
The original author MrMinimal64 ran tests for approx. 5M points and these are no mistakes he found.
|
113
108
|
|
114
109
|
## Contact
|
115
110
|
|
116
111
|
If you notice that the tz data is outdated, encounter any bugs, have
|
117
112
|
suggestions, criticism, etc. feel free to **open an Issue**, **add a Pull Requests** on Git.
|
118
113
|
|
114
|
+
## Credits
|
115
|
+
|
116
|
+
Thanks to [MrMinimal64](https://github.com/MrMinimal64) for developing the original version and giving me some advices.
|
117
|
+
|
119
118
|
## License
|
120
119
|
|
121
120
|
``timezone_finder`` is distributed under the terms of the MIT license
|
@@ -17,11 +17,19 @@ module TimezoneFinder
|
|
17
17
|
NR_SHORTCUTS_PER_LAT = 2
|
18
18
|
|
19
19
|
def initialize
|
20
|
+
@nr_of_lines = -1
|
21
|
+
|
20
22
|
@all_tz_names = []
|
21
23
|
@ids = []
|
22
24
|
@boundaries = []
|
23
25
|
@all_coords = []
|
24
26
|
@all_lengths = []
|
27
|
+
@amount_of_holes = 0
|
28
|
+
@first_hole_id_in_line = []
|
29
|
+
@related_line = []
|
30
|
+
|
31
|
+
@all_holes = []
|
32
|
+
@all_hole_lengths = []
|
25
33
|
end
|
26
34
|
|
27
35
|
# HELPERS:
|
@@ -94,10 +102,9 @@ module TimezoneFinder
|
|
94
102
|
def parse_polygons_from_json(path = 'tz_world.json')
|
95
103
|
f = open(path, 'r')
|
96
104
|
puts 'Parsing data from .json'
|
97
|
-
|
105
|
+
# file_line is the current line in the .json file being parsed. This is not the id of the Polygon!
|
106
|
+
file_line = 0
|
98
107
|
f.each_line do |row|
|
99
|
-
puts "line #{n}" if n % 1000 == 0
|
100
|
-
n += 1
|
101
108
|
# puts(row)
|
102
109
|
tz_name_match = /"TZID":\s\"(?<name>.*)"\s\}/.match(row)
|
103
110
|
# tz_name = /(TZID)/.match(row)
|
@@ -105,49 +112,124 @@ module TimezoneFinder
|
|
105
112
|
if tz_name_match
|
106
113
|
tz_name = tz_name_match['name'].gsub('\\', '')
|
107
114
|
@all_tz_names << tz_name
|
115
|
+
@nr_of_lines += 1
|
108
116
|
# puts tz_name
|
109
117
|
|
118
|
+
actual_depth = 0
|
119
|
+
counted_coordinate_pairs = 0
|
120
|
+
encountered_nr_of_coordinates = []
|
121
|
+
row.each_char do |char|
|
122
|
+
if char == '['
|
123
|
+
actual_depth += 1
|
124
|
+
elsif char == ']'
|
125
|
+
actual_depth -= 1
|
126
|
+
if actual_depth == 2
|
127
|
+
counted_coordinate_pairs += 1
|
128
|
+
elsif actual_depth == 1
|
129
|
+
encountered_nr_of_coordinates << counted_coordinate_pairs
|
130
|
+
counted_coordinate_pairs = 0
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
if actual_depth != 0
|
136
|
+
fail ArgumentError, "uneven number of brackets detected. Something is wrong in line #{file_line}"
|
137
|
+
end
|
138
|
+
|
110
139
|
coordinates = row.scan(/[-]?\d+\.?\d+/)
|
140
|
+
|
141
|
+
sum = encountered_nr_of_coordinates.inject(0) { |a, e| a + e }
|
142
|
+
if coordinates.length != sum * 2
|
143
|
+
fail ArgumentError, "There number of coordinates is counten wrong: #{coordinates.length} #{sum * 2}"
|
144
|
+
end
|
145
|
+
# TODO: detect and store all the holes in the bin
|
111
146
|
# puts coordinates
|
112
147
|
|
113
148
|
# nr_floats = coordinates.length
|
114
149
|
x_coords = []
|
115
150
|
y_coords = []
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
151
|
+
xmax = -180.0
|
152
|
+
xmin = 180.0
|
153
|
+
ymax = -90.0
|
154
|
+
ymin = 90.0
|
155
|
+
|
156
|
+
pointer = 0
|
157
|
+
# the coordiate pairs within the first brackets [ [x,y], ..., [xn, yn] ] are the polygon coordinates
|
158
|
+
# The last coordinate pair should be left out (is equal to the first one)
|
159
|
+
(0...(2 * (encountered_nr_of_coordinates[0] - 1))).each do |n|
|
160
|
+
if n.even?
|
161
|
+
x = coordinates[pointer].to_f
|
162
|
+
x_coords << x
|
163
|
+
xmax = x if x > xmax
|
164
|
+
xmin = x if x < xmin
|
120
165
|
else
|
121
|
-
|
166
|
+
y = coordinates[pointer].to_f
|
167
|
+
y_coords << y
|
168
|
+
ymax = y if y > ymax
|
169
|
+
ymin = y if y < ymin
|
122
170
|
end
|
123
|
-
i += 1
|
124
|
-
end
|
125
171
|
|
126
|
-
|
172
|
+
pointer += 1
|
173
|
+
end
|
127
174
|
|
128
175
|
@all_coords << [x_coords, y_coords]
|
129
176
|
@all_lengths << x_coords.length
|
130
|
-
# puts
|
131
|
-
# puts
|
132
|
-
xmax = -180.0
|
133
|
-
xmin = 180.0
|
134
|
-
ymax = -90.0
|
135
|
-
ymin = 90.0
|
177
|
+
# puts(x_coords)
|
178
|
+
# puts(y_coords)
|
136
179
|
|
137
|
-
|
138
|
-
xmax = x if x > xmax
|
139
|
-
xmin = x if x < xmin
|
140
|
-
end
|
180
|
+
@boundaries << [xmax, xmin, ymax, ymin]
|
141
181
|
|
142
|
-
|
143
|
-
|
144
|
-
|
182
|
+
amount_holes_this_line = encountered_nr_of_coordinates.length - 1
|
183
|
+
if amount_holes_this_line > 0
|
184
|
+
# store how many holes there are in this line
|
185
|
+
# store what the number of the first hole for this line is (for calculating the address to jump)
|
186
|
+
@first_hole_id_in_line << @amount_of_holes
|
187
|
+
# keep track of how many holes there are
|
188
|
+
@amount_of_holes += amount_holes_this_line
|
189
|
+
puts(tz_name)
|
190
|
+
|
191
|
+
(0...amount_holes_this_line).each do |_i|
|
192
|
+
@related_line << @nr_of_lines
|
193
|
+
puts(@nr_of_lines)
|
194
|
+
|
195
|
+
# puts(amount_holes_this_line)
|
196
|
+
end
|
145
197
|
end
|
146
198
|
|
147
|
-
|
199
|
+
# for every encountered hole
|
200
|
+
(1...(amount_holes_this_line + 1)).each do |i|
|
201
|
+
x_coords = []
|
202
|
+
y_coords = []
|
203
|
+
|
204
|
+
# since the last coordinate was being left out,
|
205
|
+
# we have to move the pointer 2 floats further to be in the hole data again
|
206
|
+
pointer += 2
|
207
|
+
|
208
|
+
# The last coordinate pair should be left out (is equal to the first one)
|
209
|
+
(0...(2 * (encountered_nr_of_coordinates[i] - 1))).each do |n|
|
210
|
+
if n.even?
|
211
|
+
x_coords << coordinates[pointer].to_f
|
212
|
+
else
|
213
|
+
y_coords << coordinates[pointer].to_f
|
214
|
+
end
|
215
|
+
|
216
|
+
pointer += 1
|
217
|
+
end
|
218
|
+
|
219
|
+
@all_holes << [x_coords, y_coords]
|
220
|
+
@all_hole_lengths << x_coords.length
|
221
|
+
end
|
148
222
|
end
|
223
|
+
|
224
|
+
file_line += 1
|
149
225
|
end
|
150
226
|
|
227
|
+
# so far the nr_of_lines was used to point to the current polygon but there is actually 1 more polygons in total
|
228
|
+
@nr_of_lines += 1
|
229
|
+
|
230
|
+
puts("amount_of_holes: #{@amount_of_holes}")
|
231
|
+
puts("amount of timezones: #{@nr_of_lines}")
|
232
|
+
|
151
233
|
puts("Done\n\n")
|
152
234
|
end
|
153
235
|
|
@@ -158,7 +240,6 @@ module TimezoneFinder
|
|
158
240
|
|
159
241
|
def compile_into_binary(path = 'tz_binary.bin')
|
160
242
|
nr_of_floats = 0
|
161
|
-
nr_of_lines = 0
|
162
243
|
zone_ids = []
|
163
244
|
shortcuts = {}
|
164
245
|
|
@@ -436,7 +517,7 @@ module TimezoneFinder
|
|
436
517
|
end
|
437
518
|
|
438
519
|
def construct_shortcuts(shortcuts)
|
439
|
-
puts('building
|
520
|
+
puts('building shortcuts...')
|
440
521
|
puts('currently in line:')
|
441
522
|
line = 0
|
442
523
|
@boundaries.each do |xmax, xmin, ymax, ymin|
|
@@ -536,12 +617,16 @@ EOT
|
|
536
617
|
end
|
537
618
|
end
|
538
619
|
|
539
|
-
|
620
|
+
# test_length = 0
|
540
621
|
@ids.each do |id|
|
541
|
-
|
622
|
+
# test_length += 1
|
542
623
|
zone_ids << id
|
543
624
|
end
|
544
625
|
|
626
|
+
# if test_length != @nr_of_lines
|
627
|
+
# raise ArgumentError, "#{test_length} #{@nr_of_lines} #{@ids.length}"
|
628
|
+
# end
|
629
|
+
|
545
630
|
@all_lengths.each do |length|
|
546
631
|
nr_of_floats += 2 * length
|
547
632
|
end
|
@@ -553,18 +638,63 @@ EOT
|
|
553
638
|
puts("calculating the shortcuts took: #{end_time - start_time}")
|
554
639
|
|
555
640
|
# address where the actual polygon data starts. look in the description below to get more info
|
556
|
-
polygon_address = (24 * nr_of_lines +
|
641
|
+
polygon_address = (24 * @nr_of_lines + 12)
|
557
642
|
|
558
643
|
# for every original float now 4 bytes are needed (int32)
|
559
644
|
shortcut_start_address = polygon_address + 4 * nr_of_floats
|
560
|
-
|
645
|
+
|
646
|
+
# write number of entries in shortcut field (x,y)
|
647
|
+
nr_of_entries_in_shortcut = []
|
648
|
+
shortcut_entries = []
|
649
|
+
amount_filled_shortcuts = 0
|
650
|
+
|
651
|
+
# count how many shortcut addresses will be written:
|
652
|
+
(0...(360 * NR_SHORTCUTS_PER_LNG)).each do |x|
|
653
|
+
(0...(180 * NR_SHORTCUTS_PER_LAT)).each do |y|
|
654
|
+
begin
|
655
|
+
this_lines_shortcuts = shortcuts.fetch([x, y])
|
656
|
+
shortcut_entries << this_lines_shortcuts
|
657
|
+
amount_filled_shortcuts += 1
|
658
|
+
nr_of_entries_in_shortcut << this_lines_shortcuts.length
|
659
|
+
# puts "(#{x}, #{y}, #{this_lines_shortcuts})"
|
660
|
+
rescue KeyError
|
661
|
+
nr_of_entries_in_shortcut << 0
|
662
|
+
end
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
amount_of_shortcuts = nr_of_entries_in_shortcut.length
|
667
|
+
if amount_of_shortcuts != 64_800 * NR_SHORTCUTS_PER_LNG * NR_SHORTCUTS_PER_LAT
|
668
|
+
puts(amount_of_shortcuts)
|
669
|
+
fail ArgumentError, 'this number of shortcut zones is wrong'
|
670
|
+
end
|
671
|
+
|
672
|
+
puts("number of filled shortcut zones are: #{amount_filled_shortcuts} (=#{(amount_filled_shortcuts.fdiv(amount_of_shortcuts) * 100).round(2)}% of all shortcuts)")
|
673
|
+
|
674
|
+
# for every shortcut S> and L> is written (nr of entries and address)
|
675
|
+
shortcut_space = 360 * NR_SHORTCUTS_PER_LNG * 180 * NR_SHORTCUTS_PER_LAT * 6
|
676
|
+
nr_of_entries_in_shortcut.each do |nr|
|
677
|
+
# every line in every shortcut takes up 2bytes
|
678
|
+
shortcut_space += 2 * nr
|
679
|
+
end
|
680
|
+
|
681
|
+
hole_start_address = shortcut_start_address + shortcut_space
|
682
|
+
|
683
|
+
puts("The number of polygons is: #{@nr_of_lines}")
|
561
684
|
puts("The number of floats in all the polygons is (2 per point): #{nr_of_floats}")
|
562
685
|
puts("now writing file \"#{path}\"")
|
563
686
|
output_file = open(path, 'wb')
|
564
687
|
# write nr_of_lines
|
565
|
-
output_file.write([nr_of_lines].pack('S>'))
|
688
|
+
output_file.write([@nr_of_lines].pack('S>'))
|
566
689
|
# write start address of shortcut_data:
|
567
690
|
output_file.write([shortcut_start_address].pack('L>'))
|
691
|
+
|
692
|
+
# S> amount of holes
|
693
|
+
output_file.write([@amount_of_holes].pack('S>'))
|
694
|
+
|
695
|
+
# L> Address of Hole area (end of shortcut area +1) @ 8
|
696
|
+
output_file.write([hole_start_address].pack('L>'))
|
697
|
+
|
568
698
|
# write zone_ids
|
569
699
|
zone_ids.each do |zone_id|
|
570
700
|
output_file.write([zone_id].pack('S>'))
|
@@ -603,33 +733,8 @@ EOT
|
|
603
733
|
end
|
604
734
|
|
605
735
|
puts("position after writing all polygon data (=start of shortcut section): #{output_file.tell}")
|
606
|
-
# write number of entries in shortcut field (x,y)
|
607
|
-
nr_of_entries_in_shortcut = []
|
608
|
-
shortcut_entries = []
|
609
|
-
total_entries_in_shortcuts = 0
|
610
|
-
|
611
|
-
# count how many shortcut addresses will be written:
|
612
|
-
(0...(360 * NR_SHORTCUTS_PER_LNG)).each do |x|
|
613
|
-
(0...(180 * NR_SHORTCUTS_PER_LAT)).each do |y|
|
614
|
-
begin
|
615
|
-
this_lines_shortcuts = shortcuts.fetch([x, y])
|
616
|
-
shortcut_entries << this_lines_shortcuts
|
617
|
-
total_entries_in_shortcuts += 1
|
618
|
-
nr_of_entries_in_shortcut << this_lines_shortcuts.length
|
619
|
-
# puts("(#{x}, #{y}, #{this_lines_shortcuts})")
|
620
|
-
rescue KeyError
|
621
|
-
nr_of_entries_in_shortcut << 0
|
622
|
-
end
|
623
|
-
end
|
624
|
-
end
|
625
|
-
|
626
|
-
puts("The number of filled shortcut zones are: #{total_entries_in_shortcuts}")
|
627
|
-
|
628
|
-
if nr_of_entries_in_shortcut.length != 64_800 * NR_SHORTCUTS_PER_LNG * NR_SHORTCUTS_PER_LAT
|
629
|
-
puts(nr_of_entries_in_shortcut.length)
|
630
|
-
fail 'this number of shortcut zones is wrong'
|
631
|
-
end
|
632
736
|
|
737
|
+
# [SHORTCUT AREA]
|
633
738
|
# write all nr of entries
|
634
739
|
nr_of_entries_in_shortcut.each do |nr|
|
635
740
|
fail "There are too many polygons in this shortcuts: #{nr}" if nr > 300
|
@@ -644,7 +749,7 @@ EOT
|
|
644
749
|
output_file.write([0].pack('L>'))
|
645
750
|
else
|
646
751
|
output_file.write([shortcut_address].pack('L>'))
|
647
|
-
# each
|
752
|
+
# each line_nr takes up 2 bytes of space
|
648
753
|
shortcut_address += 2 * nr
|
649
754
|
end
|
650
755
|
end
|
@@ -652,17 +757,59 @@ EOT
|
|
652
757
|
# write Line_Nrs for every shortcut
|
653
758
|
shortcut_entries.each do |entries|
|
654
759
|
entries.each do |entry|
|
655
|
-
fail entry if entry > nr_of_lines
|
760
|
+
fail entry if entry > @nr_of_lines
|
656
761
|
output_file.write([entry].pack('S>'))
|
657
762
|
end
|
658
763
|
end
|
659
764
|
|
765
|
+
# [HOLE AREA, Y = number of holes (very few: around 22)]
|
766
|
+
|
767
|
+
# '!H' for every hole store the related line
|
768
|
+
i = 0
|
769
|
+
@related_line.each do |line|
|
770
|
+
fail ArgumentError, line if line > @nr_of_lines
|
771
|
+
output_file.write([line].pack('S>'))
|
772
|
+
i += 1
|
773
|
+
end
|
774
|
+
|
775
|
+
if i > @amount_of_holes
|
776
|
+
fail ArgumentError, 'There are more related lines than holes.'
|
777
|
+
end
|
778
|
+
|
779
|
+
# 'S>' Y times [H unsigned short: nr of values (coordinate PAIRS! x,y in int32 int32) in this hole]
|
780
|
+
@all_hole_lengths.each do |length|
|
781
|
+
output_file.write([length].pack('S>'))
|
782
|
+
end
|
783
|
+
|
784
|
+
# '!I' Y times [ I unsigned int: absolute address of the byte where the data of that hole starts]
|
785
|
+
hole_address = output_file.tell + @amount_of_holes * 4
|
786
|
+
@all_hole_lengths.each do |length|
|
787
|
+
output_file.write([hole_address].pack('L>'))
|
788
|
+
# each pair of points takes up 8 bytes of space
|
789
|
+
hole_address += 8 * length
|
790
|
+
end
|
791
|
+
|
792
|
+
# Y times [ 2x i signed ints for every hole: x coords, y coords ]
|
793
|
+
# write hole polygon_data
|
794
|
+
@all_holes.each do |x_coords, y_coords|
|
795
|
+
x_coords.each do |x|
|
796
|
+
output_file.write([Helpers.coord2int(x)].pack('l>'))
|
797
|
+
end
|
798
|
+
y_coords.each do |y|
|
799
|
+
output_file.write([Helpers.coord2int(y)].pack('l>'))
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
660
803
|
last_address = output_file.tell
|
661
|
-
|
804
|
+
hole_space = last_address - hole_start_address
|
805
|
+
if shortcut_space != last_address - shortcut_start_address - hole_space
|
806
|
+
fail ArgumentError, 'shortcut space is computed wrong'
|
807
|
+
end
|
662
808
|
polygon_space = nr_of_floats * 4
|
663
809
|
|
664
|
-
puts("the
|
665
|
-
puts("the
|
810
|
+
puts("the polygon data makes up #{(polygon_space.fdiv(last_address) * 100).round(2)}% of the file")
|
811
|
+
puts("the shortcuts make up #{(shortcut_space.fdiv(last_address) * 100).round(2) }% of the file")
|
812
|
+
puts("the holes make up #{(hole_space.fdiv(last_address) * 100).round(2) }% of the file")
|
666
813
|
|
667
814
|
puts('Success!')
|
668
815
|
end
|
@@ -674,37 +821,58 @@ and it takes lot less space, without loosing too much accuracy (min accuracy is
|
|
674
821
|
|
675
822
|
no of rows (= no of polygons = no of boundaries)
|
676
823
|
approx. 28k -> use 2byte unsigned short (has range until 65k)
|
677
|
-
'
|
824
|
+
'S>' = n
|
825
|
+
|
826
|
+
L> Address of Shortcut area (end of polygons+1) @ 2
|
827
|
+
|
828
|
+
S> amount of holes @6
|
678
829
|
|
679
|
-
|
830
|
+
L> Address of Hole area (end of shortcut area +1) @ 8
|
680
831
|
|
681
|
-
'
|
832
|
+
'S>' n times [H unsigned short: zone number=ID in this line, @ 12 + 2* lineNr]
|
682
833
|
|
683
|
-
'
|
834
|
+
'S>' n times [H unsigned short: nr of values (coordinate PAIRS! x,y in long long) in this line, @ 12 + 2n + 2* lineNr]
|
684
835
|
|
685
|
-
'
|
686
|
-
@
|
836
|
+
'L>'n times [ I unsigned int: absolute address of the byte where the polygon-data of that line starts,
|
837
|
+
@ 12 + 4 * n + 4*lineNr]
|
687
838
|
|
688
839
|
|
689
840
|
|
690
|
-
n times 4 int32 (take up 4*4 per line): xmax, xmin, ymax, ymin @
|
691
|
-
'
|
841
|
+
n times 4 int32 (take up 4*4 per line): xmax, xmin, ymax, ymin @ 12 + 8n + 16* lineNr
|
842
|
+
'l>l>l>l>'
|
692
843
|
|
693
844
|
|
694
|
-
[starting @
|
845
|
+
[starting @ 12+ 24*n = polygon data start address]
|
695
846
|
(for every line: x coords, y coords:) stored @ Address section (see above)
|
696
|
-
'
|
847
|
+
'l>' * amount of points
|
848
|
+
|
849
|
+
360 * NR_SHORTCUTS_PER_LNG * 180 * NR_SHORTCUTS_PER_LAT:
|
850
|
+
[atm: 360* 1 * 180 * 2 = 129,600]
|
851
|
+
129,600 times S> number of entries in shortcut field (x,y) @ Pointer see above
|
697
852
|
|
853
|
+
|
854
|
+
[SHORTCUT AREA]
|
698
855
|
360 * NR_SHORTCUTS_PER_LNG * 180 * NR_SHORTCUTS_PER_LAT:
|
699
856
|
[atm: 360* 1 * 180 * 2 = 129,600]
|
700
|
-
129,600 times
|
857
|
+
129,600 times S> number of entries in shortcut field (x,y) @ Pointer see above
|
701
858
|
|
702
859
|
|
703
860
|
Address of first Polygon_nr in shortcut field (x,y) [0 if there is no entry] @ Pointer see above + 129,600
|
704
|
-
129,600 times
|
861
|
+
129,600 times L>
|
705
862
|
|
706
863
|
[X = number of filled shortcuts]
|
707
|
-
X times
|
864
|
+
X times S> * amount Polygon_Nr @ address stored in previous section
|
865
|
+
|
866
|
+
|
867
|
+
[HOLE AREA, Y = number of holes (very few: around 22)]
|
868
|
+
|
869
|
+
'S>' for every hole store the related line
|
870
|
+
|
871
|
+
'S>' Y times [S unsigned short: nr of values (coordinate PAIRS! x,y in int32 int32) in this hole]
|
872
|
+
|
873
|
+
'L>' Y times [ L unsigned int: absolute address of the byte where the data of that hole starts]
|
874
|
+
|
875
|
+
Y times [ 2x i signed ints for every hole: x coords, y coords ]
|
708
876
|
|
709
877
|
EOT
|
710
878
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module TimezoneFinder
|
2
|
-
VERSION = '
|
2
|
+
VERSION = '1.5.5' unless defined? TimezoneFinder::Version
|
3
3
|
# https://github.com/MrMinimal64/timezonefinder
|
4
|
-
BASED_SHA1_OF_PYTHON = '
|
4
|
+
BASED_SHA1_OF_PYTHON = 'f291dad017a839f25c472f4976d2a187de104ca8'
|
5
5
|
end
|
@@ -6,24 +6,23 @@ 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
8
|
# Return: 2 for pX left of the line from! p1 to! p2
|
9
|
-
#
|
9
|
+
# 1 for pX on the line [is not needed]
|
10
10
|
# 0 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
|
-
if x1
|
14
|
-
#
|
15
|
-
if x >
|
16
|
-
# pX is further right than
|
13
|
+
if x1 < x2
|
14
|
+
# p2 is further right than p1
|
15
|
+
if x > x2
|
16
|
+
# pX is further right than p2,
|
17
17
|
if y1 > y2
|
18
|
-
# so it has to be left of the line p1-p2
|
19
18
|
return 2
|
20
19
|
else
|
21
20
|
return 0
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
25
|
-
if x <
|
26
|
-
# pX is further left than
|
24
|
+
if x < x1
|
25
|
+
# pX is further left than p1
|
27
26
|
if y1 > y2
|
28
27
|
# so it has to be right of the line p1-p2
|
29
28
|
return 0
|
@@ -32,44 +31,22 @@ module TimezoneFinder
|
|
32
31
|
end
|
33
32
|
end
|
34
33
|
|
35
|
-
|
36
|
-
x1gtx2 = True
|
37
|
-
|
38
|
-
elsif x1 == x2
|
39
|
-
# this is a vertical line, the position of pX is also determined by y1 and y2
|
40
|
-
|
41
|
-
if y1 > y2
|
42
|
-
|
43
|
-
return 2 if x > x1
|
44
|
-
if x == x1
|
45
|
-
return 1
|
46
|
-
else
|
47
|
-
return 0
|
48
|
-
end
|
49
|
-
|
50
|
-
else
|
51
|
-
return 0 if x > x1
|
52
|
-
if x == x1
|
53
|
-
return 1
|
54
|
-
else
|
55
|
-
return 2
|
56
|
-
end
|
57
|
-
end
|
34
|
+
x1gtx2 = false
|
58
35
|
|
59
36
|
else
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
# pX is further right than p2,
|
37
|
+
# p1 is further right than p2
|
38
|
+
if x > x1
|
39
|
+
# pX is further right than p1,
|
64
40
|
if y1 > y2
|
41
|
+
# so it has to be left of the line p1-p2
|
65
42
|
return 2
|
66
43
|
else
|
67
44
|
return 0
|
68
45
|
end
|
69
46
|
end
|
70
47
|
|
71
|
-
if x <
|
72
|
-
# pX is further left than
|
48
|
+
if x < x2
|
49
|
+
# pX is further left than p2,
|
73
50
|
if y1 > y2
|
74
51
|
# so it has to be right of the line p1-p2
|
75
52
|
return 0
|
@@ -78,16 +55,23 @@ module TimezoneFinder
|
|
78
55
|
end
|
79
56
|
end
|
80
57
|
|
81
|
-
|
58
|
+
# TODO: is not return also accepted
|
59
|
+
if x1 == x2 && x == x1
|
60
|
+
# could also be equal
|
61
|
+
return 1
|
62
|
+
end
|
63
|
+
|
64
|
+
# x1 greater than x2
|
65
|
+
x1gtx2 = true
|
82
66
|
end
|
83
67
|
|
84
68
|
# x is between [x1;x2]
|
85
69
|
# compute the x-intersection of the point with the line p1-p2
|
86
|
-
# delta_y
|
70
|
+
# delta_y cannot be 0 here because of the condition 'y lies within ]y1;y2]'
|
87
71
|
# NOTE: bracket placement is important here (we are dealing with 64-bit ints!). first divide then multiply!
|
88
|
-
|
72
|
+
delta_x = ((y - y1) * ((x2 - x1).fdiv(y2 - y1))) + x1 - x
|
89
73
|
|
90
|
-
if
|
74
|
+
if delta_x > 0
|
91
75
|
if x1gtx2
|
92
76
|
if y1 > y2
|
93
77
|
return 0
|
@@ -103,7 +87,7 @@ module TimezoneFinder
|
|
103
87
|
end
|
104
88
|
end
|
105
89
|
|
106
|
-
elsif
|
90
|
+
elsif delta_x == 0
|
107
91
|
return 1
|
108
92
|
|
109
93
|
else
|
@@ -182,6 +166,21 @@ module TimezoneFinder
|
|
182
166
|
wn != 0
|
183
167
|
end
|
184
168
|
|
169
|
+
def self.all_the_same(pointer, length, id_list)
|
170
|
+
# List mustn't be empty or Null
|
171
|
+
# There is at least one
|
172
|
+
|
173
|
+
element = id_list[pointer]
|
174
|
+
pointer += 1
|
175
|
+
|
176
|
+
while pointer < length
|
177
|
+
return -1 if element != id_list[pointer]
|
178
|
+
pointer += 1
|
179
|
+
end
|
180
|
+
|
181
|
+
element
|
182
|
+
end
|
183
|
+
|
185
184
|
def self.cartesian2rad(x, y, z)
|
186
185
|
[Math.atan2(y, x), Math.asin(z)]
|
187
186
|
end
|
Binary file
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# rubocop:disable Metrics/ClassLength,Metrics/MethodLength,Metrics/LineLength
|
2
2
|
# rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity,Metrics/ParameterLists
|
3
3
|
# rubocop:disable Style/PredicateName,Style/Next,Style/AndOr
|
4
|
-
# rubocop:disable Lint/Void
|
4
|
+
# rubocop:disable Lint/Void,Lint/HandleExceptions
|
5
5
|
require_relative 'helpers'
|
6
6
|
require_relative 'timezone_names'
|
7
7
|
|
@@ -23,13 +23,45 @@ module TimezoneFinder
|
|
23
23
|
# the address where the shortcut section starts (after all the polygons) this is 34 433 054
|
24
24
|
@shortcuts_start = @binary_file.read(4).unpack('L>')[0]
|
25
25
|
|
26
|
-
@
|
27
|
-
|
28
|
-
@
|
29
|
-
|
30
|
-
@
|
26
|
+
@amount_of_holes = @binary_file.read(2).unpack('S>')[0]
|
27
|
+
|
28
|
+
@hole_area_start = @binary_file.read(4).unpack('L>')[0]
|
29
|
+
|
30
|
+
@nr_val_start_address = 2 * @nr_of_entries + 12
|
31
|
+
@adr_start_address = 4 * @nr_of_entries + 12
|
32
|
+
@bound_start_address = 8 * @nr_of_entries + 12
|
33
|
+
# @poly_start_address = 40 * @nr_of_entries + 12
|
31
34
|
@first_shortcut_address = @shortcuts_start + 259_200
|
32
35
|
|
36
|
+
@nr_val_hole_address = @hole_area_start + @amount_of_holes * 2
|
37
|
+
@adr_hole_address = @hole_area_start + @amount_of_holes * 4
|
38
|
+
# @hole_data_start = @hole_area_start + @amount_of_holes * 8
|
39
|
+
|
40
|
+
# for store for which polygons (how many) holes exits and the id of the first of those holes
|
41
|
+
@hole_registry = {}
|
42
|
+
last_encountered_line_nr = 0
|
43
|
+
first_hole_id = 0
|
44
|
+
amount_of_holes = 0
|
45
|
+
@binary_file.seek(@hole_area_start)
|
46
|
+
(0...@amount_of_holes).each do |i|
|
47
|
+
related_line = @binary_file.read(2).unpack('S>')[0]
|
48
|
+
# puts(related_line)
|
49
|
+
if related_line == last_encountered_line_nr
|
50
|
+
amount_of_holes += 1
|
51
|
+
else
|
52
|
+
if i != 0
|
53
|
+
@hole_registry.update(last_encountered_line_nr => [amount_of_holes, first_hole_id])
|
54
|
+
end
|
55
|
+
|
56
|
+
last_encountered_line_nr = related_line
|
57
|
+
first_hole_id = i
|
58
|
+
amount_of_holes = 1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# write the entry for the last hole(s) in the registry
|
63
|
+
@hole_registry.update(last_encountered_line_nr => [amount_of_holes, first_hole_id])
|
64
|
+
|
33
65
|
ObjectSpace.define_finalizer(self, self.class.__del__)
|
34
66
|
end
|
35
67
|
|
@@ -41,7 +73,7 @@ module TimezoneFinder
|
|
41
73
|
|
42
74
|
def id_of(line = 0)
|
43
75
|
# ids start at address 6. per line one unsigned 2byte int is used
|
44
|
-
@binary_file.seek((
|
76
|
+
@binary_file.seek((12 + 2 * line))
|
45
77
|
@binary_file.read(2).unpack('S>')[0]
|
46
78
|
end
|
47
79
|
|
@@ -50,7 +82,7 @@ module TimezoneFinder
|
|
50
82
|
|
51
83
|
i = 0
|
52
84
|
iterable.each do |line_nr|
|
53
|
-
@binary_file.seek((
|
85
|
+
@binary_file.seek((12 + 2 * line_nr))
|
54
86
|
id_array[i] = @binary_file.read(2).unpack('S>')[0]
|
55
87
|
i += 1
|
56
88
|
end
|
@@ -102,7 +134,24 @@ module TimezoneFinder
|
|
102
134
|
Helpers.fromfile(@binary_file, false, 4, nr_of_values)]
|
103
135
|
end
|
104
136
|
|
105
|
-
|
137
|
+
def _holes_of_line(line = 0)
|
138
|
+
amount_of_holes, hole_id = @hole_registry.fetch(line)
|
139
|
+
|
140
|
+
(0...amount_of_holes).each do |_i|
|
141
|
+
@binary_file.seek(@nr_val_hole_address + 2 * hole_id)
|
142
|
+
nr_of_values = @binary_file.read(2).unpack('S>')[0]
|
143
|
+
|
144
|
+
@binary_file.seek(@adr_hole_address + 4 * hole_id)
|
145
|
+
@binary_file.seek(@binary_file.read(4).unpack('L>')[0])
|
146
|
+
|
147
|
+
yield [Helpers.fromfile(@binary_file, false, 4, nr_of_values),
|
148
|
+
Helpers.fromfile(@binary_file, false, 4, nr_of_values)]
|
149
|
+
|
150
|
+
hole_id += 1
|
151
|
+
end
|
152
|
+
rescue KeyError
|
153
|
+
end
|
154
|
+
|
106
155
|
# This function searches for the closest polygon in the surrounding shortcuts.
|
107
156
|
# Make sure that the point does not lie within a polygon (for that case the algorithm is simply wrong!)
|
108
157
|
# Note that the algorithm won't find the closest polygon when it's on the 'other end of earth'
|
@@ -219,18 +268,16 @@ module TimezoneFinder
|
|
219
268
|
return TIMEZONE_NAMES[id_of(possible_polygons[0])] if nr_possible_polygons == 1
|
220
269
|
|
221
270
|
# initialize the list of ids
|
271
|
+
# TODO: sort from least to most occurrences
|
222
272
|
ids = possible_polygons.map { |p| id_of(p) }
|
223
273
|
|
224
|
-
# if all the polygons belong to the same zone return it
|
225
|
-
first_entry = ids[0]
|
226
|
-
if ids.count(first_entry) == nr_possible_polygons
|
227
|
-
return TIMEZONE_NAMES[first_entry]
|
228
|
-
end
|
229
|
-
|
230
274
|
# otherwise check if the point is included for all the possible polygons
|
231
275
|
(0...nr_possible_polygons).each do |i|
|
232
276
|
polygon_nr = possible_polygons[i]
|
233
277
|
|
278
|
+
same_element = Helpers.all_the_same(i, nr_possible_polygons, ids)
|
279
|
+
return TIMEZONE_NAMES[same_element] if same_element != -1
|
280
|
+
|
234
281
|
# get the boundaries of the polygon = (lng_max, lng_min, lat_max, lat_min)
|
235
282
|
# self.binary_file.seek((@bound_start_address + 32 * polygon_nr), )
|
236
283
|
@binary_file.seek((@bound_start_address + 16 * polygon_nr))
|
@@ -238,8 +285,19 @@ module TimezoneFinder
|
|
238
285
|
# only run the algorithm if it the point is withing the boundaries
|
239
286
|
unless x > boundaries[0] or x < boundaries[1] or y > boundaries[2] or y < boundaries[3]
|
240
287
|
|
241
|
-
|
242
|
-
|
288
|
+
outside_all_holes = true
|
289
|
+
# when the point is within a hole of the polygon this timezone doesn't need to be checked
|
290
|
+
_holes_of_line(polygon_nr) do |hole_coordinates|
|
291
|
+
if Helpers.inside_polygon(x, y, hole_coordinates)
|
292
|
+
outside_all_holes = false
|
293
|
+
break
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
if outside_all_holes
|
298
|
+
if Helpers.inside_polygon(x, y, coords_of(polygon_nr))
|
299
|
+
return TIMEZONE_NAMES[ids[i]]
|
300
|
+
end
|
243
301
|
end
|
244
302
|
end
|
245
303
|
end
|
@@ -267,9 +325,20 @@ module TimezoneFinder
|
|
267
325
|
@binary_file.seek((@bound_start_address + 16 * polygon_nr))
|
268
326
|
boundaries = Helpers.fromfile(@binary_file, false, 4, 4)
|
269
327
|
unless x > boundaries[0] or x < boundaries[1] or y > boundaries[2] or y < boundaries[3]
|
270
|
-
|
271
|
-
|
272
|
-
|
328
|
+
|
329
|
+
outside_all_holes = true
|
330
|
+
# when the point is within a hole of the polygon this timezone doesn't need to be checked
|
331
|
+
_holes_of_line(polygon_nr) do |hole_coordinates|
|
332
|
+
if Helpers.inside_polygon(x, y, hole_coordinates)
|
333
|
+
outside_all_holes = false
|
334
|
+
break
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
if outside_all_holes
|
339
|
+
if Helpers.inside_polygon(x, y, coords_of(polygon_nr))
|
340
|
+
return TIMEZONE_NAMES[id_of(polygon_nr)]
|
341
|
+
end
|
273
342
|
end
|
274
343
|
end
|
275
344
|
end
|
@@ -276,6 +276,7 @@ module TimezoneFinder
|
|
276
276
|
'Asia/Tehran',
|
277
277
|
'Asia/Thimphu',
|
278
278
|
'Asia/Tokyo',
|
279
|
+
'Asia/Tomsk',
|
279
280
|
'Asia/Ulaanbaatar',
|
280
281
|
'Asia/Urumqi',
|
281
282
|
'Asia/Ust-Nera',
|
@@ -328,6 +329,7 @@ module TimezoneFinder
|
|
328
329
|
'Europe/Jersey',
|
329
330
|
'Europe/Kaliningrad',
|
330
331
|
'Europe/Kiev',
|
332
|
+
'Europe/Kirov',
|
331
333
|
'Europe/Lisbon',
|
332
334
|
'Europe/Ljubljana',
|
333
335
|
'Europe/London',
|
data/timezone_finder.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
|
12
12
|
spec.summary = 'Look up timezone from lat / long offline.'
|
13
13
|
spec.description = %(
|
14
|
-
|
14
|
+
A pure Ruby library to look up timezone from latitude / longitude offline.
|
15
15
|
Ported version of 'timezonefinder' on PyPI.
|
16
16
|
).strip.gsub(/\s+/, ' ')
|
17
17
|
spec.homepage = 'https://github.com/gunyarakun/timezone_finder'
|
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:
|
4
|
+
version: 1.5.5
|
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-
|
11
|
+
date: 2016-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -80,8 +80,8 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0.40'
|
83
|
-
description:
|
84
|
-
of 'timezonefinder' on PyPI.
|
83
|
+
description: A pure Ruby library to look up timezone from latitude / longitude offline.
|
84
|
+
Ported version of 'timezonefinder' on PyPI.
|
85
85
|
email:
|
86
86
|
- tasuku-s-github@titech.ac
|
87
87
|
executables: []
|
@@ -93,6 +93,7 @@ files:
|
|
93
93
|
- ".rubocop.yml"
|
94
94
|
- ".rubocop_todo.yml"
|
95
95
|
- ".travis.yml"
|
96
|
+
- ChangeLog
|
96
97
|
- Gemfile
|
97
98
|
- LICENSE.txt
|
98
99
|
- README.md
|