mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Add aircraft photos from Planespotters.net
- Backend route to proxy photo requests from Planespotters API - Frontend displays photo in Selected Target panel when available - Photos are cached to avoid repeated API calls - Clicking photo links to full image on Planespotters.net Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -511,3 +511,39 @@ def aircraft_db_delete():
|
||||
"""Delete aircraft database."""
|
||||
result = aircraft_db.delete_database()
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@adsb_bp.route('/aircraft-photo/<registration>')
|
||||
def aircraft_photo(registration: str):
|
||||
"""Fetch aircraft photo from Planespotters.net API."""
|
||||
import requests
|
||||
|
||||
# Validate registration format (alphanumeric with dashes)
|
||||
if not registration or not all(c.isalnum() or c == '-' for c in registration):
|
||||
return jsonify({'error': 'Invalid registration'}), 400
|
||||
|
||||
try:
|
||||
# Planespotters.net public API
|
||||
url = f'https://api.planespotters.net/pub/photos/reg/{registration}'
|
||||
resp = requests.get(url, timeout=5, headers={
|
||||
'User-Agent': 'INTERCEPT-ADS-B/1.0'
|
||||
})
|
||||
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
if data.get('photos') and len(data['photos']) > 0:
|
||||
photo = data['photos'][0]
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'thumbnail': photo.get('thumbnail_large', {}).get('src'),
|
||||
'link': photo.get('link'),
|
||||
'photographer': photo.get('photographer')
|
||||
})
|
||||
|
||||
return jsonify({'success': False, 'error': 'No photo found'})
|
||||
|
||||
except requests.Timeout:
|
||||
return jsonify({'success': False, 'error': 'Request timeout'}), 504
|
||||
except Exception as e:
|
||||
logger.debug(f"Error fetching aircraft photo: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@@ -1591,6 +1591,12 @@ sudo make install</code>
|
||||
`<div style="color:#00d4ff;font-size:11px;margin-bottom:6px;">${typeDesc || typeCode}${registration ? ' • ' + registration : ''}</div>` : '';
|
||||
|
||||
container.innerHTML = `
|
||||
<div id="aircraftPhotoContainer" style="display:none;margin-bottom:10px;">
|
||||
<a id="aircraftPhotoLink" href="#" target="_blank" rel="noopener">
|
||||
<img id="aircraftPhoto" src="" alt="Aircraft photo" style="width:100%;border-radius:6px;border:1px solid #333;">
|
||||
</a>
|
||||
<div id="aircraftPhotoCredit" style="font-size:9px;color:#666;margin-top:2px;text-align:right;"></div>
|
||||
</div>
|
||||
<div class="selected-callsign">${callsign}</div>
|
||||
${typeInfo}
|
||||
${badge}
|
||||
@@ -1632,6 +1638,55 @@ sudo make install</code>
|
||||
<div class="telemetry-value">${ac.lat ? calculateDistanceNm(observerLocation.lat, observerLocation.lon, ac.lat, ac.lon).toFixed(1) + ' nm' : 'N/A'}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Fetch aircraft photo if registration is available
|
||||
if (registration) {
|
||||
fetchAircraftPhoto(registration);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache for aircraft photos to avoid repeated API calls
|
||||
const photoCache = {};
|
||||
|
||||
async function fetchAircraftPhoto(registration) {
|
||||
const container = document.getElementById('aircraftPhotoContainer');
|
||||
const img = document.getElementById('aircraftPhoto');
|
||||
const link = document.getElementById('aircraftPhotoLink');
|
||||
const credit = document.getElementById('aircraftPhotoCredit');
|
||||
|
||||
if (!container || !img) return;
|
||||
|
||||
// Check cache first
|
||||
if (photoCache[registration]) {
|
||||
const cached = photoCache[registration];
|
||||
if (cached.thumbnail) {
|
||||
img.src = cached.thumbnail;
|
||||
link.href = cached.link || '#';
|
||||
credit.textContent = cached.photographer ? `Photo: ${cached.photographer}` : '';
|
||||
container.style.display = 'block';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/adsb/aircraft-photo/${encodeURIComponent(registration)}`);
|
||||
const data = await response.json();
|
||||
|
||||
// Cache the result
|
||||
photoCache[registration] = data;
|
||||
|
||||
if (data.success && data.thumbnail) {
|
||||
img.src = data.thumbnail;
|
||||
link.href = data.link || '#';
|
||||
credit.textContent = data.photographer ? `Photo: ${data.photographer}` : '';
|
||||
container.style.display = 'block';
|
||||
} else {
|
||||
container.style.display = 'none';
|
||||
}
|
||||
} catch (err) {
|
||||
console.debug('Failed to fetch aircraft photo:', err);
|
||||
container.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupOldAircraft() {
|
||||
|
||||
Reference in New Issue
Block a user