Class: NotamConnector

Inherits:
Object
  • Object
show all
Includes:
ConnectionBase
Defined in:
src/notam_connector.rb

Constant Summary collapse

PARAMS =
{
  key:       { required: true, name: 'KEY', desc: 'ключ авторизации. текстовая комбинация букв и цифр, позволяющая идентифицировать запрос. Наличие параметра в запросе является строго обязательным' },
  request:   { name: 'REQUEST', desc: 'Метод основного запроса', values: %w[NOTAM ROUTE] },
  charset:   { name: 'CHARSET', values: %w[UTF-8 win-1251 KOI8-R KOI8-U], desc: 'кодировка символов в сообщении.' },
  date_from: { name: 'DATEFROM', example: '2020-11-16T04:25:03Z', desc: '(ISO 8601 UTC) начало интересуемого интервала времени. Временная зона — UTC. Опциональный параметр, если отсутствует, то нижнее ограничение не учитывается.' },
  date_to:   { name: 'DATETO', example: '2020-11-16T04:25:03Z', desc: '(ISO 8601 UTC) окончание интересуемого интервала времени. Временная зона — UTC. Опциональный параметр, если отсутствует, то верхнее ограничение не учитывается.' },
  points:    { name: 'POINTS', example: 'XXXX XXXX', desc: 'список маршрутных точек и аэродромов, через которые осуществляется полет. Точки/Аэродромы указываются в кодах ICAO (четырехбуквенный, маска кода XXXX). Коды Точек/Аэродромов друг от друга отделяются пробелом. Вместо кодов ICAO можно использовать ключевое слово ALL. Данное слово указывает серверу выполнить выгрузку всей имеющейся БД НОТАМ. ' },
  n_points:  { name: 'POINTS', example: 'SNNNN/YYAAAA SNNNN/YYAAAA', desc: 'список НОТАМ, где S – серия НОТАМ, NNNN – номер НОТАМ, YY – год НОТАМ, AAAA – четырехбуквенный код аэродрома (поле А).' },
  format:    { name: 'FORMAT', values: %w[NOTAM TEXT PIB BRIEFING RESTRICTION AIRWAYS CLOSED REGLAMENT TEMPORARY SNOWTAM RECEIVED DANGERAREA DB DB+ SITE OBSTACLE], desc: 'формат представления сообщений. Опционально, если не указан, то сообщения передаются в формате NOTAM.' },
  options:   { name: 'OPTIONS', type: Array[String], desc: 'Дополнительные параметры ', values: {
    '': '',
    ADDNOTAM: 'добавляет текст НОТАМ',
    LISTID: 'возврящает только номера НОТАМ',
    ADDGEOMETRY: 'добавляет геометрию к ограничениям НОТАМ',
  }},
  # request: 'ROUTE REQUEST' specific
  dep:         { name: 'DEP', example: 'XXXX XXXX', desc: 'Аэропорт вылета' },
  arr:         { name: 'ARR', example: 'XXXX XXXX', desc: 'Аэропорт посадки' },
  alt:         { name: 'ALT', example: 'XXXX XXXX', desc: 'Запасные аэродромы' },
  aircraft:    { name: 'AIRCRAFT', example: 'A320 RA-12345', desc: 'Один тип воздушного судна и регистрационный номер' },
  route:       { name: 'ROUTE', example: '413224N0624236E 413648N0674155E 423314N0685725E', desc: 'Маршрут' },
  airways:     { name: 'AIRWAYS', example: 'L412 G313', desc: 'Авиатрассы' },
  ralt:        { name: 'RALT', example: 'XXXX XXXX', desc: 'Аэродромы на маршруте (RLAT,TALT)' },
  flightlevel: { name: 'FLIGHTLEVEL', example: '270 100', desc: 'Эшелон' },
  composition:   { name: 'COMPOSITION', example: 'ALL', desc: 'Состав' },

  lang:      { name: 'LANG', values: %w[RU EN RU+EN EN+RU ALL], desc: 'язык НОТАМОВ. RU+EN/EN+RU (только для формата PIB)' },
  type:      { name: 'TYPE', values: %w[AIP	CAI MVL MO OMIT], desc: 'база данных, по которой требуется выполнить запрос.В зависимости от объема информации (полю POINTS присвоено значения ALL) параметр может игнорироваться и в ответ включаются все НОТАМ на заданном языке.' },
  compress:  { name: 'COMPRESS', desc: '(всегда GZIP) флаг, указывающий, сжимать-ли ответ.', values: {
                 TEXT: 'текст ответа не подвергается обработке',
                 ZIP: 'ответ приходит в виде zip-архива. Текст NOTAM/PIB содержится в файле notam.txt/notam.pib',
                 COMPRESS: 'для сжатия/распаковки используются компоненты qCompress/qUncompress',
                 GZIP: 'ответ приходит в виде gzip-архива'
               }}
}
ERRORS =
{
  0 =>	'The FROM is empty',
  1 =>	'FROM: found a non-numeric character',
  2 =>	'FROM: month out of range',
  3 =>	'FROM: day out of range',
  4 =>	'FROM: hours out of range',
  5 =>	'FROM: minutes out of range',
  6 =>	'The TO is empty',
  7 =>	'TO: found a non-numeric character',
  8 =>	'TO: month out of range',
  9 =>	'TO: day out of range',
  10 =>	'TO: hours out of range',
  11 =>	'TO: minutes out of range',
  12 =>	'Interval boundaries are set incorrectly',
  13 =>	'List of points is empty',
  14 =>	'Points must be described with four characters',
  15 =>	'Non-alphabetic character found in points',
  16 =>	'Format not specified',
  17 =>	'Language not specified',
  18 =>	'Information type not specified',
  19 =>	'Charset not specified',
  20 => 'Compress not specified',
  21 =>	'Invalid Format',
  22 =>	'Invalid Language',
  23 =>	'Invalid Information type',
  24 =>	'Invalid Compress',
  25 =>	'Access is denied',
  26 => "Departure airport not specified",
  27 => "Arrival airport not specified",
  28 => "",
  29 => "DEP must be described with four characters",
  30 => "Non-alphabetic character found in DEP",
  31 => "",
  32 => "ARR must be described with four characters",
  33 => "Non-alphabetic character found in ARR",
  34 => "List of ALT is empty",
  35 => "Each ALT must be described by four characters",
  36 => "Non-alphabetic character found in ALT",
  37 => "",
  38 => "Each RALT must be described by four characters",
  39 => "Non-alphabetic character found in RALT",
  40 => "Flight route not specified",
  41 => "The coordinate description format is broken",
  42 => "Duplicate codes detected",
  43 => "There is no guide to ATC zones",
  44 => "The flight level is not set or set incorrectly",
  45 => "The composition of the PIB is not specified",
  46 => "The base language for messages is not defined",
  47 => "Unknown aircraft type"
}

Instance Method Summary collapse

Methods included from ConnectionBase

#broadcast, #init, #init_connect, #init_core, #shutdown

Constructor Details

#initialize(server, key) ⇒ NotamConnector

Returns a new instance of NotamConnector.



96
# File 'src/notam_connector.rb', line 96

def initialize(server, key)=(@server = server; @key = key)

Instance Method Details

#query(params) ⇒ Object



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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'src/notam_connector.rb', line 98

def query(params)
  params[:date_from] ||= Time.now.utc
  params[:date_to] ||= Time.parse("2099-12-31").utc
  params[:compress] = 'GZIP'

  # ISO 8601 UTC
  params[:date_from] = Time.parse(params[:date_from]) if params[:date_from].is_a? String
  params[:date_to] = Time.parse(params[:date_to]) if params[:date_to].is_a? String

  # Notam server time format
  params[:date_from] = params[:date_from].strftime('%Y%m%d-%H%M%S') # '20210910-144300'
  params[:date_to] = params[:date_to].strftime('%Y%m%d-%H%M%S') # '20210910-230000'

  if(params[:type] == 'OMIT')
    params.delete(:type)
  end

  request = [
    "#{params[:request]} REQUEST", # 'NOTAM REQUEST', # Первая строка заголовка - «NOTAM REQUEST\n»
    "CHARSET #{params[:charset]}", # Вторая строка Заголовка - «CHARSET <encoding>\n»
    '', # Символ окончания заголовка «\n» - пустая строка.
    "KEY #{@key}"
  ]
  otl_current_span do
    span_context = OpenTelemetry::Trace.current_span.context
    trace_id = span_context.trace_id.unpack1('H*')
    span_id = span_context.span_id.unpack1('H*')
    trace_flags = format('%02x', span_context.trace_flags.instance_eval{ @flags }) # Two-digit hex for trace flags (e.g., sampled)
    traceparent = "00-#{trace_id}-#{span_id}-#{trace_flags}"

    request << "TRACEPARENT #{traceparent}"
  end
  request += params.except(:charset, :key, :parse, :request)
                   .transform_values { |v| v.is_a?(Array) ? v.join(' ') : v }
                   .map { |n, v|
                     raise "parameter not found: #{n.to_sym}" unless PARAMS[n.to_sym]
                     "#{PARAMS[n.to_sym][:name]} #{v}"
                   }
  request = "#{request.join("\n")}\n"
  otl_span(:request, request:) do |span|
    ip,port = @server.split(':')
    init(ip, port, :connect) do |task|
      # Первые 4 байта: размер запроса, порядок байт - обратный
      (@current_stream << [request.size].pack('L<') << request).flush

      # Если в запросе не найдено ошибок, то возвращается -1.
      code = @current_stream.read_exactly(4).unpack1('l<')
      raise [code, ERRORS[code]].to_s unless code == -1

      size = @current_stream.read_exactly(4).unpack1('L<')
      messages = @current_stream.read_exactly(size).force_encoding(Encoding::UTF_8)
      span&.add_event("response received", attributes: { event: 'Success', message: "messages size:#{messages.size}" }.transform_keys(&:to_s) )
      begin
        if params[:compress] == 'GZIP' && !messages.empty?
          gz = Zlib::GzipReader.new StringIO.new(messages)
          messages = gz.read
        end
      rescue =>e
        span&.add_event("GZIP exception", attributes: { event: 'Error', message: "messages:#{messages[0..10000]}" }.transform_keys(&:to_s) )
        raise
      end
      span&.add_event("response un Ziped", attributes: { event: 'Success', message: "messages size:#{messages.size}" }.transform_keys(&:to_s) )

      response = NotamFormatter.format messages, params[:format], params[:request]
      span&.add_event("response formatted", attributes: { event: 'Success', message: "messages size:#{messages.size}" }.transform_keys(&:to_s) )
      if params[:parse] == 'Y'
        Parser.map_messages! response[:messages], params[:format]
        span&.add_event("response parsed", attributes: { event: 'Success', message: "messages size:#{messages.size}" }.transform_keys(&:to_s) )
      end
      response
    end
  rescue =>e
    otl_current_span { _1.add_attributes(response:) } rescue nil
    raise
  end
end