class TimezoneFinder::Helpers

Public Class Methods

all_the_same(pointer, length, id_list) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

: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

# 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) click to toggle source
# File lib/timezone_finder/helpers.rb, line 293
def self.coord2int(double)
  (double * 10**7).to_i
end
coords2cartesian(lng_rad, lat_rad) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

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

# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

Ruby original like numpy.fromfile

# 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) click to toggle source

: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

# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

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]
# 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) click to toggle source
# File lib/timezone_finder/helpers.rb, line 186
def self.radians(x)
  x * Math::PI / 180.0
end
x_rotate(rad, point) click to toggle source
# 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) click to toggle source
# 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