timezone_finder 1.5.6 → 1.5.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![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
|
+
![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)
|
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.
|