Airport lookup

This commit is contained in:
James Pattinson
2025-10-23 16:59:37 +00:00
parent b2f60322a1
commit 91b734426c
3 changed files with 362 additions and 7 deletions

View File

@@ -1,5 +1,5 @@
from fastapi import APIRouter
from app.api.endpoints import auth, pprs, public, aircraft
from app.api.endpoints import auth, pprs, public, aircraft, airport
api_router = APIRouter()
@@ -7,3 +7,4 @@ api_router.include_router(auth.router, prefix="/auth", tags=["authentication"])
api_router.include_router(pprs.router, prefix="/pprs", tags=["pprs"])
api_router.include_router(public.router, prefix="/public", tags=["public"])
api_router.include_router(aircraft.router, prefix="/aircraft", tags=["aircraft"])
api_router.include_router(airport.router, prefix="/airport", tags=["airport"])

View File

@@ -0,0 +1,71 @@
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from app.api.deps import get_db, get_current_active_user
from app.models.ppr import Airport
from app.schemas.ppr import Airport as AirportSchema
from app.models.ppr import User
router = APIRouter()
@router.get("/lookup/{code_or_name}", response_model=List[AirportSchema])
async def lookup_airport_by_code_or_name(
code_or_name: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""
Lookup airport by ICAO code or name.
If input is 4 characters and all uppercase letters, treat as ICAO code.
Otherwise, search by name.
"""
clean_input = code_or_name.strip().upper()
if len(clean_input) < 2:
return []
# Check if input looks like an ICAO code (4 letters)
if len(clean_input) == 4 and clean_input.isalpha():
# Exact ICAO match first
airport = db.query(Airport).filter(Airport.icao == clean_input).first()
if airport:
return [airport]
# Then search ICAO codes that start with input
airports = db.query(Airport).filter(
Airport.icao.like(clean_input + "%")
).limit(5).all()
return airports
else:
# Search by name (case-insensitive partial match)
airports = db.query(Airport).filter(
Airport.name.ilike("%" + code_or_name + "%")
).limit(10).all()
return airports
@router.get("/search", response_model=List[AirportSchema])
async def search_airports(
q: Optional[str] = Query(None, description="Search query for ICAO code, IATA code, or airport name"),
limit: int = Query(10, ge=1, le=100, description="Maximum number of results"),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""
Search airports by ICAO code, IATA code, or name.
"""
if not q or len(q.strip()) < 2:
return []
search_term = q.strip()
clean_search = search_term.upper()
# Search across ICAO, IATA, and name fields
airports = db.query(Airport).filter(
(Airport.icao.like("%" + clean_search + "%")) |
(Airport.iata.like("%" + clean_search + "%")) |
(Airport.name.ilike("%" + search_term + "%")) |
(Airport.city.ilike("%" + search_term + "%"))
).limit(limit).all()
return airports

View File

@@ -464,6 +464,78 @@
color: #007bff;
}
/* Airport Lookup Styles */
#arrival-airport-lookup-results, #departure-airport-lookup-results {
margin-top: 0.5rem;
padding: 0.5rem;
background-color: #f8f9fa;
border-radius: 4px;
font-size: 0.9rem;
min-height: 20px;
border: 1px solid #e9ecef;
}
.airport-match {
padding: 0.3rem;
background-color: #e8f5e8;
border: 1px solid #c3e6c3;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-weight: bold;
}
.airport-no-match {
color: #6c757d;
font-style: italic;
}
.airport-searching {
color: #007bff;
}
.airport-list {
max-height: 200px;
overflow-y: auto;
border: 1px solid #dee2e6;
border-radius: 4px;
background-color: white;
}
.airport-option {
padding: 0.5rem;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background-color 0.2s ease;
display: flex;
justify-content: space-between;
align-items: center;
}
.airport-option:hover {
background-color: #f8f9fa;
}
.airport-option:last-child {
border-bottom: none;
}
.airport-code {
font-family: 'Courier New', monospace;
font-weight: bold;
color: #495057;
}
.airport-name {
color: #6c757d;
font-size: 0.85rem;
}
.airport-location {
color: #868e96;
font-size: 0.8rem;
font-style: italic;
}
.notification {
position: fixed;
top: 20px;
@@ -652,7 +724,7 @@
</div>
<div class="form-group">
<label for="ac_call">Callsign</label>
<input type="text" id="ac_call" name="ac_call">
<input type="text" id="ac_call" name="ac_call" placeholder="If different from registration">
</div>
<div class="form-group">
<label for="captain">Captain *</label>
@@ -660,7 +732,8 @@
</div>
<div class="form-group">
<label for="in_from">Arriving From *</label>
<input type="text" id="in_from" name="in_from" required placeholder="ICAO Code">
<input type="text" id="in_from" name="in_from" required placeholder="ICAO Code or Airport Name" oninput="handleArrivalAirportLookup(this.value)">
<div id="arrival-airport-lookup-results"></div>
</div>
<div class="form-group">
<label for="eta">ETA (Local Time) *</label>
@@ -681,7 +754,8 @@
</div>
<div class="form-group">
<label for="out_to">Departing To</label>
<input type="text" id="out_to" name="out_to" placeholder="ICAO Code">
<input type="text" id="out_to" name="out_to" placeholder="ICAO Code or Airport Name" oninput="handleDepartureAirportLookup(this.value)">
<div id="departure-airport-lookup-results"></div>
</div>
<div class="form-group">
<label for="etd">ETD (Local Time)</label>
@@ -1189,7 +1263,7 @@
// Ensure the datetime string is treated as UTC
const utcDateStr = dateStr.includes('Z') ? dateStr : dateStr + 'Z';
const date = new Date(utcDateStr);
return date.toISOString().slice(11, 16) + 'Z';
return date.toISOString().slice(11, 16);
}
function formatDateTime(dateStr) {
@@ -1197,7 +1271,7 @@
// Ensure the datetime string is treated as UTC
const utcDateStr = dateStr.includes('Z') ? dateStr : dateStr + 'Z';
const date = new Date(utcDateStr);
return date.toISOString().slice(0, 10) + ' ' + date.toISOString().slice(11, 16) + 'Z';
return date.toISOString().slice(0, 10) + ' ' + date.toISOString().slice(11, 16);
}
// Modal functions
@@ -1233,6 +1307,8 @@
// Clear aircraft lookup results
clearAircraftLookup();
clearArrivalAirportLookup();
clearDepartureAirportLookup();
document.getElementById('pprModal').style.display = 'block';
@@ -1659,6 +1735,213 @@
function clearAircraftLookup() {
document.getElementById('aircraft-lookup-results').innerHTML = '';
}
function clearArrivalAirportLookup() {
document.getElementById('arrival-airport-lookup-results').innerHTML = '';
}
function clearDepartureAirportLookup() {
document.getElementById('departure-airport-lookup-results').innerHTML = '';
}
// Airport Lookup Functions
let arrivalAirportLookupTimeout;
let departureAirportLookupTimeout;
function handleArrivalAirportLookup(codeOrName) {
// Clear previous timeout
if (arrivalAirportLookupTimeout) {
clearTimeout(arrivalAirportLookupTimeout);
}
// Clear results if input is too short
if (codeOrName.length < 2) {
clearArrivalAirportLookup();
return;
}
// Show searching indicator
document.getElementById('arrival-airport-lookup-results').innerHTML =
'<div class="airport-searching">Searching...</div>';
// Debounce the search - wait 300ms after user stops typing
arrivalAirportLookupTimeout = setTimeout(() => {
performArrivalAirportLookup(codeOrName);
}, 300);
}
function handleDepartureAirportLookup(codeOrName) {
// Clear previous timeout
if (departureAirportLookupTimeout) {
clearTimeout(departureAirportLookupTimeout);
}
// Clear results if input is too short
if (codeOrName.length < 2) {
clearDepartureAirportLookup();
return;
}
// Show searching indicator
document.getElementById('departure-airport-lookup-results').innerHTML =
'<div class="airport-searching">Searching...</div>';
// Debounce the search - wait 300ms after user stops typing
departureAirportLookupTimeout = setTimeout(() => {
performDepartureAirportLookup(codeOrName);
}, 300);
}
async function performArrivalAirportLookup(codeOrName) {
try {
const cleanInput = codeOrName.trim();
if (cleanInput.length < 2) {
clearArrivalAirportLookup();
return;
}
// Call the airport lookup API
const response = await authenticatedFetch(`/api/v1/airport/lookup/${encodeURIComponent(cleanInput)}`);
if (!response.ok) {
throw new Error('Failed to fetch airport data');
}
const matches = await response.json();
displayArrivalAirportLookupResults(matches, cleanInput);
} catch (error) {
console.error('Arrival airport lookup error:', error);
document.getElementById('arrival-airport-lookup-results').innerHTML =
'<div class="airport-no-match">Lookup failed - will use as entered</div>';
}
}
async function performDepartureAirportLookup(codeOrName) {
try {
const cleanInput = codeOrName.trim();
if (cleanInput.length < 2) {
clearDepartureAirportLookup();
return;
}
// Call the airport lookup API
const response = await authenticatedFetch(`/api/v1/airport/lookup/${encodeURIComponent(cleanInput)}`);
if (!response.ok) {
throw new Error('Failed to fetch airport data');
}
const matches = await response.json();
displayDepartureAirportLookupResults(matches, cleanInput);
} catch (error) {
console.error('Departure airport lookup error:', error);
document.getElementById('departure-airport-lookup-results').innerHTML =
'<div class="airport-no-match">Lookup failed - will use as entered</div>';
}
}
function displayArrivalAirportLookupResults(matches, searchTerm) {
const resultsDiv = document.getElementById('arrival-airport-lookup-results');
if (matches.length === 0) {
resultsDiv.innerHTML = '<div class="airport-no-match">No matches found - will use as entered</div>';
} else if (matches.length === 1) {
// Unique match found - auto-populate with ICAO code
const airport = matches[0];
resultsDiv.innerHTML = `
<div class="airport-match">
${airport.name} (${airport.icao})
</div>
`;
// Auto-populate with ICAO code
document.getElementById('in_from').value = airport.icao;
} else {
// Multiple matches - show clickable list
const listHtml = matches.map(airport => `
<div class="airport-option" onclick="selectArrivalAirport('${airport.icao}')">
<div>
<div class="airport-code">${airport.icao}</div>
<div class="airport-name">${airport.name}</div>
${airport.city ? `<div class="airport-location">${airport.city}, ${airport.country}</div>` : ''}
</div>
</div>
`).join('');
resultsDiv.innerHTML = `
<div class="airport-no-match" style="margin-bottom: 0.5rem;">
Multiple matches found - select one:
</div>
<div class="airport-list">
${listHtml}
</div>
`;
}
}
function displayDepartureAirportLookupResults(matches, searchTerm) {
const resultsDiv = document.getElementById('departure-airport-lookup-results');
if (matches.length === 0) {
resultsDiv.innerHTML = '<div class="airport-no-match">No matches found - will use as entered</div>';
} else if (matches.length === 1) {
// Unique match found - auto-populate with ICAO code
const airport = matches[0];
resultsDiv.innerHTML = `
<div class="airport-match">
${airport.name} (${airport.icao})
</div>
`;
// Auto-populate with ICAO code
document.getElementById('out_to').value = airport.icao;
} else {
// Multiple matches - show clickable list
const listHtml = matches.map(airport => `
<div class="airport-option" onclick="selectDepartureAirport('${airport.icao}')">
<div>
<div class="airport-code">${airport.icao}</div>
<div class="airport-name">${airport.name}</div>
${airport.city ? `<div class="airport-location">${airport.city}, ${airport.country}</div>` : ''}
</div>
</div>
`).join('');
resultsDiv.innerHTML = `
<div class="airport-no-match" style="margin-bottom: 0.5rem;">
Multiple matches found - select one:
</div>
<div class="airport-list">
${listHtml}
</div>
`;
}
}
function clearArrivalAirportLookup() {
document.getElementById('arrival-airport-lookup-results').innerHTML = '';
}
function clearDepartureAirportLookup() {
document.getElementById('departure-airport-lookup-results').innerHTML = '';
}
// Airport selection functions
function selectArrivalAirport(icaoCode) {
document.getElementById('in_from').value = icaoCode;
clearArrivalAirportLookup();
}
function selectDepartureAirport(icaoCode) {
document.getElementById('out_to').value = icaoCode;
clearDepartureAirportLookup();
}
</script>
</body>
</html>