{"id":18103971,"url":"https://github.com/svoop/aipp","last_synced_at":"2025-06-21T08:36:07.450Z","repository":{"id":56860087,"uuid":"117528940","full_name":"svoop/aipp","owner":"svoop","description":"AIP parser to convert AIP publications to AIXM or OFMX","archived":false,"fork":false,"pushed_at":"2024-12-27T13:00:55.000Z","size":772,"stargazers_count":10,"open_issues_count":2,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-13T19:40:52.185Z","etag":null,"topics":["aviation","gem","openflightmaps","ruby"],"latest_commit_sha":null,"homepage":"https://bitcetera.com","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/svoop.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"svoop","custom":"https://donorbox.org/bitcetera"}},"created_at":"2018-01-15T10:05:38.000Z","updated_at":"2024-12-27T13:00:51.000Z","dependencies_parsed_at":"2023-11-23T15:27:11.353Z","dependency_job_id":"52d6c38a-07ed-47ba-8041-b412be88c3c6","html_url":"https://github.com/svoop/aipp","commit_stats":{"total_commits":259,"total_committers":2,"mean_commits":129.5,"dds":"0.10038610038610041","last_synced_commit":"5d3ea3fd81e6b71774e101e4066fcbadb190c28a"},"previous_names":[],"tags_count":49,"template":false,"template_full_name":null,"purl":"pkg:github/svoop/aipp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svoop%2Faipp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svoop%2Faipp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svoop%2Faipp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svoop%2Faipp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/svoop","download_url":"https://codeload.github.com/svoop/aipp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svoop%2Faipp/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261094647,"owners_count":23108754,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aviation","gem","openflightmaps","ruby"],"created_at":"2024-10-31T22:13:39.631Z","updated_at":"2025-06-21T08:36:02.427Z","avatar_url":"https://github.com/svoop.png","language":"Ruby","readme":"[![Version](https://img.shields.io/gem/v/aipp.svg?style=flat)](https://rubygems.org/gems/aipp)\n[![Tests](https://img.shields.io/github/actions/workflow/status/svoop/aipp/test.yml?style=flat\u0026label=tests)](https://github.com/svoop/aipp/actions?workflow=Test)\n[![GitHub Sponsors](https://img.shields.io/github/sponsors/svoop.svg)](https://github.com/sponsors/svoop)\n\n# AIPP\n\nParser for aeronautical information available online.\n\nThis gem incluces executables to download and parse aeronautical information (HTML, PDF, XSLX, ODS and CSV), then build and export is as [AIXM](https://github.com/svoop/aixm) or [OFMX](https://github.com/openflightmaps/ofmx/wiki).\n\n* [Homepage](https://github.com/svoop/aipp)\n* [Rubydoc](https://www.rubydoc.info/gems/aipp/AIPP)\n* Author: [Sven Schwyn - Bitcetera](https://bitcetera.com)\n\nThank 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.\n\n## Table of Contents\n\n[Install](#label-Install) \u003cbr\u003e\n[Usage](#label-Usage) \u003cbr\u003e\n[Regions](#label-Regions) \u003cbr\u003e\n[Storage](#label-Storage) \u003cbr\u003e\n[References](#label-References) \u003cbr\u003e\n[Development](#label-Development)\n\n## Install\n\n### Security\n\nThis 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:\n\n```\ngem cert --add \u003c(curl -Ls https://raw.github.com/svoop/aipp/main/certs/svoop.pem)\n```\n\n### Standalone\n\nMake sure to have the [latest version of Ruby](https://www.ruby-lang.org/en/documentation/installation/) and then install this gem:\n\n```\ngem install aipp --trust-policy MediumSecurity\n```\n\n### Bundler\n\nIf you're familiar with [Bundler](https://bundler.io) powered Ruby projects, you might prefer to add the following to your \u003csamp\u003eGemfile\u003c/samp\u003e or \u003csamp\u003egems.rb\u003c/samp\u003e:\n\n```ruby\ngem 'aipp'\n```\n\nAnd then install the bundle:\n\n```\nbundle install --trust-policy MediumSecurity\n```\n\n## Usage\n\nAIPP parses different kind of information sources and converts them to different output formats depending on which executable you use:\n\nExecutable | Output Format\n-----------|--------------\n`aip2aixm` | AIXM\n`aip2ofmx` | OFMX\n\nThe parsers are organized in three levels:\n\n```\nregion            ⬅︎ aeronautical region such as \"LF\" (France)\n└── scope         ⬅︎ scope such as \"AIP\" or \"NOTAM\"\n    └── section   ⬅︎ section of the scope such as \"ENR-2.1\" or \"aerodromes\"\n```\n\nThe following scopes are currently available:\n\nScope | Content | Cache\n------|---------|------\n[AIP](lib/aipp/aip/README.md) (default) | aeronautical information publication | by AIRAC cycle\n[NOTAM](lib/aipp/notam/README.md) | notice to airmen | by effective date and hour\n\nTo list all available scopes, regions and sections:\n\n```\naip2aixm --list\n```\n\nSee the built-in help for all options:\n\n```\nnotam2aixm --help\n```\n\nExample: You wish to build the complete OFMX file for the current AIRAC cycle AIP of the region LF:\n\n```\naip2ofmx -r LF\n```\n\nYou'll find the OFMX file in the current directory if the binary exits successfully.\n\n## Regions\n\nTo implement a region, you have to create a directory \u003csamp\u003elib/aipp/regions/{REGION}/\u003c/samp\u003e off the gem root and then subdirectories for each scope as well as for support files. Here's a simplified overview for the region \"LF\" (France):\n\n```\nLF/                         ⬅︎ region \"LF\"\n├── README.md\n├── aip                     ⬅︎ scope \"AIP\"\n│   ├── AD-2.rb             ⬅︎ section \"AD-2\"\n│   └── ENR-4.3.rb          ⬅︎ section \"ENR-4.3\"\n├── notam                   ⬅︎ scope \"NOTAM\"\n│   ├── AD.rb               ⬅︎ section \"AD\"\n│   └── ENR.rb              ⬅︎ section \"ENR\"\n├── borders\n│   ├── france_atlantic_coast.geojson\n│   └── france_atlantic_territorial_sea.geojson\n├── fixtures\n│   └── aerodromes.yml\n└── helpers\n    ├── base.rb\n    └── surface.rb\n```\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⚠️\u003c/td\u003e\n    \u003ctd\u003eAll paths from here on forward are relative to the region directory.\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n### Parsers\n\nSay, you want to parse AIP ENR-4.1. You have to create the file \u003csamp\u003eaip/ENR-4.1.rb\u003c/samp\u003e which defines the class `ENR41` as follows:\n\n```ruby\nmodule AIPP::LF::AIP\n  class ENR41 \u003c AIPP::AIP::Parser\n    depends_on :ENR21, :ENR22   # declare dependencies to other parsers\n    (...)\n  end\nend\n```\n\nAnother parser might target en-route NOTAM and therefore has to go to \u003csamp\u003enotam/ENR.rb\u003c/samp\u003e like so:\n\n```ruby\nmodule AIPP::LF::NOTAM\n  class ENR \u003c AIPP::NOTAM::Parser\n    (...)\n  end\nend\n```\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⚠️\u003c/td\u003e\n    \u003ctd\u003eParser files and classes may follow AIP naming conventions such as \u003csamp\u003eENR-4.1.rb\u003c/samp\u003e. However, you're free to use arbitrary naming for parser files like \u003csamp\u003enavaids.rb\u003c/samp\u003e (e.g. if you're working with one big data source which contains the full AIP dataset you'd like to split into smaller parts).\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nThe class has to implement some methods either in the class itself or in a [helper](#Helpers) included by the class.\n\n#### Mandatory `parse` Method\n\nThe class must implement the `parse` method which contains the code to read, parse and write the data:\n\n```ruby\nmodule AIPP::LF::AIP\n  class ENR41 \u003c AIPP::AIP::Parser\n\n    def parse\n      html = read             # read the Nokogiri::HTML5 document\n      feature = (...)         # build the feature\n      add(feature: feature)   # add the feature to AIXM::Document\n    end\n\n  end\nend\n```\n\nSome AIP may be split over several files which require a little more code to load the individual HTML source files:\n\n```ruby\nmodule AIPP::LF::AIP\n  class AD2 \u003c AIPP::AIP::Parser\n\n    def parse\n      %i(one two three).each do |part|\n        html = read(\"#{aip}.#{part}\")   # read with a non-standard name\n        support_html = read('AD-0.6')   # maybe read necessary support documents\n        (...)\n      end\n    end\n\n  end\nend\n```\n\nThe parser has access to the following methods:\n\nMethod | Description\n-------|------------\n[`section`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#section-instance_method) | current section (e.g. `ENR-2.1` or `aerodromes`)\n[`read`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#read-instance_method) | download, cache and read a document from source\n[`add`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#add-instance_method) | add a [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)\n[`find`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#find-instance_method) | find previously written [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)s by object\n[`find_by`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#find_by-instance_method) | find previously written [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)s by class and attribute values\n[`unique`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#unique-instance_method) | prevent duplicate [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)s\n[`given`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#given-instance_method) | inline condition for assignments\n[`link_to`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#link_to-instance_method) | optionally checked Markdown link\n\nEqually available is the current runtime environment. All of the following objects behave like `OpenStruct`:\n\nMethod | Description\n-------|------------\n[`AIPP.cache`](https://www.rubydoc.info/gems/aipp/AIPP/Enrivonment/Cache) | cache to make transient objects available across AIPs\n[`AIPP.borders`](https://www.rubydoc.info/gems/aipp/AIPP/Enrivonment/Borders) | [borders](#Borders) of the current region\n[`AIPP.fixtures`](https://www.rubydoc.info/gems/aipp/AIPP/Enrivonment/Fixtures) | [fixtures](#Fixtures) of the current region\n[`AIPP.options`](https://www.rubydoc.info/gems/aipp/AIPP/Environment/Options) | arguments read from \u003csamp\u003eaip2aixm\u003c/samp\u003e or \u003csamp\u003eaip2ofmx\u003c/samp\u003e respectively\n[`AIPP.config`](https://www.rubydoc.info/gems/aipp/AIPP/Environment/Config) | configuration read from \u003csamp\u003econfig.yml\u003c/samp\u003e\n\nTo make the parser code more readable, a few core extensions are provided:\n\n* [`Object#blank` (ActiveSupport)](https://www.rubydoc.info/gems/activesupport/Object#blank%3F-instance_method)\n* [`NilClass`](https://www.rubydoc.info/gems/aipp/NilClass)\n* [`Integer`](https://www.rubydoc.info/gems/aipp/Integer)\n* [`String` (ActiveSupport)](https://www.rubydoc.info/gems/activesupport/String)\n* [`String`](https://www.rubydoc.info/gems/aipp/String)\n* [`Array`](https://www.rubydoc.info/gems/aipp/Array)\n* [`Hash`](https://www.rubydoc.info/gems/aipp/Hash)\n* [`Enumerable`](https://www.rubydoc.info/gems/aipp/Enumerable)\n* [`DateTime` (ActiveSupport)](https://www.rubydoc.info/gems/activesupport/DateTime)\n* [`Nokogiri`](https://www.rubydoc.info/gems/aipp/Nokogiri)\n\n#### Mandatory `origin_for` Method\n\nThe class must implement the `origin_for` method which returns an origin object describing how to download the source data (e.g. an AIP file or NOTAM message):\n\n```ruby\nmodule AIPP::LF::AIP\n  class AD2 \u003c AIPP::AIP::Parser\n\n    def origin_for(document)\n      # build and return the origin object\n    end\n\n  end\nend\n```\n\nReturn any of the following origin objects best explained by example:\n\n```\nAIPP::Downloader::File.new(\n  file: \"file.dat\",   # relative path to file\n  type: :pdf          # optional: file type if different from extension\n)\n```\n\n```\nAIPP::Downloader::File.new(\n  archive: \"foobar.zip\",     # relative path to archive\n  file: \"subdir/file.dat\",   # file to extract from archive\n  type: :pdf                 # optional: file type if different from extension\n)\n```\n\nSee [Downloader](https://www.rubydoc.info/gems/aipp/AIPP/Downloader) for more on recognised file and archive types.\n\n```\nAIPP::Downloader::HTTP.new(\n  file: \"https://example.com/foobar.zip\",   # URL where the file is located\n  type: :pdf,                               # optional: file type if different from extension\n  headers: \"Cookie: name=value\",            # optional: additional headers e.g. for session\n)\n```\n\n```\nAIPP::Downloader::HTTP.new(\n  archive: \"https://example.com/foobar.zip\",   # URL where the archive is located\n  file: \"subdir/file.dat\",                     # file to extract from archive\n  type: :pdf,                                  # optional: file type if different from extension\n  headers: \"Cookie: name=value\",               # optional: additional headers e.g. for session\n)\n```\n\nThe [excon gem](https://www.rubydoc.info/gems/excon) is used to perform HTTP requests.\n\n```\nAIPP::Downloader::GraphQL.new(\n  client: MyAPI::Client,       # GraphQL client class\n  query: MyAPI::Name::Query,   # GraphQL query class\n  variables: {                 # dynamic query parameters\n    first_name: 'Geronimo',\n    age: 50\n  }\n)\n```\n\nFor this GraphQL downloader to work, you have to declare a GraphQL client class beforehand. See the [graphql-client gem documentation](https://www.rubydoc.info/gems/graphql-client) for details, the following example fits the downloader above:\n\n```ruby\nmodule MyAPI\n  HttpAdapter = GraphQL::Client::HTTP.new(ENV['MY_API_URL']) do\n    def headers(context)\n      { \"Authorization\": \"Bearer #{ENV['MY_API_AUTHORIZATION']}\" }\n    end\n  end\n  Schema = GraphQL::Client.load_schema(HttpAdapter)\n  Client = GraphQL::Client.new(schema: Schema, execute: HttpAdapter)\n\n  class Name\n    Query = Client.parse \u003c\u003c~END\n      query ($first_name: String!, $age: Int!) {\n        queryNOTAMs(\n          filter: {first_name: $first_name, age: $age}\n        ) {\n          name\n        }\n      }\n    END\n  end\nend\n```\n\nFor performance, all downloads are cached and subsequent runs will use the cached data rather than fetching the sources anew. Each scope defines a cache time window, see the [table of scopes above](#label-Usage). You can discard existing and rebuild caches by use of the `--clean` command line argument.\n\n#### Optional `setup` Method\n\nThe class may implement the `setup` method. If present, it will be called when this parser is instantiated:\n\n```ruby\nmodule AIPP::LF::AIP\n  class AD2 \u003c AIPP::AIP::Parser\n\n    def setup\n      AIXM.config.voice_channel_separation = :any\n      AIPP.cache.setup_at ||= Time.now\n    end\n\n  end\nend\n```\n\n### Helpers\n\nHelpers are mixins defined in the \u003csamp\u003ehelpers/\u003c/samp\u003e subdirectory. All helpers are required automatically in alphabetic order.\n\n### Borders\n\nAIXM knows named borders for country boundaries. However, you might need additional borders which don't exist as named borders.\n\nYou can define additional borders as [`AIPP::Border`](https://www.rubydoc.info/gems/aipp/AIPP/Border) objects in two ways.\n\n#### From GeoJSON\n\nCreate simple GeoJSON files in the \u003csamp\u003eborders/\u003c/samp\u003e subdirectory, for example this `my_border_1.geojson`:\n\n```json\n{\n  \"type\": \"GeometryCollection\",\n  \"geometries\": [\n    {\n      \"type\": \"LineString\",\n      \"coordinates\": [\n        [6.009531650000042, 45.12013319700009],\n        [6.015747738000073, 45.12006702600007]\n      ]\n    },\n    {\n      \"type\": \"LineString\",\n      \"coordinates\": [\n        [4.896732957592112, 43.95662950764992],\n        [4.005739165537195, 44.10769266295027]\n      ]\n    }\n  ]\n}\n```\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⚠️\u003c/td\u003e\n    \u003ctd\u003eThe GeoJSON file must consist of exactly one `GeometryCollection` which may contain any number of `LineString` geometries. Only `LineString` geometries are recognised! To define a closed polygon, the first coordinates of a `LineString` must be identical to the last coordinates.\u003c/td\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n#### From Coordinates\n\nIt's also possible to create a [`AIPP::Border`](https://www.rubydoc.info/gems/aipp/AIPP/Border) objects on the fly:\n\n```ruby\nmy_border_2 = AIPP::Border.from_array(\n  [\n    [\"6.009531650000042 45.12013319700009\", \"6.015747738000073 45.12006702600007\"],\n    [\"4.896732957592112 43.95662950764992\", \"4.005739165537195 44.10769266295027\"]\n  ]\n)\n```\n\nThe coordinate pairs must be separated with whitespaces and/or commas. If you want to use this border everywhere, make sure you add it to the others:\n\n```ruby\n  borders[\"my_border_2\"] = my_border_2\n```\n\n#### Usage in Parsers\n\nIn the parser, the [`borders`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#borders-instance_method) method gives you access to all borders read from GeoJSON files:\n\n```ruby\nborders   # =\u003e { \"my_border_1\" =\u003e #\u003cAIPP::Border\u003e, \"my_border_2\" =\u003e #\u003cAIPP::Border\u003e }\n```\n\nThe border object implements simple nearest point and segment calculations to create arrays of [`AIXM::XY`](https://www.rubydoc.info/gems/aixm/AIXM/XY) which can be used with [`AIXM::Component::Geometry`](https://www.rubydoc.info/gems/aixm/AIXM/Component/Geometry).\n\nSee [`AIPP::Border`](https://www.rubydoc.info/gems/aipp/AIPP/Border) for more on this.\n\n### Fixtures\n\nFixtures are static YAML data files in the \u003csamp\u003efixtures/\u003c/samp\u003e subdirectory. All fixtures are read automatically, e.g. the contents of the \u003csamp\u003elib/aipp/regions/{REGION}/fixtures/aerodromes.yml\u003c/samp\u003e will be available from `AIPP.fixtures.aerodromes`.\n\nRead on for how to best use fixtures.\n\n### Patches\n\nWhen parsed data is faulty or missing, you may fall back to fixtures instead. This is where patches come in. You can patch any AIXM attribute setter by defining a patch block inside the AIP parser and accessing the static data via `parser.fixture`:\n\n```ruby\nmodule AIPP::LF::AIP\n  class AD2 \u003c AIP\n\n    patch AIXM::Feature::Airport, :z do |object, value|\n      throw(:abort) unless value.nil?\n      throw(:abort, 'fixture missing') unless z = AIPP.fixtures.aerodromes.dig(object.id, 'z')\n      AIXM.z(z, :qnh)\n    end\n\n  end\nend\n```\n\nThe patch receives the object and the value which is about to be assigned. It should implement something along these lines:\n\n* If the value is okay, `throw(:abort)` to leave the patch block without touching anything.\n* Otherwise, try to fetch a better value e.g. from the fixtures. If no better value can be found (e.g. outdated fixtures), `throw(:abort, \"reason\")` to leave the patch block and fail with a useful error message which contains the reason thrown.\n* At last, build and return the value object which will be assigned instead of the original value.\n\n### Source File Line Numbers\n\nIn order to reference the source of an AIXM/OFMX feature, it's necessary to know the line number where a particular node occurs in the HTML source file. You can ask any HTML element as follows:\n\n```ruby\ntr.line\n```\n\n### Errors\n\nYou should `fail` on fatal problems which must be fixed. The `--debug-on-error` command line argument will open a debug session when such an error occurs. Issue errors as usual:\n\n```ruby\nfail \"my message\"\n```\n\n### Warnings\n\nYou should `warn` on non-fatal problems which should be fixed (default) or might be ignored (`severe: false`). The `--debug-on-warning ID` command line argument will open a debug session when then warning with the given ID occurs. To issue a warning:\n\n```ruby\nwarn(\"my message\")\n```\n\nLess important warnings are only shown if the `--verbose` mode is set:\n\n```ruby\nwarn(\"my message\", severe: false)\n```\n\n### Messages\n\n#### info\n\nUse `info` for essential info messages:\n\n```ruby\ninfo(\"my message\")                  # displays \"my message\" in black\ninfo(\"my message\", color: :green)   # displays \"my message\" in green\n```\n\n#### verbose info\n\nUse `verbose_info` for in-depth info messages which are only shown if the `--verbose` mode is set:\n\n```ruby\nverbose_info(\"my message\")   # displays \"my message\" in blue\n```\n\n#### debug\n\nThe [default Ruby debugger](https://github.com/ruby/debug#debug-command-on-the-debug-console) is enabled by default, you can add a breakpoint as usual with:\n\n```ruby\ndebugger\n```\n\n## Storage\n\nAIPP uses a storage directory for configuration, caching and in order to keep the results of previous runs. The default location is `~/.aipp`, however, you can pass a different directory with the `--storage` argument.\n\nYou'll find a directory for each region and scope which contains the following items:\n\n* `sources/`\u003cbr\u003eZIP archives which cache all source files used to build.\n* `builds/`\u003cbr\u003eZIP archives of successful builds containing:\n  * the built AIXM/OFMX file\n  * `build.yaml` – context of the build process\n  * `manifest.csv` – diffable manifest (see below)\n* `config.yml`\u003cbr\u003eThis file contains configuration which will be read on subsequent runs, most notably the namespace UUID used to identify the creator of OFMX files.\n\nThe manifest is a CSV which lists every feature on a separate line along with its hashes, AIP and comment. You can `diff` or `git diff` two manifests:\n\n```diff\n$ git diff -U0 2019-09-12/manifest.csv 2019-10-10/manifest.csv\n\n--- a/2019-09-12/manifest.csv\n+++ b/2019-10-10/manifest.csv\n@@ -204 +204 @@ AD-1.3,Ahp,9e9f031e,d6f22057,Airport: LFLJ COURCHEVEL\n-AD-1.3,Ahp,9f1eed18,37ddbbde,Airport: LFQD ARRAS ROCLINCOURT\n+AD-1.3,Ahp,9f1eed18,f0e60105,Airport: LFQD ARRAS ROCLINCOURT\n@@ -312 +312 @@ AD-2,Aha,4250c9ee,04d49dc7,Address: RADIO for LFHV\n-AD-2,Aha,6b381b32,fb947716,Address: RADIO for LFPO\n+AD-2,Aha,6b381b32,b9723b7e,Address: RADIO for LFPO\n@@ -664 +663,0 @@ AD-2,Ser,3920a7fd,4545c5eb,Service: AFIS by LFGA TWR\n-AD-2,Ser,39215774,1f13f2cf,Service: APP by LFCR APP\n@@ -878 +876,0 @@ AD-2,Ser,bb5228d7,7cfb4572,Service: TWR by LFMH TWR\n-AD-2,Ser,bc72caf2,0a15b39c,Service: FIS by LFCR FIC\n(...)\n```\n\nThe advantage of `git diff` is its ability to highlight exactly which part of a line has changed. [Check out this post to learn how](https://www.viget.com/articles/dress-up-your-git-diffs-with-word-level-highlights/).\n\n## References\n\n* [Geo Maps – programmatically generated GeoJSON maps](https://github.com/simonepri/geo-maps)\n* [open flightmaps – open-source aeronautical maps](https://openflightmaps.org)\n* [AIXM Rubygem – AIXM/OFMX generator for Ruby](https://github.com/svoop/aixm)\n\n## Development\n\nTo install the development dependencies and then run the test suite:\n\n```\nbundle install\nbundle exec rake    # run tests once\nbundle exec guard   # run tests whenever files are modified\n```\n\nPlease submit issues on:\n\nhttps://github.com/svoop/aipp/issues\n\nTo contribute code, fork the project on GitHub, add your code and submit a pull request:\n\nhttps://help.github.com/articles/fork-a-repo\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","funding_links":["https://github.com/sponsors/svoop","https://donorbox.org/bitcetera"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsvoop%2Faipp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsvoop%2Faipp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsvoop%2Faipp/lists"}