ATC Location Lookup System - Design Document¶
Executive Summary¶
Overview¶
The ATC Location Lookup System automatically identifies nearby aerodromes with Air Traffic Control (ATC) services within 50km of drone flight locations. It provides pilots with essential contact information (radio frequencies and phone numbers) for coordinating with ATC when operating near controlled airspace or aerodromes. The system uses a hybrid discovery approach, combining OpenStreetMap for aerodrome discovery with NATS Aurora AIP scraping for detailed ATC data.
Business Value¶
Safety Enhancement: - Identifies aerodromes requiring ATC coordination before flight operations - Provides direct contact information (frequencies/phone numbers) for ATC communication - Warns about military aerodromes where operations may be restricted - Reduces risk of unauthorized airspace incursions near aerodromes
User Experience: - Automatic discovery - No manual aerodrome lookup required - Comprehensive coverage - Discovers all UK aerodromes (civilian + military) via OSM - Rich context - Displays aerodrome names, distances, ATC frequencies, phone numbers - Graceful degradation - Shows basic info even when detailed data unavailable - Visual warnings - Clear indicators for placeholder data and military aerodromes
Operational Efficiency: - Saves ~10-15 minutes per project (manual ATC contact lookup) - Provides auditable ATC contact records for compliance documentation - Automatic data caching reduces repeated scraping overhead
Key Capabilities¶
- Hybrid Aerodrome Discovery
- OpenStreetMap Overpass API queries for aerodrome locations
- NATS AIP scraping for detailed ATC data (frequencies, phone numbers)
- Smart caching with 60-day staleness detection
-
Fallback to placeholder records when NATS data unavailable
-
Military Aerodrome Detection
- Automatic detection via OSM tags (
military=airfield, RAF prefix) - Special handling for military bases (not in civilian AIP)
-
Clear [Military] designation in UI
-
Admin Management
- View all cached aerodromes with data quality scores
- Manual refresh for individual aerodromes
- Bulk refresh all aerodromes
- Add new aerodromes by ICAO code
-
Data completeness indicators (Placeholder, Military badges)
-
Data Quality Tracking
- Quality scores (20-100) based on data completeness
- Placeholder warnings when NATS scraping fails
- Staleness alerts for data >60 days old
Architecture Overview¶
High-Level System Diagram¶
┌─────────────────────────────────────────────────────────────────┐
│ USER INTERACTION LAYER │
├─────────────────────────────────────────────────────────────────┤
│ Project Creation Wizard │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Location │→ │ Viability│→ │ Review │→ │ Submit │ │
│ │ Select │ │ Display │ │ Warnings │ │ Create │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ↓ ↓ ↓ ↓ │
│ [Discover [Display [Show [Link │
│ Aerodromes] ATC Info] Placeholders] Aerodromes] │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ APPLICATION LOGIC LAYER │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ATCAPI (app/utils/atc_api.py) │
│ ┌────────────────────────────────────────────────┐ │
│ │ find_nearby_aerodromes(lat, lon, radius=50) │ │
│ │ 1. Query Overpass API for aerodromes │ │
│ │ 2. Extract ICAO codes from OSM │ │
│ │ 3. Check database cache │ │
│ │ 4. Scrape NATS if missing/stale │ │
│ │ 5. Store placeholder if scraping fails │ │
│ │ 6. Return sorted by distance │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ NATSAIPScraper │
│ ┌────────────────────────────────────────────────┐ │
│ │ • Construct NATS AIP URL (AIRAC dated) │ │
│ │ • Fetch HTML page │ │
│ │ • Parse coordinates (DMS to decimal) │ │
│ │ • Extract ATC frequencies │ │
│ │ • Extract phone numbers │ │
│ │ • Calculate data quality score │ │
│ └────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ DATA STORAGE LAYER │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Database (MariaDB) - PlaceOfInterest Table │
│ ┌────────────────────────────────────────────────┐ │
│ │ • osm_id: "aerodrome/{ICAO}" │ │
│ │ • place_type: "aerodrome" │ │
│ │ • name: "Edinburgh Airport" │ │
│ │ • latitude, longitude (decimal degrees) │ │
│ │ • phone: Main contact number │ │
│ │ • data_quality_score: 20-100 │ │
│ │ • last_updated: Timestamp │ │
│ │ • extra_data: JSON │ │
│ │ { │ │
│ │ "icao_code": "EGPH", │ │
│ │ "frequencies": {...}, │ │
│ │ "phone_numbers": {...}, │ │
│ │ "is_military": false, │ │
│ │ "is_placeholder": false, │ │
│ │ "airac_cycle": "2026-01-22-AIRAC" │ │
│ │ } │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ ProjectPlace Junction Table │
│ ┌────────────────────────────────────────────────┐ │
│ │ • Links aerodromes to projects │ │
│ │ • All aerodromes within 50km linked │ │
│ │ • No relevance filtering (all are relevant) │ │
│ └────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ EXTERNAL DATA SOURCES │
├─────────────────────────────────────────────────────────────────┤
│ │
│ OpenStreetMap Overpass API │
│ ┌────────────────────────────────────────────────┐ │
│ │ • Discover aerodromes by location │ │
│ │ • Extract ICAO codes │ │
│ │ • Detect military tags │ │
│ │ • Get basic coordinates/names │ │
│ └────────────────────────────────────────────────┘ │
│ ↓ │
│ NATS Aurora AIP (HTML scraping) │
│ ┌────────────────────────────────────────────────┐ │
│ │ • Civilian aerodrome ATC data │ │
│ │ • ATC frequencies and callsigns │ │
│ │ • Contact phone numbers │ │
│ │ • Operating hours │ │
│ │ • Military aerodromes → 404 (not published) │ │
│ └────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Data Flow Sequence¶
USER APPLICATION OSM API NATS AIP DATABASE
│ │ │ │ │
│ Create Project │ │ │ │
│ (Location Selected) │ │ │ │
├──────────────────────>│ │ │ │
│ │ │ │ │
│ │ Query Overpass │ │ │
│ │ (50km radius) │ │ │
│ ├──────────────────>│ │ │
│ │<──────────────────┤ │ │
│ │ [EGPH, EGQS] │ │ │
│ │ + OSM tags │ │ │
│ │ │ │ │
│ │ Check Cache │ │ │
│ │ (EGPH) │ │ │
│ ├──────────────────────────────────────────────>│
│ │<──────────────────────────────────────────────┤
│ │ EGPH: Cached (55d old, stale) │
│ │ │ │ │
│ │ Refresh EGPH │ │ │
│ │ from NATS │ │ │
│ ├──────────────────────────────────>│ │
│ │<──────────────────────────────────┤ │
│ │ 200 OK + HTML │ │ │
│ │ │ │ │
│ │ Parse + Update │ │ │
│ ├──────────────────────────────────────────────>│
│ │ │ │ │
│ │ Check Cache │ │ │
│ │ (EGQS) │ │ │
│ ├──────────────────────────────────────────────>│
│ │<──────────────────────────────────────────────┤
│ │ EGQS: Not Found │ │ │
│ │ │ │ │
│ │ Fetch EGQS │ │ │
│ │ from NATS │ │ │
│ ├──────────────────────────────────>│ │
│ │<──────────────────────────────────┤ │
│ │ 404 Not Found │ │ │
│ │ (Military base) │ │ │
│ │ │ │ │
│ │ Store Placeholder│ │ │
│ │ (OSM data only) │ │ │
│ ├──────────────────────────────────────────────>│
│ │ │ │ │
│ Display Results │ │ │ │
│ • EGPH: Full data │ │ │ │
│ • EGQS: Placeholder │ │ │ │
│ [Military] warning │ │ │ │
│<──────────────────────┤ │ │ │
│ │ │ │ │
Component Details¶
1. ATCAPI Class (app/utils/atc_api.py)¶
Core Methods:
class ATCAPI:
"""Client for UK Aerodrome ATC Information"""
DEFAULT_SEARCH_RADIUS_KM = 50
MAX_RESULTS = 5
STALENESS_THRESHOLD_DAYS = 60
@classmethod
def find_nearby_aerodromes(cls, latitude, longitude, radius_km=None):
"""
Hybrid discovery approach:
1. Query Overpass API for aerodromes
2. For each: check cache, refresh if stale, scrape if new
3. Store placeholder if NATS fails
4. Return sorted by distance
"""
@classmethod
def _discover_aerodromes_via_overpass(cls, latitude, longitude, radius_km):
"""Query OpenStreetMap for aeroway=aerodrome features"""
@classmethod
def update_aerodrome_data(cls, icao_code, airac_date=None):
"""Scrape NATS AIP and update database"""
@classmethod
def store_placeholder_aerodrome(cls, osm_data):
"""Store basic OSM data when NATS scraping fails"""
@classmethod
def get_aerodrome_by_icao(cls, icao_code):
"""Retrieve cached aerodrome from database"""
@classmethod
def link_aerodromes_to_project(cls, project_id, aerodromes_data):
"""Create ProjectPlace links for all nearby aerodromes"""
Key Configuration: - Search radius: 50km (vs 10km for general places of interest) - Max results: 5 closest aerodromes - Staleness: 60 days before refresh required - Rate limiting: 1 second delay between NATS requests
2. NATSAIPScraper Class¶
Core Methods:
class NATSAIPScraper:
"""Scraper for NATS Aurora AIP HTML pages"""
NATS_AIP_BASE_URL = "https://www.aurora.nats.co.uk/htmlAIP/Publications/{airac_date}/html/eAIP/EG-AD-2.{icao}-en-GB.html"
@classmethod
def scrape_aerodrome_page(cls, icao_code, airac_date):
"""
Fetch and parse NATS AIP HTML page
Returns:
{
'success': True/False,
'data': {
'name': 'Edinburgh Airport',
'latitude': 55.9050,
'longitude': -3.5017,
'frequencies': {'tower': ['118.705']},
'phone_numbers': {'main': '03300-271262'}
},
'error': None
}
"""
@classmethod
def _extract_coordinates(cls, soup):
"""Extract coordinates with flexible regex (handles hidden HTML)"""
@classmethod
def _extract_aerodrome_name(cls, soup, icao_code):
"""Extract name from page title/headings (4 methods + fallback)"""
@classmethod
def _extract_frequencies(cls, soup):
"""Parse ATC frequencies from AD 2.18 tables"""
@classmethod
def _extract_phone_numbers(cls, soup):
"""Extract phone numbers via regex patterns"""
@classmethod
def get_current_airac_date(cls):
"""Calculate current AIRAC cycle date (28-day cycles)"""
3. Route Integration¶
routes_new_project.py - Project creation workflow integration:
# POST /new-project/location
# After location selection, before viability display
try:
aerodromes_data = ATCAPI.find_nearby_aerodromes(
latitude, longitude, radius_km=50
)
session['nearby_aerodromes'] = aerodromes_data
except Exception as e:
logger.error(f"Aerodrome lookup error: {e}")
session['nearby_aerodromes'] = {
'success': False,
'error': str(e),
'aerodromes': []
}
# GET /new-project/viability
# Display aerodrome information
nearby_aerodromes = session.get('nearby_aerodromes')
return render_template('new_project_viability.html',
nearby_aerodromes=nearby_aerodromes)
# POST /new-project/viability
# Link aerodromes to project after creation
aerodromes_data = session.get('nearby_aerodromes')
if aerodromes_data and aerodromes_data.get('success'):
ATCAPI.link_aerodromes_to_project(new_project.id, aerodromes_data)
routes_admin.py - Admin management routes:
# GET /admin/aerodromes
# List all cached aerodromes with quality indicators
# POST /admin/aerodromes/refresh/<icao_code>
# Manual refresh single aerodrome
# POST /admin/aerodromes/refresh-all
# Bulk refresh all aerodromes (rate-limited)
# POST /admin/aerodromes/add
# Add new aerodrome by ICAO code
# POST /admin/aerodromes/delete/<icao_code>
# Delete aerodrome from cache
Data Models¶
PlaceOfInterest Table¶
Aerodrome-Specific Fields:
CREATE TABLE place_of_interest (
id INT PRIMARY KEY AUTO_INCREMENT,
osm_id VARCHAR(255) UNIQUE, -- "aerodrome/EGPH"
osm_type VARCHAR(50), -- "aerodrome"
place_type VARCHAR(50), -- "aerodrome"
place_category VARCHAR(50), -- "aviation"
name VARCHAR(255), -- "Edinburgh Airport"
latitude DECIMAL(10, 8), -- 55.90500000
longitude DECIMAL(11, 8), -- -3.50170000
phone VARCHAR(255), -- "03300-271262"
extra_data JSON, -- ATC details (see below)
last_updated DATETIME, -- Last NATS scrape
data_quality_score INT, -- 20-100
-- ... other standard fields
);
extra_data JSON Structure:
{
"icao_code": "EGPH",
"aerodrome_name": "Edinburgh Airport",
"atc_callsign": "",
"frequencies": {
"tower": ["118.705"],
"approach": ["121.200"],
"ground": ["121.750"]
},
"phone_numbers": {
"main": "03300-271262"
},
"airac_cycle": "2026-01-22-AIRAC",
"data_source": "nats_aip",
"is_military": false,
"is_placeholder": false,
"placeholder_reason": null,
"osm_tags": {},
"last_scraped": "2026-01-13T10:00:00Z"
}
Placeholder Record Example (Military Aerodrome):
{
"icao_code": "EGQS",
"aerodrome_name": "RAF Lossiemouth [Military]",
"atc_callsign": "",
"frequencies": {},
"phone_numbers": {},
"airac_cycle": null,
"data_source": "osm_only",
"is_military": true,
"is_placeholder": true,
"placeholder_reason": "NATS AIP scraping failed - data from OSM only",
"osm_tags": {
"aeroway": "aerodrome",
"military": "airfield",
"landuse": "military",
"name": "RAF Lossiemouth",
"contact:email": "LOS-A3Ops@mod.gov.uk"
},
"last_scraped": "2026-01-13T07:31:00Z"
}
ProjectPlace Junction Table¶
Purpose: Link aerodromes to projects
CREATE TABLE project_place (
id INT PRIMARY KEY AUTO_INCREMENT,
project_id INT,
place_id INT,
FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE,
FOREIGN KEY (place_id) REFERENCES place_of_interest(id) ON DELETE CASCADE
);
Linking Strategy: All aerodromes within 50km are linked (no relevance filtering - all aerodromes are potentially relevant for ATC coordination).
User Interface¶
Project Viability Template¶
Display Sections (new_project_viability.html):
-
Success Case (aerodromes found):
<div class="card"> <h5>Nearby Aerodromes with ATC</h5> <p>2 aerodrome(s) with ATC services found within 50km</p> <!-- For each aerodrome --> <div class="aerodrome-card"> <span class="badge">EGPH</span> Edinburgh Airport <span class="distance">8.5 km away</span> <!-- Frequencies --> <ul> <li>Tower: 118.705 MHz</li> <li>Approach: 121.200 MHz</li> </ul> <!-- Phone --> <p>Contact: <a href="tel:03300271262">03300-271262</a></p> <!-- View AIP button --> <a href="https://www.aurora.nats.co.uk/.../EGPH..." target="_blank"> View AIP </a> </div> </div> -
Placeholder Warning (NATS scraping failed):
<div class="alert alert-danger"> <strong>Limited Data:</strong> NATS AIP scraping failed. Only basic information from OpenStreetMap is available. <small>This may be a military aerodrome. Civilian AIP data not available.</small> </div> -
Military Indicator (detected from OSM):
<div class="alert alert-info"> <strong>Military Aerodrome:</strong> Contact ATC before operations in this area. </div> -
Stale Data Warning (>60 days old):
<div class="alert alert-warning"> Data is 75 days old. Verify with current AIP. </div>
Admin Panel¶
Aerodrome List (admin_panel/aerodromes.html):
Features: - DataTables with search/sort - Visual indicators: - Red row: Placeholder data - Yellow row: Stale data (>60 days) - Badges: [Placeholder], [Military] - Actions: View, Refresh, Delete - Bulk "Refresh All" button - "Add Aerodrome" form (ICAO input)
Quality Score Display:
<div class="progress">
<div class="progress-bar bg-success" style="width: 85%"></div>
</div>
<span>85/100</span>
Colour coding: - Green (≥75): Full data - Yellow (50-74): Partial data - Red (<50): Placeholder/minimal data
Implementation Decisions¶
Why 50km Radius?¶
Rationale: - UK drone operations limited to 400ft AGL - Controlled airspace (CTR/TMA) typically extends 10-20km from aerodromes - 50km provides awareness of aerodromes in adjacent controlled zones - Balance between comprehensiveness and performance
Comparison: - General places of interest: 10km (immediate vicinity) - Aerodromes: 50km (broader awareness)
Why Hybrid Discovery?¶
OSM First: - Comprehensive coverage (civilian + military) - Fast spatial queries - Includes military aerodromes not in civilian AIP - Provides fallback coordinates
NATS Enrichment: - Authoritative ATC data - Current AIRAC cycle frequencies - Official contact numbers - Regulatory compliance
Benefits: - No missed aerodromes (OSM discovers all) - Rich data when available (NATS provides details) - Graceful degradation (placeholder when NATS fails)
Why Database Caching?¶
Alternative Approaches Considered: 1. Real-time scraping only (slow, unreliable) 2. File-based cache (no relational queries) 3. No caching (wasteful, poor UX)
Chosen Approach: Database Cache: - Fast queries by ICAO code - Reuse existing PlaceOfInterest infrastructure - Supports spatial queries (distance calculations) - Admin interface can easily list/manage - Automatic staleness tracking via timestamps
Cache Refresh Strategy: - Automatic: On first lookup if missing or stale - Manual: Admin "Refresh" button - Bulk: Admin "Refresh All" (rate-limited 1/sec)
Why 60-Day Staleness?¶
Rationale: - AIRAC cycle: 28 days (official update frequency) - Grace period: Allow 2 cycles (56 days) before flagging stale - Practicality: ATC frequencies rarely change mid-cycle - User experience: Avoid excessive warnings
Staleness Handling: - Still usable after 60 days (better than no data) - Visual warning displayed to user - Auto-refresh attempted on next lookup - Admin can force refresh
Data Quality Scoring¶
Score Components¶
Base Score (50 points always): - Name present: +25 points - Coordinates present: +25 points (from OSM)
Additional Points (up to +50): - Has phone: +15 points - Has frequencies: +30 points (10 per type, max 30) - Has ATC callsign: +5 points
Score Ranges: - 85-100: Full data (all fields present) - 55-84: Good data (some ATC info) - 40-54: Basic data (name + coords only) - 20-39: Placeholder (OSM only, likely military)
Examples:
EGPH (Edinburgh):
Name ✓ (25) + Coords ✓ (25) + Phone ✓ (15) +
Frequencies ✓ (20) = 85/100
EGQS (RAF Lossiemouth - Placeholder):
Name ✓ (25) + Coords ✓ (25) = 50/100
But marked as placeholder → Score = 20/100
Error Handling¶
Network Failures¶
| Scenario | Response | User Impact |
|---|---|---|
| OSM timeout | Skip aerodrome discovery, show "No data available" | Project creation continues |
| NATS timeout (30s) | Store placeholder with OSM data | Show placeholder warning |
| NATS 404 | Store placeholder (military/non-existent) | Show placeholder warning with military indicator |
| NATS 500 | Retry once, then skip | Log error, aerodrome not shown |
Data Parsing Failures¶
| Scenario | Response | Fallback |
|---|---|---|
| Coordinates not found | Try fallback dictionary | Use known coordinates for 15 common aerodromes |
| Name extraction fails | Try fallback dictionary | Use "{ICAO} Airport" format |
| No frequencies found | Continue with empty dict | Display "No frequency data available" |
| Invalid HTML structure | Log warning, skip aerodrome | Don't break entire lookup |
User Experience Degradation Levels¶
Level 1: Full Success - All aerodromes discovered - All NATS data scraped successfully - No warnings displayed
Level 2: Partial Success - Some aerodromes have full data - Some have placeholders (military) - Warning badges displayed - Project creation unaffected
Level 3: Graceful Failure - OSM discovery succeeds but all NATS scraping fails - All aerodromes shown as placeholders - Major warning displayed - Project creation still succeeds
Level 4: Complete Failure - OSM discovery fails - "No aerodrome data available" message - Project creation succeeds - Feature gracefully absent
Security Considerations¶
Input Validation¶
ICAO Code Validation:
def _validate_icao_code(icao_code):
"""Validate 4-character UK ICAO code"""
if not icao_code or len(icao_code) != 4:
return False
if not icao_code.startswith('EG'):
return False
if not icao_code.isalpha():
return False
return True
Coordinate Validation:
# Validate reasonable UK bounds
if not (-10 <= longitude <= 2): # UK longitude range
raise ValueError("Invalid longitude")
if not (49 <= latitude <= 61): # UK latitude range
raise ValueError("Invalid latitude")
HTTP Request Safety¶
Timeouts: - OSM Overpass API: 15 seconds - NATS AIP: 30 seconds
Rate Limiting: - 1 second delay between NATS requests (admin bulk refresh) - Prevent DDOS of NATS servers
SSL Verification: - All HTTPS requests verify certificates - No insecure connections allowed
Data Sanitization¶
HTML Parsing: - Use BeautifulSoup for safe HTML parsing - No eval() or exec() of scraped content - Strip potentially dangerous tags
Database Storage: - JSON validation before storage - Parameterized queries (SQLAlchemy ORM) - No raw SQL string concatenation
Testing Strategy¶
Unit Tests¶
Test Coverage (to be implemented):
# test_atc_api.py
def test_calculate_distance():
"""Test Haversine distance calculation"""
# EGPH to Edinburgh Napier ≈ 8km
distance = ATCAPI._calculate_distance(
55.9533, -3.1883, # Napier
55.9050, -3.5017 # EGPH
)
assert 7000 < distance < 9000 # meters
def test_validate_icao_code():
"""Test ICAO code validation"""
assert ATCAPI._validate_icao_code('EGPH') == True
assert ATCAPI._validate_icao_code('EGQS') == True
assert ATCAPI._validate_icao_code('KJFK') == False # US code
assert ATCAPI._validate_icao_code('EGP') == False # Too short
def test_dms_to_decimal():
"""Test coordinate conversion"""
# 57°28'50"N = 57.480556
lat = NATSAIPScraper._parse_coordinate_dms('572850', 'N')
assert abs(lat - 57.480556) < 0.0001
Integration Tests¶
End-to-End Workflow Tests:
# test_atc_integration.py
def test_project_creation_with_aerodromes():
"""Test full project creation with aerodrome lookup"""
# Create project at Edinburgh Napier
response = client.post('/new-project/location', data={
'latitude': 55.9533,
'longitude': -3.1883,
# ... other fields
})
# Verify EGPH discovered and linked
project = Project.query.filter_by(title='Test Project').first()
aerodromes = project.places_of_interest.filter_by(
place_type='aerodrome'
).all()
assert len(aerodromes) >= 1
assert any(a.extra_data.get('icao_code') == 'EGPH' for a in aerodromes)
def test_placeholder_creation_for_military():
"""Test placeholder handling for military aerodromes"""
# Near RAF Lossiemouth
result = ATCAPI.find_nearby_aerodromes(57.7172, -3.3389, radius_km=20)
egqs = next((a for a in result['aerodromes'] if a['icao_code'] == 'EGQS'), None)
assert egqs is not None
assert egqs['is_military'] == True
assert egqs['is_placeholder'] == True
assert 'RAF Lossiemouth' in egqs['name']
Manual Test Locations¶
Test Case 1: Edinburgh Area - Location: 55.9533, -3.1883 (Edinburgh Napier) - Expected: EGPH (Edinburgh Airport) ~8km - Data Type: Full (NATS successful) - Verify: Frequencies, phone, quality score 85+
Test Case 2: Outer Hebrides - Location: 57.4669, -7.3704 (Benbecula) - Expected: EGPL (1.6km), EGPR (49.6km) - Data Type: Full (both civilian operations) - Verify: Beach runway note for Barra
Test Case 3: Military Base - Location: 57.7172, -3.3389 (RAF Lossiemouth) - Expected: EGQS (1.2km) - Data Type: Placeholder (military) - Verify: [Military] badge, placeholder warning, quality score 20
Test Case 4: Remote Location - Location: 57.5000, -5.0000 (Scottish Highlands) - Expected: No aerodromes within 50km - Verify: "No aerodromes within 50km" message
Admin Operations¶
Seeding Scottish Aerodromes¶
Script: scripts/seed_aerodromes.py
Usage:
# Seed 10 Scottish aerodromes
python scripts/seed_aerodromes.py
# Verify seeding
python scripts/seed_aerodromes.py --verify
Default Aerodromes: 1. EGPH - Edinburgh Airport 2. EGPF - Glasgow Airport 3. EGPK - Glasgow Prestwick Airport 4. EGPN - Dundee Airport 5. EGPE - Inverness Airport 6. EGPA - Kirkwall Airport 7. EGPO - Stornoway Airport 8. EGPB - Sumburgh Airport 9. EGEC - Campbeltown Airport 10. EGPI - Islay Airport
Maintenance Tasks¶
Manual Refresh:
Admin → Aerodromes → [Select aerodrome] → Refresh
Bulk Refresh:
Admin → Aerodromes → Refresh All (rate-limited 1/sec)
Add New Aerodrome:
Admin → Aerodromes → Add Aerodrome → Enter ICAO code
Monitor Data Quality:
Admin → Aerodromes → Sort by Quality Score → Address low scores
Future Enhancements¶
Phase 2: UK-Wide Coverage¶
Objective: Expand beyond Scotland to full UK
Tasks: - Add England, Wales, Northern Ireland aerodromes to seed script (~40-50 more) - Test coverage with projects across UK regions - Verify NATS AIP availability for all civilian aerodromes
Phase 3: European Coverage¶
Objective: Support drone operations in European countries
Challenges: - Each country has different AIP systems (not all use NATS Aurora) - ICAO codes vary by country (FR, DE, NL, etc.) - Language considerations (multi-language AIP pages) - May require country-specific scrapers
Alternatives: - Eurocontrol AIS Database (if API available) - ICAO Data+ (commercial subscription)
Phase 4: Real-Time NOTAM Integration¶
Objective: Show active NOTAMs per aerodrome
Benefits: - Alert to temporary closures - Warn about special procedures - Display active runway closures
Integration Point: Cross-reference aerodrome ICAO with NOTAM API
Phase 5: Frequency Validation¶
Objective: Verify frequencies against Ofcom database
Rationale: - Catch data entry errors in NATS AIP - Identify expired frequency allocations - Provide confidence indicators
References¶
Official Documentation¶
- NATS Aurora AIP: aurora.nats.co.uk
- OpenStreetMap Wiki: Aeroway Tag
- AIRAC Cycles: EUROCONTROL AIS
- UK Drone Regulations: CAA CAP 722
Technical Resources¶
- Overpass API: overpass-api.de
- BeautifulSoup Documentation: beautiful-soup-4.readthedocs.io
- Shapely Documentation: shapely.readthedocs.io
Version History¶
Version 1.0 - 2026-01-13 - Initial design for ATC location lookup system - Hybrid OpenStreetMap + NATS AIP approach - Military aerodrome detection and placeholder handling - Data quality scoring and staleness tracking - Admin management interface - Scottish aerodromes seeding