timezone_finder 0.0.1 → 1.5.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/gunyarakun/timezone_finder.svg?branch=master)](https://travis-ci.org/gunyarakun/timezone_finder)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/timezone_finder.svg)](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
|