diff --git a/.editorconfig b/.editorconfig
index 677ee245..f90f6abb 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -10,5 +10,5 @@ indent_style = space
[*.md]
trim_trailing_whitespace = false
-[*.{json,yaml,yml}]
+[*.{json,toml,yaml,yml}]
indent_size = 2
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000..f0f92877
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,12 @@
+# Default code owners unless overridden below
+* @MorpheusXAUT @IamLuisAUT
+
+# Explicitly specify code owners for GitHub config, such as workflows or CODEOWNERS, and other relevant files
+/.github/ @MorpheusXAUT @IamLuisAUT
+/docs/ @MorpheusXAUT @IamLuisAUT
+LICENSE* @MorpheusXAUT @IamLuisAUT
+README.md @MorpheusXAUT @IamLuisAUT
+CONTRIBUTING.md @MorpheusXAUT @IamLuisAUT
+
+# Dataset owners
+/dataset/LO/ @MorpheusXAUT @IamLuisAUT
diff --git a/.github/airac-cycles.yaml b/.github/airac-cycles.yml
similarity index 100%
rename from .github/airac-cycles.yaml
rename to .github/airac-cycles.yml
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..b7f469cc
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,40 @@
+# Dataset Changes
+
+Thank you for your contribution to the `vacs` dataset.
+
+> [!WARNING]
+> Please make sure your dataset is in the correct `dataset/{FIR}/` directory, otherwise it will not be picked up by our validations and release process.
+
+If you're contributing configuration for a new FIR, please add a small paragraph about your affiliation to the respective FIR to this PR description. We'll be glad to accept your contribution, but would like to ensure our dataset's origin is transparent.
+After the first PR was merged, we'll add you to the CODEOWNERS file for that FIR, so that future changes can be reviewed by you.
+
+If you're contributing for an existing FIR, we'll wait for feedback from the current CODEOWNER(s) before merging.
+
+_You may just delete these paragraphs from your PR description, they're just here to remind you of our contribution guidelines._
+
+## Auto-Formatting
+
+This repository uses [autofix.ci](https://autofix.ci) to automatically ensure consistent code formatting using our [prettier](https://prettier.io) configuration.
+The `autofix-ci` bot will automatically push formatting fixes to your branch if needed.
+
+> [!IMPORTANT]
+> Please **enable "Allow edits by maintainers"** so that the bot can push commits to your PR branch.
+
+Look for the **"Allow edits by maintainers"** checkbox when creating the PR (below the description box or in the right sidebar). Please ensure this checkbox is checked before submitting the PR.
+
+The `autofix-ci` bot will automatically push formatting fixes to your branch if needed (commits will have the message `chore(dataset): format with prettier`).
+
+
+If you prefer not to enable this setting:
+
+Make sure your dataset changes are formatted properly.
+If your editor supports `prettier`, formatting should happen automatically.
+
+**Malformed dataset files will automatically be rejected by CI.**
+
+Alternatively, you can run our formatter locally:
+
+1. Install [Node.js](https://nodejs.org/en/download)
+2. Run `npm install` to install `prettier` and the required plugins
+3. Run `npm run format` to format your dataset changes
+
diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml
new file mode 100644
index 00000000..b06b7527
--- /dev/null
+++ b/.github/workflows/autofix.yml
@@ -0,0 +1,40 @@
+name: autofix.ci
+on:
+ pull_request:
+ paths:
+ - "dataset/**"
+ - ".github/workflows/autofix.yml"
+ push:
+ branches: ["main"]
+ paths:
+ - "dataset/**"
+ - ".github/workflows/autofix.yml"
+
+permissions:
+ contents: read
+
+jobs:
+ autofix:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+
+ - name: Setup Node (LTS)
+ uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
+ with:
+ node-version: "lts/*"
+ cache: "npm"
+ cache-dependency-path: |
+ package-lock.json
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Format dataset
+ run: npm run format
+
+ - uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8 # v1.3.3
+ with:
+ commit-message: "chore(dataset): format with prettier"
diff --git a/.github/workflows/ci-dataset.yml b/.github/workflows/ci-dataset.yml
index d9543732..0017c6d2 100644
--- a/.github/workflows/ci-dataset.yml
+++ b/.github/workflows/ci-dataset.yml
@@ -22,13 +22,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v6.0.2
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
- uses: Swatinem/rust-cache@v2.8.2
+ uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
shared-key: "linux-x86_64"
workspaces: "tools -> target"
diff --git a/.github/workflows/ci-rust.yml b/.github/workflows/ci-rust.yml
index 8c313afc..5a7b5438 100644
--- a/.github/workflows/ci-rust.yml
+++ b/.github/workflows/ci-rust.yml
@@ -43,13 +43,13 @@ jobs:
continue-on-error: ${{ matrix.checks == 'advisories' }}
steps:
- name: Checkout
- uses: actions/checkout@v6.0.2
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: cargo deny
- uses: EmbarkStudios/cargo-deny-action@v2.0.15
+ uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2.0.15
with:
manifest-path: "tools/Cargo.toml"
command: check ${{ matrix.checks }}
@@ -60,20 +60,20 @@ jobs:
continue-on-error: true
steps:
- name: Checkout
- uses: actions/checkout@v6.0.2
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: cargo machete
- uses: bnjbvr/cargo-machete@v0.9.1
+ uses: bnjbvr/cargo-machete@7959c845782fed02ee69303126d4a12d64f1db18 # v0.9.1
check:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v6.0.2
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Rust toolchain
uses: dtolnay/rust-toolchain@stable
@@ -81,7 +81,7 @@ jobs:
components: clippy, rustfmt
- name: Cache cargo
- uses: Swatinem/rust-cache@v2.8.2
+ uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
shared-key: "linux-x86_64"
workspaces: "tools -> target"
diff --git a/.github/workflows/release-tools.yml b/.github/workflows/release-tools.yml
index c3319745..7d1884e9 100644
--- a/.github/workflows/release-tools.yml
+++ b/.github/workflows/release-tools.yml
@@ -37,7 +37,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v6.0.2
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
ref: ${{ inputs.ref }}
@@ -91,12 +91,12 @@ jobs:
release_body: ${{ steps.git-cliff.outputs.content }}
steps:
- name: Checkout
- uses: actions/checkout@v6.0.2
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Generate a changelog
- uses: orhun/git-cliff-action@v4.7.0
+ uses: orhun/git-cliff-action@e16f179f0be49ecdfe63753837f20b9531642772 # v4.7.0
id: git-cliff
with:
config: tools/cliff.toml
@@ -126,7 +126,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v6.0.2
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
@@ -137,7 +137,7 @@ jobs:
targets: ${{ matrix.target }}
- name: Cache cargo
- uses: Swatinem/rust-cache@v2.8.2
+ uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
shared-key: ${{ matrix.platform }}
workspaces: "tools -> target"
@@ -161,7 +161,7 @@ jobs:
mv "target/${{ matrix.target }}/release/${{ steps.vars.outputs.binary_name }}" "target/${{ matrix.target }}/release/${{ steps.vars.outputs.release_binary_name }}"
- name: Upload binary artifact
- uses: actions/upload-artifact@v6.0.0
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: vacs-data-${{ matrix.platform }}
if-no-files-found: error
@@ -171,7 +171,7 @@ jobs:
- name: Attest build provenance
if: ${{ !github.event.repository.private || github.event.repository.owner.type == 'Organization' }}
- uses: actions/attest-build-provenance@v3.2.0
+ uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
with:
subject-path: |
tools/target/${{ matrix.target }}/release/${{ steps.vars.outputs.release_binary_name }}
@@ -184,7 +184,7 @@ jobs:
contents: write
steps:
- name: Download binaries
- uses: actions/download-artifact@v7.0.0
+ uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0
with:
path: dist
pattern: vacs-data-*
@@ -199,7 +199,7 @@ jobs:
done
- name: Create/update GitHub release
- uses: softprops/action-gh-release@v2.5.0
+ uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
tag_name: ${{ needs.prep.outputs.tag }}
name: ${{ needs.prep.outputs.release_name }}
diff --git a/.github/workflows/tag-airac.yml b/.github/workflows/tag-airac.yml
index e1d05b82..5f4d4a07 100644
--- a/.github/workflows/tag-airac.yml
+++ b/.github/workflows/tag-airac.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v6.0.2
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check for AIRAC cycle
id: check
diff --git a/.gitignore b/.gitignore
index 83f22a2c..60f5be93 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
-# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,jetbrains,windows,linux,macos
-# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,jetbrains,windows,linux,macos
+# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,jetbrains,windows,linux,macos,node
+# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,jetbrains,windows,linux,macos,node
### JetBrains ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
@@ -162,6 +162,146 @@ Temporary Items
# iCloud generated files
*.icloud
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+### Node Patch ###
+# Serverless Webpack directories
+.webpack/
+
+# Optional stylelint cache
+
+# SvelteKit build / generate output
+.svelte-kit
+
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
@@ -207,4 +347,4 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
-# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,jetbrains,windows,linux,macos
+# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,jetbrains,windows,linux,macos,node
diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml
new file mode 100644
index 00000000..cb4d9aeb
--- /dev/null
+++ b/.idea/jsonSchemas.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
diff --git a/.idea/prettier.xml b/.idea/prettier.xml
new file mode 100644
index 00000000..0b9f16d4
--- /dev/null
+++ b/.idea/prettier.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..6a5598cc
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,4 @@
+*.rs
+**/Cargo.toml
+**/Cargo.lock
+**/target/
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..76ad26d3
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,17 @@
+{
+ "plugins": ["prettier-plugin-toml"],
+ "overrides": [
+ {
+ "files": "*.json",
+ "options": { "parser": "json" }
+ },
+ {
+ "files": "*.toml",
+ "options": { "parser": "toml" }
+ },
+ {
+ "files": "*.md",
+ "options": { "parser": "markdown" }
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 93fbc00e..b075f3be 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,21 +1,15 @@
{
"json.schemas": [
{
- "fileMatch": [
- "dataset/**/profiles/*.json"
- ],
+ "fileMatch": ["dataset/**/profiles/*.json"],
"url": "./docs/schemas/profiles.schema.json"
},
{
- "fileMatch": [
- "dataset/**/positions.json"
- ],
+ "fileMatch": ["dataset/**/positions.json"],
"url": "./docs/schemas/positions.schema.json"
},
{
- "fileMatch": [
- "dataset/**/stations.json"
- ],
+ "fileMatch": ["dataset/**/stations.json"],
"url": "./docs/schemas/stations.schema.json"
}
]
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a596b6f8..71fb46b3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,71 @@
+## Contributing to the Dataset
+
+Thank you for your interest in contributing to the `vacs` dataset!
+
+### Directory Structure
+
+All dataset contributions must be placed in the correct directory:
+
+```
+dataset/{FIR}/
+```
+
+Where `{FIR}` is the two/four letter Flight Information Region code (e.g., `LO` for Austria, `EDMM` for Munich, etc.).
+
+> [!IMPORTANT]
+> Files outside the correct directory structure will not be picked up by our validation and release process.
+
+### Auto-Formatting
+
+This repository uses [autofix.ci](https://autofix.ci) with [Prettier](https://prettier.io) to ensure consistent code formatting.
+
+When you create a pull request, please **enable "Allow edits by maintainers"**. This allows the autofix.ci bot to automatically push formatting fixes to your PR branch.
+
+#### Setting up local formatting (optional)
+
+If you prefer to format files locally before pushing:
+
+```bash
+# Install dependencies (one-time)
+npm install
+
+# Format all dataset files
+npm run format
+
+# Check formatting without modifying
+npm run format:check
+```
+
+If your editor supports Prettier, formatting can happen automatically on save. See our editor configuration in `.vscode/settings.json` for VS Code setup.
+
+### New FIR Contributions
+
+If you're contributing configuration for a **new FIR**:
+
+1. Add your dataset files to `dataset/{FIR}/`
+2. In your pull request description, include a brief paragraph about your affiliation to the FIR
+3. After your first PR is merged, we'll add you to the `CODEOWNERS` file for that FIR
+4. Future changes to your FIR will be routed to you for review
+
+### Existing FIR Contributions
+
+If you're contributing to an **existing FIR**:
+
+- Your PR will be reviewed by the current CODEOWNER(s) for that FIR
+- We'll wait for their feedback before merging
+
+### Validation
+
+All contributions are automatically validated for:
+
+- Valid JSON/TOML syntax
+- Proper formatting (via Prettier)
+- Schema compliance (structure, required fields, data types)
+
+If validation fails, you'll receive feedback in your pull request with specific error messages.
+
+---
+
## License for dataset
Any contribution intentionally submitted for inclusion in the dataset provided by the `vacs-data` project by you shall be licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) license.
diff --git a/README.md b/README.md
index 30ce7aff..a309c9df 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,8 @@ We welcome contributions from FIR staff and community members! Please refer to t
1. [Fork the repository](https://github.com/MorpheusXAUT/vacs-data/fork).
2. Make your changes in your FIR's directory (or create it if it doesn't exist).
3. Validate your changes using the provided JSON schemas or the `vacs-data` tool.
-4. [Submit a Pull Request](https://github.com/MorpheusXAUT/vacs-data/compare).
+4. [Submit a Pull Request](https://github.com/MorpheusXAUT/vacs-data/compare). Please make sure to enable "Allow edits by maintainers" so that the autofix.ci bot can automatically push formatting fixes to your PR branch.
+5. Make sure to check our [contribution guidelines](CONTRIBUTING.md) for more information.
## License
diff --git a/dataset/LO/profiles/LOVV.json b/dataset/LO/profiles/LOVV.json
index 084b14fe..9e50f1e4 100644
--- a/dataset/LO/profiles/LOVV.json
+++ b/dataset/LO/profiles/LOVV.json
@@ -483,7 +483,44 @@
},
{
"label": ["CWP"],
- "size": 6.25
+ "size": 6.25,
+ "page": {
+ "rows": 6,
+ "client_page": {
+ "include": [
+ "LO*",
+ "EDMM*",
+ "EDUU*",
+ "EDDM*",
+ "ADR*",
+ "EUC*",
+ "LD*",
+ "LH*",
+ "LIM*",
+ "LIP*",
+ "LJ*",
+ "LK*",
+ "LS*",
+ "LZ*"
+ ],
+ "exclude": ["LON*"],
+ "priority": [
+ "LO*_FMP",
+ "*_FMP",
+ "LO*_CTR",
+ "*_CTR",
+ "LOWW*_APP",
+ "LO*_APP",
+ "*_APP",
+ "LOWW*_TWR",
+ "LO*_TWR",
+ "*_TWR",
+ "LO*_GND",
+ "*_GND",
+ "LO*"
+ ]
+ }
+ }
}
]
}
diff --git a/dataset/LO/profiles/LOWW.json b/dataset/LO/profiles/LOWW.json
index 656ec1e2..8c52b126 100644
--- a/dataset/LO/profiles/LOWW.json
+++ b/dataset/LO/profiles/LOWW.json
@@ -8,59 +8,30 @@
"rows": 6,
"keys": [
{
- "label": [
- "PRA",
- "LW",
- "EC"
- ]
+ "label": ["PRA", "LW", "EC"]
},
{
- "label": [
- "PRA",
- "LS",
- "EC"
- ]
+ "label": ["PRA", "LS", "EC"]
},
{
- "label": [
- "PRA",
- "TB",
- "EC"
- ]
+ "label": ["PRA", "TB", "EC"]
},
{
- "label": [
- "PRA",
- "KV",
- "EC"
- ]
+ "label": ["PRA", "KV", "EC"]
},
{
- "label": [
- "APP",
- "VD1"
- ],
+ "label": ["APP", "VD1"],
"station_id": "LOWW_D_APP"
},
{
- "label": [
- "TWR 1"
- ],
+ "label": ["TWR 1"],
"station_id": "LOWW_TWR"
},
{
- "label": [
- "BRA",
- "LW",
- "EC"
- ]
+ "label": ["BRA", "LW", "EC"]
},
{
- "label": [
- "BRA",
- "APP",
- "EC"
- ]
+ "label": ["BRA", "APP", "EC"]
},
{
"label": []
@@ -69,138 +40,77 @@
"label": []
},
{
- "label": [
- "APP",
- "VD2"
- ],
+ "label": ["APP", "VD2"],
"station_id": "LOWW_F_APP"
},
{
- "label": [
- "TWR 2"
- ],
+ "label": ["TWR 2"],
"station_id": "LOWW_E_TWR"
},
{
- "label": [
- "BUD",
- "128.10",
- "EC"
- ]
+ "label": ["BUD", "128.10", "EC"]
},
{
- "label": [
- "BUD",
- "133.20",
- "EC"
- ]
+ "label": ["BUD", "133.20", "EC"]
},
{
- "label": [
- "MCC",
- "TAU",
- "EC"
- ]
+ "label": ["MCC", "TAU", "EC"]
},
{
"label": []
},
{
- "label": [
- "APP",
- "TFI",
- "EC"
- ],
+ "label": ["APP", "TFI", "EC"],
"station_id": "LOWW_I_APP"
},
{
"label": []
},
{
- "label": [
- "ACC",
- "N1",
- "EC"
- ],
+ "label": ["ACC", "N1", "EC"],
"station_id": "LOVV_N1"
},
{
"label": []
},
{
- "label": [
- "LOWL",
- "APP",
- "EC"
- ],
+ "label": ["LOWL", "APP", "EC"],
"station_id": "LOWL_APP"
},
{
- "label": [
- "APP",
- "VN-EC",
- "118775"
- ],
+ "label": ["APP", "VN-EC", "118775"],
"station_id": "LOWW_N_APP"
},
{
- "label": [
- "APP",
- "VB-EC",
- "134675"
- ],
+ "label": ["APP", "VB-EC", "134675"],
"station_id": "LOWW_APP"
},
{
- "label": [
- "TWR 1"
- ],
+ "label": ["TWR 1"],
"station_id": "LOWW_TWR"
},
{
- "label": [
- "ACC",
- "E1",
- "EC"
- ],
+ "label": ["ACC", "E1", "EC"],
"station_id": "LOVV_E1"
},
{
- "label": [
- "ACC",
- "S1",
- "EC"
- ],
+ "label": ["ACC", "S1", "EC"],
"station_id": "LOVV_S1"
},
{
- "label": [
- "LOWG",
- "APP",
- "EC"
- ],
+ "label": ["LOWG", "APP", "EC"],
"station_id": "LOWG_APP"
},
{
- "label": [
- "APP",
- "VM-EC",
- "125175"
- ],
+ "label": ["APP", "VM-EC", "125175"],
"station_id": "LOWW_M_APP"
},
{
- "label": [
- "APP",
- "VP-EC",
- "129050"
- ],
+ "label": ["APP", "VP-EC", "129050"],
"station_id": "LOWW_P_APP"
},
{
- "label": [
- "TWR 2"
- ],
+ "label": ["TWR 2"],
"station_id": "LOWW_E_TWR"
}
]
@@ -212,210 +122,104 @@
"rows": 6,
"keys": [
{
- "label": [
- "PRA",
- "LW",
- "PLC"
- ]
+ "label": ["PRA", "LW", "PLC"]
},
{
- "label": [
- "PRA",
- "LS",
- "PLC"
- ]
+ "label": ["PRA", "LS", "PLC"]
},
{
- "label": [
- "PRA",
- "TB",
- "PLC"
- ]
+ "label": ["PRA", "TB", "PLC"]
},
{
- "label": [
- "PRA",
- "KV",
- "PLC"
- ]
+ "label": ["PRA", "KV", "PLC"]
},
{
- "label": [
- "APP",
- "SAP"
- ]
+ "label": ["APP", "SAP"]
},
{
- "label": [
- "ACC",
- "N PLC",
- "Cont."
- ],
+ "label": ["ACC", "N PLC", "Cont."],
"station_id": "LOVV_N1"
},
{
- "label": [
- "BRA",
- "LW",
- "PLC"
- ]
+ "label": ["BRA", "LW", "PLC"]
},
{
- "label": [
- "BRA",
- "APP",
- "PLC"
- ]
+ "label": ["BRA", "APP", "PLC"]
},
{
- "label": [
- "BRA",
- "FDT1"
- ]
+ "label": ["BRA", "FDT1"]
},
{
- "label": [
- "ACC",
- "FDU",
- "NOT"
- ]
+ "label": ["ACC", "FDU", "NOT"]
},
{
- "label": [
- "FDU 1",
- "TCH"
- ]
+ "label": ["FDU 1", "TCH"]
},
{
- "label": [
- "ACC",
- "S PLC",
- "Cont."
- ],
+ "label": ["ACC", "S PLC", "Cont."],
"station_id": "LOVV_S1"
},
{
- "label": [
- "BUD",
- "128.10",
- "PLC"
- ]
+ "label": ["BUD", "128.10", "PLC"]
},
{
- "label": [
- "BUD",
- "133.20",
- "PLC"
- ]
+ "label": ["BUD", "133.20", "PLC"]
},
{
- "label": [
- "MCC",
- "TAU",
- "PLC"
- ]
+ "label": ["MCC", "TAU", "PLC"]
},
{
- "label": [
- "LOXZ",
- "APP"
- ],
+ "label": ["LOXZ", "APP"],
"station_id": "LOXZ_APP"
},
{
- "label": [
- "APP",
- "TFI",
- "PLC"
- ],
+ "label": ["APP", "TFI", "PLC"],
"station_id": "LOWW_I_APP"
},
{
- "label": [
- "ACC",
- "Rs. PLC",
- "Cont."
- ]
+ "label": ["ACC", "Rs. PLC", "Cont."]
},
{
- "label": [
- "ACC",
- "N1",
- "PLC"
- ],
+ "label": ["ACC", "N1", "PLC"],
"station_id": "LOVV_N1"
},
{
"label": []
},
{
- "label": [
- "LOWL",
- "APP",
- "PLC"
- ],
+ "label": ["LOWL", "APP", "PLC"],
"station_id": "LOWL_APP"
},
{
- "label": [
- "APP",
- "VN-PLC",
- "118775"
- ],
+ "label": ["APP", "VN-PLC", "118775"],
"station_id": "LOWW_N_APP"
},
{
- "label": [
- "APP",
- "VB-PLC",
- "134675"
- ],
+ "label": ["APP", "VB-PLC", "134675"],
"station_id": "LOWW_APP"
},
{
- "label": [
- "TCO"
- ],
+ "label": ["TCO"],
"station_id": "LOWW_TWR"
},
{
- "label": [
- "ACC",
- "E1",
- "PLC"
- ],
+ "label": ["ACC", "E1", "PLC"],
"station_id": "LOVV_E1"
},
{
- "label": [
- "ACC",
- "S1",
- "PLC"
- ],
+ "label": ["ACC", "S1", "PLC"],
"station_id": "LOVV_S1"
},
{
- "label": [
- "LOWG",
- "APP",
- "PLC"
- ],
+ "label": ["LOWG", "APP", "PLC"],
"station_id": "LOWG_APP"
},
{
- "label": [
- "APP",
- "VM-PLC",
- "125175"
- ],
+ "label": ["APP", "VM-PLC", "125175"],
"station_id": "LOWW_M_APP"
},
{
- "label": [
- "APP",
- "VP-PLC",
- "129050"
- ],
+ "label": ["APP", "VP-PLC", "129050"],
"station_id": "LOWW_P_APP"
},
{
@@ -428,14 +232,152 @@
"label": "TFI",
"page": {
"rows": 6,
- "keys": []
+ "keys": [
+ {
+ "label": ["LOAG"]
+ },
+ {
+ "label": ["LOAV"]
+ },
+ {
+ "label": ["PRA", "FIC", "WEST"]
+ },
+ {
+ "label": ["APP", "SAP"]
+ },
+ {
+ "label": ["FIC", "N", "EC"],
+ "station_id": "LOVV_I_CTR"
+ },
+ {
+ "label": ["FIC", "S", "EC"],
+ "station_id": "LOVV_I_CTR"
+ },
+ {
+ "label": ["LOAU"]
+ },
+ {
+ "label": ["LOAN"]
+ },
+ {
+ "label": ["PRA", "FIC", "EAST"]
+ },
+ {
+ "label": ["ARO"]
+ },
+ {
+ "label": ["FIC", "N", "PLC"],
+ "station_id": "LOVV_I_CTR"
+ },
+ {
+ "label": ["FIC", "S", "PLC"],
+ "station_id": "LOVV_I_CTR"
+ },
+ {
+ "label": ["LOXT"],
+ "station_id": "LOXT_TWR"
+ },
+ {
+ "label": ["LOXN", "TWR"]
+ },
+ {
+ "label": ["PRA", "FIC", "MOR"]
+ },
+ {
+ "label": ["PRA", "TB", "PLC"]
+ },
+ {
+ "label": ["APP", "TFI", "EC"],
+ "station_id": "LOWW_I_APP"
+ },
+ {
+ "label": ["TCO"],
+ "station_id": "LOWW_TWR"
+ },
+ {
+ "label": ["MIL", "INFO", "PLC"],
+ "station_id": "LOVV_M_CTR"
+ },
+ {
+ "label": ["BRA", "FIC", "PLC"]
+ },
+ {
+ "label": ["BRA", "APP", "PLC"]
+ },
+ {
+ "label": ["BRA", "TWR", "PLC"]
+ },
+ {
+ "label": ["APP", "VN-PLC", "118775"],
+ "station_id": "LOWW_N_APP"
+ },
+ {
+ "label": ["APP", "VB-PLC", "134675"],
+ "station_id": "LOWW_APP"
+ },
+ {
+ "label": ["MIL", "CTR"],
+ "station_id": "LOVV_M_CTR"
+ },
+ {
+ "label": ["LHPR"]
+ },
+ {
+ "label": ["BUD", "FIC"]
+ },
+ {
+ "label": ["LOXZ", "APP"],
+ "station_id": "LOXZ_APP"
+ },
+ {
+ "label": ["APP", "VM-PLC", "125175"],
+ "station_id": "LOWW_M_APP"
+ },
+ {
+ "label": ["APP", "VP-PLC", "129050"],
+ "station_id": "LOWW_P_APP"
+ }
+ ]
}
},
{
- "label": "SAP",
+ "label": "CWP",
"page": {
"rows": 6,
- "keys": []
+ "client_page": {
+ "include": [
+ "LO*",
+ "EDMM*",
+ "EDUU*",
+ "EDDM*",
+ "ADR*",
+ "EUC*",
+ "LD*",
+ "LH*",
+ "LIM*",
+ "LIP*",
+ "LJ*",
+ "LK*",
+ "LS*",
+ "LZ*"
+ ],
+ "exclude": ["LON*"],
+ "priority": [
+ "LO*_FMP",
+ "*_FMP",
+ "LO*_CTR",
+ "*_CTR",
+ "LOWW*_APP",
+ "LO*_APP",
+ "*_APP",
+ "LOWW*_TWR",
+ "LO*_TWR",
+ "*_TWR",
+ "LO*_GND",
+ "*_GND",
+ "LO*"
+ ]
+ }
}
}
]
diff --git a/dataset/LO/stations.json b/dataset/LO/stations.json
index a1cac8ec..9ca2df17 100644
--- a/dataset/LO/stations.json
+++ b/dataset/LO/stations.json
@@ -417,6 +417,10 @@
"id": "LOWW_I_APP",
"controlled_by": ["LOWW_I_APP", "LOVV_I_CTR"]
},
+ {
+ "id": "LOVV_M_CTR",
+ "controlled_by": ["LOVV_M_CTR"]
+ },
{
"id": "LOXT_APP",
"controlled_by": [
diff --git a/dataset/LO/stations.toml b/dataset/LO/stations.toml
index 2ad02893..da9c522f 100644
--- a/dataset/LO/stations.toml
+++ b/dataset/LO/stations.toml
@@ -1,16 +1,16 @@
[[stations]]
id = "LOVV_B1"
controlled_by = [
- "LOVV_B_CTR",
- "LOVV_BC_CTR",
- "LOVV_W_CTR",
- "LOVV_N_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
- "LOVV_BU_CTR",
- "LOVV_WU_CTR",
- "LOVV_NU_CTR",
- "LOVV_U_CTR",
+ "LOVV_B_CTR",
+ "LOVV_BC_CTR",
+ "LOVV_W_CTR",
+ "LOVV_N_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
+ "LOVV_BU_CTR",
+ "LOVV_WU_CTR",
+ "LOVV_NU_CTR",
+ "LOVV_U_CTR",
]
[[stations]]
@@ -36,28 +36,28 @@ parent_id = "LOVV_B7"
[[stations]]
id = "LOVV_B7"
controlled_by = [
- "LOVV_BU_CTR",
- "LOVV_WU_CTR",
- "LOVV_NU_CTR",
- "LOVV_U_CTR",
- "LOVV_B_CTR",
- "LOVV_BC_CTR",
- "LOVV_W_CTR",
- "LOVV_N_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
+ "LOVV_BU_CTR",
+ "LOVV_WU_CTR",
+ "LOVV_NU_CTR",
+ "LOVV_U_CTR",
+ "LOVV_B_CTR",
+ "LOVV_BC_CTR",
+ "LOVV_W_CTR",
+ "LOVV_N_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
]
[[stations]]
id = "LOVV_E1"
controlled_by = [
- "LOVV_E_CTR",
- "LOVV_N_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
- "LOVV_EU_CTR",
- "LOVV_NU_CTR",
- "LOVV_U_CTR",
+ "LOVV_E_CTR",
+ "LOVV_N_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
+ "LOVV_EU_CTR",
+ "LOVV_NU_CTR",
+ "LOVV_U_CTR",
]
[[stations]]
@@ -83,26 +83,26 @@ parent_id = "LOVV_E7"
[[stations]]
id = "LOVV_E7"
controlled_by = [
- "LOVV_EU_CTR",
- "LOVV_NU_CTR",
- "LOVV_U_CTR",
- "LOVV_E_CTR",
- "LOVV_N_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
+ "LOVV_EU_CTR",
+ "LOVV_NU_CTR",
+ "LOVV_U_CTR",
+ "LOVV_E_CTR",
+ "LOVV_N_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
]
[[stations]]
id = "LOVV_N1"
controlled_by = [
- "LOVV_F_CTR",
- "LOVV_N_CTR",
- "LOVV_E_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
- "LOVV_NU_CTR",
- "LOVV_EU_CTR",
- "LOVV_U_CTR",
+ "LOVV_F_CTR",
+ "LOVV_N_CTR",
+ "LOVV_E_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
+ "LOVV_NU_CTR",
+ "LOVV_EU_CTR",
+ "LOVV_U_CTR",
]
[[stations]]
@@ -128,28 +128,28 @@ parent_id = "LOVV_N7"
[[stations]]
id = "LOVV_N7"
controlled_by = [
- "LOVV_NU_CTR",
- "LOVV_EU_CTR",
- "LOVV_U_CTR",
- "LOVV_N_CTR",
- "LOVV_E_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
+ "LOVV_NU_CTR",
+ "LOVV_EU_CTR",
+ "LOVV_U_CTR",
+ "LOVV_N_CTR",
+ "LOVV_E_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
]
[[stations]]
id = "LOVV_S1"
controlled_by = [
- "LOVV_S_CTR",
- "LOVV_B_CTR",
- "LOVV_BC_CTR",
- "LOVV_E_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
- "LOVV_SU_CTR",
- "LOVV_BU_CTR",
- "LOVV_EU_CTR",
- "LOVV_U_CTR",
+ "LOVV_S_CTR",
+ "LOVV_B_CTR",
+ "LOVV_BC_CTR",
+ "LOVV_E_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
+ "LOVV_SU_CTR",
+ "LOVV_BU_CTR",
+ "LOVV_EU_CTR",
+ "LOVV_U_CTR",
]
[[stations]]
@@ -175,31 +175,31 @@ parent_id = "LOVV_S7"
[[stations]]
id = "LOVV_S7"
controlled_by = [
- "LOVV_SU_CTR",
- "LOVV_BU_CTR",
- "LOVV_EU_CTR",
- "LOVV_U_CTR",
- "LOVV_S_CTR",
- "LOVV_B_CTR",
- "LOVV_BC_CTR",
- "LOVV_E_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
+ "LOVV_SU_CTR",
+ "LOVV_BU_CTR",
+ "LOVV_EU_CTR",
+ "LOVV_U_CTR",
+ "LOVV_S_CTR",
+ "LOVV_B_CTR",
+ "LOVV_BC_CTR",
+ "LOVV_E_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
]
[[stations]]
id = "LOVV_W1"
controlled_by = [
- "LOVV_W_CTR",
- "LOVV_S_CTR",
- "LOVV_B_CTR",
- "LOVV_BC_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
- "LOVV_WU_CTR",
- "LOVV_SU_CTR",
- "LOVV_BU_CTR",
- "LOVV_U_CTR",
+ "LOVV_W_CTR",
+ "LOVV_S_CTR",
+ "LOVV_B_CTR",
+ "LOVV_BC_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
+ "LOVV_WU_CTR",
+ "LOVV_SU_CTR",
+ "LOVV_BU_CTR",
+ "LOVV_U_CTR",
]
[[stations]]
@@ -225,37 +225,41 @@ parent_id = "LOVV_W7"
[[stations]]
id = "LOVV_W7"
controlled_by = [
- "LOVV_WU_CTR",
- "LOVV_SU_CTR",
- "LOVV_BU_CTR",
- "LOVV_U_CTR",
- "LOVV_W_CTR",
- "LOVV_S_CTR",
- "LOVV_B_CTR",
- "LOVV_BC_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
+ "LOVV_WU_CTR",
+ "LOVV_SU_CTR",
+ "LOVV_BU_CTR",
+ "LOVV_U_CTR",
+ "LOVV_W_CTR",
+ "LOVV_S_CTR",
+ "LOVV_B_CTR",
+ "LOVV_BC_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
]
[[stations]]
id = "LOVV_I_CTR"
controlled_by = ["LOVV_I_CTR"]
+[[stations]]
+id = "LOVV_M_CTR"
+controlled_by = ["LOVV_M_CTR"]
+
[[stations]]
id = "LOWG_APP"
controlled_by = [
- "LOWG_APP",
- "LOVV_S_APP",
- "LOVV_L_CTR",
- "LOVV_S_CTR",
- "LOVV_B_CTR",
- "LOVV_BC_CTR",
- "LOVV_E_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
- "LOVV_SU_CTR",
- "LOVV_BU_CTR",
- "LOVV_EU_CTR",
+ "LOWG_APP",
+ "LOVV_S_APP",
+ "LOVV_L_CTR",
+ "LOVV_S_CTR",
+ "LOVV_B_CTR",
+ "LOVV_BC_CTR",
+ "LOVV_E_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
+ "LOVV_SU_CTR",
+ "LOVV_BU_CTR",
+ "LOVV_EU_CTR",
]
[[stations]]
@@ -286,34 +290,34 @@ controlled_by = ["LOWI_F_APP", "LOWI_E_APP", "LOWI_S_APP"]
[[stations]]
id = "LOWI_APP"
controlled_by = [
- "LOWI_APP",
- "LOWI_E_APP",
- "LOWI_S_APP",
- "LOVV_L_CTR",
- "LOVV_W_CTR",
- "LOVV_S_CTR",
- "LOVV_B_CTR",
- "LOVV_CTR",
- "LOVV_WU_CTR",
- "LOVV_SU_CTR",
- "LOVV_BU_CTR",
+ "LOWI_APP",
+ "LOWI_E_APP",
+ "LOWI_S_APP",
+ "LOVV_L_CTR",
+ "LOVV_W_CTR",
+ "LOVV_S_CTR",
+ "LOVV_B_CTR",
+ "LOVV_CTR",
+ "LOVV_WU_CTR",
+ "LOVV_SU_CTR",
+ "LOVV_BU_CTR",
]
[[stations]]
id = "LOWK_APP"
controlled_by = [
- "LOWK_APP",
- "LOVV_S_APP",
- "LOVV_L_CTR",
- "LOVV_W_CTR",
- "LOVV_S_CTR",
- "LOVV_B_CTR",
- "LOVV_BC_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
- "LOVV_WU_CTR",
- "LOVV_SU_CTR",
- "LOVV_BU_CTR",
+ "LOWK_APP",
+ "LOVV_S_APP",
+ "LOVV_L_CTR",
+ "LOVV_W_CTR",
+ "LOVV_S_CTR",
+ "LOVV_B_CTR",
+ "LOVV_BC_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
+ "LOVV_WU_CTR",
+ "LOVV_SU_CTR",
+ "LOVV_BU_CTR",
]
[[stations]]
@@ -324,15 +328,15 @@ controlled_by = ["LOWK_TWR"]
[[stations]]
id = "LOWL_APP"
controlled_by = [
- "LOWL_APP",
- "LOVV_N_APP",
- "LOVV_L_CTR",
- "LOVV_N_CTR",
- "LOVV_E_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
- "LOVV_NU_CTR",
- "LOVV_EU_CTR",
+ "LOWL_APP",
+ "LOVV_N_APP",
+ "LOVV_L_CTR",
+ "LOVV_N_CTR",
+ "LOVV_E_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
+ "LOVV_NU_CTR",
+ "LOVV_EU_CTR",
]
[[stations]]
@@ -343,18 +347,18 @@ controlled_by = ["LOWL_TWR"]
[[stations]]
id = "LOWS_APP"
controlled_by = [
- "LOWS_APP",
- "LOVV_N_APP",
- "LOVV_L_CTR",
- "LOVV_B_CTR",
- "LOVV_BC_CTR",
- "LOVV_W_CTR",
- "LOVV_N_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
- "LOVV_BU_CTR",
- "LOVV_WU_CTR",
- "LOVV_NU_CTR",
+ "LOWS_APP",
+ "LOVV_N_APP",
+ "LOVV_L_CTR",
+ "LOVV_B_CTR",
+ "LOVV_BC_CTR",
+ "LOVV_W_CTR",
+ "LOVV_N_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
+ "LOVV_BU_CTR",
+ "LOVV_WU_CTR",
+ "LOVV_NU_CTR",
]
[[stations]]
@@ -375,17 +379,17 @@ controlled_by = ["LOWW_E_TWR", "LOWW_TWR"]
[[stations]]
id = "LOWW_APP"
controlled_by = [
- "LOWW_APP",
- "LOWW_P_APP",
- "LOWW_N_APP",
- "LOWW_M_APP",
- "LOVV_L_CTR",
- "LOVV_E_CTR",
- "LOVV_N_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
- "LOVV_EU_CTR",
- "LOVV_NU_CTR",
+ "LOWW_APP",
+ "LOWW_P_APP",
+ "LOWW_N_APP",
+ "LOWW_M_APP",
+ "LOVV_L_CTR",
+ "LOVV_E_CTR",
+ "LOVV_N_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
+ "LOVV_EU_CTR",
+ "LOVV_NU_CTR",
]
[[stations]]
@@ -418,19 +422,19 @@ controlled_by = ["LOWW_I_APP", "LOVV_I_CTR"]
[[stations]]
id = "LOXT_APP"
controlled_by = [
- "LOXT_APP",
- "LOVV_M_CTR",
- "LOWW_N_APP",
- "LOWW_M_APP",
- "LOWW_APP",
- "LOWW_P_APP",
- "LOVV_L_CTR",
- "LOVV_E_CTR",
- "LOVV_N_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
- "LOVV_EU_CTR",
- "LOVV_NU_CTR",
+ "LOXT_APP",
+ "LOVV_M_CTR",
+ "LOWW_N_APP",
+ "LOWW_M_APP",
+ "LOWW_APP",
+ "LOWW_P_APP",
+ "LOVV_L_CTR",
+ "LOVV_E_CTR",
+ "LOVV_N_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
+ "LOVV_EU_CTR",
+ "LOVV_NU_CTR",
]
[[stations]]
@@ -441,23 +445,23 @@ controlled_by = ["LOXT_TWR"]
[[stations]]
id = "LOXZ_APP"
controlled_by = [
- "LOXZ_APP",
- "LOVV_M_CTR",
- "LOWG_APP",
- "LOWK_APP",
- "LOVV_S_APP",
- "LOVV_L_CTR",
- "LOVV_S_CTR",
- "LOVV_W_CTR",
- "LOVV_B_CTR",
- "LOVV_BC_CTR",
- "LOVV_E_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
- "LOVV_SU_CTR",
- "LOVV_WU_CTR",
- "LOVV_BU_CTR",
- "LOVV_EU_CTR",
+ "LOXZ_APP",
+ "LOVV_M_CTR",
+ "LOWG_APP",
+ "LOWK_APP",
+ "LOVV_S_APP",
+ "LOVV_L_CTR",
+ "LOVV_S_CTR",
+ "LOVV_W_CTR",
+ "LOVV_B_CTR",
+ "LOVV_BC_CTR",
+ "LOVV_E_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
+ "LOVV_SU_CTR",
+ "LOVV_WU_CTR",
+ "LOVV_BU_CTR",
+ "LOVV_EU_CTR",
]
[[stations]]
diff --git a/dataset/VH/positions.json b/dataset/VH/positions.json
new file mode 100644
index 00000000..c912061b
--- /dev/null
+++ b/dataset/VH/positions.json
@@ -0,0 +1,452 @@
+{
+ "positions": [
+ {
+ "id": "VHHH_DEL",
+ "prefixes": ["VHHH"],
+ "frequency": "122.150",
+ "facility_type": "DEL",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHX_DEL",
+ "prefixes": ["VHHX"],
+ "frequency": "123.225",
+ "facility_type": "DEL",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VMMC_DEL",
+ "prefixes": ["VMMC"],
+ "frequency": "121.975",
+ "facility_type": "DEL",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_1_GND",
+ "prefixes": ["VHHH"],
+ "frequency": "121.600",
+ "facility_type": "GND",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_2_GND",
+ "prefixes": ["VHHH"],
+ "frequency": "122.550",
+ "facility_type": "GND",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_3_GND",
+ "prefixes": ["VHHH"],
+ "frequency": "121.875",
+ "facility_type": "GND",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_4_GND",
+ "prefixes": ["VHHH"],
+ "frequency": "122.600",
+ "facility_type": "GND",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_5_GND",
+ "prefixes": ["VHHH"],
+ "frequency": "122.125",
+ "facility_type": "GND",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHX_GND",
+ "prefixes": ["VHHX"],
+ "frequency": "121.925",
+ "facility_type": "GND",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VMMC_GND",
+ "prefixes": ["VMMC"],
+ "frequency": "121.725",
+ "facility_type": "GND",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_S_TWR",
+ "prefixes": ["VHHH"],
+ "frequency": "118.400",
+ "facility_type": "TWR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_C_TWR",
+ "prefixes": ["VHHH"],
+ "frequency": "118.200",
+ "facility_type": "TWR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_N_TWR",
+ "prefixes": ["VHHH"],
+ "frequency": "118.700",
+ "facility_type": "TWR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_Z_TWR",
+ "prefixes": ["VHHH"],
+ "frequency": "120.600",
+ "facility_type": "TWR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_F_TWR",
+ "prefixes": ["VHHH"],
+ "frequency": "121.000",
+ "facility_type": "TWR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHX_TWR",
+ "prefixes": ["VHHX"],
+ "frequency": "124.650",
+ "facility_type": "TWR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VMMC_TWR",
+ "prefixes": ["VMMC"],
+ "frequency": "118.000",
+ "facility_type": "TWR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_APP",
+ "prefixes": ["VHHH"],
+ "frequency": "119.100",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_F_APP",
+ "prefixes": ["VHHH"],
+ "frequency": "119.500",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_P_APP",
+ "prefixes": ["VHHH"],
+ "frequency": "119.350",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_N_DEP",
+ "prefixes": ["VHHH"],
+ "frequency": "123.800",
+ "facility_type": "DEP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_S_DEP",
+ "prefixes": ["VHHH"],
+ "frequency": "122.000",
+ "facility_type": "DEP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_H_DEP",
+ "prefixes": ["VHHH"],
+ "frequency": "122.650",
+ "facility_type": "DEP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_W_APP",
+ "prefixes": ["VHHH"],
+ "frequency": "127.550",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_L_APP",
+ "prefixes": ["VHHH"],
+ "frequency": "125.175",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VMMC_APP",
+ "prefixes": ["VMMC"],
+ "frequency": "123.950",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_S_APP",
+ "prefixes": ["VHHH"],
+ "frequency": "126.300",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_C_DEP",
+ "prefixes": ["VHHH"],
+ "frequency": "123.475",
+ "facility_type": "DEP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_E_APP",
+ "prefixes": ["VHHH"],
+ "frequency": "126.500",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHH_E_DEP",
+ "prefixes": ["VHHH"],
+ "frequency": "133.825",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHK_W_CTR",
+ "prefixes": ["HKG"],
+ "frequency": "127.100",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHK_V_CTR",
+ "prefixes": ["HKG"],
+ "frequency": "125.325",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHK_D_CTR",
+ "prefixes": ["HKG"],
+ "frequency": "122.950",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHK_S_CTR",
+ "prefixes": ["HKG"],
+ "frequency": "132.150",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHK_Z_CTR",
+ "prefixes": ["HKG"],
+ "frequency": "133.700",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHK_C_CTR",
+ "prefixes": ["HKG"],
+ "frequency": "128.750",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHK_E_CTR",
+ "prefixes": ["HKG"],
+ "frequency": "118.925",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHK_K_CTR",
+ "prefixes": ["HKG"],
+ "frequency": "121.300",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHK_U_CTR",
+ "prefixes": ["HKG"],
+ "frequency": "132.525",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "VHHK_FMP",
+ "prefixes": ["HKG"],
+ "frequency": "199.995",
+ "facility_type": "FMP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "RCAA_W_CTR",
+ "prefixes": ["TPE"],
+ "frequency": "126.700",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "RCAA_L_CTR",
+ "prefixes": ["TPE"],
+ "frequency": "125.500",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "RCAA_N_CTR",
+ "prefixes": ["TPE"],
+ "frequency": "123.600",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "RCAA_E_CTR",
+ "prefixes": ["TPE"],
+ "frequency": "127.900",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "RCAA_S_CTR",
+ "prefixes": ["TPE"],
+ "frequency": "129.100",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ASEA_FSS",
+ "prefixes": ["ASEA"],
+ "frequency": "135.350",
+ "facility_type": "FSS",
+ "profile": "VHHK"
+ },
+ {
+ "id": "RPHI_1_CTR",
+ "prefixes": ["MNL"],
+ "frequency": "127.500",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "RPHI_N_CTR",
+ "prefixes": ["MNL"],
+ "frequency": "126.575",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "RPHI_CTR",
+ "prefixes": ["MNL"],
+ "frequency": "119.300",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZBBB_FSS",
+ "prefixes": ["PRC"],
+ "frequency": "133.075",
+ "facility_type": "FSS",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZSSS_S_CTR",
+ "prefixes": ["ZSSS"],
+ "frequency": "120.900",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZSSS_CTR",
+ "prefixes": ["ZSSS"],
+ "frequency": "120.950",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZSHA_CTR",
+ "prefixes": ["ZSHA"],
+ "frequency": "124.550",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZSAM_CTR",
+ "prefixes": ["ZSAM"],
+ "frequency": "125.700",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZJSA_CTR",
+ "prefixes": ["ZJSA"],
+ "frequency": "120.500",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZJSY_L_CTR",
+ "prefixes": ["ZJSY"],
+ "frequency": "133.200",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZJSY_O_CTR",
+ "prefixes": ["ZJSY"],
+ "frequency": "130.200",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZGZU_CTR",
+ "prefixes": ["ZGZU"],
+ "frequency": "132.750",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZGNN_CTR",
+ "prefixes": ["ZGNN"],
+ "frequency": "132.700",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZGZJ_APP",
+ "prefixes": ["ZGZJ"],
+ "frequency": "120.875",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZGOW_APP",
+ "prefixes": ["ZGOW"],
+ "frequency": "120.650",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZGJD_APP",
+ "prefixes": ["ZGJD"],
+ "frequency": "120.350",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZGGG_CTR",
+ "prefixes": ["ZGGG"],
+ "frequency": "128.350",
+ "facility_type": "CTR",
+ "profile": "VHHK"
+ },
+ {
+ "id": "ZGGG_APP",
+ "prefixes": ["ZGGG"],
+ "frequency": "126.550",
+ "facility_type": "APP",
+ "profile": "VHHK"
+ }
+ ]
+}
diff --git a/dataset/VH/profiles/VHHK.json b/dataset/VH/profiles/VHHK.json
new file mode 100644
index 00000000..98677675
--- /dev/null
+++ b/dataset/VH/profiles/VHHK.json
@@ -0,0 +1,294 @@
+{
+ "id": "VHHK",
+ "type": "Tabbed",
+ "tabs": [
+ {
+ "label": ["S-TWR", "+NAT"],
+ "page": {
+ "rows": 7,
+ "keys": [
+ {
+ "label": ["N-CDC"],
+ "station_id": "VHHH_DEL"
+ },
+ {
+ "label": ["S-GMC1"],
+ "station_id": "VHHH_1_GND"
+ },
+ {
+ "label": ["S-GMC2"],
+ "station_id": "VHHH_2_GND"
+ },
+ {
+ "label": ["S-GMC3"],
+ "station_id": "VHHH_3_GND"
+ },
+ {
+ "label": ["N-GMC4"],
+ "station_id": "VHHH_4_GND"
+ },
+ {
+ "label": ["N-GMC5"],
+ "station_id": "VHHH_5_GND"
+ },
+ {
+ "label": ["S-AMS"],
+ "station_id": "VHHH_S_TWR"
+ },
+ {
+ "label": ["N-AMM"],
+ "station_id": "VHHH_C_TWR"
+ },
+ {
+ "label": ["N-AMN"],
+ "station_id": "VHHH_N_TWR"
+ },
+ {
+ "label": ["N-ZNC"],
+ "station_id": "VHHH_Z_TWR"
+ },
+ {
+ "label": ["N-FIS"],
+ "station_id": "VHHH_F_TWR"
+ }
+ ]
+ }
+ },
+ {
+ "label": "E-ATCC",
+ "page": {
+ "rows": 7,
+ "keys": [
+ {
+ "label": ["E-FLC"],
+ "station_id": "VHHK_FMP"
+ },
+ {
+ "label": ["E-APP"],
+ "station_id": "VHHH_APP"
+ },
+ {
+ "label": ["E-FAD"],
+ "station_id": "VHHH_F_APP"
+ },
+ {
+ "label": ["E-PAR"],
+ "station_id": "VHHH_P_APP"
+ },
+ {
+ "label": ["E-DEN"],
+ "station_id": "VHHH_N_DEP"
+ },
+ {
+ "label": ["E-DES"],
+ "station_id": "VHHH_S_DEP"
+ },
+ {
+ "label": ["E-DEH"],
+ "station_id": "VHHH_H_DEP"
+ },
+ {
+ "label": ["E-TMW"],
+ "station_id": "VHHH_W_APP"
+ },
+ {
+ "label": ["E-TML"],
+ "station_id": "VHHH_L_APP"
+ },
+ {
+ "label": ["E-MCU"],
+ "station_id": "VMMC_APP"
+ },
+ {
+ "label": ["E-TMS"],
+ "station_id": "VHHH_S_APP"
+ },
+ {
+ "label": ["E-TDC"],
+ "station_id": "VHHH_C_DEP"
+ },
+ {
+ "label": ["E-TME"],
+ "station_id": "VHHH_E_APP"
+ },
+ {
+ "label": ["E-TDE"],
+ "station_id": "VHHH_E_DEP"
+ },
+ {
+ "label": ["E-TRW"],
+ "station_id": "VHHK_W_CTR"
+ },
+ {
+ "label": ["E-TRV"],
+ "station_id": "VHHK_V_CTR"
+ },
+ {
+ "label": ["E-TRD"],
+ "station_id": "VHHK_D_CTR"
+ },
+ {
+ "label": ["E-TRS"],
+ "station_id": "VHHK_S_CTR"
+ },
+ {
+ "label": ["E-TRZ"],
+ "station_id": "VHHK_Z_CTR"
+ },
+ {
+ "label": ["E-TRC"],
+ "station_id": "VHHK_C_CTR"
+ },
+ {
+ "label": ["E-TRE"],
+ "station_id": "VHHK_E_CTR"
+ },
+ {
+ "label": ["E-TRK"],
+ "station_id": "VHHK_K_CTR"
+ },
+ {
+ "label": ["E-TRU"],
+ "station_id": "VHHK_U_CTR"
+ }
+ ]
+ }
+ },
+ {
+ "label": ["K-TWR", "+VMMC"],
+ "page": {
+ "rows": 7,
+ "keys": [
+ {
+ "label": ["K-CDC"],
+ "station_id": "VHHX_DEL"
+ },
+ {
+ "label": ["K-GMC"],
+ "station_id": "VHHX_GND"
+ },
+ {
+ "label": ["K-AMC"],
+ "station_id": "VHHX_TWR"
+ },
+ {
+ "label": ["VMMC_DEL"],
+ "station_id": "VMMC_DEL"
+ },
+ {
+ "label": ["VMMC_GND"],
+ "station_id": "VMMC_GND"
+ },
+ {
+ "label": ["VMMC_TWR"],
+ "station_id": "VMMC_TWR"
+ }
+ ]
+ }
+ },
+ {
+ "label": "EXT",
+ "page": {
+ "rows": 7,
+ "keys": [
+ {
+ "label": ["PEW"],
+ "station_id": "RCAA_W_CTR"
+ },
+ {
+ "label": ["PEL"],
+ "station_id": "RCAA_L_CTR"
+ },
+ {
+ "label": ["PEE"],
+ "station_id": "RCAA_E_CTR"
+ },
+ {
+ "label": ["PEN"],
+ "station_id": "RCAA_N_CTR"
+ },
+ {
+ "label": ["PES"],
+ "station_id": "RCAA_S_CTR"
+ },
+ {
+ "label": ["SEA"],
+ "station_id": "ASEA_FSS"
+ },
+ {
+ "label": ["MN1"],
+ "station_id": "RPHI_1_CTR"
+ },
+ {
+ "label": ["MNN"],
+ "station_id": "RPHI_N_CTR"
+ },
+ {
+ "label": ["MNL"],
+ "station_id": "RPHI_CTR"
+ },
+ {
+ "label": ["PRC"],
+ "station_id": "ZBBB_FSS"
+ },
+ {
+ "label": ["SSS"],
+ "station_id": "ZSSS_S_CTR"
+ },
+ {
+ "label": ["SHS"],
+ "station_id": "ZSSS_CTR"
+ },
+ {
+ "label": ["SHA"],
+ "station_id": "ZSHA_CTR"
+ },
+ {
+ "label": ["SAM"],
+ "station_id": "ZSAM_CTR"
+ },
+ {
+ "label": ["JSA"],
+ "station_id": "ZJSA_CTR"
+ },
+ {
+ "label": ["SYL"],
+ "station_id": "ZJSY_L_CTR"
+ },
+ {
+ "label": ["SYO"],
+ "station_id": "ZJSY_O_CTR"
+ },
+ {
+ "label": ["GZU"],
+ "station_id": "ZGZU_CTR"
+ },
+ {
+ "label": ["GNN"],
+ "station_id": "ZGNN_CTR"
+ },
+ {
+ "label": ["GZJ"],
+ "station_id": "ZGZJ_APP"
+ },
+ {
+ "label": ["SWA"],
+ "station_id": "ZGOW_APP"
+ },
+ {
+ "label": ["ZUH"],
+ "station_id": "ZGJD_APP"
+ },
+ {
+ "label": ["GGG"],
+ "station_id": "ZGGG_CTR"
+ },
+ {
+ "label": ["GAP"],
+ "station_id": "ZGGG_APP"
+ }
+ ]
+ }
+ }
+ ]
+}
diff --git a/dataset/VH/stations.json b/dataset/VH/stations.json
new file mode 100644
index 00000000..6e3a473b
--- /dev/null
+++ b/dataset/VH/stations.json
@@ -0,0 +1,319 @@
+{
+ "stations": [
+ {
+ "id": "VHHH_DEL",
+ "parent_id": "VHHH_1_GND",
+ "controlled_by": ["VHHH_DEL"]
+ },
+ {
+ "id": "VHHX_DEL",
+ "parent_id": "VHHX_GND",
+ "controlled_by": ["VHHX_DEL"]
+ },
+ {
+ "id": "VMMC_DEL",
+ "parent_id": "VMMC_GND",
+ "controlled_by": ["VMMC_DEL"]
+ },
+ {
+ "id": "VHHH_1_GND",
+ "parent_id": "VHHH_S_TWR",
+ "controlled_by": ["VHHH_1_GND"]
+ },
+ {
+ "id": "VHHH_2_GND",
+ "parent_id": "VHHH_1_GND",
+ "controlled_by": ["VHHH_2_GND"]
+ },
+ {
+ "id": "VHHH_3_GND",
+ "parent_id": "VHHH_1_GND",
+ "controlled_by": ["VHHH_3_GND"]
+ },
+ {
+ "id": "VHHH_4_GND",
+ "parent_id": "VHHH_2_GND",
+ "controlled_by": ["VHHH_4_GND"]
+ },
+ {
+ "id": "VHHH_5_GND",
+ "parent_id": "VHHH_4_GND",
+ "controlled_by": ["VHHH_5_GND"]
+ },
+ {
+ "id": "VHHX_GND",
+ "parent_id": "VHHX_TWR",
+ "controlled_by": ["VHHX_GND"]
+ },
+ {
+ "id": "VMMC_GND",
+ "parent_id": "VMMC_TWR",
+ "controlled_by": ["VMMC_GND"]
+ },
+ {
+ "id": "VHHH_S_TWR",
+ "parent_id": "VHHH_APP",
+ "controlled_by": ["VHHH_S_TWR", "VHHH_C_TWR", "VHHH_N_TWR"]
+ },
+ {
+ "id": "VHHH_C_TWR",
+ "parent_id": "VHHH_S_TWR",
+ "controlled_by": ["VHHH_C_TWR", "VHHH_N_TWR", "VHHH_S_TWR"]
+ },
+ {
+ "id": "VHHH_N_TWR",
+ "parent_id": "VHHH_S_TWR",
+ "controlled_by": ["VHHH_N_TWR", "VHHH_C_TWR", "VHHH_S_TWR"]
+ },
+ {
+ "id": "VHHH_Z_TWR",
+ "parent_id": "VHHH_S_TWR",
+ "controlled_by": ["VHHH_Z_TWR", "VHHH_N_TWR", "VHHH_C_TWR", "VHHH_S_TWR"]
+ },
+ {
+ "id": "VHHH_F_TWR",
+ "parent_id": "VHHH_Z_TWR",
+ "controlled_by": ["VHHH_F_TWR"]
+ },
+ {
+ "id": "VHHX_TWR",
+ "parent_id": "VHHH_APP",
+ "controlled_by": ["VHHX_TWR"]
+ },
+ {
+ "id": "VMMC_TWR",
+ "parent_id": "VMMC_APP",
+ "controlled_by": ["VMMC_TWR"]
+ },
+ {
+ "id": "VHHH_APP",
+ "parent_id": "VHHK_W_CTR",
+ "controlled_by": ["VHHH_APP"]
+ },
+ {
+ "id": "VHHH_F_APP",
+ "parent_id": "VHHH_APP",
+ "controlled_by": ["VHHH_F_APP"]
+ },
+ {
+ "id": "VHHH_P_APP",
+ "parent_id": "VHHH_APP",
+ "controlled_by": ["VHHH_P_APP"]
+ },
+ {
+ "id": "VHHH_N_DEP",
+ "parent_id": "VHHH_APP",
+ "controlled_by": ["VHHH_N_DEP"]
+ },
+ {
+ "id": "VHHH_S_DEP",
+ "parent_id": "VHHH_N_DEP",
+ "controlled_by": ["VHHH_S_DEP"]
+ },
+ {
+ "id": "VHHH_H_DEP",
+ "parent_id": "VHHH_N_DEP",
+ "controlled_by": ["VHHH_H_DEP"]
+ },
+ {
+ "id": "VHHH_W_APP",
+ "parent_id": "VHHH_APP",
+ "controlled_by": ["VHHH_W_APP", "VHHK_W_CTR"]
+ },
+ {
+ "id": "VHHH_L_APP",
+ "parent_id": "VHHH_W_APP",
+ "controlled_by": ["VHHH_L_APP"]
+ },
+ {
+ "id": "VMMC_APP",
+ "parent_id": "VHHH_W_APP",
+ "controlled_by": ["VMMC_APP"]
+ },
+ {
+ "id": "VHHH_S_APP",
+ "parent_id": "VHHH_W_APP",
+ "controlled_by": ["VHHH_S_APP"]
+ },
+ {
+ "id": "VHHH_C_DEP",
+ "parent_id": "VHHH_S_APP",
+ "controlled_by": ["VHHH_C_DEP"]
+ },
+ {
+ "id": "VHHH_E_APP",
+ "parent_id": "VHHH_W_APP",
+ "controlled_by": ["VHHH_E_APP"]
+ },
+ {
+ "id": "VHHH_E_DEP",
+ "parent_id": "VHHH_E_APP",
+ "controlled_by": ["VHHH_E_DEP"]
+ },
+ {
+ "id": "VHHK_W_CTR",
+ "controlled_by": ["VHHK_W_CTR"]
+ },
+ {
+ "id": "VHHK_V_CTR",
+ "parent_id": "VHHK_W_CTR",
+ "controlled_by": ["VHHK_V_CTR"]
+ },
+ {
+ "id": "VHHK_D_CTR",
+ "parent_id": "VHHK_V_CTR",
+ "controlled_by": ["VHHK_D_CTR"]
+ },
+ {
+ "id": "VHHK_S_CTR",
+ "parent_id": "VHHK_W_CTR",
+ "controlled_by": ["VHHK_S_CTR"]
+ },
+ {
+ "id": "VHHK_Z_CTR",
+ "parent_id": "VHHK_S_CTR",
+ "controlled_by": ["VHHK_Z_CTR"]
+ },
+ {
+ "id": "VHHK_C_CTR",
+ "parent_id": "VHHK_S_CTR",
+ "controlled_by": ["VHHK_C_CTR"]
+ },
+ {
+ "id": "VHHK_E_CTR",
+ "parent_id": "VHHK_W_CTR",
+ "controlled_by": ["VHHK_E_CTR"]
+ },
+ {
+ "id": "VHHK_K_CTR",
+ "parent_id": "VHHK_E_CTR",
+ "controlled_by": ["VHHK_K_CTR"]
+ },
+ {
+ "id": "VHHK_U_CTR",
+ "parent_id": "VHHK_W_CTR",
+ "controlled_by": ["VHHK_U_CTR"]
+ },
+ {
+ "id": "VHHK_FMP",
+ "parent_id": "VHHH_W_APP",
+ "controlled_by": ["VHHK_FMP"]
+ },
+ {
+ "id": "RCAA_W_CTR",
+ "controlled_by": ["RCAA_W_CTR"]
+ },
+ {
+ "id": "RCAA_E_CTR",
+ "parent_id": "RCAA_W_CTR",
+ "controlled_by": ["RCAA_E_CTR"]
+ },
+ {
+ "id": "RCAA_S_CTR",
+ "parent_id": "RCAA_E_CTR",
+ "controlled_by": ["RCAA_S_CTR"]
+ },
+ {
+ "id": "RCAA_L_CTR",
+ "controlled_by": ["RCAA_L_CTR", "RCAA_W_CTR"]
+ },
+ {
+ "id": "RCAA_N_CTR",
+ "controlled_by": ["RCAA_N_CTR", "RCAA_L_CTR", "RCAA_W_CTR"]
+ },
+ {
+ "id": "ASEA_FSS",
+ "controlled_by": ["ASEA_FSS"]
+ },
+ {
+ "id": "RPHI_1_CTR",
+ "parent_id": "RPHI_N_CTR",
+ "controlled_by": ["RPHI_1_CTR"]
+ },
+ {
+ "id": "RPHI_N_CTR",
+ "parent_id": "RPHI_CTR",
+ "controlled_by": ["RPHI_N_CTR"]
+ },
+ {
+ "id": "RPHI_CTR",
+ "controlled_by": ["RPHI_CTR"]
+ },
+ {
+ "id": "ZBBB_FSS",
+ "controlled_by": ["ZBBB_FSS"]
+ },
+ {
+ "id": "ZSSS_S_CTR",
+ "parent_id": "ZSSS_CTR",
+ "controlled_by": ["ZSSS_S_CTR"]
+ },
+ {
+ "id": "ZSSS_CTR",
+ "parent_id": "ZSHA_CTR",
+ "controlled_by": ["ZSSS_CTR"]
+ },
+ {
+ "id": "ZSHA_CTR",
+ "controlled_by": ["ZSHA_CTR"]
+ },
+ {
+ "id": "ZSAM_CTR",
+ "parent_id": "ZSHA_CTR",
+ "controlled_by": ["ZSAM_CTR"]
+ },
+ {
+ "id": "ZJSA_CTR",
+ "controlled_by": ["ZJSA_CTR"]
+ },
+ {
+ "id": "ZJSY_L_CTR",
+ "parent_id": "ZJSA_CTR",
+ "controlled_by": ["ZJSY_L_CTR"]
+ },
+ {
+ "id": "ZJSY_O_CTR",
+ "parent_id": "ZJSA_CTR",
+ "controlled_by": ["ZJSY_O_CTR"]
+ },
+ {
+ "id": "ZGZU_CTR",
+ "controlled_by": ["ZGZU_CTR"]
+ },
+ {
+ "id": "ZGNN_CTR",
+ "parent_id": "ZGZU_CTR",
+ "controlled_by": ["ZGNN_CTR"]
+ },
+ {
+ "id": "ZGZJ_APP",
+ "parent_id": "ZGNN_CTR",
+ "controlled_by": ["ZGZJ_APP"]
+ },
+ {
+ "id": "ZGOW_APP",
+ "controlled_by": [
+ "ZGOW_APP",
+ "ZGGG_CTR",
+ "ZGZU_CTR",
+ "ZSAM_CTR",
+ "ZSHA_CTR"
+ ]
+ },
+ {
+ "id": "ZGJD_APP",
+ "parent_id": "ZGGG_CTR",
+ "controlled_by": ["ZGJD_APP"]
+ },
+ {
+ "id": "ZGGG_CTR",
+ "parent_id": "ZGZU_CTR",
+ "controlled_by": ["ZGGG_CTR"]
+ },
+ {
+ "id": "ZGGG_APP",
+ "parent_id": "ZGGG_CTR",
+ "controlled_by": ["ZGGG_APP"]
+ }
+ ]
+}
diff --git a/docs/dataset/positions.md b/docs/dataset/positions.md
index 994d7414..c26202db 100644
--- a/docs/dataset/positions.md
+++ b/docs/dataset/positions.md
@@ -57,7 +57,7 @@ Each position entry maps a VATSIM controller login to a vacs position.
| `prefixes` | Array of strings | Yes | Callsign prefixes used to match VATSIM logins when the callsign doesn't exactly match the `id` (e.g., `["LOWW"]`). |
| `frequency` | String | Yes | Primary frequency in `XXX.XXX` format (e.g., `118.700`). |
| `facility_type` | String | Yes | VATSIM facility type. Must be one of the facility types listed below. |
-| `profile_id` | String | No | Optional ID of the default profile to load for this position. |
+| `profile_id` | String | No | Optional ID of the profile to load for this position. |
## Validation Rules
@@ -171,7 +171,7 @@ What this means in practice:
### Position without a profile
-This example shows a position that does not load a default profile.
+This example shows a position that does not load a profile.
```toml
[[positions]]
@@ -184,7 +184,7 @@ facility_type = "DEL"
What this means in practice:
- The `profile_id` field is optional and can be omitted
-- Controllers logging into this position will not have a default profile automatically loaded
+- Controllers logging into this position will not have a profile automatically loaded
- Positions without profiles associated will only receive a basic view showing connected users (and their VATSIM callsign) and **will not show up as an online station for other controllers in vacs**
### Prefix matching with non-standard callsigns
diff --git a/docs/dataset/profiles.md b/docs/dataset/profiles.md
index 7ad8c26c..b1483621 100644
--- a/docs/dataset/profiles.md
+++ b/docs/dataset/profiles.md
@@ -25,8 +25,9 @@ Each profile file must contain a single profile object. There are two types of p
- **Direct Access Key**: A button that allows calling a specific station
- **Station ID**: Reference to a station defined in the stations configuration
- **Tab**: A page in a tabbed profile containing a grid of keys
-- **Geo Node**: A building block in a geo profile (container, button, or divider)
+- **Geo Node**: A building block in a geo profile (container, button, or divider). See [Geo Profile Components](#geo-profile-components).
- **Container**: A layout element that groups and arranges child nodes
+- **Client Page**: A specialized page displaying a list of all online clients, filtered and prioritized based on the client page configuration
## Profile Types
@@ -34,6 +35,8 @@ Each profile file must contain a single profile object. There are two types of p
A tabbed profile organizes keys into multiple tabs, each containing a grid of direct access keys.
+
+
**Required fields:**
- `id` (string): Unique profile identifier
@@ -64,12 +67,14 @@ A tabbed profile organizes keys into multiple tabs, each containing a grid of di
A geo profile uses a flexible container-based layout system, allowing for custom geometric arrangements of buttons and dividers.
+
+
**Required fields:**
- `id` (string): Unique profile identifier
- `type` (string): Must be `"Geo"`
- `direction` (string): Flex direction (`"row"` or `"col"`)
-- `children` (array): One or more geo nodes (containers, buttons, or dividers)
+- `children` (array): One or more [geo nodes](#geonode) (containers, buttons, or dividers)
**Structure:**
@@ -93,61 +98,100 @@ A geo profile uses a flexible container-based layout system, allowing for custom
| `id` | String | Yes | Unique profile identifier. Must start with the FIR's country code (e.g., `LOWW`, `LOVV`). |
| `type` | String | Yes | Profile type. Must be either `"Tabbed"` or `"Geo"`. |
-## Tabbed Profile Components
+## Shared Profile Components
-### Tab
+### DirectAccessPage
-Represents a single tab in a tabbed profile.
+Defines a grid layout of direct access keys.
-| Field | Type | Required | Description |
-| :------ | :--------------- | :------- | :------------------------------------ |
-| `label` | String | Yes | Tab name displayed in the interface. |
-| `page` | DirectAccessPage | Yes | The page containing the grid of keys. |
+| Field | Type | Required | Description |
+| :------------ | :--------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------ |
+| `rows` | Integer | Yes | Number of rows in the grid (minimum 1). |
+| `keys` | Array of [DirectAccessKey](#directaccesskey) | No | Array of keys to display in the grid. Mutually exclusive with `client_page`. One of `keys` or `client_page` must be set. |
+| `client_page` | [ClientPageConfig](#client-page-configuration) | No | Configuration for a dynamic client list page. Mutually exclusive with `keys`. One of `keys` or `client_page` must be set. |
-### DirectAccessPage
+### Client Page Configuration
-Defines a grid layout of direct access keys.
+A client page displays a dynamic list of online clients instead of a static grid of keys.
+
+| Field | Type | Required | Description |
+| :------------ | :------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `include` | Array of Strings | No | List of callsign patterns to include. Supports glob syntax (e.g., `"LO*"`, `"*_APP"`). If empty, all clients are eligible. |
+| `exclude` | Array of Strings | No | List of callsign patterns to exclude. Clients matching these patterns are never shown. Supports glob syntax. |
+| `priority` | Array of Strings | No | Ordered list of callsign patterns to determine sort priority. Earlier patterns have higher priority. Default: `["*_FMP", "*_CTR", "*_APP", "*_TWR", "*_GND"]`. |
+| `frequencies` | FrequencyDisplayMode | No | Controls frequency display on keys. |
+| `grouping` | ClientGroupMode | No | Controls how keys are grouped. |
+
+**Valid `frequencies` values:**
-| Field | Type | Required | Description |
-| :----- | :----------------------- | :------- | :-------------------------------------- |
-| `rows` | Integer | Yes | Number of rows in the grid (minimum 1). |
-| `keys` | Array of DirectAccessKey | Yes | Array of keys to display in the grid. |
+- `"ShowAll"` (default), `"HideAll"`
+
+**Valid `grouping` values:**
+
+- `"None"`: Do not group
+- `"Fir"`: Group by first 2 chars
+- `"Icao"`: Group by first 4 chars
+- `"FirAndIcao"` (default): Group by FIR then ICAO code
### DirectAccessKey
Represents a single callable button.
-| Field | Type | Required | Description |
-| :----------- | :--------------- | :------- | :------------------------------------------------------------------------------ |
-| `label` | Array of strings | Yes | Multi-line label (up to 3 lines). Can be empty array for blank keys. |
-| `station_id` | String | No | Station ID to call when pressed. If omitted, key is displayed but not callable. |
+| Field | Type | Required | Description |
+| :----------- | :------------------------------------ | :------- | :------------------------------------------------------------------------------------------------------------------------ |
+| `label` | String or Array of strings | Yes | Multi-line label (up to 3 lines). Can be empty string or empty array for blank keys. |
+| `station_id` | String | No | Station ID to call when pressed. Mutually exclusive with `page`. If neither is specified, the button will be disabled. |
+| `page` | [DirectAccessPage](#directaccesspage) | No | Subpage to open when pressed. Mutually exclusive with `station_id`. If neither is specified, the button will be disabled. |
+
+## Tabbed Profile Components
+
+### Tab
+
+Represents a single tab in a tabbed profile.
+
+| Field | Type | Required | Description |
+| :------ | :------------------------------------ | :------- | :--------------------------------------------------------------------------- |
+| `label` | String or Array of strings | Yes | Multi-line tab name (1-3 lines, cannot be empty) displayed in the interface. |
+| `page` | [DirectAccessPage](#directaccesspage) | Yes | The page containing the grid of keys. |
## Geo Profile Components
+### GeoNode
+
+A `GeoNode` is one of the following:
+
+- [GeoPageContainer](#geopagecontainer)
+- [GeoPageButton](#geopagebutton)
+- [GeoPageDivider](#geopagedivider)
+
### GeoPageContainer
A layout container that arranges child nodes using flexbox.
-| Field | Type | Required | Description |
-| :---------------- | :---------------- | :------- | :---------------------------------------------- |
-| `direction` | String | Yes | Flex direction: `"row"` or `"col"`. |
-| `children` | Array of GeoNodes | Yes | Child nodes (containers, buttons, or dividers). |
-| `height` | String | No | Container height (e.g., `"100%"`, `"20rem"`). |
-| `width` | String | No | Container width (e.g., `"100%"`, `"20rem"`). |
-| `padding` | Number | No | Padding on all sides (≥ 0). |
-| `padding_left` | Number | No | Left padding (≥ 0). |
-| `padding_right` | Number | No | Right padding (≥ 0). |
-| `padding_top` | Number | No | Top padding (≥ 0). |
-| `padding_bottom` | Number | No | Bottom padding (≥ 0). |
-| `gap` | Number | No | Gap between child elements (≥ 0). |
-| `justify_content` | String | No | Flexbox justify-content property. |
-| `align_items` | String | No | Flexbox align-items property. |
-
-**Valid `justify_content` values:**
+| Field | Type | Required | Description |
+| :---------------- | :---------------------------- | :------- | :---------------------------------------------- |
+| `direction` | FlexDirection | Yes | Flex direction. |
+| `children` | Array of [GeoNodes](#geonode) | Yes | Child nodes (containers, buttons, or dividers). |
+| `height` | String | No | Container height (e.g., `"100%"`, `"20rem"`). |
+| `width` | String | No | Container width (e.g., `"100%"`, `"20rem"`). |
+| `padding` | Number | No | Padding on all sides (≥ 0). |
+| `padding_left` | Number | No | Left padding (≥ 0). |
+| `padding_right` | Number | No | Right padding (≥ 0). |
+| `padding_top` | Number | No | Top padding (≥ 0). |
+| `padding_bottom` | Number | No | Bottom padding (≥ 0). |
+| `gap` | Number | No | Gap between child elements (≥ 0). |
+| `justify_content` | JustifyContent | No | Flexbox justify-content property. |
+| `align_items` | AlignItems | No | Flexbox align-items property. |
+
+**Valid `FlexDirection` values:**
+
+- `"row"`, `"col"`
+
+**Valid `JustifyContent` values:**
- `"flex-start"`, `"flex-end"`, `"center"`, `"space-between"`, `"space-around"`, `"space-evenly"`
-**Valid `align_items` values:**
+**Valid `AlignItems` values:**
- `"flex-start"`, `"flex-end"`, `"center"`, `"stretch"`, `"baseline"`
@@ -155,11 +199,11 @@ A layout container that arranges child nodes using flexbox.
A clickable button that can trigger a direct access page or call a station.
-| Field | Type | Required | Description |
-| :------ | :--------------- | :------- | :--------------------------------------------------------- |
-| `label` | Array of strings | Yes | Multi-line label (1-3 lines, cannot be empty). |
-| `size` | Number | Yes | Button size (> 0). Controls relative sizing in the layout. |
-| `page` | DirectAccessPage | No | Optional nested page to display when button is pressed. |
+| Field | Type | Required | Description |
+| :------ | :------------------------------------ | :------- | :--------------------------------------------------------- |
+| `label` | String or Array of strings | Yes | Multi-line label (1-3 lines, cannot be empty). |
+| `size` | Number | Yes | Button size (> 0). Controls relative sizing in the layout. |
+| `page` | [DirectAccessPage](#directaccesspage) | No | Optional nested page to display when button is pressed. |
### GeoPageDivider
@@ -188,6 +232,7 @@ The following validation rules apply:
- All size values (`height`, `width`) must match pattern `^\d+(%|rem)$`
- All numeric values (padding, gap, size, thickness) must be appropriate to their type (non-negative or positive)
- Labels:
+ - Can be provided as a single string or an array of strings
- Can have up to 3 lines
- Geo button labels must have at least 1 line
- Direct access key labels can be empty (for blank keys)
@@ -205,10 +250,17 @@ When configuring profiles, watch out for these issues:
## Examples
+You can find simple examples for different types of profiles below.
+
+For a more comprehensive [Geo Profile example](#geo-profile), see the [`LOVV` profile](../../dataset/LO/profiles/LOVV.json).
+For a more comprehensive [Tabbed Profile example](#tabbed-profile), see the [`LOWW` profile](../../dataset/LO/profiles/LOWW.json).
+
### Simple tabbed profile
This example defines a basic tabbed profile with one tab containing a 4-row grid of keys.
+
+
```json
{
"id": "LOWW",
@@ -253,6 +305,8 @@ What this means in practice:
This example shows a tabbed profile with multiple tabs for different operational areas.
+
+
```json
{
"id": "LOWW",
@@ -309,6 +363,8 @@ What this means in practice:
This example demonstrates a simple geo profile using a vertical container layout.
+
+
```json
{
"id": "LOVV",
@@ -321,10 +377,11 @@ This example demonstrates a simple geo profile using a vertical container layout
{
"direction": "row",
"gap": 8,
+ "padding": 2,
"children": [
{
"label": ["LOVV", "N5"],
- "size": 1,
+ "size": 10,
"page": {
"rows": 2,
"keys": [
@@ -341,7 +398,7 @@ This example demonstrates a simple geo profile using a vertical container layout
},
{
"label": ["LOVV", "S5"],
- "size": 1,
+ "size": 10,
"page": {
"rows": 2,
"keys": [
@@ -366,6 +423,112 @@ What this means in practice:
- The profile uses a geometric layout instead of fixed tabs
- The root container is a vertical column (`"col"`) that fills the full available space
-- Inside is a horizontal row (`"row"`) containing two equally-sized buttons (`size: 1`)
+- Inside is a horizontal row (`"row"`) containing two equally sized buttons (`size: 10`)
- Each button opens a nested direct access page when pressed
- The nested pages contain grids of station keys for different sectors
+
+### Subpage definition
+
+This example shows a profile with a key that opens another page (subpage) instead of calling a station.
+
+```json
+{
+ "id": "LOWW",
+ "type": "Tabbed",
+ "tabs": [
+ {
+ "label": "EC",
+ "page": {
+ "rows": 4,
+ "keys": [
+ {
+ "label": ["Other", "Sectors"],
+ "page": {
+ "rows": 4,
+ "keys": [
+ {
+ "label": ["LOVV", "W_CTR"],
+ "station_id": "LOVV_W_CTR"
+ },
+ {
+ "label": ["LOVV", "S_CTR"],
+ "station_id": "LOVV_S_CTR"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ ]
+}
+```
+
+What this means in practice:
+
+- The profile contains one tab with a single key
+- The key is labeled "Other Sectors"
+- When pressed, it replaces the current grid with a new 4-row grid
+- The new grid contains keys for `LOVV_W_CTR` and `LOVV_S_CTR`
+- This allows creating hierarchical menus of stations
+
+### Client page definition
+
+This example shows a profile with a page that displays a dynamic list of online clients.
+
+```json
+{
+ "id": "LOWW",
+ "type": "Tabbed",
+ "tabs": [
+ {
+ "label": "CWP",
+ "page": {
+ "rows": 6,
+ "client_page": {
+ "include": ["LO*", "ED*"],
+ "exclude": ["LON*", "*_GND"],
+ "grouping": "Fir",
+ "priority": ["*_CTR", "*_APP"]
+ }
+ }
+ }
+ ]
+}
+```
+
+What this means in practice:
+
+- The profile contains a tab that shows a client list
+- The page will automatically populate with online clients
+- Only callsigns starting with `LO` or `ED` are included
+- Callsigns starting with `LON` as well as all Ground positions are excluded
+- Clients are grouped by their FIR (first 2 letters of callsign)
+- Centers and Approach units are shown first in the list
+
+## Component Visuals
+
+### Direct Access Page states
+
+
+
+The screenshot above shows a direct access page with three different states:
+
+- **Station online, covered by own position**: The DA key is active with grey text (e.g., `380 E6 PLC`)
+- **Station online, covered by different position**: The DA key is active with black text (e.g., `APP VB PLN`)
+- **Station defined, but currently not covered by any position**: The DA key is inactive with black text (e.g., `APP VD1 PLN`)
+- **Station not defined**: The DA key is inactive with grey text (see [Tabbed Profile](#tabbed-profile), e.g., `PRA LW EC`)
+
+### Client Page
+
+
+
+Client page grouped by FIR (two letters)
+
+
+
+Client page grouped by ICAO (four letters)
+
+
+
+List of clients prioritized and displayed as per [Client Page Configuration](#client-page-configuration)
diff --git a/docs/dataset/stations.md b/docs/dataset/stations.md
index 6905c795..6709735c 100644
--- a/docs/dataset/stations.md
+++ b/docs/dataset/stations.md
@@ -193,13 +193,13 @@ parent_id = "LOVV_N7"
[[stations]]
id = "LOVV_N7"
controlled_by = [
- "LOVV_NU_CTR",
- "LOVV_EU_CTR",
- "LOVV_U_CTR",
- "LOVV_N_CTR",
- "LOVV_E_CTR",
- "LOVV_CTR",
- "LOVV_C_CTR",
+ "LOVV_NU_CTR",
+ "LOVV_EU_CTR",
+ "LOVV_U_CTR",
+ "LOVV_N_CTR",
+ "LOVV_E_CTR",
+ "LOVV_CTR",
+ "LOVV_C_CTR",
]
```
diff --git a/docs/images/client_page.png b/docs/images/client_page.png
new file mode 100644
index 00000000..e6a3df66
Binary files /dev/null and b/docs/images/client_page.png differ
diff --git a/docs/images/client_page_fir.png b/docs/images/client_page_fir.png
new file mode 100644
index 00000000..35100b09
Binary files /dev/null and b/docs/images/client_page_fir.png differ
diff --git a/docs/images/client_page_icao.png b/docs/images/client_page_icao.png
new file mode 100644
index 00000000..d5291c6d
Binary files /dev/null and b/docs/images/client_page_icao.png differ
diff --git a/docs/images/direct_access_page.png b/docs/images/direct_access_page.png
new file mode 100644
index 00000000..4081eb07
Binary files /dev/null and b/docs/images/direct_access_page.png differ
diff --git a/docs/images/example_basic_geo.png b/docs/images/example_basic_geo.png
new file mode 100644
index 00000000..966b8e87
Binary files /dev/null and b/docs/images/example_basic_geo.png differ
diff --git a/docs/images/example_multi_tabbed.png b/docs/images/example_multi_tabbed.png
new file mode 100644
index 00000000..bfedcafa
Binary files /dev/null and b/docs/images/example_multi_tabbed.png differ
diff --git a/docs/images/example_simple_tabbed.png b/docs/images/example_simple_tabbed.png
new file mode 100644
index 00000000..4af575d2
Binary files /dev/null and b/docs/images/example_simple_tabbed.png differ
diff --git a/docs/images/geo_page.png b/docs/images/geo_page.png
new file mode 100644
index 00000000..74487b16
Binary files /dev/null and b/docs/images/geo_page.png differ
diff --git a/docs/images/tabbed.png b/docs/images/tabbed.png
new file mode 100644
index 00000000..6cffbb1a
Binary files /dev/null and b/docs/images/tabbed.png differ
diff --git a/docs/schemas/profiles.schema.json b/docs/schemas/profiles.schema.json
index 31799884..543fee60 100644
--- a/docs/schemas/profiles.schema.json
+++ b/docs/schemas/profiles.schema.json
@@ -26,6 +26,10 @@
"type": "string"
}
},
+ "LabelStringOrLines": {
+ "description": "Label that can be either a single string or an array of strings (lines).",
+ "oneOf": [{ "type": "string" }, { "$ref": "#/$defs/LabelLines" }]
+ },
"FlexDirection": {
"type": "string",
"enum": ["row", "col"]
@@ -62,19 +66,56 @@
"type": "number",
"exclusiveMinimum": 0
},
+ "FrequencyDisplayMode": {
+ "type": "string",
+ "enum": ["ShowAll", "HideAll"],
+ "default": "ShowAll"
+ },
+ "ClientGroupMode": {
+ "type": "string",
+ "enum": ["None", "Fir", "Icao", "FirAndIcao"],
+ "default": "FirAndIcao"
+ },
+ "ClientPageConfig": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "include": {
+ "type": "array",
+ "items": { "type": "string" },
+ "default": []
+ },
+ "exclude": {
+ "type": "array",
+ "items": { "type": "string" },
+ "default": []
+ },
+ "priority": {
+ "type": "array",
+ "items": { "type": "string" },
+ "default": ["*_FMP", "*_CTR", "*_APP", "*_TWR", "*_GND"]
+ },
+ "frequencies": { "$ref": "#/$defs/FrequencyDisplayMode" },
+ "grouping": { "$ref": "#/$defs/ClientGroupMode" }
+ }
+ },
"DirectAccessKey": {
"type": "object",
"additionalProperties": false,
"required": ["label"],
"properties": {
- "label": { "$ref": "#/$defs/LabelLines" },
- "station_id": { "$ref": "#/$defs/StationId" }
+ "label": { "$ref": "#/$defs/LabelStringOrLines" },
+ "station_id": { "$ref": "#/$defs/StationId" },
+ "page": { "$ref": "#/$defs/DirectAccessPage" }
+ },
+ "not": {
+ "required": ["station_id", "page"]
}
},
"DirectAccessPage": {
"type": "object",
"additionalProperties": false,
- "required": ["rows", "keys"],
+ "required": ["rows"],
"properties": {
"rows": {
"type": "integer",
@@ -83,7 +124,12 @@
"keys": {
"type": "array",
"items": { "$ref": "#/$defs/DirectAccessKey" }
- }
+ },
+ "client_page": { "$ref": "#/$defs/ClientPageConfig" }
+ },
+ "oneOf": [{ "required": ["keys"] }, { "required": ["client_page"] }],
+ "not": {
+ "required": ["keys", "client_page"]
}
},
"Tab": {
@@ -91,7 +137,19 @@
"additionalProperties": false,
"required": ["label", "page"],
"properties": {
- "label": { "type": "string", "minLength": 1 },
+ "label": {
+ "allOf": [
+ { "$ref": "#/$defs/LabelStringOrLines" },
+ {
+ "if": { "type": "array" },
+ "then": { "minItems": 1 }
+ },
+ {
+ "if": { "type": "string" },
+ "then": { "minLength": 1 }
+ }
+ ]
+ },
"page": { "$ref": "#/$defs/DirectAccessPage" }
}
},
@@ -102,7 +160,17 @@
"properties": {
"label": {
"description": "Geo page button labels must be non-empty and cannot exceed 3 lines.",
- "allOf": [{ "$ref": "#/$defs/LabelLines" }, { "minItems": 1 }]
+ "allOf": [
+ { "$ref": "#/$defs/LabelStringOrLines" },
+ {
+ "if": { "type": "array" },
+ "then": { "minItems": 1 }
+ },
+ {
+ "if": { "type": "string" },
+ "then": { "minLength": 1 }
+ }
+ ]
},
"size": {
"$ref": "#/$defs/PositiveNumber"
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..ff929efb
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,69 @@
+{
+ "name": "vacs-data",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "vacs-data",
+ "version": "0.0.0",
+ "devDependencies": {
+ "prettier": "^3.8.1",
+ "prettier-plugin-toml": "^2.0.6"
+ }
+ },
+ "node_modules/@taplo/core": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@taplo/core/-/core-0.2.0.tgz",
+ "integrity": "sha512-r8bl54Zj1In3QLkiW/ex694bVzpPJ9EhwqT9xkcUVODnVUGirdB1JTsmiIv0o1uwqZiwhi8xNnTOQBRQCpizrQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@taplo/lib": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@taplo/lib/-/lib-0.5.0.tgz",
+ "integrity": "sha512-+xIqpQXJco3T+VGaTTwmhxLa51qpkQxCjRwezjFZgr+l21ExlywJFcDfTrNmL6lG6tqb0h8GyJKO3UPGPtSCWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@taplo/core": "^0.2.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
+ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-toml": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-toml/-/prettier-plugin-toml-2.0.6.tgz",
+ "integrity": "sha512-12N/wBuHa9jd/KVy9pRP20NMKxQfQLMseQCt66lIbLaPLItvGUcSIryE1eZZMJ7loSws6Ig3M2Elc2EreNh76w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@taplo/lib": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts"
+ },
+ "peerDependencies": {
+ "prettier": "^3.0.3"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..557c0c89
--- /dev/null
+++ b/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "vacs-data",
+ "version": "0.0.0",
+ "private": "true",
+ "description": "vacs Data Repository",
+ "scripts": {
+ "format": "prettier --write \"dataset/**/*.{json,toml}\"",
+ "format:check": "prettier --check \"dataset/**/*.{json,toml}\"",
+ "format:all": "prettier --write .",
+ "format:all:check": "prettier --check ."
+ },
+ "devDependencies": {
+ "prettier": "^3.8.1",
+ "prettier-plugin-toml": "^2.0.6"
+ }
+}
diff --git a/tools/Cargo.lock b/tools/Cargo.lock
index e43677ad..51fa5769 100644
--- a/tools/Cargo.lock
+++ b/tools/Cargo.lock
@@ -75,9 +75,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
-version = "4.5.56"
+version = "4.5.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e"
+checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a"
dependencies = [
"clap_builder",
"clap_derive",
@@ -85,9 +85,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.56"
+version = "4.5.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0"
+checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238"
dependencies = [
"anstream",
"anstyle",
@@ -232,9 +232,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
-version = "2.7.6"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "once_cell"
@@ -280,9 +280,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "regex"
-version = "1.12.2"
+version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
@@ -292,9 +292,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.13"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
@@ -303,9 +303,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.8.8"
+version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
[[package]]
name = "rustversion"
@@ -411,7 +411,22 @@ dependencies = [
"indexmap",
"serde_core",
"serde_spanned",
- "toml_datetime",
+ "toml_datetime 0.7.5+spec-1.1.0",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml"
+version = "1.0.0+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1d7e18e3dd1d31e0ee5e863a8091ffec2fcc271636586042452b656a22c8ee1"
+dependencies = [
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime 1.0.0+spec-1.1.0",
"toml_parser",
"toml_writer",
"winnow",
@@ -426,11 +441,20 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "toml_datetime"
+version = "1.0.0+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
+dependencies = [
+ "serde_core",
+]
+
[[package]]
name = "toml_parser"
-version = "1.0.6+spec-1.1.0"
+version = "1.0.7+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
+checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1"
dependencies = [
"winnow",
]
@@ -505,7 +529,7 @@ dependencies = [
[[package]]
name = "vacs-data-cli"
-version = "0.1.0"
+version = "0.4.0"
dependencies = [
"clap",
"vacs-data-diagnostics",
@@ -515,21 +539,21 @@ dependencies = [
[[package]]
name = "vacs-data-diagnostics"
-version = "0.1.0"
+version = "0.4.0"
dependencies = [
"console",
]
[[package]]
name = "vacs-data-importer"
-version = "0.1.0"
+version = "0.4.0"
dependencies = [
"console",
"encoding_rs",
"encoding_rs_io",
"serde",
"serde_json",
- "toml",
+ "toml 1.0.0+spec-1.1.0",
"vacs-data-diagnostics",
"vacs-protocol",
"vacs-vatsim",
@@ -537,7 +561,7 @@ dependencies = [
[[package]]
name = "vacs-data-validator"
-version = "0.1.0"
+version = "0.4.0"
dependencies = [
"console",
"vacs-data-diagnostics",
@@ -547,7 +571,7 @@ dependencies = [
[[package]]
name = "vacs-protocol"
version = "1.1.0"
-source = "git+https://github.com/MorpheusXAUT/vacs?branch=v2#fc588ebd0689f4e04e471b449e466594092fa452"
+source = "git+https://github.com/MorpheusXAUT/vacs?branch=v2#5336e4d2af83de97c031c7f9ff0ba95c7b241866"
dependencies = [
"serde",
"serde_json",
@@ -557,13 +581,13 @@ dependencies = [
[[package]]
name = "vacs-vatsim"
version = "0.2.1"
-source = "git+https://github.com/MorpheusXAUT/vacs?branch=v2#fc588ebd0689f4e04e471b449e466594092fa452"
+source = "git+https://github.com/MorpheusXAUT/vacs?branch=v2#5336e4d2af83de97c031c7f9ff0ba95c7b241866"
dependencies = [
"regex",
"serde",
"serde_json",
"thiserror",
- "toml",
+ "toml 0.9.11+spec-1.1.0",
"tracing",
"vacs-protocol",
]
@@ -651,6 +675,6 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
[[package]]
name = "zmij"
-version = "1.0.18"
+version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1966f8ac2c1f76987d69a74d0e0f929241c10e78136434e3be70ff7f58f64214"
+checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
diff --git a/tools/Cargo.toml b/tools/Cargo.toml
index 0a950691..f5be0634 100644
--- a/tools/Cargo.toml
+++ b/tools/Cargo.toml
@@ -3,7 +3,7 @@ resolver = "3"
members = ["cli", "diagnostics", "importer", "validator"]
[workspace.package]
-version = "0.1.0"
+version = "0.4.0"
edition = "2024"
license = "Apache-2.0 OR MIT"
repository = "https://github.com/MorpheusXAUT/vacs-data"
@@ -17,7 +17,7 @@ encoding_rs_io = "0.1.7"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
thiserror = "2.0.18"
-toml = { version = "0.9.11", features = ["serde"] }
+toml = { version = "1.0.0", features = ["serde"] }
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.22", features = ["env-filter", "json"] }
vacs-data-diagnostics = { path = "./diagnostics" }
diff --git a/tools/cli/src/cli.rs b/tools/cli/src/cli.rs
index ae5ea070..674493c2 100644
--- a/tools/cli/src/cli.rs
+++ b/tools/cli/src/cli.rs
@@ -13,7 +13,7 @@ pub struct Cli {
#[arg(short, long)]
pub quiet: bool,
- /// Logging output format
+ /// Logging output format. Supported: human, github
#[arg(long, default_value_t = LogFormat::Human)]
pub log_format: LogFormat,
@@ -28,9 +28,10 @@ pub struct Cli {
#[derive(Debug, Subcommand)]
pub enum Command {
/// Run validations on the whole dataset
+ #[command(arg_required_else_help = true)]
Validate {
- /// Dataset root to validate (positional). Defaults to repo dataset/ if found, else "."
- #[arg(value_name = "INPUT")]
+ /// Dataset root to validate (positional).
+ #[arg(value_name = "INPUT", required_unless_present = "input")]
input_pos: Option,
/// Dataset root to validate
@@ -48,13 +49,14 @@ pub enum Command {
#[derive(Debug, Subcommand)]
pub enum ImportCommand {
/// Import data from the VATglasses project, converting it to vacs dataset format
+ #[command(arg_required_else_help = true)]
Vatglasses {
/// Input JSON file (positional)
- #[arg(value_name = "INPUT")]
+ #[arg(value_name = "INPUT", required_unless_present = "input")]
input_pos: Option,
/// Output directory (positional)
- #[arg(value_name = "OUTPUT")]
+ #[arg(value_name = "OUTPUT", required_unless_present = "output")]
output_pos: Option,
/// Input JSON file
@@ -65,6 +67,10 @@ pub enum ImportCommand {
#[arg(short, long)]
output: Option,
+ /// Format to use for output files. Supported: toml, json
+ #[arg(short, long, default_value_t = vacs_data_importer::OutputFormat::Toml)]
+ format: vacs_data_importer::OutputFormat,
+
/// Overwrite existing files
#[arg(long, conflicts_with = "merge")]
overwrite: bool,
@@ -75,13 +81,14 @@ pub enum ImportCommand {
},
/// Import data from an EuroScope sectorfile, converting it to vacs dataset format
+ #[command(arg_required_else_help = true)]
Euroscope {
/// Input JSON file (positional)
- #[arg(value_name = "INPUT")]
+ #[arg(value_name = "INPUT", required_unless_present = "input")]
input_pos: Option,
/// Output directory (positional)
- #[arg(value_name = "OUTPUT")]
+ #[arg(value_name = "OUTPUT", required_unless_present = "output")]
output_pos: Option,
/// Input JSON file
@@ -92,6 +99,10 @@ pub enum ImportCommand {
#[arg(short, long)]
output: Option,
+ /// Format to use for output files. Supported: toml, json
+ #[arg(short, long, default_value_t = vacs_data_importer::OutputFormat::Toml)]
+ format: vacs_data_importer::OutputFormat,
+
/// Prefixes to filter positions by
#[arg(short, long, value_name = "PREFIX")]
prefixes: Option>,
diff --git a/tools/cli/src/main.rs b/tools/cli/src/main.rs
index e3c83f93..87f03377 100644
--- a/tools/cli/src/main.rs
+++ b/tools/cli/src/main.rs
@@ -2,7 +2,6 @@ mod cli;
use crate::cli::{Cli, Command, ImportCommand};
use clap::Parser;
-use vacs_data_diagnostics::log;
pub fn main() {
let cli = Cli::parse();
@@ -10,10 +9,7 @@ pub fn main() {
match cli.cmd {
Command::Validate { input_pos, input } => {
- let Some(input) = input.or(input_pos) else {
- log::error("Missing input path. Either provide INPUT or use --i/--input.");
- std::process::exit(2);
- };
+ let input = input.or(input_pos).unwrap();
if vacs_data_validator::validate(&input).is_err() {
std::process::exit(1);
@@ -28,18 +24,15 @@ pub fn main() {
output,
overwrite,
merge,
+ format,
},
} => {
- let Some(input) = input.or(input_pos) else {
- log::error("Missing input path. Either provide INPUT or use --i/--input.");
- std::process::exit(2);
- };
- let Some(output) = output.or(output_pos) else {
- log::error("Missing output path. Either provide OUTPUT or use --o/--output.");
- std::process::exit(2);
- };
+ let input = input.or(input_pos).unwrap();
+ let output = output.or(output_pos).unwrap();
- if vacs_data_importer::vatglasses::parse(&input, &output, overwrite, merge).is_err() {
+ if vacs_data_importer::vatglasses::parse(&input, &output, overwrite, merge, format)
+ .is_err()
+ {
std::process::exit(1);
}
}
@@ -53,20 +46,17 @@ pub fn main() {
prefixes,
overwrite,
merge,
+ format,
},
} => {
- let Some(input) = input.or(input_pos) else {
- log::error("Missing input path. Either provide INPUT or use --i/--input.");
- std::process::exit(2);
- };
- let Some(output) = output.or(output_pos) else {
- log::error("Missing output path. Either provide OUTPUT or use --o/--output.");
- std::process::exit(2);
- };
+ let input = input.or(input_pos).unwrap();
+ let output = output.or(output_pos).unwrap();
let prefixes = prefixes.unwrap_or_default();
- if vacs_data_importer::euroscope::parse(&input, &output, &prefixes, overwrite, merge)
- .is_err()
+ if vacs_data_importer::euroscope::parse(
+ &input, &output, &prefixes, overwrite, merge, format,
+ )
+ .is_err()
{
std::process::exit(1);
}
diff --git a/tools/deny.toml b/tools/deny.toml
index 4eb8a1a7..af451cfc 100644
--- a/tools/deny.toml
+++ b/tools/deny.toml
@@ -4,8 +4,12 @@ all-features = true
[licenses]
allow = ["MIT", "Apache-2.0"]
exceptions = [
- { allow = ["BSD-3-Clause"], crate = "encoding_rs" },
- { allow = ["Unicode-3.0"], crate = "unicode-ident" },
+ { allow = [
+ "BSD-3-Clause",
+ ], crate = "encoding_rs" },
+ { allow = [
+ "Unicode-3.0",
+ ], crate = "unicode-ident" },
]
[licenses.private]
@@ -19,6 +23,4 @@ allow-wildcard-paths = true
[sources]
unknown-registry = "deny"
unknown-git = "deny"
-allow-git = [
- "https://github.com/MorpheusXAUT/vacs"
-]
+allow-git = ["https://github.com/MorpheusXAUT/vacs"]
diff --git a/tools/importer/src/euroscope.rs b/tools/importer/src/euroscope.rs
index fc208214..8356ed36 100644
--- a/tools/importer/src/euroscope.rs
+++ b/tools/importer/src/euroscope.rs
@@ -15,6 +15,7 @@ pub fn parse(
prefixes: &[String],
overwrite: bool,
merge: bool,
+ format: crate::OutputFormat,
) -> Result<(), Box> {
log::info(format_args!(
"Parsing EuroScope sectorfile data from {input:?} to {output:?}"
@@ -23,8 +24,14 @@ pub fn parse(
crate::check_input_exists(input)?;
crate::ensure_output_directory(output)?;
- let output_positions =
- crate::check_output_file(output, "positions.toml", "Positions", overwrite, merge)?;
+ let ext = format.ext();
+ let output_positions = crate::check_output_file(
+ output,
+ &format!("positions.{ext}"),
+ "Positions",
+ overwrite,
+ merge,
+ )?;
let file = match std::fs::File::open(input) {
Ok(f) => f,
@@ -104,13 +111,14 @@ pub fn parse(
.then_with(|| a.id.cmp(&b.id))
});
- let serialized_positions = match toml::to_string_pretty(&PositionConfigFile { positions }) {
- Ok(s) => s,
- Err(err) => {
- log::error(format_args!("Failed to serialize positions: {err:?}"));
- return Err(err.into());
- }
- };
+ let serialized_positions =
+ match crate::format::serialize(&PositionConfigFile { positions }, format) {
+ Ok(s) => s,
+ Err(err) => {
+ log::error(format_args!("Failed to serialize positions: {err:?}"));
+ return Err(err);
+ }
+ };
crate::write_output_file(&output_positions, &serialized_positions, "Positions")?;
diff --git a/tools/importer/src/format.rs b/tools/importer/src/format.rs
new file mode 100644
index 00000000..70d4ba74
--- /dev/null
+++ b/tools/importer/src/format.rs
@@ -0,0 +1,62 @@
+use serde::Serialize;
+use std::fmt;
+use std::str::FromStr;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum OutputFormat {
+ #[default]
+ Toml,
+ Json,
+}
+
+impl OutputFormat {
+ #[must_use]
+ pub const fn variants() -> &'static [&'static str] {
+ &["toml", "json"]
+ }
+}
+
+impl fmt::Display for OutputFormat {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ OutputFormat::Toml => write!(f, "toml"),
+ OutputFormat::Json => write!(f, "json"),
+ }
+ }
+}
+
+impl FromStr for OutputFormat {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result {
+ match s.to_ascii_lowercase().as_str() {
+ "toml" => Ok(OutputFormat::Toml),
+ "json" => Ok(OutputFormat::Json),
+ _ => Err(format!(
+ "Invalid output format: {}. Supported formats: {}",
+ s,
+ Self::variants().join(", ")
+ )),
+ }
+ }
+}
+
+impl OutputFormat {
+ #[must_use]
+ pub const fn ext(&self) -> &'static str {
+ match self {
+ OutputFormat::Toml => "toml",
+ OutputFormat::Json => "json",
+ }
+ }
+}
+
+pub fn serialize(
+ value: &T,
+ format: OutputFormat,
+) -> Result> {
+ match format {
+ OutputFormat::Toml => Ok(toml::to_string_pretty(value)?),
+ OutputFormat::Json => Ok(serde_json::to_string_pretty(value)?),
+ }
+}
diff --git a/tools/importer/src/lib.rs b/tools/importer/src/lib.rs
index ea7ad78e..f493aaee 100644
--- a/tools/importer/src/lib.rs
+++ b/tools/importer/src/lib.rs
@@ -1,6 +1,9 @@
pub mod euroscope;
+pub mod format;
pub mod vatglasses;
+pub use format::OutputFormat;
+
use std::path::{Path, PathBuf};
use vacs_data_diagnostics::log;
diff --git a/tools/importer/src/vatglasses.rs b/tools/importer/src/vatglasses.rs
index f40a9d24..f63ea7bc 100644
--- a/tools/importer/src/vatglasses.rs
+++ b/tools/importer/src/vatglasses.rs
@@ -13,6 +13,7 @@ pub fn parse(
output: &PathBuf,
overwrite: bool,
merge: bool,
+ format: crate::OutputFormat,
) -> Result<(), Box> {
log::info(format_args!(
"Parsing VATglasses data from {input:?} to {output:?}"
@@ -21,11 +22,22 @@ pub fn parse(
crate::check_input_exists(input)?;
crate::ensure_output_directory(output)?;
- let output_stations =
- crate::check_output_file(output, "stations.toml", "Stations", overwrite, merge)?;
-
- let output_positions =
- crate::check_output_file(output, "positions.toml", "Positions", overwrite, merge)?;
+ let ext = format.ext();
+ let output_stations = crate::check_output_file(
+ output,
+ &format!("stations.{ext}"),
+ "Stations",
+ overwrite,
+ merge,
+ )?;
+
+ let output_positions = crate::check_output_file(
+ output,
+ &format!("positions.{ext}"),
+ "Positions",
+ overwrite,
+ merge,
+ )?;
let file = match std::fs::File::open(input) {
Ok(f) => f,
@@ -82,11 +94,11 @@ pub fn parse(
stations.stations.sort_by(|a, b| a.id.cmp(&b.id));
- let serialized_stations = match toml::to_string_pretty(&stations) {
+ let serialized_stations = match crate::format::serialize(&stations, format) {
Ok(s) => s,
Err(err) => {
log::error(format_args!("Failed to serialize stations: {err:?}"));
- return Err(err.into());
+ return Err(err);
}
};
@@ -132,11 +144,11 @@ pub fn parse(
.then_with(|| a.id.cmp(&b.id))
});
- let serialized_positions = match toml::to_string_pretty(&positions) {
+ let serialized_positions = match crate::format::serialize(&positions, format) {
Ok(s) => s,
Err(err) => {
log::error(format_args!("Failed to serialize positions: {err:?}"));
- return Err(err.into());
+ return Err(err);
}
};