├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── .gitignore ├── .ruby-version ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── bin ├── console └── setup ├── certs └── svoop.pem ├── checksums ├── notam-0.1.0.gem.sha512 ├── notam-0.1.1.gem.sha512 ├── notam-0.1.2.gem.sha512 ├── notam-0.1.3.gem.sha512 ├── notam-1.0.0.gem.sha512 ├── notam-1.1.0.gem.sha512 ├── notam-1.1.1.gem.sha512 ├── notam-1.1.2.gem.sha512 ├── notam-1.1.3.gem.sha512 ├── notam-1.1.3.pre1.gem.sha512 ├── notam-1.1.4.gem.sha512 ├── notam-1.1.5.gem.sha512 └── notam-1.1.6.gem.sha512 ├── gems.rb ├── guardfile.rb ├── lib ├── locales │ └── en.yml ├── notam.rb ├── notam │ ├── errors.rb │ ├── item.rb │ ├── item │ │ ├── a.rb │ │ ├── b.rb │ │ ├── c.rb │ │ ├── d.rb │ │ ├── e.rb │ │ ├── f.rb │ │ ├── footer.rb │ │ ├── g.rb │ │ ├── header.rb │ │ └── q.rb │ ├── message.rb │ ├── schedule.rb │ ├── translation.rb │ └── version.rb └── tasks │ ├── fixtures.rake │ └── yard.rake ├── notam.gemspec ├── rakefile.rb ├── sig └── notam.rbs └── spec ├── factory.rb ├── lib └── notam │ ├── item │ ├── a_spec.rb │ ├── b_spec.rb │ ├── c_spec.rb │ ├── d_spec.rb │ ├── e_spec.rb │ ├── f_spec.rb │ ├── footer_spec.rb │ ├── g_spec.rb │ ├── header_spec.rb │ └── q_spec.rb │ ├── item_spec.rb │ ├── message_spec.rb │ ├── schedule_spec.rb │ ├── translation_spec.rb │ └── version_spec.rb └── spec_helper.rb /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: svoop 2 | custom: "https://donorbox.org/bitcetera" 3 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | strategy: 6 | fail-fast: false 7 | matrix: 8 | os: [ubuntu-latest] 9 | ruby: ['3.1', '3.2', '3.3', '3.4'] 10 | name: test (Ruby ${{ matrix.ruby }} on ${{ matrix.os }}) 11 | runs-on: ${{ matrix.os }} 12 | steps: 13 | - name: Check out 14 | uses: actions/checkout@v3 15 | - name: Set up Ruby ${{ matrix.ruby }} 16 | uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: ${{ matrix.ruby }} 19 | bundler-cache: true 20 | - name: Fetch up-to-date NOTAM fixtues for tests 21 | run: bundle exec rake fixtures:fetch 22 | - name: Run tests 23 | run: SPEC_SCOPE=all bundle exec rake 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # direnv 5 | .direnv 6 | .envrc 7 | 8 | # Editors 9 | .nova 10 | .vscode 11 | *.sublime* 12 | 13 | # Ruby 14 | gems.locked 15 | pkg/* 16 | *.gem 17 | .bundle 18 | .yardoc 19 | spec/fixtures/* 20 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Main 2 | 3 | Nothing so far 4 | 5 | ## 1.1.6 6 | 7 | #### Changes 8 | * Update Ruby to 3.4 9 | * Add ignore list for fixtures broken upstream 10 | 11 | ## 1.1.5 12 | 13 | #### Changes 14 | * Add support for Ruby 3.3 15 | 16 | ## 1.1.4 17 | 18 | #### Changes 19 | * Drop obsolete countries gem 20 | 21 | ## 1.1.3 22 | 23 | #### Changes 24 | * Don't wrap years when breaking down date ranges 25 | * Allow HJ and HN in schedules 26 | 27 | ## 1.1.2 28 | 29 | #### Fixes 30 | * Carry over last base date instead of first base date 31 | 32 | ## 1.1.1 33 | 34 | #### Fixes 35 | * Carry over base date for multiple D-item elements which partially omit to 36 | mention the month in every element 37 | 38 | ## 1.1.0 39 | 40 | #### Additions 41 | * Extract subject group and condition group on Q item 42 | 43 | ## 1.0.0 44 | 45 | #### Breaking Changes 46 | * `NOTAM::Schedule.parse` now returns an array of `NOTAM_Schedule` instances 47 | instead of just a single one. 48 | 49 | #### Changes 50 | * Edge case tolerant extraction of `PART n OF n` and `END PART n OF n` markers 51 | 52 | #### Additions 53 | * Support for datetime ranges (i.e. `1 APR 2000-20 MAY 2000`) as well as times 54 | across midnight (i.e. `1 APR 1900-0500`) on D items. 55 | * Wrap all exceptions raised while parsing items. 56 | 57 | ## 0.1.3 58 | 59 | #### Fixes 60 | * Reverse accidentally flipped F and G item. 61 | 62 | ## 0.1.2 63 | 64 | #### Changes 65 | * The five day schedules are calculated starting today if `effective_at` is 66 | in the past. 67 | 68 | ## 0.1.1 69 | 70 | #### Changes 71 | * Update dependency on AIXM gem 72 | 73 | ## 0.1.0 74 | 75 | #### Initial Implementation 76 | * Require Ruby 3.0 77 | * `NOTAM::Message` and `NOTAM::Item` (Header, Q, A-G, Footer) 78 | * `NOTAM::Schedule` with useful tools like `slice` and `resolve` 79 | * Expansion of contractions on E item 80 | * Tests against live NOTAM 81 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Sven Schwyn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Version](https://img.shields.io/gem/v/notam.svg?style=flat)](https://rubygems.org/gems/notam) 2 | [![Tests](https://img.shields.io/github/actions/workflow/status/svoop/notam/test.yml?style=flat&label=tests)](https://github.com/svoop/notam/actions?workflow=Test) 3 | [![Code Climate](https://img.shields.io/codeclimate/maintainability/svoop/notam.svg?style=flat)](https://codeclimate.com/github/svoop/notam/) 4 | [![GitHub Sponsors](https://img.shields.io/github/sponsors/svoop.svg)](https://github.com/sponsors/svoop) 5 | 6 | # NOTAM 7 | 8 | Parser for [NOTAM (Notice to Air Missions)](https://www.icao.int/safety/istars/pages/notams.aspx) messages in Ruby. 9 | 10 | * [Homepage](https://github.com/svoop/notam) 11 | * [API](https://www.rubydoc.info/gems/notam) 12 | * Author: [Sven Schwyn - Bitcetera](https://bitcetera.com) 13 | 14 | Thank you for supporting free and open-source software by sponsoring on [GitHub](https://github.com/sponsors/svoop) or on [Donorbox](https://donorbox.com/bitcetera). Any gesture is appreciated, from a single Euro for a ☕️ cup of coffee to 🍹 early retirement. 15 | 16 | ## Install 17 | 18 | ### Security 19 | 20 | This gem is [cryptographically signed](https://guides.rubygems.org/security/#using-gems) in order to assure it hasn't been tampered with. Unless already done, please add the author's public key as a trusted certificate now: 21 | 22 | ``` 23 | gem cert --add <(curl -Ls https://raw.github.com/svoop/notam/main/certs/svoop.pem) 24 | ``` 25 | 26 | ### Bundler 27 | 28 | Add the following to the `Gemfile` or `gems.rb` of your [Bundler](https://bundler.io) powered Ruby project: 29 | 30 | ```ruby 31 | gem 'notam' 32 | ``` 33 | 34 | And then install the bundle: 35 | 36 | ``` 37 | bundle install --trust-policy MediumSecurity 38 | ``` 39 | 40 | ## Usage 41 | 42 | ```ruby 43 | raw_notam_text_message = <<~END 44 | W0902/22 NOTAMN 45 | Q) LSAS/QRRCA/V/BO/W/000/148/4624N00702E004 46 | A) LSAS PART 2 OF 3 B) 2204110900 C) 2205131400 EST 47 | D) APR 11 SR MINUS15-1900, 20-21 26-28 MAY 03-05 10-12 0530-2100, APR 48 | 14 22 29 MAY 06 13 0530-1400, APR 19 25 MAY 02 09 0800-2100 49 | E) R-AREA LS-R7 HONGRIN ACT DUE TO FRNG. 50 | F) GND 51 | G) 14800FT AMSL 52 | END PART 2 OF 3 53 | CREATED: 11 Apr 2022 06:10:00 54 | SOURCE: LSSNYNYX 55 | END 56 | 57 | notam = NOTAM.parse(raw_notam_text_message) 58 | notam.data # => Hash 59 | ``` 60 | 61 | The resulting hash for this example looks as follows: 62 | 63 | ```ruby 64 | { 65 | id: "W0902/22", 66 | id_series: "W", 67 | id_number: 902, 68 | id_year: 2022, 69 | new?: true, 70 | fir: "LSAS", 71 | subject_group: :airspace_restrictions, 72 | subject: :restricted_area, 73 | condition_group: :changes, 74 | condition: :activated, 75 | traffic: :vfr, 76 | purpose: [:operational_significance, :flight_operations], 77 | scope: [:navigation_warning], 78 | lower_limit: #, 79 | upper_limit: #, 80 | center_point: #, 81 | radius: #, 82 | locations: ["LSAS"], 83 | part_index: 2, 84 | part_index_max: 3, 85 | effective_at: 2022-04-11 09:00:00 UTC, 86 | expiration_at: 2022-05-13 14:00:00 UTC, 87 | estimated_expiration?: false, 88 | no_expiration?: true, 89 | schedules: [ 90 | #, 91 | #, 92 | #, 93 | # 94 | ], 95 | five_day_schedules: [ 96 | #, 97 | # 98 | ], 99 | content: "R-AREA LS-R7 HONGRIN ACT DUE TO FRNG.", 100 | translated_content: "R-AREA LS-R7 HONGRIN ACTIVE DUE TO FRNG.", 101 | created: 2022-04-11 06:10:00 UTC, 102 | source: "LSSNYNYX" 103 | } 104 | ``` 105 | 106 | A few highlights to note here: 107 | 108 | * Value classes of the [AIXM gem](https://rubygems.org/gems/aixm) are used to ease further processing. 109 | * Schedules can be pretty complex, therefore a simpler `five_day_schedule` is calculated for the day the NOTAM becomes effective and the four subsequent days. This short term schedule does not contain exceptions nor events such as sunrises anymore. Furthermore, you can calculate different custom sub-schedules using `slice` and `resolve`. 110 | * Content is processed to `translated_content`. As of now, known english contractions are expanded. Feel free to contribute non-english locale files read by the [I18n gem](https://rubygems.org/gems/i18n). 111 | 112 | Since NOTAM may contain a certain level of redundancy, the parser does some integrity checks, fixes the payload if possible and issues a warning. 113 | 114 | You get a `NOTAM::ParseError` in case the raw NOTAM text message fails to be parsed. This error object features two notable methods: 115 | 116 | * `item` – the faulty item (if already available) 117 | * `cause` – the underlying error object (if any) 118 | 119 | If you're sure the NOTAM is correct, please [submit an issue](#development) or fix the bug and [submit a pull request](#development). 120 | 121 | See the [API documentation](https://www.rubydoc.info/gems/notam) for more. 122 | 123 | ⚠️ Only NOTAM compatible with the ICAO annex 15 are supported for now. Most notably in the USA other NOTAM formats exist which cannot be parsed using this gem. 124 | 125 | ### Anatomy of a NOTAM message 126 | 127 | A NOTAM message consists of the following items in order: 128 | 129 | * Header: ID and type of NOTAM 130 | * [Q item](https://www.rubydoc.info/gems/notam/NOTAM/Q): Essential information such as purpose or center point and radius 131 | * [A item](https://www.rubydoc.info/gems/notam/NOTAM/A): Affected locations 132 | * [B item](https://www.rubydoc.info/gems/notam/NOTAM/B): When the NOTAM becomes effective 133 | * [C item](https://www.rubydoc.info/gems/notam/NOTAM/C): When the NOTAM expires 134 | * [D item](https://www.rubydoc.info/gems/notam/NOTAM/D): Activity schedules (optional) 135 | * [E item](https://www.rubydoc.info/gems/notam/NOTAM/E): Free text description 136 | * [F item](https://www.rubydoc.info/gems/notam/NOTAM/F): Upper limit (optional) 137 | * [G item](https://www.rubydoc.info/gems/notam/NOTAM/G): Lower limit (optional) 138 | * Footer: Any number of lines with metadata such as `CREATED` and `SOURCE` 139 | 140 | Furthermore, oversized NOTAM may be split into several partial messages which contain with `PART n OF n` and `END PART n OF n` markers. This is an unofficial extension and therefore the markers may be found in different places such as on the A item, on the E item or even somewhere in between. 141 | 142 | ### FIR 143 | 144 | Four letter FIR codes assigned by the ICAO follow some logic, albeit there exist exceptions and inconsistencies e.g. for historical reasons. Let's take an easy example: 145 | 146 | ``` 147 | L F M M 148 | ┬ ┬ ─┬─ 149 | │ │ └─ subsection: MM => Marseille 150 | │ └──── geopolitical unit: F => France 151 | └────── global area: L => lower Europe 152 | ``` 153 | 154 | The informal use of only the first two letters often stands for a combination of all subsections contained therein. Example: `LF` is a combination of `LFBB`, `LFEE`, `LFFF`, `LFMM` and `LFRR`. 155 | 156 | FIR codes ending with `XX` specify more than one subsection. Example: `LFXX` can be any combination of at least two subsections within `LF`. In NOTAM, this notation may be used on the Q item if (and only if) the affected subsections are listed on the A item. 157 | 158 | ### Series 159 | 160 | The first letter of the NOTAM ID is identifying the series. The following example is part of series `S`: 161 | 162 | ``` 163 | S0054/02 NOTAMN 164 | ``` 165 | 166 | AIS are free to define series as they please, however, a few conventions have emerged: 167 | 168 | * **Series A**
General rules, en-route navigation and communication facilities, airspace restrictions and activities taking place above FL 245 as well as information concerning major international aerodromes. 169 | * **Series B**
Information on airspace restrictions, on activities taking place at or below FL 245 and on other international aerodromes at which IFR flights are permitted. 170 | * **Series C**
Information on other international aerodromes at which only VFR flights are permitted. 171 | * **Series D**
Information on national aerodromes 172 | * **Series E**
Information on heliports 173 | * **Series S** (aka: SNOWTAM)
Surface condition reports 174 | * **Series T**
Reserved for NOTAM processing units in cases when basic operational information was not triggered by the issuing AIS. 175 | * **Series V** (aka: ASHTAM)
Volcano ash condition reports 176 | 177 | ### Special NOTAM 178 | 179 | #### Checklist 180 | 181 | Checklist NOTAM are periodically issued lists of all currently effective NOTAM. They are used for cross checking and can usually be ignored for flight planning. Their Q item contain `QKKKK` which is decoded as `condition: :checklist`, here's an example: 182 | 183 | ``` 184 | Q) EDXX/QKKKK/K /K /K /000/999/5123N01018E999 185 | ^^^^^ 186 | ``` 187 | 188 | #### Trigger 189 | 190 | Trigger NOTAM are referring to another source of information such as AIP SUP (AIP supplement). Their Q item contain `Q..TT` which is decoded as `condition: :trigger`, here's an example: 191 | 192 | ``` 193 | Q) LFXX/QRTTT/IV/BO /W /000/035/4708N00029E010 194 | ^ ^^ 195 | ``` 196 | 197 | Note: Trigger NOTAM are never published as series `T`. 198 | 199 | ### Schedules 200 | 201 | For compatibility, schedule dates and times are expressed using the corresponding classes from the [AIXM gem](https://rubygems.org/gems/aixm): 202 | 203 | * [Date](https://www.rubydoc.info/gems/aixm/AIXM/Schedule/Date) 204 | * [Day](https://www.rubydoc.info/gems/aixm/AIXM/Schedule/Day) 205 | * [Time](https://www.rubydoc.info/gems/aixm/AIXM/Schedule/Time) 206 | 207 | Raw and parsed NOTAM schedule times differ in how the "end of day" is encoded: 208 | 209 | NOTAM | Beginning of Day | End of Day | Remarks 210 | -------|------------------|------------|-------- 211 | Raw | `"0000"` | `"2359"` | `"2400"` is considered illegal 212 | Parsed | `00:00` | `24:00` | the Ruby way 213 | 214 | ### References 215 | 216 | * [ICAO Doc 8126: Aeronautical Information Services Manual](https://www.icao.int/NACC/Documents/eDOCS/AIM/8126_unedited_en%20Jul2021.pdf) 217 | * [ICAO Doc 10066: Aeronautical Information Management](https://ffac.ch/wp-content/uploads/2020/11/ICAO-Doc-10066-Aeronautical-Information-Management.pdf) 218 | * [EUROCONTROL Guidelines Operating Procedures AIS Dynamic Data (OPADD)](https://www.eurocontrol.int/sites/default/files/2021-07/eurocontrol-guidelines-opadd-ed4-1.pdf) 219 | * [NOTAM Q Codes](https://www.faa.gov/air_traffic/publications/atpubs/notam_html/appendix_b.html) 220 | * [NOTAM Contractions](https://www.notams.faa.gov/downloads/contractions.pdf) 221 | * [NOTAM format cheat sheet](http://vat-air.dk/files/ICAO%20NOTAM%20format.pdf) 222 | * [Introduction on Wikipedia](https://en.wikipedia.org/wiki/NOTAM) 223 | 224 | ## Translations 225 | 226 | You find the translations for each available language in `lib/locales/`. Additional translations are very welcome provided you have sufficient aeronautical background knowledge. 227 | 228 | Please [create a translation request issue](https://github.com/svoop/notam/issues), then duplicate the `lib/locales/en.yml` reference language file and translate it. 229 | 230 | ## Tests and Fixtures 231 | 232 | The test suite may run against live NOTAM depending on whether and how you set the `SPEC_SCOPE` environment variable: 233 | 234 | ``` 235 | export SPEC_SCOPE=none # don't run against any NOTAM fixtures (default) 236 | export SPEC_SCOPE=W0214/22 # run against given NOTAM fixture only 237 | export SPEC_SCOPE=all # run against all NOTAM fixtures 238 | export SPEC_SCOPE=all-fast # run against all NOTAM fixtures but exit on the first failure 239 | ``` 240 | 241 | The NOTAM fixtures are written to `spec/fixtures`, you can manage them using a Rake tasks: 242 | 243 | ``` 244 | rake --tasks fixtures 245 | ``` 246 | 247 | ## Development 248 | 249 | To install the development dependencies and then run the test suite: 250 | 251 | ``` 252 | bundle install 253 | bundle exec rake # run tests once 254 | bundle exec guard # run tests whenever files are modified 255 | ``` 256 | 257 | You're welcome to [submit issues](https://github.com/svoop/notam/issues) and contribute code by [forking the project and submitting pull requests](https://docs.github.com/en/get-started/quickstart/fork-a-repo). 258 | 259 | ## Trivia 260 | 261 | In the early days, the acronym [NOTAM](https://en.wikipedia.org/wiki/NOTAM) ment "Notice to Airmen". As many women roam the skies these days as well, it has finally been redefined as "Notice to Air Missions". 262 | 263 | ## License 264 | 265 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 266 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "notam" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | # (If you use this, don't forget to add pry to your Gemfile!) 11 | # require "pry" 12 | # Pry.start 13 | 14 | require "irb" 15 | IRB.start(__FILE__) 16 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /certs/svoop.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDODCCAiCgAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhydWJ5 3 | L0RDPWJpdGNldGVyYS9EQz1jb20wHhcNMjIxMTA2MTIzNjUwWhcNMjMxMTA2MTIz 4 | NjUwWjAjMSEwHwYDVQQDDBhydWJ5L0RDPWJpdGNldGVyYS9EQz1jb20wggEiMA0G 5 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDcLg+IHjXYaUlTSU7R235lQKD8ZhEe 6 | KMhoGlSUonZ/zo1OT3KXcqTCP1iMX743xYs6upEGALCWWwq+nxvlDdnWRjF3AAv7 7 | ikC+Z2BEowjyeCCT/0gvn4ohKcR0JOzzRaIlFUVInlGSAHx2QHZ2N8ntf54lu7nd 8 | L8CiDK8rClsY4JBNGOgH9UC81f+m61UUQuTLxyM2CXfAYkj/sGNTvFRJcNX+nfdC 9 | hM9r2kH1+7wsa8yG7wJ2IkrzNACD8v84oE6qVusN8OLEMUI/NaEPVPbw2LUM149H 10 | PVa0i729A4IhroNnFNmw4wOC93ARNbM1+LW36PLMmKjKudf5Exg8VmDVAgMBAAGj 11 | dzB1MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBSfK8MtR62mQ6oN 12 | yoX/VKJzFjLSVDAdBgNVHREEFjAUgRJydWJ5QGJpdGNldGVyYS5jb20wHQYDVR0S 13 | BBYwFIEScnVieUBiaXRjZXRlcmEuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAYG2na 14 | ye8OE2DANQIFM/xDos/E4DaPWCJjX5xvFKNKHMCeQYPeZvLICCwyw2paE7Otwk6p 15 | uvbg2Ks5ykXsbk5i6vxDoeeOLvmxCqI6m+tHb8v7VZtmwRJm8so0eSX0WvTaKnIf 16 | CAn1bVUggczVdNoBXw9WAILKyw9bvh3Ft740XZrR74sd+m2pGwjCaM8hzLvrVbGP 17 | DyYhlBeRWyQKQ0WDIsiTSRhzK8HwSTUWjvPwx7SEdIU/HZgyrk0ETObKPakVu6bH 18 | kAyiRqgxF4dJviwtqI7mZIomWL63+kXLgjOjMe1SHxfIPo/0ji6+r1p4KYa7o41v 19 | fwIwU1MKlFBdsjkd 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /checksums/notam-0.1.0.gem.sha512: -------------------------------------------------------------------------------- 1 | c4ccbad3bec58333da3e85d4781c233175f3b4e54ad8680d13fca8130e82e9dc60b3d7b2fdaf04acc92624871d2f43817e7044a0c3d0026d36da284c8281c122 2 | -------------------------------------------------------------------------------- /checksums/notam-0.1.1.gem.sha512: -------------------------------------------------------------------------------- 1 | e7d06369cb5f52ea6da23d4f9720cef62637703dbcd36ac9b84dda4377043ea257e0ae7713d0dd289fdb06a393c34dbe7db1f39d211afaf37dbc45b2019b0ad4 2 | -------------------------------------------------------------------------------- /checksums/notam-0.1.2.gem.sha512: -------------------------------------------------------------------------------- 1 | b27dea37c054e5b6933c8d86009cac4daa4b84f81d449681fc5b8d2eb5ba641669d042970ce71794b787c02b3b3c7932248118a615f5efe4e1265b7114a5a3c9 2 | -------------------------------------------------------------------------------- /checksums/notam-0.1.3.gem.sha512: -------------------------------------------------------------------------------- 1 | cca651057598c4976dad371510306da766e273b64f5b2f03f78ffff8df8faa55d55c81d9700edb75f3dba6c59a65f83b77da73d2ce2dc9a28bf8333af3e6943f 2 | -------------------------------------------------------------------------------- /checksums/notam-1.0.0.gem.sha512: -------------------------------------------------------------------------------- 1 | 7905715f56eda18cc336b26e8e0fb9357dc2f3d39a30ca4fff65ceb4fbedd8bf9a72522e61aeffd083c70177269abc2801e1abbc48414e427ccc59605807449a 2 | -------------------------------------------------------------------------------- /checksums/notam-1.1.0.gem.sha512: -------------------------------------------------------------------------------- 1 | a3ff9b2281b1b89989db7e874ee131a6b929661dd8789ce43370febaf6295d502cb1bd4c87a234a20483954c7fb0b70c9f8894c6a41f061ad8bcc0f95417890e 2 | -------------------------------------------------------------------------------- /checksums/notam-1.1.1.gem.sha512: -------------------------------------------------------------------------------- 1 | 8d7c359fc9ec17a507b4ed0815e6dcc42a33d20e0845d6f095291657524e5a142dc2bba110f42bfa03940172d131b62b75d5ca0addecfa4f64229d2217decb9a 2 | -------------------------------------------------------------------------------- /checksums/notam-1.1.2.gem.sha512: -------------------------------------------------------------------------------- 1 | 06eeaef5647c87944fbf7323b26d8c3241c43dc198f16066a8b6b72d67a4af29b29125f599192ad77673433dd5dc44fc4ef1643a2c5b9647c3654ffd8da6d2f9 2 | -------------------------------------------------------------------------------- /checksums/notam-1.1.3.gem.sha512: -------------------------------------------------------------------------------- 1 | d67ee16ccf9042c086cf6ae1a190c0d7e5dfda72b269f24c942ee8982dc0c7f8e34a612d08a497672d1a40ce020362bef55f8a6b4ec62808caf59f5cc1dec173 2 | -------------------------------------------------------------------------------- /checksums/notam-1.1.3.pre1.gem.sha512: -------------------------------------------------------------------------------- 1 | a093c96bc79b925874adb013174488df8a3aca742e12b5b51b2032f3dc37ccb22aeb0665c69cb464757f5f62486646c7692d5201aca74339eba061edd4b732c3 2 | -------------------------------------------------------------------------------- /checksums/notam-1.1.4.gem.sha512: -------------------------------------------------------------------------------- 1 | 2bc3e9df3e61173039271e0d9133d4809f938095cd404753fd627efdc8a4e142c03d633f9c33d76e3cbbb6ac935257a8ddff07b818faaf17c93f83a17419ab80 2 | -------------------------------------------------------------------------------- /checksums/notam-1.1.5.gem.sha512: -------------------------------------------------------------------------------- 1 | dfa81b0cb343a8057ceec568327dc6e23b25d3fad36edfcc78d28b2d3e01c40a6bfc27f64a5a2c6da84bedda86dc47e1264654746a41d90a55b12b11d4715dda 2 | -------------------------------------------------------------------------------- /checksums/notam-1.1.6.gem.sha512: -------------------------------------------------------------------------------- 1 | b0b7b216fa896d9f0e7517bfcc197fddbf24d01bc875ab8f3c61283263cfb7fb25297d127994c263988be6d0acacfaa9a730506b2b0a2b62dbc54189c6a1d5ba 2 | -------------------------------------------------------------------------------- /gems.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in notam.gemspec 6 | gemspec 7 | 8 | gem "rake", "~> 13.0" 9 | 10 | gem "minitest", "~> 5.0" 11 | -------------------------------------------------------------------------------- /guardfile.rb: -------------------------------------------------------------------------------- 1 | clearing :on 2 | 3 | guard :minitest do 4 | watch(%r{^spec/(.+)_spec\.rb}) 5 | watch(%r{^lib/(.+)\.rb}) { "spec/lib/#{_1[1]}_spec.rb" } 6 | watch(%r{^spec/(spec_helper|factory)\.rb}) { 'spec' } 7 | end 8 | -------------------------------------------------------------------------------- /lib/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | firs: 3 | AGGG: "Honiara ACC" 4 | ANAU: "Nauru ACC" 5 | AYPM: "Port Moresby ACC" 6 | BGGL: "Nuuk ACC" 7 | BIRD: "Reykjavík ACC" 8 | CZEG: "Edmonton ACC" 9 | CZQM: "Moncton Southern ACC" 10 | CZQX: "Gander Domestic ACC" 11 | CZUL: "Montreal ACC" 12 | CZVR: "Vancouver ACC" 13 | CZWG: "Winnipeg ACC" 14 | CZYZ: "Toronto ACC" 15 | DAAA: "Alger ACC" 16 | DGAC: "Accra ACC" 17 | DIII: "Abidjan ACC" 18 | DNKK: "Kano ACC" 19 | DRRR: "Niamey ACC" 20 | DTTC: "Tunis ACC" 21 | EZZZ: "Eurocontrol" 22 | EBBU: "Brussels ACC" 23 | EDGG: "Langen ACC" 24 | EDMM: "Munich ACC" 25 | EDUU: "Rhein ACC" 26 | EDVV: "Hannover ACC" 27 | EDWW: "Bremen ACC" 28 | EDYY: "Maastricht ACC" 29 | EETT: "Tallinn ACC" 30 | EFIN: "Helsinki ACC" 31 | EGGX: "Shanwick Oceanic OCA" 32 | EGPX: "Scottish ACC" 33 | EGQQ: "Scottish ACC (Mil)" 34 | EGTT: "London ACC" 35 | EHAA: "Amsterdam ACC" 36 | EISN: "Shannon ACC" 37 | EKDK: "Copenhagen ACC" 38 | ENOB: "Bodo Oceanic OCA" 39 | ENOR: "Polaris ACC" 40 | EPWW: "Warszawa ACC" 41 | ESAA: "Suecia ACC" 42 | ESMM: "Malmo ACC" 43 | ESOS: "Stockholm ACC" 44 | EVRR: "Riga ACC" 45 | EYVL: "Vilnius ACC" 46 | FABL: "Bloemfontein ACC" 47 | FACA: "Cape Town ACC" 48 | FACT: "Cape Town ACC" 49 | FADN: "Durban ACC" 50 | FAJO: "Johannesburg Oceanic ACC" 51 | FAJX: "Johannesburg ACC" 52 | FAPX: "Port Elizabeth ACC" 53 | FBGR: "Gaborone ACC" 54 | FCCC: "Brazzaville ACC" 55 | FIMM: "Mauritius ACC" 56 | FKKK: "Douala ACC" 57 | FLFI: "Lusaka ACC" 58 | FMCX: "Comoros ACC" 59 | FMMM: "Antananarivo ACC" 60 | FNAN: "Luanda ACC" 61 | FOOO: "Libreville ACC" 62 | FQBE: "Beira ACC" 63 | FSSS: "Seychelles ACC" 64 | FTTT: "N'Djamena ACC" 65 | FVHF: "Harare ACC" 66 | FWLL: "Lilongwe ACC" 67 | FYWF: "Windhoek ACC" 68 | FZZA: "Kinshasa ACC" 69 | GCCC: "Canarias ACC" 70 | GLRB: "Roberts ACC" 71 | GMMM: "Casablanca ACC" 72 | GOOO: "Dakar Oceanic ACC" 73 | GVSC: "Sal Oceanic ACC" 74 | HAAA: "Addis Ababa ACC" 75 | HBBA: "Bujumbura ACC" 76 | HCSM: "Mogadishu ACC" 77 | HECC: "Cairo ACC" 78 | HHAA: "Asmara ACC" 79 | HKNA: "Nairobi ACC" 80 | HLLL: "Tripoli ACC" 81 | HRYR: "Kigali ACC" 82 | HSSS: "Khartoum ACC" 83 | HTDC: "Dar Es Salaam ACC" 84 | HUEC: "Entebbe ACC" 85 | KZAB: "Albuquerque ARTCC" 86 | KZAK: "Oakland Oceanic ARTCC" 87 | KZAU: "Chicago ARTCC" 88 | KZBW: "Boston ARTCC" 89 | KZDC: "Washington ARTCC" 90 | KZDV: "Denver ARTCC" 91 | KZFW: "Ft Worth ARTCC" 92 | KZHU: "Houston ARTCC" 93 | KZID: "Indianapolis ARTCC" 94 | KZJX: "Jacksonville ARTCC" 95 | KZKC: "Kansas City ARTCC" 96 | KZLA: "Los Angeles ARTCC" 97 | KZLC: "Salt Lake ARTCC" 98 | KZMA: "Miami ARTCC" 99 | KZME: "Memphis ARTCC" 100 | KZMP: "Minneapolis ARTCC" 101 | KZNY: "New York ARTCC" 102 | KZOA: "Oakland ARTCC" 103 | KZOB: "Cleveland ARTCC" 104 | KZSE: "Seattle ARTCC" 105 | KZTL: "Atlanta ARTCC" 106 | KZWY: "New York Oceanic ARTCC" 107 | LAAA: "Tirana ACC" 108 | LBSR: "Sofia ACC" 109 | LBWR: "Varna ACC" 110 | LCCC: "Nicosia ACC" 111 | LDZO: "Zagreb ACC" 112 | LECB: "Barcelona ACC" 113 | LECM: "Madrid ACC" 114 | LECS: "Sevilla ACC" 115 | LFBB: "Bordeaux ACC" 116 | LFEE: "Reims ACC" 117 | LFFF: "Paris ACC" 118 | LFMM: "Marseille ACC" 119 | LFRR: "Brest ACC" 120 | LGGG: "Athens ACC" 121 | LHCC: "Budapest ACC" 122 | LIBB: "Brindisi ACC" 123 | LIMM: "Milano ACC" 124 | LIRR: "Roma ACC" 125 | LJLA: "Ljubljana ACC" 126 | LKAA: "Praha ACC" 127 | LLLL: "Tel-Aviv ACC" 128 | LMMM: "Malta ACC" 129 | LOVV: "Wien ACC" 130 | LPPC: "Lisboa ACC" 131 | LPPO: "Santa Maria Oceanic ACC" 132 | LQSB: "Sarajevo ACC" 133 | LRBB: "Bucuresti ACC" 134 | LSAG: "Geneve ACC" 135 | LSAS: "Switzerland ACC" 136 | LSAZ: "Zurich ACC" 137 | LTAA: "Ankara ACC" 138 | LTBB: "Istanbul ACC" 139 | LUUU: "Chisinau ACC" 140 | LWSS: "Skopje ACC" 141 | LYBA: "Beograd ACC" 142 | LZBB: "Bratislava ACC" 143 | MDCS: "Santo Domingo ACC" 144 | MHTG: "Central American ACC" 145 | MKJK: "Kingston ACC" 146 | MMFO: "Mazatlan Oceanic ACC" 147 | MMFR: "Mexico ACC" 148 | MPZL: "Panama ACC" 149 | MTEG: "Port-Au-Prince ACC" 150 | MUFH: "Habana ACC" 151 | MYNA: "Nassau ACC" 152 | NFFF: "Nadi ACC" 153 | NTTT: "Tahiti ACC" 154 | NWWX: "Noumea ACC" 155 | NZZC: "New Zealand ACC" 156 | NZZO: "Auckland Oceanic ACC" 157 | OAKX: "Kabul ACC" 158 | OBBB: "Bahrain ACC" 159 | OEJD: "Jeddah ACC" 160 | OIIX: "Tehran ACC" 161 | OJAC: "Amman ACC" 162 | OKAC: "Kuwait ACC" 163 | OLBB: "Beirut ACC" 164 | OMAE: "Emirates ACC" 165 | OOMM: "Muscat ACC" 166 | OPKR: "Karachi ACC" 167 | OPLR: "Lahore ACC" 168 | ORBB: "Baghdad ACC" 169 | ORMM: "ORMM FIR" 170 | OSTT: "Damascus ACC" 171 | OYSC: "Sanaa ACC" 172 | PAZA: "Anchorage ACC" 173 | PAZN: "Anchorage Oceanic ACC" 174 | PHZH: "Honolulu ACC" 175 | RCAA: "Taipei ACC" 176 | RJJJ: "Fukuoka ACC" 177 | RKRR: "Incheon ACC" 178 | RPHI: "Manila ACC" 179 | SACF: "Cordoba ACC" 180 | SACU: "Cordoba UIR" 181 | SAEF: "Ezeiza ACC" 182 | SAEU: "Ezeiza UIR" 183 | SAMF: "Mendoza ACC" 184 | SAMV: "Mendoza UIR" 185 | SARR: "Resistencia ACC" 186 | SAVF: "Comodoro Rivadavia ACC" 187 | SAVU: "Comodoro Rivadavia UIR" 188 | SBAO: "Atlantico ACC" 189 | SBAZ: "Amazonica ACC" 190 | SBBS: "Brasilia ACC" 191 | SBCW: "Curitiba ACC" 192 | SBRE: "Recife ACC" 193 | SCCZ: "Punta Arenas ACC" 194 | SCEZ: "Santiago ACC" 195 | SCFZ: "Antofagasta ACC" 196 | SCIZ: "Easter Island ACC" 197 | SCTZ: "Puerto Montt ACC" 198 | SEFG: "Guayaquil ACC" 199 | SGFA: "Asuncion ACC" 200 | SKEC: "Barranquilla ACC" 201 | SKED: "Bogota ACC" 202 | SLLF: "La Paz ACC" 203 | SMPM: "Paramaribo ACC" 204 | SOOO: "Rochambeau ACC" 205 | SPIM: "Lima ACC" 206 | SUEO: "Montevideo ACC" 207 | SVZM: "Maiquetia ACC" 208 | SYGC: "Georgetown ACC" 209 | TJZS: "San Juan ACC" 210 | TNCF: "Curacao ACC" 211 | TTZP: "Piarco ACC" 212 | UAAX: "Almaty ACC" 213 | UACX: "Astana ACC" 214 | UAFX: "Bishkek ACC" 215 | UASS: "Semipalatinsk ACC" 216 | UDDD: "Yerevan ACC" 217 | UEMH: "Tyoply Klyuch ACC" 218 | UENN: "Nyurba ACC" 219 | UESS: "Chersky ACC" 220 | UESU: "Zyryanka ACC" 221 | UEVV: "Gigansk ACC" 222 | UGEE: "Yerevan/Zvartnots ACC" 223 | UGGG: "Tbilisi ACC" 224 | UHBI: "Magdagachi ACC" 225 | UHHH: "Khabarovsk/Novy" 226 | UHMI: "Mys Shmidta ACC" 227 | UHMM: "Magadan Oceanic" 228 | UHMP: "Pevek ACC" 229 | UHNN: "Nikolayevsk-na-Amure ACC" 230 | UHPT: "Tilichiki ACC" 231 | UHPU: "Ust-Khairyozovo ACC" 232 | UHSH: "Okha ACC" 233 | UIKB: "Bodaybo ACC" 234 | UIKK: "Kirensk ACC" 235 | UKBV: "Kyiv ACC" 236 | UKCV: "Donetsk ACC" 237 | UKDV: "Dnepropetrovsk ACC" 238 | UKFV: "Simferopol ACC" 239 | UKHV: "Kharkiv ACC" 240 | UKLV: "Lviv ACC" 241 | UKOV: "Odesa ACC" 242 | ULLL: "Sankt Peterburg ACC" 243 | ULOL: "Velikiye Luki ACC" 244 | UMKD: "Kazan ACC" 245 | UMMV: "Minsk ACC" 246 | UNLL: "Kolpashevo ACC" 247 | UOTT: "Turukhansk ACC" 248 | URRV: "Rostov-Na-Donu ACC" 249 | USDK: "Mys Kamenny ACC" 250 | USHB: "Beryozovo ACC" 251 | USHH: "Khanty-Mansiysk ACC" 252 | UTAK: "Turkmenbashi ACC" 253 | UTNR: "Nukus ACC" 254 | UTSD: "Samarkand ACC" 255 | UTTR: "Tashkent ACC" 256 | UUWV: "Moscow ACC" 257 | UUYW: "Vorkuta ACC" 258 | UUYY: "Syktyvkar ACC" 259 | UWOO: "Orenburg/Tsentralny ACC" 260 | VABF: "Mumbai ACC" 261 | VCCC: "Colombo ACC" 262 | VDPF: "Phnom Penh ACC" 263 | VECF: "Kolkata ACC" 264 | VGFR: "Dhaka ACC" 265 | VHHK: "Hong Kong ACC" 266 | VIDF: "Delhi ACC" 267 | VLIV: "Vientiane ACC" 268 | VLVT: "Vientiane ACC" 269 | VNSM: "Kathmandu ACC" 270 | VOMF: "Chennai ACC" 271 | VRMF: "Male ACC" 272 | VTBB: "Bangkok ACC" 273 | VVHM: "Ho Chi Minh ACC" 274 | VVHN: "Hanoi ACC" 275 | VYMD: "Mandalay Sector" 276 | VYYF: "Yangon ACC" 277 | WAAF: "Ujung Pandang" 278 | WAAZ: "Ujung Pandang ACC" 279 | WABZ: "Biak Sector" 280 | WADZ: "Bali Sector" 281 | WAJZ: "Jayapura Sector" 282 | WAKZ: "Merauke Sector" 283 | WALZ: "Balikpapan Sector" 284 | WAMZ: "Manado Sector" 285 | WAOZ: "Banjarmasin Sector" 286 | WAPZ: "Ambon Sector" 287 | WATZ: "Kupang Sector" 288 | WBFC: "Kota Kinabalu ACC" 289 | WIIF: "Jakarta" 290 | WIIZ: "Jakarta ACC" 291 | WIMZ: "Medan Sector" 292 | WIOZ: "Pontianak Sector" 293 | WIPZ: "Palembang Sector" 294 | WMFC: "Kuala Lumpur ACC" 295 | WSJC: "Singapore ACC" 296 | YBBB: "Brisbane ACC" 297 | YMMM: "Melbourne ACC" 298 | ZBPE: "Beijing ACC" 299 | ZGJD: "Zhuhai ACC" 300 | ZGZU: "Guangzhou ACC" 301 | ZHWH: "Wuhan ACC" 302 | ZJSA: "Sanya ACC" 303 | ZKKP: "Pyongyang ACC" 304 | ZLHW: "Lanzhou ACC" 305 | ZMUB: "Ulan Bator ACC" 306 | ZPKM: "Kunming ACC" 307 | ZSHA: "Shanghai ACC" 308 | ZWUQ: "Urumqi ACC" 309 | ZYSH: "Shenyang ACC" 310 | subject_groups: 311 | airspace_organization: "airspace organization" 312 | communications_and_surveillance_facilities: "communications and surveillance facilities" 313 | facilities_and_services: "facilities and services" 314 | gnss_services: "GNSS services" 315 | instrument_and_microwave_landing_system: "instrument and microwave landing system" 316 | checklist: "checklist" 317 | lighting_facilities: "lighting facilities" 318 | movement_and_landing_area: "movement and landing area" 319 | terminal_and_en_route_navigation_facilities: "terminal and en route navigation facilities" 320 | other_information: "other information" 321 | air_traffic_procedures: "air traffic procedures" 322 | airspace_restrictions: "airspace restrictions" 323 | air_traffic_and_volmet_services: "air traffic and VOLMET services" 324 | warning: "warning" 325 | other: "other" 326 | subjects: 327 | minimum_altitude: "minimum altitude" 328 | class_bcde_surface_area: "class B/C/D/E surface area" 329 | air_defense_identification_zone: "air defense identification zone" 330 | control_area: "control area" 331 | flight_information_region: "flight information region" 332 | upper_control_area: "upper control area" 333 | minimum_usable_flight_level: "minimum usable flight level" 334 | area_navigation_route: "area navigation route" 335 | oceanic_control_area: "oceanic control area" 336 | reporting_point: "reporting point" 337 | ats_route: "ATS route" 338 | terminal_control_area: "terminal control area" 339 | upper_flight_information_region: "upper flight information region" 340 | upper_advisory_area: "upper advisory area" 341 | significant_point: "significant point" 342 | aerodrome_traffic_zone: "aerodrome traffic zone" 343 | air_ground_facility: "air/ground facility" 344 | automatic_dependent_surveillance_broadcast: "automatic dependent surveillance (broadcast)" 345 | automatic_dependent_surveillance_contract: "automatic dependent surveillance (contract)" 346 | controller_pilot_data_link: "controller-pilot data link communications" 347 | en_route_surveillance_radar: "en route surveillance radar" 348 | ground_controlled_approach_system: "ground controlled approach system (GCA)" 349 | selective_calling_system: "selective calling system (SELCAL)" 350 | surface_movement_radar: "surface movement radar" 351 | precision_approach_radar: "precision approach radar (PAR)" 352 | surveillance_radar_element_of_par: "surveillance radar element of precision approach radar system" 353 | secondary_surveillance_radar: "secondary surveillance radar (SSR)" 354 | terminal_area_surveillance_radar: "terminal area surveillance radar (TAR)" 355 | aerodrome: "aerodrome" 356 | friction_measuring_device: "friction measuring device" 357 | ceiling_measurement_equipment: "ceiling measurement equipment" 358 | docking_system: "docking system" 359 | oxygen: "oxygen" 360 | fire_fighting_and_rescue: "fire fighting and rescue" 361 | ground_movement_control: "ground movement control" 362 | helicopter_alighting_area: "helicopter alighting area/platform" 363 | aircraft_de_icing: "aircraft de-icing" 364 | oils: "oils" 365 | landing_direction_indicator: "landing direction indicator" 366 | meteorological_service: "meteorological service" 367 | fog_dispersal_system: "fog dispersal system" 368 | heliport: "heliport" 369 | snow_removal_equipment: "snow removal equipment" 370 | transmissometer: "transmissometer" 371 | fuel_availability: "fuel availability" 372 | wind_direction_indicator: "wind direction indicator" 373 | customs: "customs/immigration" 374 | gnss_airfield_specific_operations: "GNSS airfield-specific operations" 375 | gnss_area_wide_operations: "GNSS area-wide operations" 376 | instrument_landing_system: "instrument landing system" 377 | dme_associated_with_ils: "DME associated with ILS" 378 | glide_path: "glide path (ILS)" 379 | inner_marker: "inner marker (ILS)" 380 | localizer: "localizer (ILS)" 381 | middle_marker: "middle marker (ILS)" 382 | localizer_without_ils: "localizer (not associated with ILS)" 383 | outer_marker: "outer marker (ILS)" 384 | ils_category_1: "ILS Category I" 385 | ils_category_2: "ILS Category II" 386 | ils_category_3: "ILS Category III" 387 | microwave_landing_system: "microwave landing system (MLS)" 388 | locator_outer: "locator outer (ILS)" 389 | locator_middle: "locator middle (ILS)" 390 | checklist: "checklist of NOTAM" 391 | approach_lighting_system: "approach lighting system" 392 | aerodrome_beacon: "aerodrome beacon" 393 | runway_centre_line_lights: "runway centre line lights" 394 | landing_direction_indicator_lights: "landing direction indicator lights" 395 | runway_edge_lights: "runway edge lights" 396 | sequenced_flashing_lights: "sequenced flashing lights" 397 | pilot_controlled_lighting: "pilot-controlled lighting" 398 | high_intensity_runway_lights: "high intensity runway lights" 399 | runway_end_identifier_lights: "runway end identifier lights" 400 | runway_alignment_indicator_lights: "runway alignment indicator lights" 401 | category_2_components_of_als: "category II components of approach lighting system" 402 | low_intensity_runway_lights: "low intensity runway lights" 403 | medium_intensity_runway_lights: "medium intensity runway lights" 404 | precision_approach_path_indicator: "precision approach path indicator" 405 | all_landing_area_lighting_facilities: "all landing area lighting facilities" 406 | stopway_lights: "stopway lights" 407 | threshold_lights: "threshold lights" 408 | helicopter_approach_path_indicator: "helicopter approach path indicator" 409 | visual_approach_slope_indicator_system: "visual approach slope indicator system" 410 | heliport_lighting: "heliport lighting" 411 | taxiway_centre_line_lights: "taxiway centre line lights" 412 | taxiway_edge_lights: "taxiway edge lights" 413 | runway_touchdown_zone_lights: "runway touchdown zone lights" 414 | movement_area: "movement area" 415 | bearing_strength: "bearing strength" 416 | clearway: "clearway" 417 | declared_distances: "declared distances" 418 | taxiing_guidance_system: "taxiing guidance system" 419 | runway_arresting_gear: "runway arresting gear" 420 | parking_area: "parking area" 421 | daylight_markings: "daylight markings" 422 | apron: "apron" 423 | stopbar: "stopbar" 424 | aircraft_stands: "aircraft stands" 425 | runway: "runway" 426 | stopway: "stopway" 427 | threshold: "threshold" 428 | runway_turning_bay: "runway turning bay" 429 | strip_shoulder: "strip/shoulder" 430 | taxiway: "taxiway(s)" 431 | rapid_exit_taxiway: "rapid exit taxiway" 432 | all_radio_navigation_facilities: "all radio navigation facilities" 433 | nondirectional_radio_beacon: "nondirectional radio beacon" 434 | decca: "DECCA" 435 | dme: "distance measuring equipment (DME)" 436 | fan_marker: "fan marker" 437 | locator: "locator" 438 | vor_dme: "VOR/DME" 439 | tacan: "TACAN" 440 | omega: "OMEGA" 441 | vortac: "VORTAC" 442 | vor: "VOR" 443 | aeronautical_information_service: "aeronautical information service" 444 | obstacle: "obstacle" 445 | aircraft_entry_requirements: "aircraft entry requirements" 446 | obstacle_lights: "obstacle lights" 447 | rescue_coordination_centre: "rescue coordination centre" 448 | standard_instrument_arrival: "standard instrument arrival" 449 | standard_vfr_arrival: "standard VFR arrival" 450 | contingency_procedures: "contingency procedures" 451 | standard_instrument_departure: "standard instrument departure" 452 | standard_vfr_departure: "standard VFR departure" 453 | flow_control_procedure: "flow control procedure" 454 | holding_procedure: "holding procedure" 455 | instrument_approach_procedure: "instrument approach procedure" 456 | vfr_approach_procedure: "VFR approach procedure" 457 | flight_plan_processing: "flight plan processing (filing and related contingency)" 458 | aerodrome_operating_minima: "aerodrome operating minima" 459 | noise_operating_restriction: "noise operating restriction" 460 | obstacle_clearance_altitude: "obstacle clearance altitude and height" 461 | radio_failure_procedure: "radio failure procedure" 462 | transition_altitude_or_level: "transition altitude or transition level" 463 | missed_approach_procedure: "missed approach procedure" 464 | minimum_holding_altitude: "minimum holding altitude" 465 | adiz_procedure: "ADIZ procedure" 466 | airspace_reservation: "airspace reservation" 467 | danger_area: "danger area" 468 | military_operating_area: "military operating area" 469 | overflying: "overflying" 470 | prohibited_area: "prohibited area" 471 | restricted_area: "restricted area" 472 | temporary_restricted_area: "temporary restricted area" 473 | automatic_terminal_information_service: "automatic terminal information service" 474 | ats_reporting_office: "ATS reporting office" 475 | area_control_centre: "area control centre" 476 | flight_information_service: "flight information service" 477 | aerodrome_flight_information_service: "aerodrome flight information service" 478 | flow_control_centre: "flow control centre" 479 | oceanic_area_control_centre: "oceanic area control centre" 480 | approach_control_service: "approach control service" 481 | flight_service_station: "flight service station" 482 | aerodrome_control_tower: "aerodrome control tower" 483 | upper_area_control_centre: "upper area control centre" 484 | volmet_broadcast: "VOLMET broadcast" 485 | upper_advisory_service: "upper advisory service" 486 | air_display: "air display" 487 | aerobatics: "aerobatics" 488 | captive_balloon_or_kite: "captive balloon or kite" 489 | demolition_of_explosives: "demolition of explosives" 490 | exercises: "exercises" 491 | air_refueling: "air refueling" 492 | glider_flying: "glider flying" 493 | blasting: "blasting" 494 | banner_towing: "banner/target towing" 495 | ascent_of_free_balloon: "ascent of free balloon" 496 | missile_gun_firing: "missile/gun/rocket firing" 497 | parachute_paragliding_or_hang_gliding: "parachute jumping/paragliding/hang gliding" 498 | radioactive_or_toxic_materials: "radioactive materials or toxic chemicals" 499 | blowing_gas: "burning or blowing gas" 500 | mass_movement_of_aircraft: "mass movement of aircraft" 501 | unmanned_aircraft: "unmanned aircraft" 502 | formation_flight: "formation flight" 503 | volcanic_activity: "significant volcanic activity" 504 | aerial_survey: "aerial survey" 505 | model_flying: "model flying" 506 | other: "other" 507 | condition_groups: 508 | availability: "availabiity" 509 | changes: "changes" 510 | hazard_conditions: "hazard conditions" 511 | checklist: "checklist" 512 | limitations: "limitations" 513 | trigger: "trigger" 514 | other: "other" 515 | conditions: 516 | withdrawn_for_maintenance: "withdrawn for maintenance" 517 | available_for_daylight_operation: "available for daylight operation" 518 | flight_checked_and_found_reliable: "flight checked and found reliable" 519 | operating_but_ground_checked_only: "operating but ground checked only (awaiting flight check)" 520 | hours_of_service: "hours of service" 521 | resumed_normal_operations: "resumed normal operations" 522 | operative_subject_to_previously_published_conditions: "operative (or reoperative) subject to previously published conditions/limitations" 523 | military_operations_only: "military operations only" 524 | available_for_night_operation: "available for night operation" 525 | operational: "operational" 526 | available_with_prior_permission: "available but prior permission required" 527 | available_on_request: "available on request" 528 | unserviceable: "unserviceable" 529 | not_available: "not available" 530 | completely_withdrawn: "completely withdrawn" 531 | previously_announced_shutdown_canceled: "previously announced shutdown has been canceled" 532 | activated: "activated" 533 | completed: "completed" 534 | deactivated: "deactivated" 535 | erected: "erected" 536 | operating_frequency_changed: "operating frequency(ies) changed to specified" 537 | downgraded: "downgraded" 538 | changed: "changed" 539 | identification_changed: "identification or radio call sign changed to specified" 540 | realigned: "realigned" 541 | displaced: "displaced" 542 | canceled: "canceled" 543 | operating: "operating" 544 | operating_on_reduced_power: "operating on reduced power" 545 | temporarily_replaced: "temporarily replaced" 546 | installed: "installed" 547 | on_test: "on test (do not use)" 548 | braking_action: "braking action is as specified (from 1 poor to 5 good)" 549 | friction_coefficient: "friction coefficient as specified" 550 | covered_by_compacted_snow: "covered by compacted snow to specified depth" 551 | covered_by_dry_snow: "covered by dry snow to specified depth" 552 | covered_by_water: "covered by water to specified depth" 553 | free_of_snow_and_ice: "totally free of snow and ice" 554 | grass_cutting_in_progress: "grass cutting in progress" 555 | hazard: "hazard" 556 | covered_by_ice: "covered by ice" 557 | launch_planned: "launch planned" 558 | bird_migration_in_progress: "bird migration in progress" 559 | snow_clearance_completed: "snow clearance completed" 560 | marked: "marked" 561 | covered_by_wet_snow: "covered by wet snow or slush to specified depth" 562 | obscured_by_snow: "obscured by snow" 563 | snow_clearance_in_progress: "snow clearance in progress" 564 | operation_canceled: "operation canceled" 565 | standing_water: "standing water" 566 | sanding_in_progress: "sanding in progress" 567 | approach_according_to_signal_area_only: "approach according to signal area only" 568 | launch_in_progress: "launch in progress" 569 | work_completed: "work completed" 570 | work_in_progress: "work in progress" 571 | concentration_of_birds: "concentration of birds" 572 | snow_banks_exist: "snow banks exist" 573 | covered_by_frozen_ruts: "covered by frozen ruts and ridges" 574 | checklist: "checklist of NOTAM" 575 | operating_on_auxiliary_power_supply: "operating on auxiliary power supply" 576 | reserved_for_aircraft_based_therein: "reserved for aircraft based therein" 577 | closed: "closed" 578 | unsafe: "unsafe" 579 | operating_without_auxiliary_power_supply: "operating without auxiliary power supply" 580 | interference: "interference" 581 | operating_without_identification: "operating without identification" 582 | unserviceable_for_heavier_aircraft: "unserviceable for aircraft heavier than specified" 583 | closed_to_ifr_operations: "closed to IFR operations" 584 | operating_as_fixed_light: "operating as a fixed light" 585 | usable_for_smaller_only: "usable for specified length and width only" 586 | closed_to_night_operations: "closed to all night operations" 587 | prohibited: "prohibited to specified" 588 | restricted_to_runways_and_taxiways: "aircraft restricted to runways and taxiways" 589 | subject_to_interruption: "subject to interruption" 590 | limited: "limited to specified" 591 | closed_to_vfr_operations: "closed to VFR operations" 592 | will_take_place: "will take place" 593 | operating_but_caution_advised: "operating but caution advised" 594 | trigger: "trigger NOTAM" 595 | other: "other subject as specified in plain language" 596 | traffic: 597 | ifr_and_vfr: "IFR and VFR" 598 | ifr: "IFR" 599 | vfr: "VFR" 600 | checklist: "checklist of NOTAM" 601 | purposes: 602 | immediate_attention: "immediate attention of flight crews" 603 | operational_significance: "operational significance (PIB entry)" 604 | flight_operations: "concerning flight operations" 605 | miscellaneous: "miscellaneous (no PIB entry)" 606 | checklist: "checklist of NOTAM" 607 | scopes: 608 | aerodrome: "aerodrome" 609 | en_route: "en-route" 610 | navigation_warning: "navigation warning" 611 | checklist: "checklist of NOTAM" 612 | contractions: 613 | airport_lighting: "airport lighting" 614 | braking_action_fair: "braking action fair" 615 | braking_action_nil: "braking action nil" 616 | braking_action_poor: "braking action poor" 617 | departure_procedure: "departure procedure" 618 | fan_marker: "fan marker" 619 | touchdown_zone_lights: "touchdown zone lights" 620 | omni_directional_radio_range: "omni-directional radio range" 621 | airport_beacon: "airport beacon" 622 | above: "above" 623 | area_control_center: "area control center (ARTCC)" 624 | accumulate: "accumulate" 625 | aircraft: "aircraft" 626 | air_carrier: "air carrier" 627 | active: "active" 628 | adjacent: "adjacent" 629 | advised: "advised" 630 | airport_facility_directory: "airport facility directory" 631 | above_ground_level: "above ground level" 632 | approach_lighting_system: "approach lighting system" 633 | altitude: "altitude" 634 | altimeter: "altimeter" 635 | alternate: "alternate" 636 | alternately: "alternately" 637 | altimeter_setting: "altimeter setting" 638 | amendment: "amendment" 639 | airport_manager: "airport manager" 640 | automatic_meteorological_observing_system: "automatic meteorological observing system" 641 | airport: "airport" 642 | approach: "approach" 643 | approach_control: "approach control" 644 | aircraft_rescue_and_fire_fighting: "aircraft rescue and fire fighting" 645 | arrival: "arrival/arrive" 646 | automatic_surface_observing_system: "automatic surface observing system" 647 | asphalt: "asphalt" 648 | air_traffic_control: "air traffic control" 649 | air_traffic_control_command_center: "air traffic control command center" 650 | automatic_terminal_information_service: "automatic terminal information service" 651 | automatic_weather_reporting_system: "automatic weather reporting system" 652 | authority: "authority" 653 | available: "available" 654 | automatic_weather_observing_system: "automatic weather observing and reporting system" 655 | airway: "airway" 656 | azimuth: "azimuth" 657 | back_course: "back course" 658 | beacon: "beacon" 659 | snowbanks_containing_earth: "snowbank(s) containing earth or gravel" 660 | below: "below" 661 | bound: "bound" 662 | bearing: "bearing" 663 | beyond: "beyond" 664 | class_a_airspace: "class A airspace" 665 | category: "category" 666 | class_b_airspace: "class B airspace" 667 | class_b_surface_area: "class B surface area" 668 | class_c_airspace: "class C airspace" 669 | counterclockwise: "counterclockwise" 670 | class_c_surface_area: "class C surface area" 671 | clearance_delivery: "clearance delivery" 672 | class_d_airspace: "class D airspace" 673 | class_d_surface_area: "class D surface area" 674 | class_e_airspace: "class E airspace" 675 | class_e_surface_area: "class E surface area" 676 | code_of_federal_regulations: "code of federal regulations" 677 | class_g_airspace: "class G airspace" 678 | channel: "channel" 679 | change_or_modification: "change or modification" 680 | ceiling: "ceiling" 681 | check: "check" 682 | centre_line: "centre line" 683 | clockwise: "clockwise" 684 | clear: "clear/clearance/cleared to" 685 | closed: "closed" 686 | climb: "climb" 687 | commissioned: "commissioned" 688 | cancel: "cancel" 689 | centerline: "centerline" 690 | communications: "communications" 691 | concrete: "concrete" 692 | coupled: "coupled" 693 | course: "course" 694 | contact: "contact" 695 | control: "control" 696 | daylight: "daylight" 697 | decommission: "decommission" 698 | decommissioned: "decommissioned" 699 | direct: "direct" 700 | degrees: "degrees" 701 | departure: "departure/depart" 702 | decision_height: "decision height" 703 | disabled: "disabled" 704 | distance: "distance" 705 | delay_or_delayed: "delay or delayed" 706 | delete: "delete" 707 | daily: "daily" 708 | distance_measuring_equipment: "distance measuring equipment" 709 | demonstration: "demonstration" 710 | dewpoint_temperature: "dewpoint temperature" 711 | snowbanks_caused_by_wind: "snowbank(s) caused by wind action" 712 | displaced: "displaced" 713 | east: "east" 714 | eastbound: "eastbound" 715 | en_route_flight_advisory_service: "en route flight advisory service" 716 | elevation: "elevation" 717 | engine: "engine" 718 | en_route: "en route" 719 | entire: "entire" 720 | except: "except" 721 | facility_or_facilities: "facility or facilities" 722 | final_approach_fix: "final approach fix" 723 | flight_data_center: "flight data center" 724 | flight_inspection_temporay: "flight inspection temporay" 725 | flight_inspection_permanent: "flight inspection permanent" 726 | from: "from" 727 | final_approach: "final approach" 728 | feet_per_minute: "feet per minute" 729 | frequency: "frequency" 730 | fly_runway_heading: "fly runway heading" 731 | friday: "friday" 732 | frozen: "frozen" 733 | flight_service_station: "(automated) flight service station" 734 | foot: "foot/feet" 735 | ground_control: "ground control" 736 | ground_control_approach: "ground control approach" 737 | ground_communications_outlet: "ground communications outlet" 738 | government: "government" 739 | glide_path: "glide path" 740 | global_positioning_system: "global positioning system" 741 | gravel: "gravel" 742 | height_above_airport: "height above airport" 743 | height_above_touchdown: "height above touchdown" 744 | heading: "heading" 745 | helicopter: "helicopter" 746 | heliport: "heliport" 747 | high_intensity_runway_lights: "high intensity runway lights" 748 | hazardous_inflight_weather_advisory_service: "hazardous inflight weather advisory service" 749 | holding: "holding" 750 | holiday: "holiday" 751 | holding_pattern: "holding pattern" 752 | hour: "hour" 753 | initial_approach_fix: "initial approach fix" 754 | instrument_approach_procedure: "instrument approach procedure" 755 | inbound: "inbound" 756 | identification: "identification" 757 | identify: "identify/identifier" 758 | intermediate_fix: "intermediate fix" 759 | instrument_landing_system: "instrument landing system" 760 | inner_marker: "inner marker" 761 | instrument_meteorological_conditions: "instrument meteorological conditions" 762 | inch: "inch(es)" 763 | indefinitely: "indefinitely" 764 | information: "information" 765 | inoperative: "inoperative" 766 | instrument: "instrument" 767 | intersection: "intersection" 768 | international: "international" 769 | intensity: "intensity" 770 | ice_on_runway: "ice on runway(s)" 771 | knots: "knots" 772 | left: "left" 773 | local_airport_advisory: "local airport advisory" 774 | latitude: "latitude" 775 | limited_aviation_weather_reporting_station: "limited aviation weather reporting station" 776 | pounds: "pound(s)" 777 | local_control: "local control" 778 | local: "local/locally/location" 779 | located: "located" 780 | localizer_type_directional_aid: "localizer type directional aid" 781 | light_or_lighting: "light or lighting" 782 | lighted: "lighted" 783 | low_intensity_runway_lights: "low intensity runway lights" 784 | low_level_wind_shear_alert_system: "low level wind shear alert system" 785 | compass_locator_at_ils_middle_marker: "compass locator at ils middle marker" 786 | landing: "landing" 787 | localizer: "localizer" 788 | compass_locator_at_ils_outer_marker: "compass locator at ils outer marker" 789 | longitude: "longitude" 790 | long_range_navigation: "long range navigation" 791 | loose_snow_on_runway: "loose snow on runway(s)" 792 | left_turn: "left turn" 793 | magnetic: "magnetic" 794 | maintenance: "maintenance/maintain" 795 | medium_intensity_approach_light_system: "medium intensity approach light system" 796 | medium_intensity_approach_light_system_with_sequenced_flashers: "medium intensity approach light system with sequenced flashers" 797 | medium_intensity_approach_light_system_with_runway_alignment_indicator_lights: "medium intensity approach light system with runway alignment indicator lights" 798 | missed_approach_point: "missed approach point" 799 | minimum_crossing_altitude: "minimum crossing altitude" 800 | minimum_descent_altitude: "minimum descent altitude" 801 | minimum_en_route_altitude: "minimum en route altitude" 802 | medium: "medium" 803 | minutes: "minute(s)" 804 | medium_intensity_runway_lights: "medium intensity runway lights" 805 | marker: "marker" 806 | microwave_landing_system: "microwave landing system" 807 | middle_marker: "middle marker" 808 | minimum: "minimum" 809 | monitor: "monitor/monitoring/monitored" 810 | minimum_obstruction_clearance: "minimum obstruction clearance" 811 | monday: "monday" 812 | minimum_reception_altitude: "minimum reception altitude" 813 | minimum_safe_altitude: "minimum safe/sector altitude" 814 | minimum_safe_altitude_warning: "minimum safe altitude warning" 815 | message: "message" 816 | mean_sea_level: "mean sea level" 817 | mu_meters: "mu meters" 818 | mud: "mud" 819 | municipal: "municipal" 820 | north: "north" 821 | not_authorized: "not authorized" 822 | navigation: "navigation" 823 | northbound: "northbound" 824 | nondirectional_radio_beacon: "nondirectional radio beacon" 825 | northeast: "northeast" 826 | night: "night" 827 | nautical_miles: "nautical mile(s)" 828 | nautical_mile_radius: "nautical mile radius" 829 | nonstandard: "nonstandard" 830 | no_procedure_turn_required: "no procedure turn required" 831 | number: "number" 832 | notice_to_air_missions_publication: "notice to air missions publication" 833 | northwest: "northwest" 834 | obscured: "obscured/obscure/obscuring" 835 | obstacle: "obstacle/obstruction" 836 | outer_marker: "outer marker" 837 | operate: "operate/operator/operative" 838 | operations: "operation(s)" 839 | original: "original" 840 | out_of_service: "out of service" 841 | over: "over" 842 | personnel_and_equipment_working: "personnel and equipment working" 843 | passengers: "passenger(s)" 844 | precision_approach_path_indicator: "precision approach path indicator" 845 | precision_approach_radar: "precision approach radar" 846 | parallel: "parallel" 847 | pattern: "pattern" 848 | pilot_controlled_lighting: "pilot controlled lighting" 849 | permanent: "permanent" 850 | parachute_jumping_exercise: "parachute jumping exercise" 851 | practice_low_approach: "practice low approach" 852 | plow: "plow/plowed" 853 | prior_notice_required: "prior notice required" 854 | prior_permission_required: "prior permission required" 855 | psuedo_random_noise: "psuedo random noise" 856 | procedure: "procedure" 857 | propeller: "propeller" 858 | packed_snow_on_runway: "packed snow on runway(s)" 859 | patchy: "patchy" 860 | procedure_turn: "procedure turn" 861 | private: "private" 862 | runway_alignment_indicator_lights: "runway alignment indicator lights" 863 | remote_automatic_meteorological_observing_system: "remote automatic meteorological observing system" 864 | remote_communication_air_ground_facility: "remote communication air/ground facility" 865 | runway_center_line: "runway center line" 866 | runway_center_line_lights: "runway center line lights" 867 | remote_communication_outlet: "remote communication outlet" 868 | receive_or_receiver: "receive or receiver" 869 | runway_end_identifier_lights: "runway end identifier lights" 870 | relocated: "relocated" 871 | report: "report" 872 | runway_lead_in_light_system: "runway lead-in light system" 873 | remainder: "remainder" 874 | remarks: "remarks" 875 | area_navigation: "area navigation" 876 | replace: "replace" 877 | required: "required" 878 | runway_remaining_lights: "runway remaining lights" 879 | en_route_surveillance_radar: "en route surveillance radar" 880 | reservation: "reservation" 881 | right_turn: "right turn" 882 | route: "route" 883 | remote_transmitter_receiver: "remote transmitter/receiver" 884 | return_to_service: "return to service" 885 | rough: "rough" 886 | runway_visual_range: "runway visual range" 887 | runway_visual_range_midpoint: "runway visual range midpoint" 888 | runway_visual_range_rollout: "runway visual range rollout" 889 | runway_visual_range_touchdown: "runway visual range touchdown" 890 | runway: "runway" 891 | south: "south" 892 | sand: "sand/sanded" 893 | saturday: "saturday" 894 | supplementary_aviation_weather_reporting_station: "supplementary aviation weather reporting station" 895 | southbound: "southbound" 896 | simplified_directional_facility: "simplified directional facility" 897 | southeast: "southeast" 898 | sequence_flashing_lights: "sequence flashing lights" 899 | simultaneous: "simultaneous(ly)" 900 | packed_snow_and_ice_on_runway: "packed or compacted snow and ice on runway(s)" 901 | schedule: "schedule/scheduled" 902 | slush_on_runway: "slush on runway(s)" 903 | snow: "snow" 904 | snowbanks_caused_by_plowing: "snowbank(s) caused by plowing" 905 | single: "single" 906 | speed: "speed" 907 | simplified_short_approach_lighting_with_sequence_flashers: "simplified short approach lighting with sequence flashers" 908 | simplified_short_approach_lighting_with_runway_alignment_indicator_lights: "simplified short approach lighting with runway alignment indicator lights" 909 | simplified_short_approach_lighting_system: "simplified short approach lighting system" 910 | secondary_surveillance_radar: "secondary surveillance radar" 911 | straight_in_approach: "straight-in approach" 912 | standard_terminal_arrival: "standard terminal arrival" 913 | sunday: "sunday" 914 | service: "service" 915 | satellite_vehicle_number: "satellite vehicle number" 916 | southwest: "southwest" 917 | swept: "swept or broomed" 918 | temperature: "temperature" 919 | tactical_air_navigational_aid: "tactical air navigational aid (azimuth and DME)" 920 | terminal_area_surveillance_radar: "terminal area surveillance radar" 921 | terminal_doppler_weather_radar: "terminal doppler weather radar" 922 | touchdown_zone: "touchdown zone" 923 | temporary_or_temporarily: "temporary or temporarily" 924 | traffic: "traffic" 925 | temporary_flight_restriction: "temporary flight restriction" 926 | touch_and_go_landings: "touch-and-go landings" 927 | thin: "thin" 928 | threshold: "threshold" 929 | through: "through" 930 | thursday: "thursday" 931 | until: "until" 932 | takeoff: "takeoff" 933 | traffic_management: "traffic management" 934 | traffic_management_program_alert: "traffic management program alert" 935 | terminal: "terminal" 936 | training: "training" 937 | transition: "transition" 938 | transient: "transient" 939 | tuesday: "tuesday" 940 | airport_control_tower: "airport control tower" 941 | taxiway: "taxiway" 942 | unmanned_air_vehicles: "unmanned air vehicles" 943 | until_further_notice: "until further notice" 944 | unavailable: "unavailable" 945 | unlighted: "unlighted" 946 | unmarked: "unmarked" 947 | unmonitored: "unmonitored" 948 | unreliable: "unreliable" 949 | unusable: "unusable" 950 | visual_approach_slope_indicator_system: "visual approach slope indicator system" 951 | visual_descent_point: "visual descent point" 952 | by_way_of: "by way of" 953 | versus: "versus/instead" 954 | visibility: "visibility" 955 | visual_meteorological_conditions: "visual meteorological conditions" 956 | volume: "volume" 957 | vor_and_tacan: "collocated VOR and TACAN" 958 | west: "west" 959 | westbound: "westbound" 960 | wednesday: "wednesday" 961 | with_effect_from_or_effective_from: "with effect from or effective from" 962 | within: "within" 963 | with_immediate_effect_or_effective_immediately: "with immediate effect or effective immediately" 964 | work_in_progress: "work in progress" 965 | monday_through_friday: "monday through friday" 966 | saturday_and_sunday: "saturday and sunday" 967 | wind: "wind" 968 | waypoint: "waypoint" 969 | wet_snow_on_runway: "wet snow on runway(s)" 970 | water_on_runway: "water on runway(s)" 971 | weather: "weather" 972 | -------------------------------------------------------------------------------- /lib/notam.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'aixm' 4 | require 'i18n' 5 | 6 | require_relative "notam/version" 7 | require_relative "notam/errors" 8 | require_relative "notam/translation" 9 | 10 | require_relative "notam/message" 11 | require_relative "notam/schedule" 12 | 13 | require_relative "notam/item" 14 | require_relative "notam/item/header" 15 | require_relative "notam/item/q" 16 | require_relative "notam/item/a" 17 | require_relative "notam/item/b" 18 | require_relative "notam/item/c" 19 | require_relative "notam/item/d" 20 | require_relative "notam/item/e" 21 | require_relative "notam/item/f" 22 | require_relative "notam/item/g" 23 | require_relative "notam/item/footer" 24 | 25 | I18n.load_path << Pathname(__dir__).join('locales').glob('*.yml') 26 | I18n.available_locales = [:en] 27 | I18n.default_locale = :en 28 | -------------------------------------------------------------------------------- /lib/notam/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NOTAM 4 | class ParseError < StandardError 5 | attr_reader :item 6 | 7 | def initialize(message, item: nil) 8 | @item = item 9 | super(message) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/notam/item.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NOTAM 4 | 5 | # Items are the building blocks of a NOTAM message. They usually consist of 6 | # only one line of plain text each, however, D and E items may span over 7 | # multiple lines of plain text. 8 | class Item 9 | 10 | RE = /[QA-G]\)\s/.freeze 11 | 12 | ID_RE = /(?[A-Z])(?\d{4})\/(?\d{2})/.freeze 13 | ICAO_RE = /[A-Z]{4}/.freeze 14 | TIME_RE = /(?:\d{2})(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])(?:[01]\d|[2][0-4])(?:[0-5]\d)/.freeze 15 | 16 | # Raw NOTAM item text 17 | # 18 | # @return [String] 19 | attr_reader :text 20 | 21 | # Captures from the default item regexp +RE+ 22 | # 23 | # @return [MatchData] 24 | attr_reader :captures 25 | 26 | # Parsed NOTAM message payload 27 | # 28 | # @return [Hash] 29 | attr_reader :data 30 | 31 | # Analyses the raw NOTAM item text and initialize the corresponding item 32 | # object. 33 | # 34 | # @note Some NOTAM items (most notably {NOTAM::D}) depend on previous items 35 | # for meaningful parsing and may fail if this information is not made 36 | # available by passing the NOTAM message payload parsed so far as +data+. 37 | # 38 | # @example 39 | # NOTAM::Item.new('A0135/20 NOTAMN') # => # 40 | # NOTAM::Item.new('B) 0208231540') # => # 41 | # NOTAM::Item.new('foobar') # => NOTAM::ParseError 42 | # 43 | # @param text [String] 44 | # @param data [Hash] 45 | # @return [NOTAM::Header, NOTAM::Q, NOTAM::A, NOTAM::B, NOTAM::C, 46 | # NOTAM::D, NOTAM::E, NOTAM::F, NOTAM::G, NOTAM::Footer] 47 | def initialize(text, data: {}) 48 | @text, @data = text.strip, data 49 | end 50 | 51 | class << self 52 | # @!visibility private 53 | def new(text, data: {}) 54 | NOTAM.const_get(type(text)).allocate.instance_eval do 55 | initialize(text, data: data) 56 | self 57 | end 58 | end 59 | 60 | # Analyses the raw NOTAM item text and detect its type. 61 | # 62 | # @example 63 | # NOTAM::Item.type('A0135/20 NOTAMN') # => :Header 64 | # NOTAM::Item.type('B) 0208231540') # => :B 65 | # NOTAM::Item.type('SOURCE: LFNT') # => :Footer 66 | # NOTAM::Item.type('foobar') # => NOTAM::ParseError 67 | # 68 | # @raise [NOTAM::ParseError] 69 | # @return [String] 70 | def type(text) 71 | case text.strip 72 | when /\A([A-GQ])\)/ then $1 73 | when NOTAM::Header::RE then 'Header' 74 | when NOTAM::Footer::RE then 'Footer' 75 | else fail(NOTAM::ParseError, 'item not recognized') 76 | end.to_sym 77 | end 78 | end 79 | 80 | # Matches the raw NOTAM item text against +RE+ and populates {#captures}. 81 | # 82 | # @note May be extended or overwritten in subclasses, but must always 83 | # return +self+! 84 | # 85 | # @example 86 | # NOTAM::Item.new('A0135/20 NOTAMN').parse # => # 87 | # NOTAM::Item.new('foobar').parse # => NOTAM::ParseError 88 | # 89 | # @abstract 90 | # @raise [NOTAM::ParseError] 91 | # @return [self] 92 | def parse 93 | if match_data = self.class::RE.match(text) 94 | begin 95 | @captures = match_data.named_captures 96 | self 97 | rescue 98 | fail! "invalid #{self.class.to_s.split('::').last} item" 99 | end 100 | else 101 | fail! 'text does not match regexp' 102 | end 103 | end 104 | 105 | # Merges the return values of the given methods into the +data+ hash. 106 | # 107 | # @note Must be extended in subclasses. 108 | # 109 | # @abstract 110 | # @params methods [Array] 111 | # @return [self] 112 | def merge(*methods) 113 | fail 'nothing to merge' unless methods.any? 114 | methods.each { @data[_1] = send(_1) } 115 | @data.compact! 116 | self 117 | end 118 | 119 | # Type of the item 120 | # 121 | # @return [Symbol] either +:Header+, +:Q+, +(:A..:G)+ or +:Footer+ 122 | def type 123 | self.class.to_s[7..].to_sym 124 | end 125 | 126 | # Raise {NOTAM::ParseError} along with some debugging information. 127 | # 128 | # @param message [String] optional error message 129 | # @raise [NOTAM::ParseError] 130 | def fail!(message=nil) 131 | fail ParseError.new([message, text].compact.join(': '), item: self) 132 | end 133 | 134 | # @return [String] 135 | def inspect 136 | %Q(#<#{self.class} "#{truncated_text}">) 137 | end 138 | 139 | private 140 | 141 | def time(timestamp) 142 | short_year, month, day, hour, minute = timestamp.scan(/\d{2}/).map(&:to_i) 143 | millenium = Time.now.year / 100 * 100 144 | Time.utc(millenium + short_year, month, day, hour, minute) 145 | end 146 | 147 | def limit(value, unit, base) 148 | if captures['base'] 149 | d = AIXM.d(value.to_i, unit).to_ft 150 | AIXM.z(d.dim.round, { 'AMSL' => :qnh, 'AGL' => :qfe }[base]) 151 | else 152 | AIXM.z(value.to_i, :qne) 153 | end 154 | end 155 | 156 | def truncated_text(start: 3, length: 40) 157 | if text.length > start + length - 1 158 | text[start, length - 1] + '…' 159 | else 160 | text[start..] 161 | end 162 | end 163 | 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /lib/notam/item/a.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NOTAM 4 | 5 | # The A item defines the locations (ICAO codes) affected by this NOTAM. 6 | class A < Item 7 | 8 | RE = %r( 9 | \A 10 | A\)\s? 11 | (?(?:#{ICAO_RE}\s?)+) 12 | \z 13 | )x.freeze 14 | 15 | # @return [Array] 16 | def locations 17 | captures['locations'].split(/\s/) 18 | end 19 | 20 | # @see NOTAM::Item#merge 21 | def merge 22 | super(:locations) 23 | end 24 | 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/notam/item/b.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NOTAM 4 | 5 | # The B item defines when the NOTAM goes into effect. 6 | class B < Item 7 | 8 | RE = %r( 9 | \A 10 | B\)\s? 11 | (?#{TIME_RE}) 12 | \z 13 | )x.freeze 14 | 15 | # @return [Time] 16 | def effective_at 17 | time(captures['effective_at']) 18 | end 19 | 20 | # @see NOTAM::Item#merge 21 | def merge 22 | super(:effective_at) 23 | end 24 | 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/notam/item/c.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NOTAM 4 | 5 | # The C item defines when the NOTAM expires. 6 | class C < Item 7 | 8 | RE = %r( 9 | \A 10 | C\)\s? 11 | (? 12 | PERM| 13 | (?#{TIME_RE}) \s? (?EST)? 14 | ) 15 | \z 16 | )x.freeze 17 | 18 | # @return [Time, nil] 19 | def expiration_at 20 | time(captures['expiration_at']) unless no_expiration? 21 | end 22 | 23 | # @return [Boolean] 24 | def estimated_expiration? 25 | !captures['estimated'].nil? 26 | end 27 | 28 | # @return [Boolean] 29 | def no_expiration? 30 | captures['permanent'] == 'PERM' 31 | end 32 | 33 | # @see NOTAM::Item#merge 34 | def merge 35 | super(:expiration_at, :estimated_expiration?, :no_expiration?) 36 | end 37 | 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/notam/item/d.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NOTAM 4 | 5 | # The D item contains timesheets to further narrow when exactly the NOTAM 6 | # is effective. 7 | class D < Item 8 | 9 | # Activity schedules 10 | # 11 | # @return [Array] 12 | attr_reader :schedules 13 | 14 | # @see NOTAM::Item#parse 15 | def parse 16 | base_date = AIXM.date(data[:effective_at]) 17 | @schedules = text.sub(/\AD\)/, '').split(',').flat_map do |string| 18 | Schedule.parse(string, base_date: base_date).tap do |schedule| 19 | base_date = schedule.last.last_date || base_date # carry over month 20 | end 21 | end 22 | self 23 | rescue 24 | fail! 'invalid D item' 25 | end 26 | 27 | # Whether the D item is active at the given time. 28 | # 29 | # @param at [Time] 30 | # @return [Boolean] 31 | def active?(at:) 32 | schedules.any? { _1.active?(at: at, xy: data[:center_point]) } 33 | end 34 | 35 | # Calculate the relevant, consolidated schedules for five days. 36 | # 37 | # The beginning of the five day window is either today (if +effective_at+ 38 | # is in the past) or +effective_at+ (otherwise). 39 | # 40 | # @return [Array] 41 | def five_day_schedules 42 | schedules.map do |schedule| 43 | schedule 44 | .slice(AIXM.date(five_day_base), AIXM.date(five_day_base + 4 * 86_400)) 45 | .resolve(on: AIXM.date(five_day_base), xy: data[:center_point]) 46 | end.map { _1 unless _1.empty? }.compact 47 | end 48 | 49 | # @see NOTAM::Item#merge 50 | def merge 51 | super(:schedules, :five_day_schedules) 52 | end 53 | 54 | private 55 | 56 | # @return [Time] 57 | def five_day_base 58 | @five_day_base ||= [data[:effective_at], Time.now.utc.round].max 59 | end 60 | 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/notam/item/e.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NOTAM 4 | 5 | # The E item contains a textual description of what this NOTAM is all about. 6 | class E < Item 7 | 8 | RE = %r( 9 | \A 10 | E\)\s? 11 | (?.+) 12 | \z 13 | )mx.freeze 14 | 15 | def content 16 | captures['content'] 17 | end 18 | 19 | def translated_content 20 | content.split(/\b/).map do |word| 21 | (NOTAM::expand(word, translate: true) || word).upcase 22 | end.join 23 | end 24 | 25 | # @see NOTAM::Item#merge 26 | def merge 27 | super(:content, :translated_content) 28 | end 29 | 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/notam/item/f.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NOTAM 4 | 5 | # The F item defines the lower limit for this NOTAM. 6 | class F < Item 7 | 8 | RE = %r( 9 | \A 10 | F\)\s? 11 | (? 12 | SFC|GND|UNL| 13 | (?\d+)\s?(?FT|M)\s?(?AMSL|AGL)| 14 | (?FL)\s?(?\d+) 15 | ) 16 | \z 17 | )x.freeze 18 | 19 | # @return [AIXM::Z] 20 | def lower_limit 21 | case captures['all'] 22 | when 'UNL' then AIXM::UNLIMITED 23 | when 'SFC', 'GND' then AIXM::GROUND 24 | else limit(*captures.values_at('value', 'unit', 'base')) 25 | end 26 | end 27 | 28 | # @see NOTAM::Item#merge 29 | def merge 30 | super(:lower_limit) 31 | end 32 | 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/notam/item/footer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NOTAM 4 | 5 | # The footer items contain meta information. 6 | class Footer < Item 7 | 8 | RE = %r( 9 | \A 10 | (?CREATED|SOURCE):\s* 11 | (?.+) 12 | \z 13 | )x.freeze 14 | 15 | # @return [String] 16 | def key 17 | captures['key'].downcase.to_sym 18 | end 19 | 20 | # @return [String, Time] 21 | def value 22 | case key 23 | when :created then Time.parse(captures['value'] + ' UTC') 24 | else captures['value'] 25 | end 26 | end 27 | 28 | # @see NOTAM::Item#merge 29 | def merge 30 | data[key] = value 31 | self 32 | end 33 | 34 | # @return [String] 35 | def inspect 36 | %Q(#<#{self.class} "#{truncated_text(start: 0)}">) 37 | end 38 | 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/notam/item/g.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NOTAM 4 | 5 | # The G item defines the upper limit for this NOTAM. 6 | class G < Item 7 | 8 | RE = %r( 9 | \A 10 | G\)\s? 11 | (? 12 | SFC|GND|UNL| 13 | (?\d+)\s?(?FT|M)\s?(?AMSL|AGL)| 14 | (?FL)\s?(?\d+) 15 | ) 16 | \z 17 | )x.freeze 18 | 19 | # @return [AIXM::Z] 20 | def upper_limit 21 | case captures['all'] 22 | when 'UNL' then AIXM::UNLIMITED 23 | when 'SFC', 'GND' then AIXM::GROUND 24 | else limit(*captures.values_at('value', 'unit', 'base')) 25 | end 26 | end 27 | 28 | # @see NOTAM::Item#merge 29 | def merge 30 | super(:upper_limit) 31 | end 32 | 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/notam/item/header.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using AIXM::Refinements 4 | 5 | module NOTAM 6 | 7 | # The header item contains the NOTAM ID as well as information as to whether 8 | # its a new NOTAM or merely replacing or cancelling another one. 9 | class Header < Item 10 | 11 | RE = %r( 12 | \A 13 | (?#{ID_RE})\s+ 14 | NOTAM(?[NRC])\s* 15 | (?#{ID_RE.decapture})? 16 | \z 17 | )x.freeze 18 | 19 | # @return [String] ID of this message 20 | def id 21 | captures['id'] 22 | end 23 | 24 | # @return [String] series letter 25 | def id_series 26 | captures['id_series'] 27 | end 28 | 29 | # @return [Integer] serial number 30 | def id_number 31 | captures['id_number'].to_i 32 | end 33 | 34 | # @return [Integer] year identifier 35 | def id_year 36 | captures['id_year'].to_i + (Date.today.year / 1000 * 1000) 37 | end 38 | 39 | # @return [Boolean] +true+ if this is a new message, +false+ if it 40 | # replaces or cancels another message 41 | def new? 42 | captures['operation'] == 'N' 43 | end 44 | 45 | # @return [String, nil] message being replaced by this message or +nil+ 46 | # if this message is a new or cancelling one 47 | def replaces 48 | captures['old_id'] if captures['operation'] == 'R' 49 | end 50 | 51 | # @return [String, nil] message being cancelled by this message or +nil+ 52 | # if this message is a new or replacing one 53 | def cancels 54 | captures['old_id'] if captures['operation'] == 'C' 55 | end 56 | 57 | # @see NOTAM::Item#parse 58 | def parse 59 | super 60 | fail! "invalid operation" unless (new? && !captures['old_id']) || replaces || cancels 61 | self 62 | rescue 63 | fail! 'invalid Header item' 64 | end 65 | 66 | # @see NOTAM::Item#merge 67 | def merge 68 | super(:id, :id_series, :id_number, :id_year, :new?, :replaces, :cancels) 69 | end 70 | 71 | # @return [String] 72 | def inspect 73 | %Q(#<#{self.class} "#{truncated_text(start: 0)}">) 74 | end 75 | 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/notam/item/q.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NOTAM 4 | 5 | # The Q item provides the context such as the FIR or conditions. 6 | class Q < Item 7 | 8 | RE = %r( 9 | \A 10 | Q\)\s? 11 | (?#{ICAO_RE})/ 12 | Q(?[A-Z]{2})(?[A-Z]{2})/ 13 | (?IV|(?:[IVK]\s?))/ 14 | (?NBO|BO\s?|(?:[BMK]\s{0,2}))/ 15 | (?A[EW]|(?:[AEWK]\s?))/ 16 | (?\d{3})/ 17 | (?\d{3})/ 18 | (?\d{2})(?\d{2})(?[NS]) 19 | (?\d{3})(?\d{2})(?[EW]) 20 | (?\d{3}) 21 | \z 22 | )x.freeze 23 | 24 | # @return [String] 25 | def fir 26 | captures['fir'] 27 | end 28 | 29 | # @return [Symbol] 30 | def subject_group 31 | NOTAM.subject_group_for(captures['subject'][0,1]) 32 | end 33 | 34 | # @return [Symbol] 35 | def subject 36 | NOTAM.subject_for(captures['subject']) 37 | end 38 | 39 | # @return [Symbol] 40 | def condition_group 41 | NOTAM.condition_group_for(captures['condition'][0,1]) 42 | end 43 | 44 | # @return [Symbol] 45 | def condition 46 | NOTAM.condition_for(captures['condition']) 47 | end 48 | 49 | # @return [Symbol] 50 | def traffic 51 | NOTAM.traffic_for(captures['traffic'].strip) 52 | end 53 | 54 | # @return [Array] 55 | def purpose 56 | captures['purpose'].strip.chars.map { NOTAM.purpose_for(_1) } 57 | end 58 | 59 | # @return [Array] 60 | def scope 61 | captures['scope'].strip.chars.map { NOTAM.scope_for(_1) } 62 | end 63 | 64 | # @return [AIXM::Z] lower limit (QNE flight level) or {AIXM::GROUND} 65 | # (aka: 0ft QFE) 66 | def lower_limit 67 | if (limit = captures['lower_limit'].to_i).zero? 68 | AIXM::GROUND 69 | else 70 | AIXM.z(captures['lower_limit'].to_i, :qne) 71 | end 72 | end 73 | 74 | # @return [AIXM::Z] upper limit (QNE flight level) or {AIXM::UNLIMITED} 75 | # (aka: FL999 QNE) 76 | def upper_limit 77 | AIXM.z(captures['upper_limit'].to_i, :qne) 78 | end 79 | 80 | # @return [AIXM::XY] approximately affected area center point 81 | def center_point 82 | AIXM.xy( 83 | lat: %Q(#{captures['lat_deg']}°#{captures['lat_min']}'00"#{captures['lat_dir']}), 84 | long: %Q(#{captures['long_deg']}°#{captures['long_min']}'00"#{captures['long_dir']}) 85 | ) 86 | end 87 | 88 | # @return [AIXM::D] approximately affected area radius 89 | def radius 90 | AIXM.d(captures['radius'].to_i, :nm) 91 | end 92 | 93 | # @see NOTAM::Item#merge 94 | def merge 95 | super(:fir, :subject_group, :subject, :condition_group, :condition, :traffic, :purpose, :scope, :lower_limit, :upper_limit, :center_point, :radius) 96 | end 97 | 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/notam/message.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NOTAM 4 | 5 | # NOTAM messages are plain text and consist of several ordered items: 6 | # 7 | # WDDDD/DD ... <- Header (mandatory) 8 | # Q) ... <- Q item: context (mandatory) 9 | # A) ... <- A item: locations (mandatory) 10 | # B) ... <- B item: effective from (mandatory) 11 | # C) ... <- C item: effective until (optional) 12 | # D) ... <- D item: timesheets (optional, may contain newlines) 13 | # E) ... <- E item: description (mandatory, may contain newlines) 14 | # F) ... <- F item: upper limit (optional) 15 | # G) ... <- G item: lower limit (optional) 16 | # CREATED: ... <- Footer (optional) 17 | # SOURCE: ... <- Footer (optional) 18 | # 19 | # Furthermore, oversized NOTAM may be split into several partial messages 20 | # which contain with +PART n OF n+ and +END PART n OF n+ markers. This is an 21 | # unofficial extension and therefore the markers may be found in different 22 | # places such as on the A item, on the E item or even somewhere in between. 23 | class Message 24 | 25 | UNSUPPORTED_FORMATS = %r( 26 | \A 27 | ![A-Z]{3,5} | # USA: NOTAM (D), FDC etc 28 | \w{3}\s\w{3}\s\([OU]\) | # USA: (O) and (U) NOTAM 29 | \w{3}\s[A-Z]\d{4}/\d{2}\sMILITARY # USA: military 30 | )xi.freeze 31 | 32 | PART_RE = %r( 33 | (?:END\s+)?PART\s+(?\d+)\s+OF\s+(?\d+) 34 | )xim.freeze 35 | 36 | FINGERPRINTS = %w[Q) A) B) C) D) E) F) G) CREATED: SOURCE:].freeze 37 | 38 | # Raw NOTAM text message 39 | # 40 | # @return [String] 41 | attr_reader :text 42 | 43 | # NOTAM item objects 44 | # 45 | # @return [Array] 46 | attr_reader :items 47 | 48 | # Parsed NOTAM message payload 49 | # 50 | # @return [Hash] 51 | attr_reader :data 52 | 53 | def initialize(text) 54 | fail(NOTAM::ParserError, "unsupported format") unless self.class.supported_format? text 55 | @text, @items, @data = text, [], {} 56 | itemize(departition(@text)).each do |raw_item| 57 | item = NOTAM::Item.new(raw_item, data: @data).parse.merge 58 | @items << item 59 | end 60 | end 61 | 62 | def inspect 63 | %Q(#<#{self.class} #{data[:id]}>) 64 | end 65 | alias :to_s :inspect 66 | 67 | # Whether the NOTAM is active at the given time. 68 | # 69 | # @param at [Time] 70 | # @return [Boolean] 71 | def active?(at:) 72 | (data[:effective_at]..data[:expiration_at]).include?(at) && 73 | (!(d_item = item(:D)) || d_item.active?(at: at)) 74 | end 75 | 76 | # Item of the given type 77 | # 78 | # @param type [Symbol, nil] either +:Header+, +:Q+, +(:A..:G)+ or +:Footer+ 79 | def item(type) 80 | items.find { _1.type == type } 81 | end 82 | 83 | class << self 84 | undef_method :new 85 | 86 | # Parse the given raw NOTAM text message to create a new {NOTAM::Message} 87 | # object. 88 | # 89 | # @return [NOTAM::Message] 90 | def parse(text) 91 | allocate.instance_eval do 92 | initialize(text) 93 | self 94 | end 95 | end 96 | 97 | # Whether the given raw NOTAM text message is in a supported format. 98 | # 99 | # @return [Boolean] 100 | def supported_format?(text) 101 | !text.match? UNSUPPORTED_FORMATS 102 | end 103 | end 104 | 105 | private 106 | 107 | # @return [String] 108 | def departition(text) 109 | text.gsub(PART_RE, '').tap do 110 | if $~ # part marker found 111 | @data.merge!( 112 | part_index: $~[:part_index].to_i, 113 | part_index_max: $~[:part_index_max].to_i 114 | ) 115 | end 116 | end 117 | end 118 | 119 | # @return [Array] 120 | def itemize(text) 121 | lines = text.gsub(/\s(#{NOTAM::Item::RE})/, "\n\\1").split("\n") 122 | last_index = -1 123 | [lines.first].tap do |array| 124 | lines[1..].each do |line| 125 | index = FINGERPRINTS.index(line.scan(/\A[A-Z]+?[):]/).first).to_i 126 | if index > last_index 127 | array << line 128 | last_index = index 129 | else 130 | array.push([array.pop, line].join("\n")) 131 | end 132 | end 133 | end 134 | end 135 | end 136 | 137 | # Shortcut of NOTAM::Message.parse 138 | # 139 | # @see NOTAM::Message.parse 140 | def self.parse(text) 141 | NOTAM::Message.parse(text) 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /lib/notam/schedule.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | using AIXM::Refinements 4 | 5 | module NOTAM 6 | 7 | # Structure to accommodate individual schedules used on D items 8 | class Schedule 9 | EVENTS = { 'SR' => :sunrise, 'SS' => :sunset }.freeze 10 | EVENT_HOURS = { sunrise: AIXM.time('06:00'), sunset: AIXM.time('18:00') }.freeze 11 | OPERATIONS = { 'PLUS' => 1, 'MINUS' => -1 }.freeze 12 | MONTHS = { 'JAN' => 1, 'FEB' => 2, 'MAR' => 3, 'APR' => 4, 'MAY' => 5, 'JUN' => 6, 'JUL' => 7, 'AUG' => 8, 'SEP' => 9, 'OCT' => 10, 'NOV' => 11, 'DEC' => 12 }.freeze 13 | DAYS = { 'MON' => :monday, 'TUE' => :tuesday, 'WED' => :wednesday, 'THU' => :thursday, 'FRI' => :friday, 'SAT' => :saturday, 'SUN' => :sunday, 'DAILY' => :any, 'DLY' => :any }.freeze 14 | 15 | DATE_RE = /[0-2]\d|3[01]/.freeze 16 | DAY_RE = /#{DAYS.keys.join('|')}/.freeze 17 | MONTH_RE = /#{MONTHS.keys.join('|')}/.freeze 18 | HCODE_RE = /(?H24|HJ|HN)/.freeze 19 | HOUR_RE = /(?[01]\d|2[0-4])(?[0-5]\d)/.freeze 20 | OPERATIONS_RE = /#{OPERATIONS.keys.join('|')}/.freeze 21 | EVENT_RE = /(?SR|SS)(?:\s(?#{OPERATIONS_RE})(?\d+))?/.freeze 22 | TIME_RE = /#{HOUR_RE}|#{EVENT_RE}/.freeze 23 | TIME_RANGE_RE = /#{TIME_RE}-#{TIME_RE}|#{HCODE_RE}/.freeze 24 | DATETIME_RE = /(?:(?#{MONTH_RE}) )?(?#{DATE_RE}) (?