Class: SGS::Navigate

Inherits:
Object
  • Object
show all
Defined in:
lib/sgs/navigate.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mission) ⇒ Navigate

Initialize the navigational parameters



56
57
58
59
60
# File 'lib/sgs/navigate.rb', line 56

def initialize(mission)
  @mission = mission
  @course = nil
  @swing = 45
end

Instance Attribute Details

#courseObject (readonly)

Returns the value of attribute course.



52
53
54
# File 'lib/sgs/navigate.rb', line 52

def course
  @course
end

#gpsObject (readonly)

Returns the value of attribute gps.



52
53
54
# File 'lib/sgs/navigate.rb', line 52

def gps
  @gps
end

#ottoObject (readonly)

Returns the value of attribute otto.



52
53
54
# File 'lib/sgs/navigate.rb', line 52

def otto
  @otto
end

#waypointObject (readonly)

What is the next waypoint?



322
323
324
# File 'lib/sgs/navigate.rb', line 322

def waypoint
  @waypoint
end

Instance Method Details

#active?Boolean

Check we’re active - basically, are there any more waypoints left?

Returns:

  • (Boolean)


186
187
188
# File 'lib/sgs/navigate.rb', line 186

def active?
  @mission.status.current_waypoint < @mission.attractors.count
end

#compute_bearings(waypoints) ⇒ Object

Compute the bearing for every attractor or repellor



154
155
156
157
158
# File 'lib/sgs/navigate.rb', line 154

def compute_bearings(waypoints)
  waypoints.each do |waypt|
    waypt.compute_bearing(@gps.location)
  end
end

#compute_new_courseObject

Compute a new course based on our position and other information.



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
108
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
# File 'lib/sgs/navigate.rb', line 80

def compute_new_course
  #
  # Update our local copy of the course based on what Otto says.
  puts "Compute new course..."
  unless @course
    #
    # First time through, the current course is whichever way the boat
    # is pointing.
    @course = Course.new
    @course.heading = @otto.compass
  end
  #
  # Really it's the AWA we're interested in, not the boat heading.
  @course.awa = @otto.awa
  @course.compute_wind
  p @course
  #
  # First off, compute distance and bearing from our current location
  # to every attractor and repellor. We only look at forward attractors,
  # not ones behind us.
  compute_bearings(@mission.attractors[@mission.status.current_waypoint..-1])
  compute_bearings(@mission.repellors)
  #
  # Right. Now look to see if we've achieved the current waypoint and
  # adjust, accordingly
  while active? and reached?
    next_waypoint!
  end
  return true unless active?
  puts "Angle to next waypoint: #{@waypoint.bearing.angle_d}d"
  puts "Adjusted distance to waypoint is #{@waypoint.distance}"
  #
  # Now, start the vector field analysis by examining headings either side
  # of the bearing to the waypoint.
  best_course = @course
  best_relvmg = 0.0
  puts "Currently on a #{@course.tack_name} tack (heading is #{@course.heading_d} degrees)"
  (-@swing..@swing).each do |alpha_d|
    new_course = Course.new(@course.wind)
    new_course.heading = waypoint.bearing.angle + Bearing.dtor(alpha_d)
    #
    # Ignore head-to-wind cases, as they're pointless. When looking at
    # the list of waypoints to compute relative VMG, only look to the next
    # three or so waypoints.
    next if new_course.speed < 0.001
    relvmg = 0.0
    relvmg = new_course.relative_vmg(@mission.attractors[@mission.status.current_waypoint])
    end_wpt = @mission.status.current_waypoint + 3
    if end_wpt >= @mission.attractors.count
      end_wpt = @mission.attractors.count - 1
    end
    @mission.attractors[@mission.status.current_waypoint..end_wpt].each do |waypt|
      relvmg += new_course.relative_vmg(waypt)
    end
    @mission.repellors.each do |waypt|
      relvmg -= new_course.relative_vmg(waypt)
    end
    relvmg *= 0.1 if new_course.tack != @course.tack
    if relvmg > best_relvmg
      best_relvmg = relvmg
      best_course = new_course
    end
  end
  puts "Best course: AWA: #{best_course.awa_d} degrees, Course: #{best_course.heading_d} degrees, Speed: #{best_course.speed} knots"
  p best_course
  if best_course.tack != @course.tack
    puts "TACKING!!!!"
  end
  @course = best_course
  return false
end

#curposObject

What is our current position?



316
317
318
# File 'lib/sgs/navigate.rb', line 316

def curpos
  @curpos ||= GPS.load
end

#elapsedObject

How long has the mission been active?



180
181
182
# File 'lib/sgs/navigate.rb', line 180

def elapsed
  @time - @start_time
end

#missionObject

Navigate the mission. This is the main “meat and potatoes” navigation. It concerns itself with finding the best route to the next mark and sailing to that



301
302
# File 'lib/sgs/navigate.rb', line 301

def mission
end

#mission_abortObject

The mission is aborted. Determine what to do next



311
312
# File 'lib/sgs/navigate.rb', line 311

def mission_abort
end

#mission_endObject

The mission has ended - sail to the rendezvous point



306
307
# File 'lib/sgs/navigate.rb', line 306

def mission_end
end

Compute the best heading based on our current position and the position of the current attractor. This is where the heavy-lifting happens. Returns TRUE if we’re done.



66
67
68
69
70
71
72
73
74
75
76
# File 'lib/sgs/navigate.rb', line 66

def navigate
  if @mission.status.current_waypoint == -1
    @mission.status.current_waypoint = 0
    @mission.status.distance = 0
  end
  set_waypoint
  puts "Attempting to navigate to #{@waypoint}..."
  pull_gps_data
  pull_otto_data
  return compute_new_course
end

#next_waypoint!Object

Advance to the next waypoint. Return TRUE if there actually is one…



215
216
217
218
219
# File 'lib/sgs/navigate.rb', line 215

def next_waypoint!
  @mission.status.current_waypoint += 1
  puts "Attempting to navigate to new waypoint: #{waypoint}"
  set_waypoint
end

#olympic_courseObject

Navigate around an olympic triangle. Sail one nautical mile upwind of the current position, then sail to a point to the left-side of the course which is at an angle of 120 degrees to the wind. From there, sail back to the start position



294
295
# File 'lib/sgs/navigate.rb', line 294

def olympic_course
end

#overall_distanceObject

Compute the remaining distance from the current location



246
247
248
249
250
251
252
253
254
255
# File 'lib/sgs/navigate.rb', line 246

def overall_distance
  dist = 0.0
  loc = @where
  @mission.attractors[@mission.status.current_waypoint..-1].each do |wpt|
    wpt.compute_bearing(loc)
    dist += wpt.bearing.distance
    loc = wpt.location
  end
  dist
end

#pull_gps_dataObject

Pull the latest GPS data. Failure is not an option.



259
260
261
262
263
264
265
266
267
# File 'lib/sgs/navigate.rb', line 259

def pull_gps_data
  loop do
    @gps = GPS.load
    puts "GPS: #{@gps}"
    break if @gps.valid?
    puts "Retrying GPS..."
    sleep 1
  end
end

#pull_otto_dataObject

Pull the latest Otto data.



271
272
273
274
275
276
277
278
279
280
# File 'lib/sgs/navigate.rb', line 271

def pull_otto_data
  #
  # Pull the latest Otto data...
  @otto = Otto.load
  puts "OTTO:"
  p @otto
  puts "Compass: #{@otto.compass}"
  puts "AWA: #{@otto.awa}"
  puts "Wind: #{@otto.wind}"
end

#reached?Boolean

Have we reached the waypoint? Note that even though the waypoints have a “reached” circle, we discard the last 10m on the basis that it is within the GPS error.

Returns:

  • (Boolean)


194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/sgs/navigate.rb', line 194

def reached?
  puts "ARE WE THERE YET? (dist=#{@waypoint.distance})"
  p @waypoint
  return true if @waypoint.distance <= 0.0054
  #
  # Check to see if the next WPT is nearer than the current one
  #if current_wpt < (@mission.attractors.count - 1)
  #  next_wpt = @mission.attractors[@current_wpt + 1]
  #  brng = @mission.attractors[@current_wpt].location - next_wpt.location
  #  angle = Bearing.absolute(waypoint.bearing.angle - next_wpt.bearing.angle)
  #  return true if brng.distance > next_wpt.distance and
  #                 angle > (0.25 * Math::PI) and
  #                 angle < (0.75 * Math::PI)
  #end
  puts "... Sadly, no."
  return false
end

#set_position(time, loc) ⇒ Object

Set new position



162
163
164
165
166
# File 'lib/sgs/navigate.rb', line 162

def set_position(time, loc)
  @where = loc
  @time = time
  @track << TrackPoint.new(@time, @where)
end

#set_waypointObject

Set the waypoint instance variable based on where we are



223
224
225
# File 'lib/sgs/navigate.rb', line 223

def set_waypoint
  @waypoint = @mission.attractors[@mission.status.current_waypoint]
end

#simulated_movement(how_long = 60) ⇒ Object

Advance the mission by a number of seconds (computing the new location in the process). Fake out the speed and thus the location.



171
172
173
174
175
176
# File 'lib/sgs/navigate.rb', line 171

def simulated_movement(how_long = 60)
  puts "Advancing mission by #{how_long}s"
  distance = @course.speed * how_long.to_f / 3600.0
  puts "Travelled #{distance * 1852.0} metres in that time."
  set_position(@time + how_long, @where + Bearing.new(@course.heading, distance))
end

#status_strObject

Return the mission status as a string



229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/sgs/navigate.rb', line 229

def status_str
  mins = elapsed / 60
  hours = mins / 60
  mins %= 60
  days = hours / 24
  hours %= 24
  str = ">>> #{@time}, "
  if days < 1
    str += "%dh%02dm" % [hours, mins]
  else
    str += "+%dd%%02dh%02dm" % [days, hours, mins]
  end
  str + ": My position is #{@where}"
end

#upwind_downwind_courseObject

Navigate a course up to a windward mark which is one nautical mile upwind of the start position. From there, navigate downwind to the finish position



286
287
# File 'lib/sgs/navigate.rb', line 286

def upwind_downwind_course
end