class SGS::Mission

Handle a specific mission.

Attributes

attractors[RW]
course[RW]
distance[RW]
repellors[RW]
time[RW]
track[RW]
where[RW]

Public Class Methods

load(filename) click to toggle source

Load a new mission from the missions directory.

# File lib/sgs/mission.rb, line 60
def self.load(filename)
  mission = new
  mission.read(File.open(filename))
  mission
end
new() click to toggle source

Create the attractors and repellors as well as the track array and other items. @where is our current TrackPoint, @current_wpt is the waypoint we're working (-1 if none), @course is the heading/speed the boat is on.

# File lib/sgs/mission.rb, line 46
def initialize
  @attractors = []
  @repellors = []
  @track = nil
  @current_wpt = -1
  @start_time = @time = nil
  @where = nil
  @course = Course.new
  @distance = 0
  @swing = 60
end

Public Instance Methods

active?() click to toggle source

On-mission means we have something to do. In other words, we have a waypoint to get to.

# File lib/sgs/mission.rb, line 164
def active?
  @current_wpt >= 0 and @current_wpt < @attractors.count
end
commence(time = nil) click to toggle source

Commence a mission…

# File lib/sgs/mission.rb, line 68
def commence(time = nil)
  @start_time = @time = time || Time.now
  @track = [TrackPoint.new(time, @where)]
  @current_wpt = 0
end
elapsed() click to toggle source

How long has the mission been active?

# File lib/sgs/mission.rb, line 170
def elapsed
  @time - @start_time
end
kml_write(file) click to toggle source

Write the track data as a KML file.

  xml.LineString {
    xml.extrude 1
    xml.tessellate 1
    xml.coordinates wpt.to_kml
  }
}
xml.Placemark {
  xml.styleUrl "#attractorLine"
  xml.LineString {
    xml.extrude 1
    xml.tessellate 1
    xml.coordinates wpt.to_axis_kml
  }
}
# File lib/sgs/mission.rb, line 313
def kml_write(file)
  builder = Nokogiri::XML::Builder.new do |xml|
    xml.kml('xmlns' => 'http://www.opengis.net/kml/2.2',
            'xmlns:gx' => 'http://www.google.com/kml/ext/2.2') {
      xml.Folder {
        xml_line_style(xml, "attractorLine", "0xffcf0000", 4)
        xml_line_style(xml, "repellorLine", "0xff00007f", 4)
        xml_line_style(xml, "trackLine")
        @attractors.each do |wpt|
          xml.Placemark {
            xml.name wpt.name
            xml.styleUrl "#attractorLine"
            xml.Point {
              xml.coordinates wpt.location.to_kml
            }
          }
        end
        @repellors.each do |wpt|
          xml.Placemark {
            xml.name wpt.name
            xml.styleUrl "#repellorLine"
            xml.Point {
              xml.coordinates wpt.location.to_kml
            }
          }
        end
        xml.Placemark {
          xml.name "Track"
          xml.styleUrl "#trackLine"
          xml.GX_Track {
            @track.each do |pt|
              xml.when pt.time.strftime('%Y-%m-%dT%H:%M:%S+00:00')
              end
            @track.each do |pt|
              xml.GX_coord pt.location.to_kml(' ')
            end
          }
        }
      }
    }
  end
  # Requires a hack to get rid of the 'gx:' for the when tag.
  file.puts builder.to_xml.gsub(/GX_/, 'gx:')
end
navigate() click to toggle source

Compute the best heading based on our current position and the position of the current attractor. This is where the heavy-lifting happens

next_waypoint!() click to toggle source

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

# File lib/sgs/mission.rb, line 205
def next_waypoint!
  raise "No mission currently active" unless active?
  @current_wpt += 1
  puts "Attempting to navigate to #{waypoint.name}" if active?
end
overall_distance() click to toggle source

Compute the remaining distance from the current location

# File lib/sgs/mission.rb, line 230
def overall_distance
  start_wpt = active? ? @current_wpt : 0
  dist = 0.0
  loc = @where
  @attractors[start_wpt..-1].each do |wpt|
    wpt.compute_bearing(loc)
    dist += wpt.bearing.distance
    loc = wpt.location
  end
  dist
 end
reached?() click to toggle source

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.

# File lib/sgs/mission.rb, line 184
def reached?
  @distance = @attractors[@current_wpt].distance
  puts "ARE WE THERE YET? (dist=#{@distance})"
  return true if @distance <= 0.0027
  #
  # Check to see if the next WPT is nearer than the current one
  #if @current_wpt < (@attractors.count - 1)
  #  next_wpt = @attractors[@current_wpt + 1]
  #  brng = @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
read(file) click to toggle source

Parse a mission file.

# File lib/sgs/mission.rb, line 244
def read(file)
  file.each do |line|
    unless line =~ /^#/
      args = line.split(':')
      code = args[0]
      loc = Location.parse_str(args[1])
      nrml = Bearing.dtor args[2].to_i
      dist = args[3].to_f
      name = args[4].chomp
      case code
      when /[Xx]/
        @where = loc
        @course.wind = Bearing.new(nrml, dist)
      when /\d/
        @attractors[code.to_i] = Waypoint.new(loc, nrml, dist, name)
      when /[Rr]/
        @repellors << Waypoint.new(loc, nrml, dist, name, true)
      end
    end
  end
  @current_wpt = -1
end
set_position(time, loc) click to toggle source

Set new position

# File lib/sgs/mission.rb, line 145
def set_position(time, loc)
  @where = loc
  @time = time
  @track << TrackPoint.new(@time, @where)
end
simulated_movement(how_long = 60) click to toggle source

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

# File lib/sgs/mission.rb, line 154
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_str() click to toggle source

Return the mission status as a string

# File lib/sgs/mission.rb, line 213
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
terminate() click to toggle source

Terminate a mission.

# File lib/sgs/mission.rb, line 76
def terminate
  puts "***** Mission terminated! *****"
  @current_wpt = -1
end
track_save(filename) click to toggle source

Save the track.

# File lib/sgs/mission.rb, line 293
def track_save(filename)
  kml_write(File.open(filename, 'w'))
end
waypoint() click to toggle source

Return the current waypoint.

# File lib/sgs/mission.rb, line 176
def waypoint
  active? ? @attractors[@current_wpt] : nil
end
write(filename) click to toggle source

Write a mission to a file.

# File lib/sgs/mission.rb, line 269
def write(filename)
  File.open(filename, 'w') do |file|
    file.puts "#\n# My starting position."
    file.puts ["X", @where.to_s, @course.wind.angle_d.to_i, @course.wind.distance.to_i, "Starting position"].join(':')
    file.puts "#\n# Attractors."
    @attractors.each_with_index do |wpt, i|
      file.puts "%d:%s:%d:%f:%s" % [i,
                                    wpt.location,
                                    wpt.normal_d,
                                    wpt.radius,
                                    wpt.name]
    end
    file.puts "#\n# Repellors."
    @repellors.each do |wpt|
      file.puts "r:%s:%d:%f:%s" % [wpt.location,
                                    wpt.normal_d,
                                    wpt.radius,
                                    wpt.name]
    end
  end
end
xml_line_style(xml, label, color = "0xffffffff", width = 1) click to toggle source

Do a line style. The colour is of the form aabbggrr for some unknown reason…

# File lib/sgs/mission.rb, line 361
def xml_line_style(xml, label, color = "0xffffffff", width = 1)
  xml.Style(:id => label) {
    xml.LineStyle {
      xml.color color
      xml.width width
      xml.GX_labelVisibility 1
    }
  }
end