Class: TimezoneFinder::Helpers
- Inherits:
-
Object
- Object
- TimezoneFinder::Helpers
- Defined in:
- lib/timezone_finder/helpers.rb
Class Method Summary collapse
- .all_the_same(pointer, length, id_list) ⇒ Object
- .cartesian2coords(x, y, z) ⇒ Object
- .cartesian2rad(x, y, z) ⇒ Object
-
.compute_min_distance(lng_rad, lat_rad, p0_lng, p0_lat, pm1_lng, pm1_lat, p1_lng, p1_lat) ⇒ Object
:param lng_rad: lng of px in radians :param lat_rad: lat of px in radians :param p0_lng: lng of p0 in radians :param p0_lat: lat of p0 in radians :param pm1_lng: lng of pm1 in radians :param pm1_lat: lat of pm1 in radians :param p1_lng: lng of p1 in radians :param p1_lat: lat of p1 in radians :return: shortest distance between pX and the polygon section (pm1—p0—p1) in radians.
- .coord2int(double) ⇒ Object
- .coords2cartesian(lng_rad, lat_rad) ⇒ Object
- .degrees(x) ⇒ Object
-
.distance_to_point_on_equator(lng_rad, lat_rad, lng_rad_p1) ⇒ Object
uses the simplified haversine formula for this special case (lat_p1 = 0) :param lng_rad: the longitude of the point in radians :param lat_rad: the latitude of the point :param lng_rad_p1: the latitude of the point1 on the equator (lat=0) :return: distance between the point and p1 (lng_rad_p1,0) in km this is only an approximation since the earth is not a real sphere.
- .distance_to_polygon(lng_rad, lat_rad, nr_points, points) ⇒ Object
- .distance_to_polygon_exact(lng_rad, lat_rad, nr_points, points, trans_points) ⇒ Object
-
.fromfile(file, unsigned, byte_width, count) ⇒ Object
Ruby original like numpy.fromfile.
-
.haversine(lng_p1, lat_p1, lng_p2, lat_p2) ⇒ Object
:param lng_p1: the longitude of point 1 in radians :param lat_p1: the latitude of point 1 in radians :param lng_p2: the longitude of point 1 in radians :param lat_p2: the latitude of point 1 in radians :return: distance between p1 and p2 in km this is only an approximation since the earth is not a real sphere.
- .inside_polygon(x, y, coords) ⇒ Object
- .int2coord(int32) ⇒ Object
-
.position_to_line(x, y, x1, x2, y1, y2) ⇒ Object
tests if a point pX(x,y) is Left|On|Right of an infinite line from p1 to p2 Return: -1 for pX left of the line from! p1 to! p2 0 for pX on the line [is not needed] 1 for pX right of the line this approach is only valid because we already know that y lies within ]y1;y2].
- .radians(x) ⇒ Object
- .x_rotate(rad, point) ⇒ Object
- .y_rotate(rad, point) ⇒ Object
Class Method Details
.all_the_same(pointer, length, id_list) ⇒ Object
167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/timezone_finder/helpers.rb', line 167 def self.all_the_same(pointer, length, id_list) # List mustn't be empty or Null # There is at least one element = id_list[pointer] pointer += 1 while pointer < length return -1 if element != id_list[pointer] pointer += 1 end element end |
.cartesian2coords(x, y, z) ⇒ Object
194 195 196 |
# File 'lib/timezone_finder/helpers.rb', line 194 def self.cartesian2coords(x, y, z) [degrees(Math.atan2(y, x)), degrees(Math.asin(z))] end |
.cartesian2rad(x, y, z) ⇒ Object
182 183 184 |
# File 'lib/timezone_finder/helpers.rb', line 182 def self.cartesian2rad(x, y, z) [Math.atan2(y, x), Math.asin(z)] end |
.compute_min_distance(lng_rad, lat_rad, p0_lng, p0_lat, pm1_lng, pm1_lat, p1_lng, p1_lat) ⇒ Object
:param lng_rad: lng of px in radians :param lat_rad: lat of px in radians :param p0_lng: lng of p0 in radians :param p0_lat: lat of p0 in radians :param pm1_lng: lng of pm1 in radians :param pm1_lat: lat of pm1 in radians :param p1_lng: lng of p1 in radians :param p1_lat: lat of p1 in radians :return: shortest distance between pX and the polygon section (pm1—p0—p1) in radians
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/timezone_finder/helpers.rb', line 249 def self.compute_min_distance(lng_rad, lat_rad, p0_lng, p0_lat, pm1_lng, pm1_lat, p1_lng, p1_lat) # rotate coordinate system (= all the points) so that p0 would have lat_rad=lng_rad=0 (=origin) # z rotation is simply substracting the lng_rad # convert the points to the cartesian coorinate system px_cartesian = coords2cartesian(lng_rad - p0_lng, lat_rad) p1_cartesian = coords2cartesian(p1_lng - p0_lng, p1_lat) pm1_cartesian = coords2cartesian(pm1_lng - p0_lng, pm1_lat) px_cartesian = y_rotate(p0_lat, px_cartesian) p1_cartesian = y_rotate(p0_lat, p1_cartesian) pm1_cartesian = y_rotate(p0_lat, pm1_cartesian) # for both p1 and pm1 separately do: # rotate coordinate system so that this point also has lat_p1_rad=0 and lng_p1_rad>0 (p0 does not change!) rotation_rad = Math.atan2(p1_cartesian[2], p1_cartesian[1]) p1_cartesian = x_rotate(rotation_rad, p1_cartesian) lng_p1_rad = Math.atan2(p1_cartesian[1], p1_cartesian[0]) px_retrans_rad = cartesian2rad(*x_rotate(rotation_rad, px_cartesian)) # if lng_rad of px is between 0 (<-point1) and lng_rad of point 2: # the distance between point x and the 'equator' is the shortest # if the point is not between p0 and p1 the distance to the closest of the two points should be used # so clamp/clip the lng_rad of px to the interval of [0; lng_rad p1] and compute the distance with it temp_distance = distance_to_point_on_equator(px_retrans_rad[0], px_retrans_rad[1], [[px_retrans_rad[0], lng_p1_rad].min, 0].max) # ATTENTION: vars are being reused. p1 is actually pm1 here! rotation_rad = Math.atan2(pm1_cartesian[2], pm1_cartesian[1]) p1_cartesian = x_rotate(rotation_rad, pm1_cartesian) lng_p1_rad = Math.atan2(p1_cartesian[1], p1_cartesian[0]) px_retrans_rad = cartesian2rad(*x_rotate(rotation_rad, px_cartesian)) [ temp_distance, distance_to_point_on_equator(px_retrans_rad[0], px_retrans_rad[1], [[px_retrans_rad[0], lng_p1_rad].min, 0].max) ].min end |
.coord2int(double) ⇒ Object
293 294 295 |
# File 'lib/timezone_finder/helpers.rb', line 293 def self.coord2int(double) (double * 10**7).to_i end |
.coords2cartesian(lng_rad, lat_rad) ⇒ Object
214 215 216 |
# File 'lib/timezone_finder/helpers.rb', line 214 def self.coords2cartesian(lng_rad, lat_rad) [Math.cos(lng_rad) * Math.cos(lat_rad), Math.sin(lng_rad) * Math.cos(lat_rad), Math.sin(lat_rad)] end |
.degrees(x) ⇒ Object
190 191 192 |
# File 'lib/timezone_finder/helpers.rb', line 190 def self.degrees(x) x * 180.0 / Math::PI end |
.distance_to_point_on_equator(lng_rad, lat_rad, lng_rad_p1) ⇒ Object
uses the simplified haversine formula for this special case (lat_p1 = 0) :param lng_rad: the longitude of the point in radians :param lat_rad: the latitude of the point :param lng_rad_p1: the latitude of the point1 on the equator (lat=0) :return: distance between the point and p1 (lng_rad_p1,0) in km this is only an approximation since the earth is not a real sphere
224 225 226 227 |
# File 'lib/timezone_finder/helpers.rb', line 224 def self.distance_to_point_on_equator(lng_rad, lat_rad, lng_rad_p1) # 2* for the distance in rad and * 12742 (mean diameter of earth) for the distance in km 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)) end |
.distance_to_polygon(lng_rad, lat_rad, nr_points, points) ⇒ Object
332 333 334 335 336 337 338 339 340 341 |
# File 'lib/timezone_finder/helpers.rb', line 332 def self.distance_to_polygon(lng_rad, lat_rad, nr_points, points) min_distance = 40_100_000 (0...nr_points).each do |i| min_distance = [min_distance, haversine(lng_rad, lat_rad, radians(int2coord(points[0][i])), radians(int2coord(points[1][i])))].min end min_distance end |
.distance_to_polygon_exact(lng_rad, lat_rad, nr_points, points, trans_points) ⇒ Object
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/timezone_finder/helpers.rb', line 297 def self.distance_to_polygon_exact(lng_rad, lat_rad, nr_points, points, trans_points) # transform all points (long long) to coords (0...nr_points).each do |i| trans_points[0][i] = radians(int2coord(points[0][i])) trans_points[1][i] = radians(int2coord(points[1][i])) end # check points -2, -1, 0 first pm1_lng = trans_points[0][0] pm1_lat = trans_points[1][0] p1_lng = trans_points[0][-2] p1_lat = trans_points[1][-2] min_distance = compute_min_distance(lng_rad, lat_rad, trans_points[0][-1], trans_points[1][-1], pm1_lng, pm1_lat, p1_lng, p1_lat) index_p0 = 1 index_p1 = 2 (0...(((nr_points / 2.0) - 1).ceil.to_i)).each do |_i| p1_lng = trans_points[0][index_p1] p1_lat = trans_points[1][index_p1] min_distance = [min_distance, compute_min_distance(lng_rad, lat_rad, trans_points[0][index_p0], trans_points[1][index_p0], pm1_lng, pm1_lat, p1_lng, p1_lat)].min index_p0 += 2 index_p1 += 2 pm1_lng = p1_lng pm1_lat = p1_lat end min_distance end |
.fromfile(file, unsigned, byte_width, count) ⇒ Object
Ruby original like numpy.fromfile
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/timezone_finder/helpers.rb', line 345 def self.fromfile(file, unsigned, byte_width, count) if unsigned case byte_width when 2 unpack_format = 'S<*' end else case byte_width when 4 unpack_format = 'l<*' when 8 unpack_format = 'q<*' end end unless unpack_format raise "#{unsigned ? 'unsigned' : 'signed'} #{byte_width}-byte width is not supported in fromfile" end file.read(count * byte_width).unpack(unpack_format) end |
.haversine(lng_p1, lat_p1, lng_p2, lat_p2) ⇒ Object
:param lng_p1: the longitude of point 1 in radians :param lat_p1: the latitude of point 1 in radians :param lng_p2: the longitude of point 1 in radians :param lat_p2: the latitude of point 1 in radians :return: distance between p1 and p2 in km this is only an approximation since the earth is not a real sphere
235 236 237 238 |
# File 'lib/timezone_finder/helpers.rb', line 235 def self.haversine(lng_p1, lat_p1, lng_p2, lat_p2) # 2* for the distance in rad and * 12742(mean diameter of earth) for the distance in km 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)) end |
.inside_polygon(x, y, coords) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/timezone_finder/helpers.rb', line 109 def self.inside_polygon(x, y, coords) wn = 0 i = 0 y1 = coords[1][0] # TODO: why start with both y1=y2= y[0]? coords[1].each do |y2| if y1 < y if y2 >= y x1 = coords[0][i - 1] x2 = coords[0][i] # print(long2coord(x), long2coord(y), long2coord(x1), long2coord(x2), long2coord(y1), long2coord(y2), # position_to_line(x, y, x1, x2, y1, y2)) if position_to_line(x, y, x1, x2, y1, y2) == -1 # point is left of line # return true when its on the line?! this is very unlikely to happen! # and would need to be checked every time! wn += 1 end end else if y2 < y x1 = coords[0][i - 1] x2 = coords[0][i] if position_to_line(x, y, x1, x2, y1, y2) == 1 # point is right of line wn -= 1 end end end y1 = y2 i += 1 end y1 = coords[1][-1] y2 = coords[1][0] if y1 < y if y2 >= y x1 = coords[0][-1] x2 = coords[0][0] if position_to_line(x, y, x1, x2, y1, y2) == -1 # point is left of line wn += 1 end end else if y2 < y x1 = coords[0][-1] x2 = coords[0][0] if position_to_line(x, y, x1, x2, y1, y2) == 1 # point is right of line wn -= 1 end end end wn != 0 end |
.int2coord(int32) ⇒ Object
289 290 291 |
# File 'lib/timezone_finder/helpers.rb', line 289 def self.int2coord(int32) int32.fdiv(10**7) end |
.position_to_line(x, y, x1, x2, y1, y2) ⇒ Object
tests if a point pX(x,y) is Left|On|Right of an infinite line from p1 to p2
Return: -1 for pX left of the line from! p1 to! p2
0 for pX on the line [is not needed]
1 for pX right of the line
this approach is only valid because we already know that y lies within ]y1;y2]
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/timezone_finder/helpers.rb', line 10 def self.position_to_line(x, y, x1, x2, y1, y2) if x1 < x2 # p2 is further right than p1 if x > x2 # pX is further right than p2, if y1 > y2 return -1 else return 1 end end if x < x1 # pX is further left than p1 if y1 > y2 # so it has to be right of the line p1-p2 return 1 else return -1 end end x1gtx2 = false else # p1 is further right than p2 if x > x1 # pX is further right than p1, if y1 > y2 # so it has to be left of the line p1-p2 return -1 else return 1 end end if x < x2 # pX is further left than p2, if y1 > y2 # so it has to be right of the line p1-p2 return 1 else return -1 end end # TODO: is not return also accepted if x1 == x2 && x == x1 # could also be equal return 0 end # x1 greater than x2 x1gtx2 = true end # x is between [x1;x2] # compute the x-intersection of the point with the line p1-p2 # delta_y cannot be 0 here because of the condition 'y lies within ]y1;y2]' # NOTE: bracket placement is important here (we are dealing with 64-bit ints!). first divide then multiply! delta_x = ((y - y1) * (x2 - x1).fdiv(y2 - y1)) + x1 - x if delta_x > 0 if x1gtx2 if y1 > y2 return 1 else return -1 end else if y1 > y2 return 1 else return -1 end end elsif delta_x == 0 return 0 else if x1gtx2 if y1 > y2 return -1 else return 1 end else if y1 > y2 return -1 else return 1 end end end end |
.radians(x) ⇒ Object
186 187 188 |
# File 'lib/timezone_finder/helpers.rb', line 186 def self.radians(x) x * Math::PI / 180.0 end |
.x_rotate(rad, point) ⇒ Object
198 199 200 201 202 203 204 |
# File 'lib/timezone_finder/helpers.rb', line 198 def self.x_rotate(rad, point) # Attention: this rotation uses radians! # x stays the same sin_rad = Math.sin(rad) cos_rad = Math.cos(rad) [point[0], point[1] * cos_rad + point[2] * sin_rad, point[2] * cos_rad - point[1] * sin_rad] end |
.y_rotate(rad, point) ⇒ Object
206 207 208 209 210 211 212 |
# File 'lib/timezone_finder/helpers.rb', line 206 def self.y_rotate(rad, point) # y stays the same # this is actually a rotation with -rad (use symmetry of sin/cos) sin_rad = Math.sin(rad) cos_rad = Math.cos(rad) [point[0] * cos_rad + point[2] * sin_rad, point[1], point[2] * cos_rad - point[0] * sin_rad] end |