timezone_finder 1.5.6 → 1.5.7
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 +6 -0
- data/README.md +34 -18
- data/lib/timezone_finder/file_converter.rb +17 -17
- data/lib/timezone_finder/gem_version.rb +2 -2
- data/lib/timezone_finder/helpers.rb +8 -10
- data/lib/timezone_finder/timezone_finder.rb +129 -38
- data/timezone_finder.gemspec +2 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df6c6cb43816fb26a809e41666886aa1482abdb6
|
4
|
+
data.tar.gz: db835dab01964df180ee3d9edbe56439ae521ddf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3dd3d72c1dd9dd6b43949748874c9aea5e2591ddb87f3f3413a8b5cf4b29147c8ec753aecac0ae27559de63f1b5e46436e95a3d4f21a05b985e1fef46a8e9636
|
7
|
+
data.tar.gz: 497db6944dba486a6db9b4033b8c6d9540b46f082d67737a905ecc2589125396c17498599c319cbcb9d5b960dc24078e74f2c81216ad7f9d69f21313cae21371
|
data/ChangeLog
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
== 2017-03-14 version 1.5.7
|
2
|
+
|
3
|
+
* clarified usage of the API in the Readme
|
4
|
+
* all functions are now keyword-args only (to prevent lng lat mix-up errors)
|
5
|
+
* sorting the polygons to check in the order of how often their zones appear gives a speed bonus
|
6
|
+
|
1
7
|
== 2016-06-19 version 1.5.6
|
2
8
|
|
3
9
|
* using little endian encoding now
|
data/README.md
CHANGED
@@ -2,13 +2,16 @@
|
|
2
2
|
|
3
3
|
[](https://travis-ci.org/gunyarakun/timezone_finder)
|
4
4
|
[](https://badge.fury.io/rb/timezone_finder)
|
5
|
+

|
5
6
|
|
6
|
-
|
7
|
+
**NOTE:** version 1.5.7 is incompatible with version 1.5.6 because the argument were changed into named arguments following the original Python version's change.
|
8
|
+
|
9
|
+
This is a fast and lightweight pure ruby project with no runtime dependency for looking up the corresponding
|
7
10
|
timezone for a given lat/lng on earth entirely offline.
|
8
11
|
|
9
12
|
This project is derived from
|
10
13
|
[timezonefinder](https://pypi.python.org/pypi/timezonefinder)
|
11
|
-
([github](https://github.com/MrMinimal64/timezonefinder
|
14
|
+
([github](https://github.com/MrMinimal64/timezonefinder)).
|
12
15
|
|
13
16
|
The underlying timezone data is based on work done by [Eric Muller](http://efele.net/maps/tz/world/).
|
14
17
|
|
@@ -36,16 +39,17 @@ tf = TimezoneFinder.create
|
|
36
39
|
|
37
40
|
#### timezone\_at():
|
38
41
|
|
39
|
-
This is the default function to check which timezone a point lies
|
42
|
+
This is the default function to check which timezone a point lies within.
|
40
43
|
If no timezone has been found, `nil` is being returned.
|
41
|
-
|
42
|
-
|
43
|
-
(without checking if the point is included
|
44
|
+
|
45
|
+
**PLEASE NOTE:** This approach is optimized for speed and the common case to only query points within a timezone.
|
46
|
+
The last possible timezone in proximity is always returned (without checking if the point is really included).
|
47
|
+
So results might be misleading for points outside of any timezone.
|
44
48
|
|
45
49
|
```ruby
|
46
|
-
|
47
|
-
|
48
|
-
puts tf.timezone_at(
|
50
|
+
longitude = 13.358
|
51
|
+
latitude = 52.5061
|
52
|
+
puts tf.timezone_at(lng: longitude, lat: latitude)
|
49
53
|
# = Europe/Berlin
|
50
54
|
```
|
51
55
|
|
@@ -55,18 +59,19 @@ This function is for making sure a point is really inside a timezone. It is slow
|
|
55
59
|
are checked until one polygon is matched.
|
56
60
|
|
57
61
|
```ruby
|
58
|
-
puts tf.certain_timezone_at(
|
62
|
+
puts tf.certain_timezone_at(lng: longitude, lat: latitude)
|
59
63
|
# = Europe/Berlin
|
60
64
|
```
|
61
65
|
|
62
66
|
#### Proximity algorithm
|
63
67
|
|
64
68
|
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
|
69
|
+
This returns the closest timezone of all polygons within +-1 degree lng and +-1 degree lat (or nil).
|
66
70
|
|
67
71
|
```ruby
|
68
|
-
|
69
|
-
|
72
|
+
longitude = 12.773955
|
73
|
+
latitude = 55.578595
|
74
|
+
puts tf.closest_timezone_at(lng: longitude, lat: latitude)
|
70
75
|
# = Europe/Copenhagens
|
71
76
|
```
|
72
77
|
|
@@ -75,7 +80,7 @@ puts tf.closest_timezone_at(*point)
|
|
75
80
|
To increase search radius even more, use the `delta_degree`-option:
|
76
81
|
|
77
82
|
```ruby
|
78
|
-
puts tf.closest_timezone_at(
|
83
|
+
puts tf.closest_timezone_at(lng: longitude, lat: latitude, delta_degree: 3)
|
79
84
|
# = Europe/Copenhagens
|
80
85
|
```
|
81
86
|
|
@@ -85,10 +90,10 @@ I recommend only slowly increasing the search radius, since computation time inc
|
|
85
90
|
|
86
91
|
Also keep in mind that x degrees lat are not the same distance apart than x degree lng (earth is a sphere)!
|
87
92
|
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. (
|
93
|
+
then increase the radius once more and take this result. (should only make a difference in really rare cases)
|
89
94
|
|
90
|
-
With `exact_computation=true` the distance to every polygon edge is computed (way more complicated)
|
91
|
-
|
95
|
+
With `exact_computation=true` the distance to every polygon edge is computed (way more complicated), instead of just evaluating the distances to all the vertices.
|
96
|
+
This only makes a real difference when polygons are very close.
|
92
97
|
|
93
98
|
With `return_distances=true` the output looks like this:
|
94
99
|
|
@@ -97,11 +102,22 @@ With `return_distances=true` the output looks like this:
|
|
97
102
|
Note that some polygons might not be tested (for example when a zone is found to be the closest already).
|
98
103
|
To prevent this use `force_evaluation=true`.
|
99
104
|
|
105
|
+
```ruby
|
106
|
+
longitude = 42.1052479
|
107
|
+
latitude = -16.622686
|
108
|
+
puts tf.closest_timezone_at(lng: longitude, lat: latitude, delta_degree: 2,
|
109
|
+
exact_computation: true, return_distances: true, force_evaluation: true)
|
110
|
+
# = ["uninhabited",
|
111
|
+
# [238.1846260648566, 267.91867468894895, 207.43831938964382, 209.6790144988556, 228.4213564154256, 80.66907784731693, 217.1092486625455, 293.54672523493076, 304.527493783916],
|
112
|
+
# ["Africa/Maputo", "Africa/Maputo", "Africa/Maputo", "Africa/Maputo", "Africa/Maputo", "uninhabited", "Indian/Antananarivo", "Indian/Antananarivo", "Indian/Antananarivo"]
|
113
|
+
# ]
|
114
|
+
```
|
115
|
+
|
100
116
|
## Developer
|
101
117
|
|
102
118
|
### Using the conversion tool:
|
103
119
|
|
104
|
-
Make sure you installed the GDAL framework (
|
120
|
+
Make sure you installed the GDAL framework (that's for converting .shp shapefiles into .json)
|
105
121
|
Change to the directory of the timezone\_finder package (location of ``file_converter.rb``) in your terminal and then:
|
106
122
|
|
107
123
|
```sh
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# rubocop:disable Metrics/ClassLength,Metrics/MethodLength,Metrics/LineLength
|
3
3
|
# rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity,Metrics/ParameterLists
|
4
|
-
# rubocop:disable Style/PredicateName
|
4
|
+
# rubocop:disable Style/PredicateName
|
5
5
|
# rubocop:disable Lint/Void
|
6
6
|
require 'set'
|
7
7
|
require_relative 'helpers'
|
@@ -112,7 +112,7 @@ module TimezoneFinder
|
|
112
112
|
# tz_name = /(TZID)/.match(row)
|
113
113
|
# puts tz_name
|
114
114
|
if tz_name_match
|
115
|
-
tz_name = tz_name_match['name'].
|
115
|
+
tz_name = tz_name_match['name'].delete('\\')
|
116
116
|
@all_tz_names << tz_name
|
117
117
|
@nr_of_lines += 1
|
118
118
|
# puts tz_name
|
@@ -135,14 +135,14 @@ module TimezoneFinder
|
|
135
135
|
end
|
136
136
|
|
137
137
|
if actual_depth != 0
|
138
|
-
|
138
|
+
raise ArgumentError, "uneven number of brackets detected. Something is wrong in line #{file_line}"
|
139
139
|
end
|
140
140
|
|
141
141
|
coordinates = row.scan(/[-]?\d+\.?\d+/)
|
142
142
|
|
143
143
|
sum = encountered_nr_of_coordinates.inject(0) { |a, e| a + e }
|
144
144
|
if coordinates.length != sum * 2
|
145
|
-
|
145
|
+
raise ArgumentError, "There number of coordinates is counten wrong: #{coordinates.length} #{sum * 2}"
|
146
146
|
end
|
147
147
|
# TODO: detect and store all the holes in the bin
|
148
148
|
# puts coordinates
|
@@ -386,7 +386,7 @@ module TimezoneFinder
|
|
386
386
|
|
387
387
|
nr_of_intersects = intersects.length
|
388
388
|
if nr_of_intersects.odd?
|
389
|
-
|
389
|
+
raise 'an uneven number of intersections has been accounted'
|
390
390
|
end
|
391
391
|
|
392
392
|
(0...nr_of_intersects).step(2).each do |i|
|
@@ -458,7 +458,7 @@ module TimezoneFinder
|
|
458
458
|
|
459
459
|
nr_of_intersects = intersects.length
|
460
460
|
if nr_of_intersects.odd?
|
461
|
-
|
461
|
+
raise 'an uneven number of intersections has been accounted'
|
462
462
|
end
|
463
463
|
|
464
464
|
possible_latitudes = []
|
@@ -591,10 +591,10 @@ EOT
|
|
591
591
|
EOT
|
592
592
|
|
593
593
|
if shortcuts_for_line.length > column_nrs.length * row_nrs.length
|
594
|
-
|
594
|
+
raise 'there are more shortcuts than before now. there is something wrong with the algorithm!'
|
595
595
|
end
|
596
596
|
if shortcuts_for_line.length < 3
|
597
|
-
|
597
|
+
raise 'algorithm not valid! less than 3 zones detected (should be at least 4)'
|
598
598
|
end
|
599
599
|
|
600
600
|
else
|
@@ -668,7 +668,7 @@ EOT
|
|
668
668
|
amount_of_shortcuts = nr_of_entries_in_shortcut.length
|
669
669
|
if amount_of_shortcuts != 64_800 * NR_SHORTCUTS_PER_LNG * NR_SHORTCUTS_PER_LAT
|
670
670
|
puts(amount_of_shortcuts)
|
671
|
-
|
671
|
+
raise ArgumentError, 'this number of shortcut zones is wrong'
|
672
672
|
end
|
673
673
|
|
674
674
|
puts("number of filled shortcut zones are: #{amount_filled_shortcuts} (=#{(amount_filled_shortcuts.fdiv(amount_of_shortcuts) * 100).round(2)}% of all shortcuts)")
|
@@ -716,7 +716,7 @@ EOT
|
|
716
716
|
|
717
717
|
if shortcut_start_address != polygon_address
|
718
718
|
# both should be the same!
|
719
|
-
|
719
|
+
raise 'shortcut_start_address and polygon_address should now be the same!'
|
720
720
|
end
|
721
721
|
|
722
722
|
# write boundary_data
|
@@ -739,7 +739,7 @@ EOT
|
|
739
739
|
# [SHORTCUT AREA]
|
740
740
|
# write all nr of entries
|
741
741
|
nr_of_entries_in_shortcut.each do |nr|
|
742
|
-
|
742
|
+
raise "There are too many polygons in this shortcuts: #{nr}" if nr > 300
|
743
743
|
output_file.write([nr].pack('S<'))
|
744
744
|
end
|
745
745
|
|
@@ -759,7 +759,7 @@ EOT
|
|
759
759
|
# write Line_Nrs for every shortcut
|
760
760
|
shortcut_entries.each do |entries|
|
761
761
|
entries.each do |entry|
|
762
|
-
|
762
|
+
raise entry if entry > @nr_of_lines
|
763
763
|
output_file.write([entry].pack('S<'))
|
764
764
|
end
|
765
765
|
end
|
@@ -769,13 +769,13 @@ EOT
|
|
769
769
|
# 'S<' for every hole store the related line
|
770
770
|
i = 0
|
771
771
|
@related_line.each do |line|
|
772
|
-
|
772
|
+
raise ArgumentError, line if line > @nr_of_lines
|
773
773
|
output_file.write([line].pack('S<'))
|
774
774
|
i += 1
|
775
775
|
end
|
776
776
|
|
777
777
|
if i > @amount_of_holes
|
778
|
-
|
778
|
+
raise ArgumentError, 'There are more related lines than holes.'
|
779
779
|
end
|
780
780
|
|
781
781
|
# 'S<' Y times [H unsigned short: nr of values (coordinate PAIRS! x,y in int32 int32) in this hole]
|
@@ -805,13 +805,13 @@ EOT
|
|
805
805
|
last_address = output_file.tell
|
806
806
|
hole_space = last_address - hole_start_address
|
807
807
|
if shortcut_space != last_address - shortcut_start_address - hole_space
|
808
|
-
|
808
|
+
raise ArgumentError, 'shortcut space is computed wrong'
|
809
809
|
end
|
810
810
|
polygon_space = nr_of_floats * 4
|
811
811
|
|
812
812
|
puts("the polygon data makes up #{(polygon_space.fdiv(last_address) * 100).round(2)}% of the file")
|
813
|
-
puts("the shortcuts make up #{(shortcut_space.fdiv(last_address) * 100).round(2)
|
814
|
-
puts("the holes make up #{(hole_space.fdiv(last_address) * 100).round(2)
|
813
|
+
puts("the shortcuts make up #{(shortcut_space.fdiv(last_address) * 100).round(2)}% of the file")
|
814
|
+
puts("the holes make up #{(hole_space.fdiv(last_address) * 100).round(2)}% of the file")
|
815
815
|
|
816
816
|
puts('Success!')
|
817
817
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module TimezoneFinder
|
2
|
-
VERSION = '1.5.
|
2
|
+
VERSION = '1.5.7'.freeze unless defined? TimezoneFinder::Version
|
3
3
|
# https://github.com/MrMinimal64/timezonefinder
|
4
|
-
BASED_SHA1_OF_PYTHON = '
|
4
|
+
BASED_SHA1_OF_PYTHON = '82bc84ab145c69494c1efe15c0afc9cdd46ab626'.freeze
|
5
5
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# rubocop:disable Metrics/ClassLength,Metrics/MethodLength,Metrics/LineLength
|
2
2
|
# rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity,Metrics/ParameterLists
|
3
|
-
# rubocop:disable Style/PredicateName,Style/Next
|
4
|
-
# rubocop:disable Lint/Void
|
5
3
|
module TimezoneFinder
|
6
4
|
class Helpers
|
7
5
|
# tests if a point pX(x,y) is Left|On|Right of an infinite line from p1 to p2
|
@@ -69,7 +67,7 @@ module TimezoneFinder
|
|
69
67
|
# compute the x-intersection of the point with the line p1-p2
|
70
68
|
# delta_y cannot be 0 here because of the condition 'y lies within ]y1;y2]'
|
71
69
|
# NOTE: bracket placement is important here (we are dealing with 64-bit ints!). first divide then multiply!
|
72
|
-
delta_x = ((y - y1) * (
|
70
|
+
delta_x = ((y - y1) * (x2 - x1).fdiv(y2 - y1)) + x1 - x
|
73
71
|
|
74
72
|
if delta_x > 0
|
75
73
|
if x1gtx2
|
@@ -210,7 +208,7 @@ module TimezoneFinder
|
|
210
208
|
# this is actually a rotation with -rad (use symmetry of sin/cos)
|
211
209
|
sin_rad = Math.sin(rad)
|
212
210
|
cos_rad = Math.cos(rad)
|
213
|
-
[point[0] * cos_rad
|
211
|
+
[point[0] * cos_rad + point[2] * sin_rad, point[1], point[2] * cos_rad - point[0] * sin_rad]
|
214
212
|
end
|
215
213
|
|
216
214
|
def self.coords2cartesian(lng_rad, lat_rad)
|
@@ -225,7 +223,7 @@ module TimezoneFinder
|
|
225
223
|
# this is only an approximation since the earth is not a real sphere
|
226
224
|
def self.distance_to_point_on_equator(lng_rad, lat_rad, lng_rad_p1)
|
227
225
|
# 2* for the distance in rad and * 12742 (mean diameter of earth) for the distance in km
|
228
|
-
|
226
|
+
12_742 * 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
227
|
end
|
230
228
|
|
231
229
|
# :param lng_p1: the longitude of point 1 in radians
|
@@ -236,7 +234,7 @@ module TimezoneFinder
|
|
236
234
|
# this is only an approximation since the earth is not a real sphere
|
237
235
|
def self.haversine(lng_p1, lat_p1, lng_p2, lat_p2)
|
238
236
|
# 2* for the distance in rad and * 12742(mean diameter of earth) for the distance in km
|
239
|
-
|
237
|
+
12_742 * 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))
|
240
238
|
end
|
241
239
|
|
242
240
|
# :param lng_rad: lng of px in radians
|
@@ -320,7 +318,7 @@ module TimezoneFinder
|
|
320
318
|
|
321
319
|
min_distance = [min_distance,
|
322
320
|
compute_min_distance(lng_rad, lat_rad, trans_points[0][index_p0], trans_points[1][index_p0],
|
323
|
-
|
321
|
+
pm1_lng, pm1_lat, p1_lng, p1_lat)].min
|
324
322
|
|
325
323
|
index_p0 += 2
|
326
324
|
index_p1 += 2
|
@@ -332,11 +330,11 @@ module TimezoneFinder
|
|
332
330
|
end
|
333
331
|
|
334
332
|
def self.distance_to_polygon(lng_rad, lat_rad, nr_points, points)
|
335
|
-
min_distance =
|
333
|
+
min_distance = 40_100_000
|
336
334
|
|
337
335
|
(0...nr_points).each do |i|
|
338
336
|
min_distance = [min_distance, haversine(lng_rad, lat_rad, radians(int2coord(points[0][i])),
|
339
|
-
|
337
|
+
radians(int2coord(points[1][i])))].min
|
340
338
|
end
|
341
339
|
|
342
340
|
min_distance
|
@@ -360,7 +358,7 @@ module TimezoneFinder
|
|
360
358
|
end
|
361
359
|
|
362
360
|
unless unpack_format
|
363
|
-
|
361
|
+
raise "#{unsigned ? 'unsigned' : 'signed'} #{byte_width}-byte width is not supported in fromfile"
|
364
362
|
end
|
365
363
|
|
366
364
|
file.read(count * byte_width).unpack(unpack_format)
|
@@ -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
|
-
# rubocop:disable Style/
|
4
|
-
# rubocop:disable Lint/
|
3
|
+
# rubocop:disable Style/Next,Style/AndOr
|
4
|
+
# rubocop:disable Lint/HandleExceptions
|
5
5
|
require_relative 'helpers'
|
6
6
|
require_relative 'timezone_names'
|
7
7
|
|
@@ -62,12 +62,12 @@ module TimezoneFinder
|
|
62
62
|
# write the entry for the last hole(s) in the registry
|
63
63
|
@hole_registry.update(last_encountered_line_nr => [amount_of_holes, first_hole_id])
|
64
64
|
|
65
|
-
ObjectSpace.define_finalizer(self, self.class.__del__)
|
65
|
+
ObjectSpace.define_finalizer(self, self.class.__del__(@binary_file))
|
66
66
|
end
|
67
67
|
|
68
|
-
def self.__del__
|
68
|
+
def self.__del__(file)
|
69
69
|
proc do
|
70
|
-
|
70
|
+
file.close
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
@@ -152,6 +152,97 @@ module TimezoneFinder
|
|
152
152
|
rescue KeyError
|
153
153
|
end
|
154
154
|
|
155
|
+
# sorts the polygons_id list from least to most occurrences of the zone ids (->speed up)
|
156
|
+
# approx. 0.24% of all realistic points benefit from sorting (0.4% for random points)
|
157
|
+
# = percentage of sorting usage for 100k points
|
158
|
+
# in most of those cases there are only two types of zones (= entries in counted_zones) and one of them
|
159
|
+
# has only one entry. That means after checking one polygon timezone_at() already stops.
|
160
|
+
# Sorting only really makes sense for closest_timezone_at().
|
161
|
+
# :param polygon_id_list:
|
162
|
+
# :param nr_of_polygons: length of polygon_id_list
|
163
|
+
# :param dont_sort: if this is set to True, the sorting algorithms is skipped
|
164
|
+
# :return: sorted list of polygon_ids, sorted list of zone_ids, boolean: do all entries belong to the same zone
|
165
|
+
def compile_id_list(polygon_id_list, nr_of_polygons, dont_sort: false)
|
166
|
+
all_equal = lambda do |input_data|
|
167
|
+
x = nil
|
168
|
+
for x in input_data
|
169
|
+
# first_val = x
|
170
|
+
break
|
171
|
+
end
|
172
|
+
input_data.each do |y|
|
173
|
+
return false if x != y
|
174
|
+
end
|
175
|
+
true
|
176
|
+
end
|
177
|
+
|
178
|
+
# print(polygon_id_list)
|
179
|
+
# print(zone_id_list)
|
180
|
+
zone_id_list = [0] * nr_of_polygons
|
181
|
+
if dont_sort
|
182
|
+
pointer_local = 0
|
183
|
+
first_id = id_of(polygon_id_list[0])
|
184
|
+
equal = true
|
185
|
+
polygon_id_list.each do |polygon_id|
|
186
|
+
zone_id = id_of(polygon_id)
|
187
|
+
equal = false if zone_id != first_id
|
188
|
+
zone_id_list[pointer_local] = zone_id
|
189
|
+
pointer_local += 1
|
190
|
+
end
|
191
|
+
|
192
|
+
return polygon_id_list, zone_id_list, equal
|
193
|
+
end
|
194
|
+
|
195
|
+
counted_zones = {}
|
196
|
+
pointer_local = 0
|
197
|
+
polygon_id_list.each do |polygon_id|
|
198
|
+
zone_id = id_of(polygon_id)
|
199
|
+
zone_id_list[pointer_local] = zone_id
|
200
|
+
pointer_local += 1
|
201
|
+
counted_zones[zone_id] = counted_zones.fetch(zone_id, 0) + 1
|
202
|
+
end
|
203
|
+
# print(counted_zones)
|
204
|
+
|
205
|
+
return polygon_id_list, zone_id_list, true if counted_zones.length == 1
|
206
|
+
|
207
|
+
if all_equal.call(counted_zones.values)
|
208
|
+
return polygon_id_list, zone_id_list, false
|
209
|
+
end
|
210
|
+
|
211
|
+
counted_zones_sorted = counted_zones.sort_by { |_key, value| value }
|
212
|
+
# print(counted_zones_sorted)
|
213
|
+
|
214
|
+
sorted_polygon_id_list = [0] * nr_of_polygons
|
215
|
+
sorted_zone_id_list = [0] * nr_of_polygons
|
216
|
+
|
217
|
+
pointer_output = 0
|
218
|
+
pointer_output2 = 0
|
219
|
+
counted_zones_sorted.each do |zone_id, amount|
|
220
|
+
# write all polygons from this zone in the new list
|
221
|
+
pointer_local = 0
|
222
|
+
detected_polygons = 0
|
223
|
+
while detected_polygons < amount
|
224
|
+
if zone_id_list[pointer_local] == zone_id
|
225
|
+
# the polygon at the pointer has the wanted zone_id
|
226
|
+
detected_polygons += 1
|
227
|
+
sorted_polygon_id_list[pointer_output] = polygon_id_list[pointer_local]
|
228
|
+
pointer_output += 1
|
229
|
+
end
|
230
|
+
|
231
|
+
pointer_local += 1
|
232
|
+
end
|
233
|
+
|
234
|
+
(0...amount).each do |_pointer_local|
|
235
|
+
sorted_zone_id_list[pointer_output2] = zone_id
|
236
|
+
pointer_output2 += 1
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# print(sorted_polygon_id_list)
|
241
|
+
# print(sorted_zone_id_list)
|
242
|
+
|
243
|
+
[sorted_polygon_id_list, sorted_zone_id_list, false]
|
244
|
+
end
|
245
|
+
|
155
246
|
# This function searches for the closest polygon in the surrounding shortcuts.
|
156
247
|
# Make sure that the point does not lie within a polygon (for that case the algorithm is simply wrong!)
|
157
248
|
# Note that the algorithm won't find the closest polygon when it's on the 'other end of earth'
|
@@ -172,7 +263,7 @@ module TimezoneFinder
|
|
172
263
|
# ( 'tz_name_of_the_closest_polygon',[ distances to all polygons in km], [tz_names of all polygons])
|
173
264
|
# :param force_evaluation:
|
174
265
|
# :return: the timezone name of the closest found polygon, the list of distances or None
|
175
|
-
def closest_timezone_at(lng, lat, delta_degree
|
266
|
+
def closest_timezone_at(lng: nil, lat: nil, delta_degree: 1, exact_computation: false, return_distances: false, force_evaluation: false)
|
176
267
|
exact_routine = lambda do |polygon_nr|
|
177
268
|
coords = coords_of(polygon_nr)
|
178
269
|
nr_points = coords[0].length
|
@@ -187,17 +278,17 @@ module TimezoneFinder
|
|
187
278
|
end
|
188
279
|
|
189
280
|
if lng > 180.0 or lng < -180.0 or lat > 90.0 or lat < -90.0
|
190
|
-
|
281
|
+
raise "The coordinates are out ouf bounds: (#{lng}, #{lat})"
|
191
282
|
end
|
192
283
|
|
193
|
-
if exact_computation
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
284
|
+
routine = if exact_computation
|
285
|
+
exact_routine
|
286
|
+
else
|
287
|
+
normal_routine
|
288
|
+
end
|
198
289
|
|
199
290
|
# the maximum possible distance is half the perimeter of earth pi * 12743km = 40,054.xxx km
|
200
|
-
min_distance =
|
291
|
+
min_distance = 40_100
|
201
292
|
# transform point X into cartesian coordinates
|
202
293
|
current_closest_id = nil
|
203
294
|
central_x_shortcut = (lng + 180).floor.to_i
|
@@ -206,7 +297,7 @@ module TimezoneFinder
|
|
206
297
|
lng = Helpers.radians(lng)
|
207
298
|
lat = Helpers.radians(lat)
|
208
299
|
|
209
|
-
|
300
|
+
possible_polygons = []
|
210
301
|
|
211
302
|
# there are 2 shortcuts per 1 degree lat, so to cover 1 degree two shortcuts (rows) have to be checked
|
212
303
|
# the highest shortcut is 0
|
@@ -223,32 +314,30 @@ module TimezoneFinder
|
|
223
314
|
(left..right).each do |x|
|
224
315
|
(top..bottom).each do |y|
|
225
316
|
polygons_of_shortcut(x, y).each do |p|
|
226
|
-
|
317
|
+
possible_polygons << p if possible_polygons.index(p).nil?
|
227
318
|
end
|
228
319
|
end
|
229
320
|
end
|
230
321
|
|
231
|
-
polygons_in_list =
|
322
|
+
polygons_in_list = possible_polygons.length
|
232
323
|
|
233
324
|
return nil if polygons_in_list == 0
|
234
325
|
|
235
326
|
# initialize the list of ids
|
236
|
-
|
327
|
+
# TODO sorting doesn't give a bonus here?!
|
328
|
+
possible_polygons, ids, zones_are_equal = compile_id_list(possible_polygons, polygons_in_list,
|
329
|
+
dont_sort: true)
|
237
330
|
|
238
331
|
# if all the polygons in this shortcut belong to the same zone return it
|
239
|
-
|
240
|
-
|
241
|
-
unless return_distances || force_evaluation
|
242
|
-
return TIMEZONE_NAMES[first_entry]
|
243
|
-
# TODO: sort from least to most occurrences
|
244
|
-
end
|
332
|
+
if zones_are_equal
|
333
|
+
return TIMEZONE_NAMES[ids[0]] unless return_distances || force_evaluation
|
245
334
|
end
|
246
335
|
|
247
336
|
distances = [nil] * polygons_in_list
|
248
337
|
pointer = 0
|
249
338
|
if force_evaluation
|
250
|
-
|
251
|
-
distance = routine.call(
|
339
|
+
possible_polygons.each do |possible_polygon|
|
340
|
+
distance = routine.call(possible_polygon)
|
252
341
|
distances[pointer] = distance
|
253
342
|
if distance < min_distance
|
254
343
|
min_distance = distance
|
@@ -269,11 +358,11 @@ module TimezoneFinder
|
|
269
358
|
|
270
359
|
else
|
271
360
|
# this polygon has to be checked
|
272
|
-
distance = routine.call(
|
361
|
+
distance = routine.call(possible_polygons[pointer])
|
273
362
|
distances[pointer] = distance
|
274
363
|
|
275
364
|
already_checked[pointer] = true
|
276
|
-
if distance < min_distance
|
365
|
+
if distance < min_distance # rubocop:disable Metrics/BlockNesting
|
277
366
|
min_distance = distance
|
278
367
|
current_closest_id = ids[pointer]
|
279
368
|
# whole list has to be searched again!
|
@@ -300,9 +389,9 @@ module TimezoneFinder
|
|
300
389
|
# :param lng: longitude of the point in degree (-180 to 180)
|
301
390
|
# :param lat: latitude in degree (90 to -90)
|
302
391
|
# :return: the timezone name of the matching polygon or None
|
303
|
-
def timezone_at(lng
|
392
|
+
def timezone_at(lng: 0.0, lat: 0.0)
|
304
393
|
if lng > 180.0 or lng < -180.0 or lat > 90.0 or lat < -90.0
|
305
|
-
|
394
|
+
raise "The coordinates are out ouf bounds: (#{lng}, #{lat})"
|
306
395
|
end
|
307
396
|
|
308
397
|
possible_polygons = shortcuts_of(lng, lat)
|
@@ -318,24 +407,23 @@ module TimezoneFinder
|
|
318
407
|
return TIMEZONE_NAMES[id_of(possible_polygons[0])] if nr_possible_polygons == 1
|
319
408
|
|
320
409
|
# initialize the list of ids
|
321
|
-
#
|
322
|
-
ids = possible_polygons
|
410
|
+
# and sort possible_polygons from least to most occurrences of zone_id
|
411
|
+
possible_polygons, ids, only_one_zone = compile_id_list(possible_polygons, nr_possible_polygons)
|
412
|
+
|
413
|
+
return TIMEZONE_NAMES[ids[0]] if only_one_zone
|
323
414
|
|
324
415
|
# otherwise check if the point is included for all the possible polygons
|
325
416
|
(0...nr_possible_polygons).each do |i|
|
326
417
|
polygon_nr = possible_polygons[i]
|
327
418
|
|
328
|
-
same_element = Helpers.all_the_same(i, nr_possible_polygons, ids)
|
329
|
-
return TIMEZONE_NAMES[same_element] if same_element != -1
|
330
|
-
|
331
419
|
# get the boundaries of the polygon = (lng_max, lng_min, lat_max, lat_min)
|
332
420
|
@binary_file.seek((@bound_start_address + 16 * polygon_nr))
|
333
421
|
boundaries = Helpers.fromfile(@binary_file, false, 4, 4)
|
334
|
-
# only run the algorithm if
|
422
|
+
# only run the expensive algorithm if the point is withing the boundaries
|
335
423
|
unless x > boundaries[0] or x < boundaries[1] or y > boundaries[2] or y < boundaries[3]
|
336
424
|
|
337
425
|
outside_all_holes = true
|
338
|
-
# when the point is within a hole of the polygon this timezone doesn't need to be checked
|
426
|
+
# when the point is within a hole of the polygon, this timezone doesn't need to be checked
|
339
427
|
_holes_of_line(polygon_nr) do |hole_coordinates|
|
340
428
|
if Helpers.inside_polygon(x, y, hole_coordinates)
|
341
429
|
outside_all_holes = false
|
@@ -349,6 +437,9 @@ module TimezoneFinder
|
|
349
437
|
end
|
350
438
|
end
|
351
439
|
end
|
440
|
+
# when after the current polygon only polygons from the same zone appear, return this zone
|
441
|
+
same_element = Helpers.all_the_same(i + 1, nr_possible_polygons, ids)
|
442
|
+
return TIMEZONE_NAMES[same_element] if same_element != -1
|
352
443
|
end
|
353
444
|
nil
|
354
445
|
end
|
@@ -358,9 +449,9 @@ module TimezoneFinder
|
|
358
449
|
# :param lng: longitude of the point in degree
|
359
450
|
# :param lat: latitude in degree
|
360
451
|
# :return: the timezone name of the polygon the point is included in or None
|
361
|
-
def certain_timezone_at(lng
|
452
|
+
def certain_timezone_at(lng: 0.0, lat: 0.0)
|
362
453
|
if lng > 180.0 or lng < -180.0 or lat > 90.0 or lat < -90.0
|
363
|
-
|
454
|
+
raise "The coordinates are out ouf bounds: (#{lng}, #{lat})"
|
364
455
|
end
|
365
456
|
|
366
457
|
possible_polygons = shortcuts_of(lng, lat)
|
data/timezone_finder.gemspec
CHANGED
@@ -10,11 +10,11 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ['tasuku-s-github@titech.ac']
|
11
11
|
|
12
12
|
spec.summary = 'Look up timezone from lat / long offline.'
|
13
|
-
spec.description
|
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
|
-
spec.homepage
|
17
|
+
spec.homepage = 'https://github.com/gunyarakun/timezone_finder'
|
18
18
|
spec.license = 'MIT'
|
19
19
|
|
20
20
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
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.7
|
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:
|
11
|
+
date: 2017-03-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -126,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
126
|
version: '0'
|
127
127
|
requirements: []
|
128
128
|
rubyforge_project:
|
129
|
-
rubygems_version: 2.
|
129
|
+
rubygems_version: 2.6.10
|
130
130
|
signing_key:
|
131
131
|
specification_version: 4
|
132
132
|
summary: Look up timezone from lat / long offline.
|