svoop/ogn_client-ruby

View on GitHub
exe/ogn2kml

Summary

Maintainability
Test Coverage
#!/usr/bin/env ruby

require 'bundler/inline'
require 'optparse'

gemfile do
  source 'https://rubygems.org'
  ruby '>= 2.4'
  gem 'builder', '~> 3'
  gem 'ogn_client-ruby', '~> 0.2', require: 'ogn_client'
end

class Converter
  def initialize
    OptionParser.new do |o|
      o.banner = <<~END
        Convert raw OGN APRS to KML tracks.
        Usage: ogn2kml [options] infile
      END
      o.on('-a', '--about', 'author and license information') { puts 'Written by Sven Schwyn (bitcetera.com) and distributed under MIT license.'; exit }
      o.on('-c', '--callsign STRING', String, 'aircraft callsign (e.g. FLRAABBCC)') { |v| @callsign = v }
      o.on('-d', '--date YYYY-MM-DD', String, 'date the APRS messages were recorded (default: today)') { |v| @date = v }
      o.on('-o', '--outfile FILE', String, 'generated KML file (default: INFILE.kml)') { |v| @outfile = v.sub(/\.kml$/, '') + '.kml' }
    end.parse!
    @infile = ARGV.pop
    fail 'infile not found' unless @infile && File.exists?(@infile)
    @outfile ||= @infile.sub(/\.\w+$/, '') + '.kml'
  end

  def convert!
    lines = File.readlines(@infile)
    points = lines.map do |line|
      message = OGNClient::Message.parse(line, date: @date)
      if message.is_a?(OGNClient::SenderBeacon) && (!@callsign || @callsign == message.callsign)
        {
          when: message.time.xmlschema,
          coord: "#{message.longitude} #{message.latitude} #{message.altitude}",
          angles: "#{message.heading} 0 0",
          time: message.time.getlocal.strftime('%H:%M:%S'),
          ground_speed: message.ground_speed.to_i.to_s,
          climb_rate: message.climb_rate.to_i.to_s,
          turn_rate: message.turn_rate.to_i.to_s
        }
      end
    end.compact
    xml = Builder::XmlMarkup.new indent: 2
    xml.instruct!
    xml.kml(
      xmlns: 'http://www.opengis.net/kml/2.2',
      'xmlns:gx': 'http://www.google.com/kml/ext/2.2'
    ) do
      xml.Document do
        xml.name 'OGN'
        # Styles
        xml.Style(id: 'track_normal') do
          xml.IconStyle do
            xml.Icon do
              xml.href 'http://earth.google.com/images/kml-icons/track-directional/track-0.png'
            end
          end
          xml.LineStyle do
            xml.color '99ffac59'
            xml.width '6'
          end
        end
        xml.Style(id: 'track_highlight') do
          xml.IconStyle do
            xml.scale '1.2'
            xml.Icon do
              xml.href 'http://earth.google.com/images/kml-icons/track-directional/track-0.png'
            end
          end
          xml.LineStyle do
            xml.color '99ffac59'
            xml.width '8'
          end
        end
        xml.StyleMap(id: 'track') do
          xml.Pair do
            xml.key 'normal'
            xml.styleUrl '#track_normal'
          end
          xml.Pair do
            xml.key 'highlight'
            xml.styleUrl '#track_highlight'
          end
        end
        # Tracks
        xml.Folder do
          xml.name 'Tracks'
          xml.Placemark do
            xml.name @callsign || 'all'
            xml.styleUrl '#track'
            xml.tag!('gx:Track') do
              xml.altitudeMode 'absolute'
              points.each { |p| xml.when p[:when] }
              points.each { |p| xml.tag!('gx:coord', p[:coord]) }
              points.each { |p| xml.tag!('gx:angles', p[:angles]) }
              xml.ExtendedData do
                xml.SchemaData(schemaUrl: '#schema') do
                  xml.tag!('gx:SimpleArrayData', name: 'Time') do
                    points.each { |p| xml.tag!('gx:value', p[:time]) }
                  end
                  xml.tag!('gx:SimpleArrayData', name: 'Ground Speed') do
                    points.each { |p| xml.tag!('gx:value', p[:ground_speed]) }
                  end
                  xml.tag!('gx:SimpleArrayData', name: 'Turn Rate') do
                    points.each { |p| xml.tag!('gx:value', p[:turn_rate]) }
                  end
                end
              end
            end
          end
        end
      end
    end
    File.write(@outfile, xml.target!)
  end
end

begin
  Converter.new.convert!
#rescue => exception
#  puts "#{File.basename($0)}: #{exception.message}"
#  exit 1
end