Class: Angle

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/angle.rb

Overview

Class Angle is a utility class that allows * Angle arithmetic ( +,-,,/,,% and unary +,-) Angle comparison ( <=>, hence, <, >, >=, <=, == ) * Conversion to and from degrees and radians * Conversion to string as radians or DMS format

Direct Known Subclasses

Latitude, Longitude

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(angle = 0, radians = false) ⇒ Angle

Returns a new instance of Angle.

Parameters:

  • angle (#to_f, #to_radians) (defaults to: 0)

    may be anything that has a to_f and to_radians.

  • radians (true, false, :radians) (defaults to: false)

    Angle is in degrees unless radians == true (or set to :radians).



18
19
20
21
22
23
24
25
26
27
# File 'lib/angle.rb', line 18

def initialize(angle = 0, radians = false)
  # assumes that we are getting an angle in degrees.
  @angle = if radians == true || radians == :radians
             angle.to_f # works for String, Fixed, other Angles and for Float.
           elsif angle.instance_of?(Array)
             self.class.decimal_deg(*angle).to_radians
           else
             angle.to_radians # we have a String and Numeric class version of this. Another Angle will work too.
           end
end

Instance Attribute Details

#angleFloat Also known as: value

Returns stored in radians.

Returns:

  • (Float)

    stored in radians



12
13
14
# File 'lib/angle.rb', line 12

def angle
  @angle
end

Class Method Details

.decimal_deg(degrees = 0, minutes = 0, seconds = 0, direction = 'N') ⇒ Float

Class level function that converts 4 arguments into decimal degrees.

Parameters:

  • degrees (Numeric) (defaults to: 0)
  • minutes (Numeric) (defaults to: 0)
  • seconds (Numeric) (defaults to: 0)
  • direction ('N', 'S', 'E', 'W') (defaults to: 'N')

    Optional, as the direction might be specified by a negative value for degrees

Returns:

  • (Float)

    signed decimal degrees.



35
36
37
38
39
40
41
42
43
# File 'lib/angle.rb', line 35

def self.decimal_deg(degrees = 0, minutes = 0, seconds = 0, direction = 'N')
  s = { 'N' => 1, 'S' => -1, 'E' => 1, 'W' => -1, 'n' => 1, 's' => -1, 'e' => 1, 'w' => -1 }
  sign = s[direction]
  sign = 1 if sign.nil? # Assume 'N' or 'E' if the direction is not set.
  # Shouldn't have a negative value for degrees if the direction is specified.
  # I am defaulting to the degrees sign if it is set, otherwise the direction given
  sign = -1 if degrees.sign == -1 || (degrees == 0 && (minutes < 0 || (minutes == 0 && seconds < 0)))
  return sign * (degrees.abs + (minutes / 60.0) + (seconds / 3600.0))
end

.decimal_deg_from_ary(a) ⇒ Float

Class level function that takes an array of [degress,minutes, seconds, direction] or [degrees,minutes,seconds]

Parameters:

  • a (Array)

    Array is expanded and passed as degrees,minutes,seconds,direction to Angle#decimal_deg

Returns:

  • (Float)

    signed decimal degrees.



48
49
50
# File 'lib/angle.rb', line 48

def self.decimal_deg_from_ary(a)
  return self.decimal_deg(*a)
end

.degrees(d = 0) ⇒ Angle

Class level function equivalent to Angle.new(d, false) or just Angle.new(d)

Parameters:

  • d (#to_radians) (defaults to: 0)

    Angle in degrees, to convert to radians, to create new Angle object from.

Returns:



92
93
94
# File 'lib/angle.rb', line 92

def self.degrees(d = 0)
  return self.new(d.to_radians, true)
end

.dms(angle, radians = false) ⇒ Array

Class level utility function to return the angle as deg,min,sec Assumes decimal degress unless radians == true . Nb. * That min will be negative if the angle is negative and deg == 0 * That sec will be negative if the angle is negative and deg == 0 && min == 0

Parameters:

  • angle (#to_f, #to_degrees)

    may be anything that has a to_f and to_radians.

  • radians (true, false, :radians) (defaults to: false)

    Angle is in degrees unless radians == true (or set to :radians).

Returns:

  • (Array)

    of signed deg, min, sec.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/angle.rb', line 59

def self.dms(angle, radians = false)
  angle = angle.to_f # means Strings, Numeric, and anything accepting a #to_f will work.
  angle = angle.to_degrees if radians == true || radians == :radians
  v = angle.abs
  deg = v.floor
  min = ((v - deg) * 60.0).floor
  sec = ((v - deg - (min / 60.0)) * 3600.0)

  if angle < 0
    if deg == 0
      if min == 0
        sec = -sec
      else
        min = -min
      end
    else
      deg = -deg
    end
  end

  return deg, min, sec
end

.radians(r = 0) ⇒ Angle

Class level function equivalent to Angle.new(r, true)

Parameters:

  • r (#to_f) (defaults to: 0)

    Value in radians to create the new Angle class with.

Returns:



85
86
87
# File 'lib/angle.rb', line 85

def self.radians(r = 0)
  return self.new(r.to_f, true) # Nb. self is Angle, be we don't Angle.new, as we want subclasses to return their class, not Angle.
end

Instance Method Details

#%(other) ⇒ Angle, self

Binary mod operator. Can add angles and numbers, or two angles.

Parameters:

Returns:



157
158
159
# File 'lib/angle.rb', line 157

def %(other)
  return self.class.radians(@angle % other)
end

#*(other) ⇒ Angle, self

Binary multiply operator. Can add angles and numbers, or two angles.

Parameters:

Returns:



136
137
138
# File 'lib/angle.rb', line 136

def *(other)
  self.class.radians(@angle * other)
end

#**(other) ⇒ Angle, self

Binary power of operator. Can add angles and numbers, or two angles.

Parameters:

Returns:



143
144
145
# File 'lib/angle.rb', line 143

def **(other)
  return self.class.radians(@angle**other)
end

#+(other) ⇒ Angle, self

Binary addition operator. Can add angles and numbers, or two angles.

Parameters:

Returns:



122
123
124
# File 'lib/angle.rb', line 122

def +(other)
  return self.class.radians(@angle + other)
end

#+@Angle, self

unary +

Returns:

  • (Angle, self)

    equivalent to @angle



109
110
111
# File 'lib/angle.rb', line 109

def +@
  return self.class.radians(@angle) # Nb. Not Angle.new, as we want subclasses to return their class, not Angle.
end

#-(other) ⇒ Angle, self

Binary subtraction operator. Can add angles and numbers, or two angles.

Parameters:

Returns:



129
130
131
# File 'lib/angle.rb', line 129

def -(other)
  return self.class.radians(@angle - other)
end

#-@Angle, self

Unary -

Returns:

  • (Angle, self)

    -@angle



115
116
117
# File 'lib/angle.rb', line 115

def -@
  return self.class.radians(-@angle)
end

#/(other) ⇒ Angle, self

Binary division operator. Can add angles and numbers, or two angles.

Parameters:

Returns:



150
151
152
# File 'lib/angle.rb', line 150

def /(other)
  return self.class.radians(@angle / other)
end

#<=>(other) ⇒ true, false

Provides test for Module Comparable, giving us <,>,<=,>=,== between angles

Parameters:

  • angle (Angle, Float)

    Can be another Angle, or a Numeric value to compare @angle with.

Returns:

  • (true, false)


99
100
101
102
103
104
105
# File 'lib/angle.rb', line 99

def <=>(other)
  @angle <=> if other.instance_of?(Angle)
               other.angle
             else
               other
             end
end

#absFloat

Returns the absolute angle of the angle in radians.

Returns:

  • (Float)

    the absolute angle of the angle in radians



226
227
228
# File 'lib/angle.rb', line 226

def abs
  return @angle.abs
end

#coerce(angle) ⇒ Array

Returns the angle parameter as a Float and the @angle parameter from this class.

Parameters:

Returns:

  • (Array)

    the angle parameter as a Float and the @angle parameter from this class.



216
217
218
# File 'lib/angle.rb', line 216

def coerce(angle)
  return [ Float(angle), @angle ]
end

#reverseFloat

Returns the reverse angle in radians. i.e. angle + PI (or angle + 180 degrees).

Returns:

  • (Float)

    the reverse angle in radians. i.e. angle + PI (or angle + 180 degrees)



237
238
239
240
241
242
# File 'lib/angle.rb', line 237

def reverse
  if (angle = @angle + Math::PI) > Math::PI * 2
    angle -= Math::PI * 2
  end
  return self.class.new(angle, true)
end

#signFixnum

Returns the sign of the angle. 1 for positive, -1 for negative.

Returns:

  • (Fixnum)

    the sign of the angle. 1 for positive, -1 for negative.



221
222
223
# File 'lib/angle.rb', line 221

def sign
  return @angle.sign
end

#strf(fmt = "%d %2m'%2.4s\"") ⇒ String

formated output of the angle. formats are: * %wd output the degrees as an integer. ** where w is 0, 1, 2 or 3 and represents the field width. * 1 is the default, which indicates that at least 1 digit is displayed * 2 indicates that at least 2 digits are displayed. 1 to 9 will be displayed as 01 0 to 09 0 *** 3 indicates that at least 4 digits are displayed. 10 to 99 will be displayed as 010 0 to 099 0

  • %w.pD outputs degrees as a float.

    • p is the number of decimal places.

    • p is the number of decimal places.

  • %wm output minutes as an integer.

    • where the width w is 0, 1 , 2 with similar meaning to %d. p is again the number of decimal places.

  • %w.pM outputs minutes as a float .e.g. 01.125'.

    • p is the number of decimal places.

  • %wW outputs secs/60 as a float without the leading '0.'. Used with %m like this %2m'%4W , to get minute marker before the decimal places. e.g. -37 001'.1167 rather than -37 001.1167'

    • p is the number of decimal places.

  • %w.ps output seconds as a float.

    • where the width w is 1 , 2 with similar meaning to %d. p is again the number of decimal places.

  • %N outputs N if the angle is positive and S if the angle is negative.

  • %E outputs E if the angle is positive and W if the angle is negative.

  • %r outputs Radians

  • %% outputs %

Other strings in the format are printed as is.

Parameters:

  • fmt (String) (defaults to: "%d %2m'%2.4s\"")

    The default format is a signed deg minutes’seconds“ with leading 0’s in the minutes and seconds and 4 decimal places for seconds.

Returns:



288
289
290
291
292
293
294
295
296
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
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/angle.rb', line 288

def strf(fmt = "%d %2m'%2.4s\"")
  tokens = fmt.scan(/%[0-9.]*[%dmsDMNErW]|[^%]*/)
  have_dir = have_dms = false
  tokens.collect! do |t|
    if t[0, 1] == '%' # format
      had_dot = false
      decimals = -1
      width = -1
      format = nil
      t[1..-1].scan(/[0-9]+|\.|[0-9]+|[%dmsDMNErW]/) do |t2|
        case t2
        when /[0-9]+/
          if had_dot
            decimals = t2.to_i
          else
            width = t2.to_i
          end
        when '%', /[Dr]/
          format = t2
        when /[dmsMwW]/
          have_dms = true
          format = t2
        when /[NE]/
          have_dir = true
          format = t2
        when '.'
          had_dot = true
        else
          raise "unknown format character '#{t2}'" # shouldn't be able to get here.
        end
      end
      [ :format, width, decimals, format ]
    else
      [ :filler, t ]
    end
  end

  deg, min, sec = to_dms if have_dms

  s = ''
  tokens.each do |t|
    if t[0] == :format
      case t[3]
      when '%'
        s += '%'
      when 'd'
        s += s_int(deg, t[1], have_dir)
      when 'D'
        s += s_float(@angle.to_deg, t[1], t[2], have_dir)
      when 'm'
        s += s_int(min, t[1], have_dir)
      when 'M'
        s += s_float(min + (sec / 60), t[1], t[2], have_dir)
      when 'W'
        s += s_only_places(sec / 60, t[1])
      when 's'
        s += s_float(sec, t[1], t[2], have_dir)
      when 'r'
        s += s_float(@angle, t[1], t[2], have_dir)
      when 'N'
        s += (@angle < 0 ? 'S' : 'N')
      when 'E'
        s += (@angle < 0 ? 'W' : 'E')
      end
    else
      s += t[1] # the fillers.
    end
  end

  return s
end

#to_bearingFloat

Compass bearings are clockwise, Math angles are counter clockwise.

Returns:

  • (Float)

    angle as compass bearing in radians.



232
233
234
# File 'lib/angle.rb', line 232

def to_bearing
  return self.class.new((Math::PI * 2) - @angle, true)
end

#to_degreesFloat Also known as: to_deg

Returns angle in degrees.

Returns:

  • (Float)

    angle in degrees



162
163
164
# File 'lib/angle.rb', line 162

def to_degrees
  return @angle.to_degrees
end

#to_dmsArray

Returns @angle as decimal_degrees Nb. * That min will be negative if the angle is negative and deg == 0 * That sec will be negative if the angle is negative and deg == 0 && min == 0

Returns:

  • (Array)

    of signed Floats: degrees,minutes,seconds



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/angle.rb', line 182

def to_dms
  d = to_degrees.abs
  deg = d.floor
  min = ((d - deg) * 60).floor
  sec = ((d - deg - (min / 60.0)) * 3600.0)

  if @angle < 0
    if deg == 0
      if min == 0
        sec = -sec
      else
        min = -min
      end
    else
      deg = -deg
    end
  end

  return deg, min, sec
end

#to_iFixnum Also known as: to_int

Returns the angle truncated to an integer, in radians.

Returns:

  • (Fixnum)

    the angle truncated to an integer, in radians.



207
208
209
# File 'lib/angle.rb', line 207

def to_i
  return to_radians.to_i
end

#to_radiansFloat Also known as: to_rad, to_r, to_f

Returns angle in radians.

Returns:

  • (Float)

    angle in radians



171
172
173
# File 'lib/angle.rb', line 171

def to_radians
  return @angle
end

#to_s(fmt = nil) ⇒ String

Returns angle in radians as a string.

Parameters:

  • fmt (String) (defaults to: nil)

    Optional format string passed to Angle#strf

Returns:

  • (String)

    angle in radians as a string.



246
247
248
249
250
# File 'lib/angle.rb', line 246

def to_s(fmt = nil)
  return to_radians.to_s if fmt.nil?

  return strf(fmt)
end