timezone_finder 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +9 -0
- data/.gitignore +20 -0
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +46 -0
- data/README.md +122 -0
- data/Rakefile +10 -0
- data/lib/timezone_finder.rb +8 -0
- data/lib/timezone_finder/file_converter.rb +716 -0
- data/lib/timezone_finder/gem_version.rb +5 -0
- data/lib/timezone_finder/helpers.rb +357 -0
- data/lib/timezone_finder/timezone_data.bin +0 -0
- data/lib/timezone_finder/timezone_finder.rb +279 -0
- data/lib/timezone_finder/timezone_names.rb +420 -0
- data/timezone_finder.gemspec +30 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7655adbedfe635064333831aa217409072acbf95
|
4
|
+
data.tar.gz: f009e325c8ec9f02d7edadec013f3e5e5f14a152
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0e0b6c8bf962b11f2ebbbd11fbc9b0aa6d48ed2a521630c3aae43f89cfe77cb48be89e78b5f6881fba74eeb6bc9b8608175012ff9c76ca20a45a976551c4e37c
|
7
|
+
data.tar.gz: fdbfd1f79f3a124fd6382f9b56d12f992db6d5cf464306b9db3b50f06ce7bbc199a5f8b0d8c6592d5a46b4e6eecca3315e016ee861db639ed097ee155c0eed6e
|
data/.editorconfig
ADDED
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.rubocop_todo.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Tasuku SUENAGA a.k.a. gunyarakun
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
23
|
+
This software is transported from timezonefinder on Python, which is subject to MIT license.
|
24
|
+
Here is the original license for timezonefinder on Python.
|
25
|
+
|
26
|
+
The MIT License (MIT)
|
27
|
+
|
28
|
+
Copyright (c) 2016 MrMinimal64
|
29
|
+
|
30
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
31
|
+
of this software and associated documentation files (the "Software"), to deal
|
32
|
+
in the Software without restriction, including without limitation the rights
|
33
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
34
|
+
copies of the Software, and to permit persons to whom the Software is
|
35
|
+
furnished to do so, subject to the following conditions:
|
36
|
+
|
37
|
+
The above copyright notice and this permission notice shall be included in all
|
38
|
+
copies or substantial portions of the Software.
|
39
|
+
|
40
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
41
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
42
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
43
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
44
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
45
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
46
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# timezone\_finder
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/gunyarakun/timezone_finder.svg?branch=master)](https://travis-ci.org/gunyarakun/timezone_finder)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/timezone_finder.svg)](https://badge.fury.io/rb/timezone_finder)
|
5
|
+
|
6
|
+
This is a fast and lightweight ruby project to lookup the corresponding
|
7
|
+
timezone for a given lat/lng on earth entirely offline.
|
8
|
+
|
9
|
+
This project is derived from
|
10
|
+
[timezonefinder](https://pypi.python.org/pypi/timezonefinder)
|
11
|
+
([github](https://github.com/MrMinimal64/timezonefinder>)).
|
12
|
+
|
13
|
+
The underlying timezone data is based on work done by [Eric Muller](http://efele.net/maps/tz/world/).
|
14
|
+
|
15
|
+
Timezones at sea and Antarctica are not yet supported (because somewhat
|
16
|
+
special rules apply there).
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
in your terminal simply:
|
21
|
+
|
22
|
+
```sh
|
23
|
+
gem install timezone_finder
|
24
|
+
```
|
25
|
+
|
26
|
+
(you might need to run this command as administrator)
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
### Basics:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
require 'timezone_finder'
|
34
|
+
tf = TimezoneFinder.create
|
35
|
+
```
|
36
|
+
|
37
|
+
#### fast algorithm:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# point = (longitude, latitude)
|
41
|
+
point = (13.358, 52.5061)
|
42
|
+
puts tf.timezone_at(*point)
|
43
|
+
# = Europe/Berlin
|
44
|
+
```
|
45
|
+
|
46
|
+
#### To make sure a point is really inside a timezone (slower):
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
puts tf.certain_timezone_at(*point)
|
50
|
+
# = Europe/Berlin
|
51
|
+
```
|
52
|
+
|
53
|
+
#### To find the closest timezone (slow):
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# only use this when the point is not inside a polygon!
|
57
|
+
# this checks all the polygons within +-1 degree lng and +-1 degree lat
|
58
|
+
point = (12.773955, 55.578595)
|
59
|
+
puts tf.closest_timezone_at(*point)
|
60
|
+
# = Europe/Copenhagens
|
61
|
+
```
|
62
|
+
|
63
|
+
#### To increase search radius even more (very slow):
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# this checks all the polygons within +-3 degree lng and +-3 degree lat
|
67
|
+
# I recommend only slowly increasing the search radius
|
68
|
+
# keep in mind that x degrees lat are not the same distance apart than x degree lng!
|
69
|
+
puts tf.closest_timezone_at(*point, 3)
|
70
|
+
# = Europe/Copenhagens
|
71
|
+
```
|
72
|
+
|
73
|
+
(to make sure you really got the closest timezone increase the search
|
74
|
+
radius until you get a result. then increase the radius once more and
|
75
|
+
take this result.)
|
76
|
+
|
77
|
+
## Developer
|
78
|
+
|
79
|
+
### Using the conversion tool:
|
80
|
+
|
81
|
+
Make sure you installed the GDAL framework (thats for converting .shp shapefiles into .json)
|
82
|
+
Change to the directory of the timezone\_finder package (location of ``file_converter.rb``) in your terminal and then:
|
83
|
+
|
84
|
+
```sh
|
85
|
+
wget http://efele.net/maps/tz/world/tz_world.zip
|
86
|
+
# on mac: curl "http://efele.net/maps/tz/world/tz_world.zip" -o "tz_world.zip"
|
87
|
+
unzip tz_world
|
88
|
+
ogr2ogr -f GeoJSON -t_srs crs:84 tz_world.json ./world/tz_world.shp
|
89
|
+
rm ./world/ -r
|
90
|
+
rm tz_world.zip
|
91
|
+
```
|
92
|
+
|
93
|
+
There has to be a tz\_world.json (of approx. 100MB) in the folder together with the ``file_converter.rb`` now.
|
94
|
+
Then you should run the converter by:
|
95
|
+
|
96
|
+
```sh
|
97
|
+
ruby file_converter.rb
|
98
|
+
```
|
99
|
+
|
100
|
+
this converts the .json into the needed .bin (overwriting the old version!) and updating the used timezone names.
|
101
|
+
|
102
|
+
## Known Issues
|
103
|
+
|
104
|
+
The original author MrMinimal64 ran tests for approx. 5M points and this are the mistakes he found:
|
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.
|
113
|
+
|
114
|
+
## Contact
|
115
|
+
|
116
|
+
If you notice that the tz data is outdated, encounter any bugs, have
|
117
|
+
suggestions, criticism, etc. feel free to **open an Issue**, **add a Pull Requests** on Git.
|
118
|
+
|
119
|
+
## License
|
120
|
+
|
121
|
+
``timezone_finder`` is distributed under the terms of the MIT license
|
122
|
+
(see LICENSE.txt).
|
data/Rakefile
ADDED
@@ -0,0 +1,716 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# rubocop:disable Metrics/ClassLength,Metrics/MethodLength,Metrics/LineLength
|
3
|
+
# rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity,Metrics/ParameterLists
|
4
|
+
# rubocop:disable Style/PredicateName,Style/Next
|
5
|
+
# rubocop:disable Lint/Void
|
6
|
+
require 'set'
|
7
|
+
require_relative 'helpers'
|
8
|
+
|
9
|
+
module TimezoneFinder
|
10
|
+
class FileConverter
|
11
|
+
# Don't change this setup or timezonefinder wont work!
|
12
|
+
# different setups of shortcuts are not supported, because then addresses in the .bin
|
13
|
+
# would need to be calculated depending on how many shortcuts are being used.
|
14
|
+
# number of shortcuts per longitude
|
15
|
+
NR_SHORTCUTS_PER_LNG = 1
|
16
|
+
# shortcuts per latitude
|
17
|
+
NR_SHORTCUTS_PER_LAT = 2
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@all_tz_names = []
|
21
|
+
@ids = []
|
22
|
+
@boundaries = []
|
23
|
+
@all_coords = []
|
24
|
+
@all_lengths = []
|
25
|
+
end
|
26
|
+
|
27
|
+
# HELPERS:
|
28
|
+
|
29
|
+
# TODO
|
30
|
+
# :return:
|
31
|
+
def update_zone_names(path = 'timezone_names.rb')
|
32
|
+
puts('updating the zone names now')
|
33
|
+
unique_zones = []
|
34
|
+
@all_tz_names.each do |zone_name|
|
35
|
+
unique_zones << zone_name if unique_zones.index(zone_name).nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
unique_zones.sort!
|
39
|
+
|
40
|
+
@all_tz_names.each do |zone_name|
|
41
|
+
# the ids of the polygons have to be set correctly
|
42
|
+
@ids << unique_zones.index(zone_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
# write all unique zones into the file at path with the syntax of a ruby array
|
46
|
+
|
47
|
+
file = open(path, 'w')
|
48
|
+
file.write("module TimezoneFinder\n")
|
49
|
+
file.write(" TIMEZONE_NAMES = [\n")
|
50
|
+
unique_zones.each do |zone_name|
|
51
|
+
file.write(" '#{zone_name}',\n")
|
52
|
+
end
|
53
|
+
|
54
|
+
file.write(" ].freeze\n")
|
55
|
+
file.write("end\n")
|
56
|
+
puts("Done\n\n")
|
57
|
+
end
|
58
|
+
|
59
|
+
def inside_polygon(x, y, x_coords, y_coords)
|
60
|
+
def is_left_of(x, y, x1, x2, y1, y2)
|
61
|
+
(x2 - x1) * (y - y1) - (x - x1) * (y2 - y1)
|
62
|
+
end
|
63
|
+
|
64
|
+
n = y_coords.length - 1
|
65
|
+
|
66
|
+
wn = 0
|
67
|
+
(0...n).each do |i|
|
68
|
+
iplus = i + 1
|
69
|
+
if y_coords[i] <= y
|
70
|
+
# puts('Y1<=y')
|
71
|
+
if y_coords[iplus] > y
|
72
|
+
# puts('Y2>y')
|
73
|
+
if is_left_of(x, y, x_coords[i], x_coords[iplus], y_coords[i], y_coords[iplus]) > 0
|
74
|
+
wn += 1
|
75
|
+
# puts('wn is:')
|
76
|
+
# puts(wn)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
else
|
80
|
+
# puts('Y1>y')
|
81
|
+
if y_coords[iplus] <= y
|
82
|
+
# puts('Y2<=y')
|
83
|
+
if is_left_of(x, y, x_coords[i], x_coords[iplus], y_coords[i], y_coords[iplus]) < 0
|
84
|
+
wn -= 1
|
85
|
+
# puts('wn is:')
|
86
|
+
# puts(wn)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
wn != 0
|
92
|
+
end
|
93
|
+
|
94
|
+
def parse_polygons_from_json(path = 'tz_world.json')
|
95
|
+
f = open(path, 'r')
|
96
|
+
puts 'Parsing data from .json'
|
97
|
+
n = 0
|
98
|
+
f.each_line do |row|
|
99
|
+
puts "line #{n}" if n % 1000 == 0
|
100
|
+
n += 1
|
101
|
+
# puts(row)
|
102
|
+
tz_name_match = /"TZID":\s\"(?<name>.*)"\s\}/.match(row)
|
103
|
+
# tz_name = /(TZID)/.match(row)
|
104
|
+
# puts tz_name
|
105
|
+
if tz_name_match
|
106
|
+
tz_name = tz_name_match['name'].gsub('\\', '')
|
107
|
+
@all_tz_names << tz_name
|
108
|
+
# puts tz_name
|
109
|
+
|
110
|
+
coordinates = row.scan(/[-]?\d+\.?\d+/)
|
111
|
+
# puts coordinates
|
112
|
+
|
113
|
+
# nr_floats = coordinates.length
|
114
|
+
x_coords = []
|
115
|
+
y_coords = []
|
116
|
+
i = 0
|
117
|
+
coordinates.each do |coord|
|
118
|
+
if i.even?
|
119
|
+
x_coords << coord.to_f
|
120
|
+
else
|
121
|
+
y_coords << coord.to_f
|
122
|
+
end
|
123
|
+
i += 1
|
124
|
+
end
|
125
|
+
|
126
|
+
fail "#{i} Floats in line #{n} found. Should be even (pairs or (x,y) )" if i.odd?
|
127
|
+
|
128
|
+
@all_coords << [x_coords, y_coords]
|
129
|
+
@all_lengths << x_coords.length
|
130
|
+
# puts x_coords
|
131
|
+
# puts y_coords
|
132
|
+
xmax = -180.0
|
133
|
+
xmin = 180.0
|
134
|
+
ymax = -90.0
|
135
|
+
ymin = 90.0
|
136
|
+
|
137
|
+
x_coords.each do |x|
|
138
|
+
xmax = x if x > xmax
|
139
|
+
xmin = x if x < xmin
|
140
|
+
end
|
141
|
+
|
142
|
+
y_coords.each do |y|
|
143
|
+
ymax = y if y > ymax
|
144
|
+
ymin = y if y < ymin
|
145
|
+
end
|
146
|
+
|
147
|
+
@boundaries << [xmax, xmin, ymax, ymin]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
puts("Done\n\n")
|
152
|
+
end
|
153
|
+
|
154
|
+
def ints_of(line = 0)
|
155
|
+
x_coords, y_coords = @all_coords[line]
|
156
|
+
[x_coords.map { |x| Helpers.coord2int(x) }, y_coords.map { |y| Helpers.coord2int(y) }]
|
157
|
+
end
|
158
|
+
|
159
|
+
def compile_into_binary(path = 'tz_binary.bin')
|
160
|
+
nr_of_floats = 0
|
161
|
+
nr_of_lines = 0
|
162
|
+
zone_ids = []
|
163
|
+
shortcuts = {}
|
164
|
+
|
165
|
+
def x_shortcut(lng)
|
166
|
+
# puts lng if lng < -180 or lng >= 180
|
167
|
+
# raise 'longitude out of bounds'
|
168
|
+
((lng + 180) * NR_SHORTCUTS_PER_LNG).floor
|
169
|
+
end
|
170
|
+
|
171
|
+
def y_shortcut(lat)
|
172
|
+
# puts lat if lat < -90 or lat >= 90
|
173
|
+
# raise 'this latitude is out of bounds'
|
174
|
+
((90 - lat) * NR_SHORTCUTS_PER_LAT).floor
|
175
|
+
end
|
176
|
+
|
177
|
+
def big_zone(xmax, xmin, ymax, ymin)
|
178
|
+
# returns true if a zone with those boundaries could have more than 4 shortcuts
|
179
|
+
(xmax - xmin) > (2.0 / NR_SHORTCUTS_PER_LNG) && (ymax - ymin) > (2.0 / NR_SHORTCUTS_PER_LAT)
|
180
|
+
end
|
181
|
+
|
182
|
+
def included_shortcut_row_nrs(max_lat, min_lat)
|
183
|
+
(y_shortcut(max_lat)..y_shortcut(min_lat)).to_a
|
184
|
+
end
|
185
|
+
|
186
|
+
def included_shortcut_column_nrs(max_lng, min_lng)
|
187
|
+
(x_shortcut(min_lng)..x_shortcut(max_lng)).to_a
|
188
|
+
end
|
189
|
+
|
190
|
+
def longitudes_to_check(max_lng, min_lng)
|
191
|
+
output_list = []
|
192
|
+
step = 1.0 / NR_SHORTCUTS_PER_LNG
|
193
|
+
current = (min_lng * NR_SHORTCUTS_PER_LNG).ceil.fdiv(NR_SHORTCUTS_PER_LNG)
|
194
|
+
last = (max_lng * NR_SHORTCUTS_PER_LNG).floor.fdiv(NR_SHORTCUTS_PER_LNG)
|
195
|
+
|
196
|
+
while current < last
|
197
|
+
output_list << current
|
198
|
+
current += step
|
199
|
+
end
|
200
|
+
|
201
|
+
output_list << last
|
202
|
+
output_list
|
203
|
+
end
|
204
|
+
|
205
|
+
def latitudes_to_check(max_lat, min_lat)
|
206
|
+
output_list = []
|
207
|
+
step = 1.0 / NR_SHORTCUTS_PER_LAT
|
208
|
+
current = (min_lat * NR_SHORTCUTS_PER_LAT).ceil.fdiv(NR_SHORTCUTS_PER_LAT)
|
209
|
+
last = (max_lat * NR_SHORTCUTS_PER_LAT).floor.fdiv(NR_SHORTCUTS_PER_LAT)
|
210
|
+
while current < last
|
211
|
+
output_list << current
|
212
|
+
current += step
|
213
|
+
end
|
214
|
+
|
215
|
+
output_list << last
|
216
|
+
output_list
|
217
|
+
end
|
218
|
+
|
219
|
+
# returns the x intersection from a horizontal line in y with the line from x1,y1 to x1,y2
|
220
|
+
def compute_x_intersection(y, x1, x2, y1, y2)
|
221
|
+
delta_y = y2 - y1
|
222
|
+
return x1 if delta_y == 0
|
223
|
+
((y - y1) * (x2 - x1)).fdiv(delta_y) + x1
|
224
|
+
end
|
225
|
+
|
226
|
+
# returns the y intersection from a vertical line in x with the line from x1,y1 to x1,y2
|
227
|
+
def compute_y_intersection(x, x1, x2, y1, y2)
|
228
|
+
delta_x = x2 - x1
|
229
|
+
return x1 if delta_x == 0
|
230
|
+
((x - x1) * (y2 - y1)).fdiv(delta_x) + y1
|
231
|
+
end
|
232
|
+
|
233
|
+
def x_intersections(y, x_coords, y_coords)
|
234
|
+
# puts(x_coords.to_s)
|
235
|
+
# puts(y)
|
236
|
+
# puts(y_coords.to_s)
|
237
|
+
|
238
|
+
intersects = []
|
239
|
+
(0...(y_coords.length - 1)).each do |i|
|
240
|
+
iplus1 = i + 1
|
241
|
+
if y_coords[i] <= y
|
242
|
+
# puts('Y1<=y')
|
243
|
+
if y_coords[iplus1] > y
|
244
|
+
# this was a crossing. compute the intersect
|
245
|
+
# puts('Y2>y')
|
246
|
+
intersects << compute_x_intersection(y, x_coords[i], x_coords[iplus1], y_coords[i], y_coords[iplus1])
|
247
|
+
end
|
248
|
+
else
|
249
|
+
# puts('Y1>y')
|
250
|
+
if y_coords[iplus1] <= y
|
251
|
+
# this was a crossing. compute the intersect
|
252
|
+
# puts('Y2<=y')
|
253
|
+
intersects << compute_x_intersection(y, x_coords[i], x_coords[iplus1], y_coords[i], y_coords[iplus1])
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
intersects
|
258
|
+
end
|
259
|
+
|
260
|
+
def y_intersections(x, x_coords, y_coords)
|
261
|
+
intersects = []
|
262
|
+
(0...(y_coords.length - 1)).each do |i|
|
263
|
+
iplus1 = i + 1
|
264
|
+
if x_coords[i] <= x
|
265
|
+
if x_coords[iplus1] > x
|
266
|
+
# this was a crossing. compute the intersect
|
267
|
+
intersects << compute_y_intersection(x, x_coords[i], x_coords[iplus1], y_coords[i], y_coords[iplus1])
|
268
|
+
end
|
269
|
+
else
|
270
|
+
if x_coords[iplus1] <= x
|
271
|
+
# this was a crossing. compute the intersect
|
272
|
+
intersects << compute_y_intersection(x, x_coords[i], x_coords[iplus1], y_coords[i], y_coords[iplus1])
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
intersects
|
277
|
+
end
|
278
|
+
|
279
|
+
def compute_exact_shortcuts(xmax, xmin, ymax, ymin, line)
|
280
|
+
shortcuts_for_line = Set.new
|
281
|
+
|
282
|
+
# x_longs = binary_reader.x_coords_of(line)
|
283
|
+
longs = ints_of(line)
|
284
|
+
x_longs = longs[0]
|
285
|
+
y_longs = longs[1]
|
286
|
+
|
287
|
+
# y_longs = binary_reader.y_coords_of(line)
|
288
|
+
y_longs << y_longs[0]
|
289
|
+
x_longs << x_longs[0]
|
290
|
+
|
291
|
+
step = 1.0 / NR_SHORTCUTS_PER_LAT
|
292
|
+
# puts('checking the latitudes')
|
293
|
+
latitudes_to_check(ymax, ymin).each do |lat|
|
294
|
+
# puts(lat)
|
295
|
+
# puts(coordinate_to_longlong(lat))
|
296
|
+
# puts(y_longs)
|
297
|
+
# puts(x_intersections(coordinate_to_longlong(lat), x_longs, y_longs))
|
298
|
+
# raise
|
299
|
+
intersects = x_intersections(Helpers.coord2int(lat), x_longs, y_longs).map do |x|
|
300
|
+
Helpers.int2coord(x)
|
301
|
+
end.sort
|
302
|
+
# puts(intersects.to_s)
|
303
|
+
|
304
|
+
nr_of_intersects = intersects.length
|
305
|
+
if nr_of_intersects.odd?
|
306
|
+
fail 'an uneven number of intersections has been accounted'
|
307
|
+
end
|
308
|
+
|
309
|
+
(0...nr_of_intersects).step(2).each do |i|
|
310
|
+
possible_longitudes = []
|
311
|
+
# collect all the zones between two intersections [in,out,in,out,...]
|
312
|
+
iplus = i + 1
|
313
|
+
intersection_in = intersects[i]
|
314
|
+
intersection_out = intersects[iplus]
|
315
|
+
if intersection_in == intersection_out
|
316
|
+
# the polygon has a point exactly on the border of a shortcut zone here!
|
317
|
+
# only select the top shortcut if it is actually inside the polygon (point a little up is inside)
|
318
|
+
if inside_polygon(Helpers.coord2int(intersection_in), Helpers.coord2int(lat) + 1, x_longs,
|
319
|
+
y_longs)
|
320
|
+
shortcuts_for_line.add([x_shortcut(intersection_in), y_shortcut(lat) - 1])
|
321
|
+
end
|
322
|
+
# the bottom shortcut is always selected
|
323
|
+
shortcuts_for_line.add([x_shortcut(intersection_in), y_shortcut(lat)])
|
324
|
+
|
325
|
+
else
|
326
|
+
# add all the shortcuts for the whole found area of intersection
|
327
|
+
possible_y_shortcut = y_shortcut(lat)
|
328
|
+
|
329
|
+
# both shortcuts should only be selected when the polygon doesnt stays on the border
|
330
|
+
middle = intersection_in + (intersection_out - intersection_in) / 2
|
331
|
+
if inside_polygon(Helpers.coord2int(middle), Helpers.coord2int(lat) + 1, x_longs, y_longs)
|
332
|
+
while intersection_in < intersection_out
|
333
|
+
possible_longitudes << intersection_in
|
334
|
+
intersection_in += step
|
335
|
+
end
|
336
|
+
|
337
|
+
possible_longitudes << intersection_out
|
338
|
+
|
339
|
+
# the shortcut above and below of the intersection should be selected!
|
340
|
+
possible_y_shortcut_min1 = possible_y_shortcut - 1
|
341
|
+
possible_longitudes.each do |possible_x_coord|
|
342
|
+
shortcuts_for_line.add([x_shortcut(possible_x_coord), possible_y_shortcut])
|
343
|
+
shortcuts_for_line.add([x_shortcut(possible_x_coord), possible_y_shortcut_min1])
|
344
|
+
end
|
345
|
+
else
|
346
|
+
# polygon does not cross the border!
|
347
|
+
while intersection_in < intersection_out
|
348
|
+
possible_longitudes << intersection_in
|
349
|
+
intersection_in += step
|
350
|
+
end
|
351
|
+
|
352
|
+
possible_longitudes << intersection_out
|
353
|
+
|
354
|
+
# only the shortcut above of the intersection should be selected!
|
355
|
+
possible_longitudes.each do |possible_x_coord|
|
356
|
+
shortcuts_for_line.add([x_shortcut(possible_x_coord), possible_y_shortcut])
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# puts('now all the longitudes to check')
|
364
|
+
# same procedure horizontally
|
365
|
+
step = 1.0 / NR_SHORTCUTS_PER_LAT
|
366
|
+
longitudes_to_check(xmax, xmin).each do |lng|
|
367
|
+
# puts(lng)
|
368
|
+
# puts(coordinate_to_longlong(lng))
|
369
|
+
# puts(x_longs)
|
370
|
+
# puts(x_intersections(coordinate_to_longlong(lng), x_longs, y_longs))
|
371
|
+
intersects = y_intersections(Helpers.coord2int(lng), x_longs, y_longs).map do |y|
|
372
|
+
Helpers.int2coord(y)
|
373
|
+
end.sort
|
374
|
+
# puts(intersects)
|
375
|
+
|
376
|
+
nr_of_intersects = intersects.length
|
377
|
+
if nr_of_intersects.odd?
|
378
|
+
fail 'an uneven number of intersections has been accounted'
|
379
|
+
end
|
380
|
+
|
381
|
+
possible_latitudes = []
|
382
|
+
(0...nr_of_intersects).step(2).each do |i|
|
383
|
+
# collect all the zones between two intersections [in,out,in,out,...]
|
384
|
+
iplus = i + 1
|
385
|
+
intersection_in = intersects[i]
|
386
|
+
intersection_out = intersects[iplus]
|
387
|
+
if intersection_in == intersection_out
|
388
|
+
# the polygon has a point exactly on the border of a shortcut here!
|
389
|
+
# only select the left shortcut if it is actually inside the polygon (point a little left is inside)
|
390
|
+
if inside_polygon(Helpers.coord2int(lng) - 1, Helpers.coord2int(intersection_in), x_longs,
|
391
|
+
y_longs)
|
392
|
+
shortcuts_for_line.add([x_shortcut(lng) - 1, y_shortcut(intersection_in)])
|
393
|
+
end
|
394
|
+
# the right shortcut is always selected
|
395
|
+
shortcuts_for_line.add([x_shortcut(lng), y_shortcut(intersection_in)])
|
396
|
+
|
397
|
+
else
|
398
|
+
# add all the shortcuts for the whole found area of intersection
|
399
|
+
possible_x_shortcut = x_shortcut(lng)
|
400
|
+
|
401
|
+
# both shortcuts should only be selected when the polygon doesnt stays on the border
|
402
|
+
middle = intersection_in + (intersection_out - intersection_in) / 2
|
403
|
+
if inside_polygon(Helpers.coord2int(lng) - 1, Helpers.coord2int(middle), x_longs,
|
404
|
+
y_longs)
|
405
|
+
while intersection_in < intersection_out
|
406
|
+
possible_latitudes << intersection_in
|
407
|
+
intersection_in += step
|
408
|
+
end
|
409
|
+
|
410
|
+
possible_latitudes << intersection_out
|
411
|
+
|
412
|
+
# both shortcuts right and left of the intersection should be selected!
|
413
|
+
possible_x_shortcut_min1 = possible_x_shortcut - 1
|
414
|
+
possible_latitudes.each do |possible_latitude|
|
415
|
+
shortcuts_for_line.add([possible_x_shortcut, y_shortcut(possible_latitude)])
|
416
|
+
shortcuts_for_line.add([possible_x_shortcut_min1, y_shortcut(possible_latitude)])
|
417
|
+
end
|
418
|
+
|
419
|
+
else
|
420
|
+
while intersection_in < intersection_out
|
421
|
+
possible_latitudes << intersection_in
|
422
|
+
intersection_in += step
|
423
|
+
end
|
424
|
+
# only the shortcut right of the intersection should be selected!
|
425
|
+
possible_latitudes << intersection_out
|
426
|
+
|
427
|
+
possible_latitudes.each do |possible_latitude|
|
428
|
+
shortcuts_for_line.add([possible_x_shortcut, y_shortcut(possible_latitude)])
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
shortcuts_for_line
|
436
|
+
end
|
437
|
+
|
438
|
+
def construct_shortcuts(shortcuts)
|
439
|
+
puts('building shortucts...')
|
440
|
+
puts('currently in line:')
|
441
|
+
line = 0
|
442
|
+
@boundaries.each do |xmax, xmin, ymax, ymin|
|
443
|
+
# xmax, xmin, ymax, ymin = boundaries_of(line=line)
|
444
|
+
if line % 1000 == 0
|
445
|
+
puts("line #{line}")
|
446
|
+
# puts([xmax, xmin, ymax, ymin])
|
447
|
+
end
|
448
|
+
|
449
|
+
column_nrs = included_shortcut_column_nrs(xmax, xmin)
|
450
|
+
row_nrs = included_shortcut_row_nrs(ymax, ymin)
|
451
|
+
|
452
|
+
if big_zone(xmax, xmin, ymax, ymin)
|
453
|
+
|
454
|
+
<<EOT
|
455
|
+
puts("line #{line}")
|
456
|
+
puts('This is a big zone! computing exact shortcuts')
|
457
|
+
puts('Nr of entries before')
|
458
|
+
puts(len(column_nrs) * row_nrs.length)
|
459
|
+
|
460
|
+
puts('columns and rows before optimisation:')
|
461
|
+
|
462
|
+
puts(column_nrs)
|
463
|
+
puts(row_nrs)
|
464
|
+
EOT
|
465
|
+
|
466
|
+
# This is a big zone! compute exact shortcuts with the whole polygon points
|
467
|
+
shortcuts_for_line = compute_exact_shortcuts(xmax, xmin, ymax, ymin, line)
|
468
|
+
# n += shortcuts_for_line.length
|
469
|
+
|
470
|
+
<<EOT
|
471
|
+
accurracy = 1000000000000
|
472
|
+
while len(shortcuts_for_line) < 3 and accurracy > 10000000000
|
473
|
+
shortcuts_for_line = compute_exact_shortcuts(line=i,accurracy)
|
474
|
+
accurracy = (accurracy/10).to_i
|
475
|
+
end
|
476
|
+
EOT
|
477
|
+
min_x_shortcut = column_nrs[0]
|
478
|
+
max_x_shortcut = column_nrs[-1]
|
479
|
+
min_y_shortcut = row_nrs[0]
|
480
|
+
max_y_shortcut = row_nrs[-1]
|
481
|
+
shortcuts_to_remove = []
|
482
|
+
|
483
|
+
# remove shortcuts from outside the possible/valid area
|
484
|
+
shortcuts_for_line.each do |x, y|
|
485
|
+
shortcuts_to_remove << [x, y] if x < min_x_shortcut
|
486
|
+
shortcuts_to_remove << [x, y] if x > max_x_shortcut
|
487
|
+
shortcuts_to_remove << [x, y] if y < min_y_shortcut
|
488
|
+
shortcuts_to_remove << [x, y] if y > max_y_shortcut
|
489
|
+
end
|
490
|
+
|
491
|
+
shortcuts_to_remove.each do |s|
|
492
|
+
shortcuts_for_line.delete(s)
|
493
|
+
end
|
494
|
+
|
495
|
+
<<EOT
|
496
|
+
puts('and after:')
|
497
|
+
puts(shortcuts_for_line.length)
|
498
|
+
|
499
|
+
column_nrs_after = Set.new
|
500
|
+
row_nrs_after = Set.new
|
501
|
+
shortcuts_for_line.each do |x, y|
|
502
|
+
column_nrs_after.add(x)
|
503
|
+
row_nrs_after.add(y)
|
504
|
+
end
|
505
|
+
puts(column_nrs_after)
|
506
|
+
puts(row_nrs_after)
|
507
|
+
puts(shortcuts_for_line)
|
508
|
+
EOT
|
509
|
+
|
510
|
+
if shortcuts_for_line.length > column_nrs.length * row_nrs.length
|
511
|
+
fail 'there are more shortcuts than before now. there is something wrong with the algorithm!'
|
512
|
+
end
|
513
|
+
if shortcuts_for_line.length < 3
|
514
|
+
fail 'algorithm not valid! less than 3 zones detected (should be at least 4)'
|
515
|
+
end
|
516
|
+
|
517
|
+
else
|
518
|
+
|
519
|
+
shortcuts_for_line = []
|
520
|
+
column_nrs.each do |column_nr|
|
521
|
+
row_nrs.each do |row_nr|
|
522
|
+
shortcuts_for_line << [column_nr, row_nr]
|
523
|
+
|
524
|
+
# puts(shortcuts_for_line)
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
shortcuts_for_line.each do |shortcut|
|
530
|
+
shortcuts[shortcut] = shortcuts.fetch(shortcut, []) + [line]
|
531
|
+
end
|
532
|
+
|
533
|
+
line += 1
|
534
|
+
# puts('collected entries:')
|
535
|
+
# puts(n)
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
puts('reading the converted .csv file')
|
540
|
+
@ids.each do |id|
|
541
|
+
nr_of_lines += 1
|
542
|
+
zone_ids << id
|
543
|
+
end
|
544
|
+
|
545
|
+
@all_lengths.each do |length|
|
546
|
+
nr_of_floats += 2 * length
|
547
|
+
end
|
548
|
+
|
549
|
+
start_time = Time.now
|
550
|
+
construct_shortcuts(shortcuts)
|
551
|
+
end_time = Time.now
|
552
|
+
|
553
|
+
puts("calculating the shortcuts took: #{end_time - start_time}")
|
554
|
+
|
555
|
+
# address where the actual polygon data starts. look in the description below to get more info
|
556
|
+
polygon_address = (24 * nr_of_lines + 6)
|
557
|
+
|
558
|
+
# for every original float now 4 bytes are needed (int32)
|
559
|
+
shortcut_start_address = polygon_address + 4 * nr_of_floats
|
560
|
+
puts("The number of polygons is: #{nr_of_lines}")
|
561
|
+
puts("The number of floats in all the polygons is (2 per point): #{nr_of_floats}")
|
562
|
+
puts("now writing file \"#{path}\"")
|
563
|
+
output_file = open(path, 'wb')
|
564
|
+
# write nr_of_lines
|
565
|
+
output_file.write([nr_of_lines].pack('S>'))
|
566
|
+
# write start address of shortcut_data:
|
567
|
+
output_file.write([shortcut_start_address].pack('L>'))
|
568
|
+
# write zone_ids
|
569
|
+
zone_ids.each do |zone_id|
|
570
|
+
output_file.write([zone_id].pack('S>'))
|
571
|
+
end
|
572
|
+
# write number of values
|
573
|
+
@all_lengths.each do |length|
|
574
|
+
output_file.write([length].pack('S>'))
|
575
|
+
end
|
576
|
+
|
577
|
+
# write polygon_addresses
|
578
|
+
@all_lengths.each do |length|
|
579
|
+
output_file.write([polygon_address].pack('L>'))
|
580
|
+
# data of the next polygon is at the address after all the space the points take
|
581
|
+
# nr of points stored * 2 ints per point * 4 bytes per int
|
582
|
+
polygon_address += 8 * length
|
583
|
+
end
|
584
|
+
|
585
|
+
if shortcut_start_address != polygon_address
|
586
|
+
# both should be the same!
|
587
|
+
fail 'shortcut_start_address and polygon_address should now be the same!'
|
588
|
+
end
|
589
|
+
|
590
|
+
# write boundary_data
|
591
|
+
@boundaries.each do |b|
|
592
|
+
output_file.write(b.map { |c| Helpers.coord2int(c) }.pack('l>l>l>l>'))
|
593
|
+
end
|
594
|
+
|
595
|
+
# write polygon_data
|
596
|
+
@all_coords.each do |x_coords, y_coords|
|
597
|
+
x_coords.each do |x|
|
598
|
+
output_file.write([Helpers.coord2int(x)].pack('l>'))
|
599
|
+
end
|
600
|
+
y_coords.each do |y|
|
601
|
+
output_file.write([Helpers.coord2int(y)].pack('l>'))
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
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
|
+
|
633
|
+
# write all nr of entries
|
634
|
+
nr_of_entries_in_shortcut.each do |nr|
|
635
|
+
fail "There are too many polygons in this shortcuts: #{nr}" if nr > 300
|
636
|
+
output_file.write([nr].pack('S>'))
|
637
|
+
end
|
638
|
+
|
639
|
+
# write Address of first Polygon_nr in shortcut field (x,y)
|
640
|
+
# Attention: 0 is written when no entries are in this shortcut
|
641
|
+
shortcut_address = output_file.tell + 259_200 * NR_SHORTCUTS_PER_LNG * NR_SHORTCUTS_PER_LAT
|
642
|
+
nr_of_entries_in_shortcut.each do |nr|
|
643
|
+
if nr == 0
|
644
|
+
output_file.write([0].pack('L>'))
|
645
|
+
else
|
646
|
+
output_file.write([shortcut_address].pack('L>'))
|
647
|
+
# each polygon takes up 2 bytes of space
|
648
|
+
shortcut_address += 2 * nr
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
# write Line_Nrs for every shortcut
|
653
|
+
shortcut_entries.each do |entries|
|
654
|
+
entries.each do |entry|
|
655
|
+
fail entry if entry > nr_of_lines
|
656
|
+
output_file.write([entry].pack('S>'))
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
last_address = output_file.tell
|
661
|
+
shortcut_space = last_address - shortcut_start_address
|
662
|
+
polygon_space = nr_of_floats * 4
|
663
|
+
|
664
|
+
puts("the shortcuts make up #{((shortcut_space / last_address) * 100).round(2) }% of the file")
|
665
|
+
puts("the polygon data makes up #{((polygon_space / last_address) * 100)}.round(2)% of the file")
|
666
|
+
|
667
|
+
puts('Success!')
|
668
|
+
end
|
669
|
+
|
670
|
+
<<EOT
|
671
|
+
Data format in the .bin:
|
672
|
+
IMPORTANT: all coordinates (floats) are converted to int32 (multiplied by 10^7). This makes computations much faster
|
673
|
+
and it takes lot less space, without loosing too much accuracy (min accuracy is 1cm still at the equator)
|
674
|
+
|
675
|
+
no of rows (= no of polygons = no of boundaries)
|
676
|
+
approx. 28k -> use 2byte unsigned short (has range until 65k)
|
677
|
+
'!H' = n
|
678
|
+
|
679
|
+
I Address of Shortcut area (end of polygons+1) @ 2
|
680
|
+
|
681
|
+
'!H' n times [H unsigned short: zone number=ID in this line, @ 6 + 2* lineNr]
|
682
|
+
|
683
|
+
'!H' n times [H unsigned short: nr of values (coordinate PAIRS! x,y in long long) in this line, @ 6 + 2n + 2* lineNr]
|
684
|
+
|
685
|
+
'!I'n times [ I unsigned int: absolute address of the byte where the polygon-data of that line starts,
|
686
|
+
@ 6 + 4 * n + 4*lineNr]
|
687
|
+
|
688
|
+
|
689
|
+
|
690
|
+
n times 4 int32 (take up 4*4 per line): xmax, xmin, ymax, ymin @ 6 + 8n + 16* lineNr
|
691
|
+
'!iiii'
|
692
|
+
|
693
|
+
|
694
|
+
[starting @ 6+ 24*n = polygon data start address]
|
695
|
+
(for every line: x coords, y coords:) stored @ Address section (see above)
|
696
|
+
'!i' * amount of points
|
697
|
+
|
698
|
+
360 * NR_SHORTCUTS_PER_LNG * 180 * NR_SHORTCUTS_PER_LAT:
|
699
|
+
[atm: 360* 1 * 180 * 2 = 129,600]
|
700
|
+
129,600 times !H number of entries in shortcut field (x,y) @ Pointer see above
|
701
|
+
|
702
|
+
|
703
|
+
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 !I
|
705
|
+
|
706
|
+
[X = number of filled shortcuts]
|
707
|
+
X times !H * amount Polygon_Nr @ address stored in previous section
|
708
|
+
|
709
|
+
EOT
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
file_converter = TimezoneFinder::FileConverter.new
|
714
|
+
file_converter.parse_polygons_from_json('tz_world.json')
|
715
|
+
file_converter.update_zone_names('timezone_names.rb')
|
716
|
+
file_converter.compile_into_binary('timezone_data.bin')
|