code-lever/spektrum-log

View on GitHub
lib/spektrum/log/file.rb

Summary

Maintainability
B
5 hrs
Test Coverage
module Spektrum
  module Log

    class File

      attr_reader :records, :flights

      # Determines if the file at the given URI is a Spektrum telemetry log file.
      #
      # @param uri URI to file to read
      # @return [Spektrum::Log::File] loaded file if the file is a Spektrum log file, nil otherwise
      def self.spektrum?(uri)
        File.new(uri) rescue nil
      end

      def initialize(uri)
        headers = []
        headers_complete = false
        records = []
        @flights = []

        first_word = true

        file = BufferedFile.new(uri, 'rb')
        loop do
          first4 = file.read(4)

          # quick check to see if this could even be a Spektrum TLM file
          if first_word
            if first4.nil? || (0xFFFFFFFF != first4.unpack('V')[0])
              raise ArgumentError, 'File does not appear to be a Spektrum log'
            end
            first_word = false
          end

          if first4.nil?
            if headers_complete || !records.empty?
              # we have records, this is a new entry
              @flights << Flight.new(headers, records)
              headers = []
              records = []
            end
            break
          end

          first = first4.unpack('V')[0]
          if 0xFFFFFFFF == first
            if headers_complete || !records.empty?
              # we have records, this is a new entry
              @flights << Flight.new(headers, records)
              headers = []
              records = []
              headers_complete = false
            end

            rest = file.read(32)
            headers << Headers.create(rest)

            headers_complete = rest.unpack('S')[0] == 0x1717
          else
            data = file.read(16)
            type = data[0].unpack('C')[0]
            records << Records.create(type, first, data)
          end
        end
      rescue => e
        raise ArgumentError, "File does not appear to be a Spektrum log (#{e})"
      ensure
        file.close
      end

      # Gets the total duration of all flights contained within.
      #
      # @return [Float] total duration of all flights, in seconds
      def duration
        @flights.map(&:duration).reduce(&:+)
      end

      # Determines if KML methods can be called for this file.
      #
      # @return [Boolean] true if KML can be generated for this file, false otherwise
      def to_kml?
        @flights.any?(&:to_kml?)
      end

      # Converts the file into a KML document containing placemarks for each
      # flight containing GPS data.
      #
      # @param options [Hash] hash containing options for file
      # @return [String] KML document for all applicable flights in the file
      # @see #to_kml_file file options
      def to_kml(options = {})
        raise RuntimeError, 'No coordinates available for KML generation' unless to_kml?
        to_kml_file(options).render
      end

      # Converts the file into a KMLFile containing placemarks for each flight containing
      # GPS data.
      #
      # @param options [Hash] hash containing options for file
      # @option options [String] :name name option of KML::Document
      # @option options [String] :description name option of KML::Document
      # @return [KMLFile] file for the flight
      def to_kml_file(options = {})
        raise RuntimeError, 'No coordinates available for KML generation' unless to_kml?
        options = apply_default_file_options(options)

        style = 'kmlfile-style-id'
        kml_flights = @flights.select(&:to_kml?)
        marks = kml_flights.each_with_object({ :style_url => "##{style}" }).map(&:to_kml_placemark)

        kml = KMLFile.new
        kml.objects << KML::Document.new(
          :name => options[:name],
          :description => options[:description],
          :styles => [
            KML::Style.new(
              :id => style,
              :line_style => KML::LineStyle.new(:color => '7F00FFFF', :width => 4),
              :poly_style => KML::PolyStyle.new(:color => '7F00FF00')
            )
          ],
          :features => marks
        )
        kml
      end

      private

      def apply_default_file_options(options)
        options = { :name => 'Spektrum TLM GPS Path' }.merge(options)
        options = { :description => 'Flight paths for GPS telemetry data' }.merge(options)
        options
      end

    end

  end
end